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