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