1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.os.Parcel; 21 import android.os.Parcelable; 22 import android.util.Log; 23 24 import java.util.ArrayList; 25 import java.util.Calendar; 26 import java.util.GregorianCalendar; 27 28 /** @hide */ 29 public class SyncStatusInfo implements Parcelable { 30 private static final String TAG = "Sync"; 31 32 static final int VERSION = 6; 33 34 private static final int MAX_EVENT_COUNT = 10; 35 36 /** 37 * Number of sync sources. KEEP THIS AND SyncStorageEngine.SOURCES IN SYNC. 38 */ 39 private static final int SOURCE_COUNT = 6; 40 41 @UnsupportedAppUsage 42 public final int authorityId; 43 44 /** 45 * # of syncs for each sync source, etc. 46 */ 47 public static class Stats { 48 public long totalElapsedTime; 49 public int numSyncs; 50 public int numSourcePoll; 51 public int numSourceOther; 52 public int numSourceLocal; 53 public int numSourceUser; 54 public int numSourcePeriodic; 55 public int numSourceFeed; 56 public int numFailures; 57 public int numCancels; 58 59 /** Copy all the stats to another instance. */ copyTo(Stats to)60 public void copyTo(Stats to) { 61 to.totalElapsedTime = totalElapsedTime; 62 to.numSyncs = numSyncs; 63 to.numSourcePoll = numSourcePoll; 64 to.numSourceOther = numSourceOther; 65 to.numSourceLocal = numSourceLocal; 66 to.numSourceUser = numSourceUser; 67 to.numSourcePeriodic = numSourcePeriodic; 68 to.numSourceFeed = numSourceFeed; 69 to.numFailures = numFailures; 70 to.numCancels = numCancels; 71 } 72 73 /** Clear all the stats. */ clear()74 public void clear() { 75 totalElapsedTime = 0; 76 numSyncs = 0; 77 numSourcePoll = 0; 78 numSourceOther = 0; 79 numSourceLocal = 0; 80 numSourceUser = 0; 81 numSourcePeriodic = 0; 82 numSourceFeed = 0; 83 numFailures = 0; 84 numCancels = 0; 85 } 86 87 /** Write all the stats to a parcel. */ writeToParcel(Parcel parcel)88 public void writeToParcel(Parcel parcel) { 89 parcel.writeLong(totalElapsedTime); 90 parcel.writeInt(numSyncs); 91 parcel.writeInt(numSourcePoll); 92 parcel.writeInt(numSourceOther); 93 parcel.writeInt(numSourceLocal); 94 parcel.writeInt(numSourceUser); 95 parcel.writeInt(numSourcePeriodic); 96 parcel.writeInt(numSourceFeed); 97 parcel.writeInt(numFailures); 98 parcel.writeInt(numCancels); 99 } 100 101 /** Read all the stats from a parcel. */ readFromParcel(Parcel parcel)102 public void readFromParcel(Parcel parcel) { 103 totalElapsedTime = parcel.readLong(); 104 numSyncs = parcel.readInt(); 105 numSourcePoll = parcel.readInt(); 106 numSourceOther = parcel.readInt(); 107 numSourceLocal = parcel.readInt(); 108 numSourceUser = parcel.readInt(); 109 numSourcePeriodic = parcel.readInt(); 110 numSourceFeed = parcel.readInt(); 111 numFailures = parcel.readInt(); 112 numCancels = parcel.readInt(); 113 } 114 } 115 116 public long lastTodayResetTime; 117 118 public final Stats totalStats = new Stats(); 119 public final Stats todayStats = new Stats(); 120 public final Stats yesterdayStats = new Stats(); 121 122 @UnsupportedAppUsage 123 public long lastSuccessTime; 124 @UnsupportedAppUsage 125 public int lastSuccessSource; 126 @UnsupportedAppUsage 127 public long lastFailureTime; 128 @UnsupportedAppUsage 129 public int lastFailureSource; 130 @UnsupportedAppUsage 131 public String lastFailureMesg; 132 @UnsupportedAppUsage 133 public long initialFailureTime; 134 @UnsupportedAppUsage 135 public boolean pending; 136 @UnsupportedAppUsage 137 public boolean initialize; 138 139 public final long[] perSourceLastSuccessTimes = new long[SOURCE_COUNT]; 140 public final long[] perSourceLastFailureTimes = new long[SOURCE_COUNT]; 141 142 // Warning: It is up to the external caller to ensure there are 143 // no race conditions when accessing this list 144 @UnsupportedAppUsage 145 private ArrayList<Long> periodicSyncTimes; 146 147 private final ArrayList<Long> mLastEventTimes = new ArrayList<>(); 148 private final ArrayList<String> mLastEvents = new ArrayList<>(); 149 150 @UnsupportedAppUsage SyncStatusInfo(int authorityId)151 public SyncStatusInfo(int authorityId) { 152 this.authorityId = authorityId; 153 } 154 155 @UnsupportedAppUsage getLastFailureMesgAsInt(int def)156 public int getLastFailureMesgAsInt(int def) { 157 final int i = ContentResolver.syncErrorStringToInt(lastFailureMesg); 158 if (i > 0) { 159 return i; 160 } else { 161 Log.d(TAG, "Unknown lastFailureMesg:" + lastFailureMesg); 162 return def; 163 } 164 } 165 describeContents()166 public int describeContents() { 167 return 0; 168 } 169 writeToParcel(Parcel parcel, int flags)170 public void writeToParcel(Parcel parcel, int flags) { 171 parcel.writeInt(VERSION); 172 parcel.writeInt(authorityId); 173 174 // Note we can't use Stats.writeToParcel() here; see the below constructor for the reason. 175 parcel.writeLong(totalStats.totalElapsedTime); 176 parcel.writeInt(totalStats.numSyncs); 177 parcel.writeInt(totalStats.numSourcePoll); 178 parcel.writeInt(totalStats.numSourceOther); 179 parcel.writeInt(totalStats.numSourceLocal); 180 parcel.writeInt(totalStats.numSourceUser); 181 182 parcel.writeLong(lastSuccessTime); 183 parcel.writeInt(lastSuccessSource); 184 parcel.writeLong(lastFailureTime); 185 parcel.writeInt(lastFailureSource); 186 parcel.writeString(lastFailureMesg); 187 parcel.writeLong(initialFailureTime); 188 parcel.writeInt(pending ? 1 : 0); 189 parcel.writeInt(initialize ? 1 : 0); 190 if (periodicSyncTimes != null) { 191 parcel.writeInt(periodicSyncTimes.size()); 192 for (long periodicSyncTime : periodicSyncTimes) { 193 parcel.writeLong(periodicSyncTime); 194 } 195 } else { 196 parcel.writeInt(-1); 197 } 198 parcel.writeInt(mLastEventTimes.size()); 199 for (int i = 0; i < mLastEventTimes.size(); i++) { 200 parcel.writeLong(mLastEventTimes.get(i)); 201 parcel.writeString(mLastEvents.get(i)); 202 } 203 // Version 4 204 parcel.writeInt(totalStats.numSourcePeriodic); 205 206 // Version 5 207 parcel.writeInt(totalStats.numSourceFeed); 208 parcel.writeInt(totalStats.numFailures); 209 parcel.writeInt(totalStats.numCancels); 210 211 parcel.writeLong(lastTodayResetTime); 212 213 todayStats.writeToParcel(parcel); 214 yesterdayStats.writeToParcel(parcel); 215 216 // Version 6. 217 parcel.writeLongArray(perSourceLastSuccessTimes); 218 parcel.writeLongArray(perSourceLastFailureTimes); 219 } 220 221 @UnsupportedAppUsage SyncStatusInfo(Parcel parcel)222 public SyncStatusInfo(Parcel parcel) { 223 int version = parcel.readInt(); 224 if (version != VERSION && version != 1) { 225 Log.w("SyncStatusInfo", "Unknown version: " + version); 226 } 227 authorityId = parcel.readInt(); 228 229 // Note we can't use Stats.writeToParcel() here because the data is persisted and we need 230 // to be able to read from the old format too. 231 totalStats.totalElapsedTime = parcel.readLong(); 232 totalStats.numSyncs = parcel.readInt(); 233 totalStats.numSourcePoll = parcel.readInt(); 234 totalStats.numSourceOther = parcel.readInt(); 235 totalStats.numSourceLocal = parcel.readInt(); 236 totalStats.numSourceUser = parcel.readInt(); 237 lastSuccessTime = parcel.readLong(); 238 lastSuccessSource = parcel.readInt(); 239 lastFailureTime = parcel.readLong(); 240 lastFailureSource = parcel.readInt(); 241 lastFailureMesg = parcel.readString(); 242 initialFailureTime = parcel.readLong(); 243 pending = parcel.readInt() != 0; 244 initialize = parcel.readInt() != 0; 245 if (version == 1) { 246 periodicSyncTimes = null; 247 } else { 248 final int count = parcel.readInt(); 249 if (count < 0) { 250 periodicSyncTimes = null; 251 } else { 252 periodicSyncTimes = new ArrayList<Long>(); 253 for (int i = 0; i < count; i++) { 254 periodicSyncTimes.add(parcel.readLong()); 255 } 256 } 257 if (version >= 3) { 258 mLastEventTimes.clear(); 259 mLastEvents.clear(); 260 final int nEvents = parcel.readInt(); 261 for (int i = 0; i < nEvents; i++) { 262 mLastEventTimes.add(parcel.readLong()); 263 mLastEvents.add(parcel.readString()); 264 } 265 } 266 } 267 if (version < 4) { 268 // Before version 4, numSourcePeriodic wasn't persisted. 269 totalStats.numSourcePeriodic = 270 totalStats.numSyncs - totalStats.numSourceLocal - totalStats.numSourcePoll 271 - totalStats.numSourceOther 272 - totalStats.numSourceUser; 273 if (totalStats.numSourcePeriodic < 0) { // Consistency check. 274 totalStats.numSourcePeriodic = 0; 275 } 276 } else { 277 totalStats.numSourcePeriodic = parcel.readInt(); 278 } 279 if (version >= 5) { 280 totalStats.numSourceFeed = parcel.readInt(); 281 totalStats.numFailures = parcel.readInt(); 282 totalStats.numCancels = parcel.readInt(); 283 284 lastTodayResetTime = parcel.readLong(); 285 286 todayStats.readFromParcel(parcel); 287 yesterdayStats.readFromParcel(parcel); 288 } 289 if (version >= 6) { 290 parcel.readLongArray(perSourceLastSuccessTimes); 291 parcel.readLongArray(perSourceLastFailureTimes); 292 } 293 } 294 SyncStatusInfo(SyncStatusInfo other)295 public SyncStatusInfo(SyncStatusInfo other) { 296 authorityId = other.authorityId; 297 298 other.totalStats.copyTo(totalStats); 299 other.todayStats.copyTo(todayStats); 300 other.yesterdayStats.copyTo(yesterdayStats); 301 302 lastTodayResetTime = other.lastTodayResetTime; 303 304 lastSuccessTime = other.lastSuccessTime; 305 lastSuccessSource = other.lastSuccessSource; 306 lastFailureTime = other.lastFailureTime; 307 lastFailureSource = other.lastFailureSource; 308 lastFailureMesg = other.lastFailureMesg; 309 initialFailureTime = other.initialFailureTime; 310 pending = other.pending; 311 initialize = other.initialize; 312 if (other.periodicSyncTimes != null) { 313 periodicSyncTimes = new ArrayList<Long>(other.periodicSyncTimes); 314 } 315 mLastEventTimes.addAll(other.mLastEventTimes); 316 mLastEvents.addAll(other.mLastEvents); 317 318 copy(perSourceLastSuccessTimes, other.perSourceLastSuccessTimes); 319 copy(perSourceLastFailureTimes, other.perSourceLastFailureTimes); 320 } 321 copy(long[] to, long[] from)322 private static void copy(long[] to, long[] from) { 323 System.arraycopy(from, 0, to, 0, to.length); 324 } 325 326 @UnsupportedAppUsage setPeriodicSyncTime(int index, long when)327 public void setPeriodicSyncTime(int index, long when) { 328 // The list is initialized lazily when scheduling occurs so we need to make sure 329 // we initialize elements < index to zero (zero is ignore for scheduling purposes) 330 ensurePeriodicSyncTimeSize(index); 331 periodicSyncTimes.set(index, when); 332 } 333 334 @UnsupportedAppUsage getPeriodicSyncTime(int index)335 public long getPeriodicSyncTime(int index) { 336 if (periodicSyncTimes != null && index < periodicSyncTimes.size()) { 337 return periodicSyncTimes.get(index); 338 } else { 339 return 0; 340 } 341 } 342 343 @UnsupportedAppUsage removePeriodicSyncTime(int index)344 public void removePeriodicSyncTime(int index) { 345 if (periodicSyncTimes != null && index < periodicSyncTimes.size()) { 346 periodicSyncTimes.remove(index); 347 } 348 } 349 350 /** */ addEvent(String message)351 public void addEvent(String message) { 352 if (mLastEventTimes.size() >= MAX_EVENT_COUNT) { 353 mLastEventTimes.remove(MAX_EVENT_COUNT - 1); 354 mLastEvents.remove(MAX_EVENT_COUNT - 1); 355 } 356 mLastEventTimes.add(0, System.currentTimeMillis()); 357 mLastEvents.add(0, message); 358 } 359 360 /** */ getEventCount()361 public int getEventCount() { 362 return mLastEventTimes.size(); 363 } 364 365 /** */ getEventTime(int i)366 public long getEventTime(int i) { 367 return mLastEventTimes.get(i); 368 } 369 370 /** */ getEvent(int i)371 public String getEvent(int i) { 372 return mLastEvents.get(i); 373 } 374 375 /** Call this when a sync has succeeded. */ setLastSuccess(int source, long lastSyncTime)376 public void setLastSuccess(int source, long lastSyncTime) { 377 lastSuccessTime = lastSyncTime; 378 lastSuccessSource = source; 379 lastFailureTime = 0; 380 lastFailureSource = -1; 381 lastFailureMesg = null; 382 initialFailureTime = 0; 383 384 if (0 <= source && source < perSourceLastSuccessTimes.length) { 385 perSourceLastSuccessTimes[source] = lastSyncTime; 386 } 387 } 388 389 /** Call this when a sync has failed. */ setLastFailure(int source, long lastSyncTime, String failureMessage)390 public void setLastFailure(int source, long lastSyncTime, String failureMessage) { 391 lastFailureTime = lastSyncTime; 392 lastFailureSource = source; 393 lastFailureMesg = failureMessage; 394 if (initialFailureTime == 0) { 395 initialFailureTime = lastSyncTime; 396 } 397 398 if (0 <= source && source < perSourceLastFailureTimes.length) { 399 perSourceLastFailureTimes[source] = lastSyncTime; 400 } 401 } 402 403 @UnsupportedAppUsage 404 public static final @android.annotation.NonNull Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() { 405 public SyncStatusInfo createFromParcel(Parcel in) { 406 return new SyncStatusInfo(in); 407 } 408 409 public SyncStatusInfo[] newArray(int size) { 410 return new SyncStatusInfo[size]; 411 } 412 }; 413 414 @UnsupportedAppUsage ensurePeriodicSyncTimeSize(int index)415 private void ensurePeriodicSyncTimeSize(int index) { 416 if (periodicSyncTimes == null) { 417 periodicSyncTimes = new ArrayList<Long>(0); 418 } 419 420 final int requiredSize = index + 1; 421 if (periodicSyncTimes.size() < requiredSize) { 422 for (int i = periodicSyncTimes.size(); i < requiredSize; i++) { 423 periodicSyncTimes.add((long) 0); 424 } 425 } 426 } 427 428 /** 429 * If the last reset was not today, move today's stats to yesterday's and clear today's. 430 */ maybeResetTodayStats(boolean clockValid, boolean force)431 public void maybeResetTodayStats(boolean clockValid, boolean force) { 432 final long now = System.currentTimeMillis(); 433 434 if (!force) { 435 // Last reset was the same day, nothing to do. 436 if (areSameDates(now, lastTodayResetTime)) { 437 return; 438 } 439 440 // Hack -- on devices with no RTC, until the NTP kicks in, the device won't have the 441 // correct time. So if the time goes back, don't reset, unless we're sure the current 442 // time is correct. 443 if (now < lastTodayResetTime && !clockValid) { 444 return; 445 } 446 } 447 448 lastTodayResetTime = now; 449 450 todayStats.copyTo(yesterdayStats); 451 todayStats.clear(); 452 } 453 areSameDates(long time1, long time2)454 private static boolean areSameDates(long time1, long time2) { 455 final Calendar c1 = new GregorianCalendar(); 456 final Calendar c2 = new GregorianCalendar(); 457 458 c1.setTimeInMillis(time1); 459 c2.setTimeInMillis(time2); 460 461 return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) 462 && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR); 463 } 464 } 465