SyncStatusInfo.java revision f74cf940fe39297ad3afef9f28d40c838dac7bf7
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
17package android.content;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.util.Log;
22
23import java.util.ArrayList;
24import java.util.Calendar;
25import java.util.GregorianCalendar;
26
27/** @hide */
28public 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. */
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. */
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. */
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. */
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
139    public SyncStatusInfo(int authorityId) {
140        this.authorityId = authorityId;
141    }
142
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
153    public int describeContents() {
154        return 0;
155    }
156
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
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
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
308    private static void copy(long[] to, long[] from) {
309        System.arraycopy(from, 0, to, 0, to.length);
310    }
311
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
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
327    public void removePeriodicSyncTime(int index) {
328        if (periodicSyncTimes != null && index < periodicSyncTimes.size()) {
329            periodicSyncTimes.remove(index);
330        }
331    }
332
333    /** */
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    /** */
344    public int getEventCount() {
345        return mLastEventTimes.size();
346    }
347
348    /** */
349    public long getEventTime(int i) {
350        return mLastEventTimes.get(i);
351    }
352
353    /** */
354    public String getEvent(int i) {
355        return mLastEvents.get(i);
356    }
357
358    /** Call this when a sync has succeeded. */
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. */
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
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     */
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
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