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