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