diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java index 3f64515772586..0eea47a248af4 100644 --- a/core/java/android/content/SyncStatusInfo.java +++ b/core/java/android/content/SyncStatusInfo.java @@ -20,6 +20,9 @@ import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import android.util.Pair; + +import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.Calendar; @@ -139,10 +142,10 @@ public class SyncStatusInfo implements Parcelable { public final long[] perSourceLastSuccessTimes = new long[SOURCE_COUNT]; public final long[] perSourceLastFailureTimes = new long[SOURCE_COUNT]; - // Warning: It is up to the external caller to ensure there are - // no race conditions when accessing this list - @UnsupportedAppUsage - private ArrayList periodicSyncTimes; + // Warning: It is up to the external caller to ensure there are + // no race conditions when accessing this list + @UnsupportedAppUsage + private ArrayList periodicSyncTimes; private final ArrayList mLastEventTimes = new ArrayList<>(); private final ArrayList mLastEvents = new ArrayList<>(); @@ -292,9 +295,28 @@ public class SyncStatusInfo implements Parcelable { } } + /** + * Copies all data from the given SyncStatusInfo object. + * + * @param other the SyncStatusInfo object to copy data from + */ public SyncStatusInfo(SyncStatusInfo other) { authorityId = other.authorityId; + copyFrom(other); + } + /** + * Copies all data from the given SyncStatusInfo object except for its authority id. + * + * @param authorityId the new authority id + * @param other the SyncStatusInfo object to copy data from + */ + public SyncStatusInfo(int authorityId, SyncStatusInfo other) { + this.authorityId = authorityId; + copyFrom(other); + } + + private void copyFrom(SyncStatusInfo other) { other.totalStats.copyTo(totalStats); other.todayStats.copyTo(todayStats); other.yesterdayStats.copyTo(yesterdayStats); @@ -323,6 +345,14 @@ public class SyncStatusInfo implements Parcelable { System.arraycopy(from, 0, to, 0, to.length); } + public int getPeriodicSyncTimesSize() { + return periodicSyncTimes == null ? 0 : periodicSyncTimes.size(); + } + + public void addPeriodicSyncTime(long time) { + periodicSyncTimes = ArrayUtils.add(periodicSyncTimes, time); + } + @UnsupportedAppUsage public void setPeriodicSyncTime(int index, long when) { // The list is initialized lazily when scheduling occurs so we need to make sure @@ -347,6 +377,24 @@ public class SyncStatusInfo implements Parcelable { } } + /** + * Populates {@code mLastEventTimes} and {@code mLastEvents} with the given list.
+ * Note: This method is mainly used to repopulate the event info from disk and it will clear + * both {@code mLastEventTimes} and {@code mLastEvents} before populating. + * + * @param lastEventInformation the list to populate with + */ + public void populateLastEventsInformation(ArrayList> lastEventInformation) { + mLastEventTimes.clear(); + mLastEvents.clear(); + final int size = lastEventInformation.size(); + for (int i = 0; i < size; i++) { + final Pair lastEventInfo = lastEventInformation.get(i); + mLastEventTimes.add(lastEventInfo.first); + mLastEvents.add(lastEventInfo.second); + } + } + /** */ public void addEvent(String message) { if (mLastEventTimes.size() >= MAX_EVENT_COUNT) { diff --git a/core/proto/android/server/syncstorageengine.proto b/core/proto/android/server/syncstorageengine.proto new file mode 100644 index 0000000000000..87eb1b3c5d95e --- /dev/null +++ b/core/proto/android/server/syncstorageengine.proto @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package com.android.server.content; +import "frameworks/base/core/proto/android/privacy.proto"; + +option java_multiple_files = true; + +/** + * Stores relevant information from a DayStats object in SyncStorageEngine. + */ +message SyncStatisticsProto { + + message DayStats { + optional int32 day = 1; // day of the year - defined by SyncStorageEngine#getCurrentDayLocked() + optional int32 success_count = 2; + optional int64 success_time = 3; // time since epoch + optional int32 failure_count = 4; + optional int64 failure_time = 5; // time since epoch + } + + repeated DayStats stats = 1; +} + +/** + * Stores relevant information from a SyncStatusInfo object. + */ +message SyncStatusProto { + + message StatusInfo { + + message Stats { + optional int64 total_elapsed_time = 1; // time since epoch + optional int32 num_syncs = 2; + optional int32 num_failures = 3; + optional int32 num_cancels = 4; + optional int32 num_source_other = 5; + optional int32 num_source_local = 6; + optional int32 num_source_poll = 7; + optional int32 num_source_user = 8; + optional int32 num_source_periodic = 9; + optional int32 num_source_feed = 10; + } + + message LastEventInfo { + optional int64 last_event_time = 1; // time since epoch + optional string last_event = 2; + } + + // Note: version doesn't need to be stored in proto because of how protos store information but + // leaving field number 1 open in case we find a usage for it in the future. + optional int32 authority_id = 2; + optional int64 last_success_time = 3; // time since epoch + optional int32 last_success_source = 4; + optional int64 last_failure_time = 5; // time since epoch + optional int32 last_failure_source = 6; + optional string last_failure_message = 7; + optional int64 initial_failure_time = 8; // time since epoch + optional bool pending = 9; + optional bool initialize = 10; + repeated int64 periodic_sync_times = 11; // times since epoch + repeated LastEventInfo last_event_info = 12; + optional int64 last_today_reset_time = 13; // time since epoch + optional Stats total_stats = 14; + optional Stats today_stats = 15; + optional Stats yesterday_stats = 16; + repeated int64 per_source_last_success_times = 17; // times since epoch + repeated int64 per_source_last_failure_times = 18; // times since epoch + } + + repeated StatusInfo status = 1; +} diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index ebaa5a1bd475e..dea47db62b9c6 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -51,6 +51,8 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -64,6 +66,9 @@ import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Calendar; @@ -428,7 +433,8 @@ public class SyncStorageEngine { } // Primary list of all syncable authorities. Also our global lock. - private final SparseArray mAuthorities = + @VisibleForTesting + final SparseArray mAuthorities = new SparseArray(); private final HashMap mAccounts @@ -437,7 +443,8 @@ public class SyncStorageEngine { private final SparseArray> mCurrentSyncs = new SparseArray>(); - private final SparseArray mSyncStatus = + @VisibleForTesting + final SparseArray mSyncStatus = new SparseArray(); private final ArrayList mSyncHistory = @@ -453,7 +460,8 @@ public class SyncStorageEngine { private int mNextAuthorityId = 0; // We keep 4 weeks of stats. - private final DayStats[] mDayStats = new DayStats[7*4]; + @VisibleForTesting + final DayStats[] mDayStats = new DayStats[7*4]; private final Calendar mCal; private int mYear; private int mYearInDays; @@ -464,6 +472,18 @@ public class SyncStorageEngine { private int mSyncRandomOffset; + // STOPSHIP: b/143656271 this should be true on launch + private static final boolean DELETE_LEGACY_PARCEL_FILES = false; + private static final String LEGACY_STATUS_FILE_NAME = "status.bin"; + private static final String LEGACY_STATISTICS_FILE_NAME = "stats.bin"; + + private static final String SYNC_DIR_NAME = "sync"; + private static final String ACCOUNT_INFO_FILE_NAME = "accounts.xml"; + private static final String STATUS_FILE_NAME = "status"; + private static final String STATISTICS_FILE_NAME = "stats"; + + private File mSyncDir; + /** * This file contains the core engine state: all accounts and the * settings for them. It must never be lost, and should be changed @@ -508,14 +528,15 @@ public class SyncStorageEngine { com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically); File systemDir = new File(dataDir, "system"); - File syncDir = new File(systemDir, "sync"); - syncDir.mkdirs(); + mSyncDir = new File(systemDir, SYNC_DIR_NAME); + mSyncDir.mkdirs(); - maybeDeleteLegacyPendingInfoLocked(syncDir); + maybeDeleteLegacyPendingInfoLocked(mSyncDir); - mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"), "sync-accounts"); - mStatusFile = new AtomicFile(new File(syncDir, "status.bin"), "sync-status"); - mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"), "sync-stats"); + mAccountInfoFile = new AtomicFile(new File(mSyncDir, ACCOUNT_INFO_FILE_NAME), + "sync-accounts"); + mStatusFile = new AtomicFile(new File(mSyncDir, STATUS_FILE_NAME), "sync-status"); + mStatisticsFile = new AtomicFile(new File(mSyncDir, STATISTICS_FILE_NAME), "sync-stats"); readAccountInfoLocked(); readStatusLocked(); @@ -2017,15 +2038,10 @@ public class SyncStorageEngine { public static final int STATUS_FILE_END = 0; public static final int STATUS_FILE_ITEM = 100; - /** - * Read all sync status back in to the initial engine state. - */ - private void readStatusLocked() { - if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { - Slog.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile()); - } + private void readStatusParcelLocked(File parcel) { try { - byte[] data = mStatusFile.readFully(); + final AtomicFile parcelFile = new AtomicFile(parcel); + byte[] data = parcelFile.readFully(); Parcel in = Parcel.obtain(); in.unmarshall(data, 0, data.length); in.setDataPosition(0); @@ -2036,9 +2052,6 @@ public class SyncStorageEngine { SyncStatusInfo status = new SyncStatusInfo(in); if (mAuthorities.indexOfKey(status.authorityId) >= 0) { status.pending = false; - if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { - Slog.v(TAG_FILE, "Adding status for id " + status.authorityId); - } mSyncStatus.put(status.authorityId, status); } } catch (Exception e) { @@ -2050,15 +2063,247 @@ public class SyncStorageEngine { break; } } - } catch (java.io.IOException e) { + } catch (IOException e) { Slog.i(TAG, "No initial status"); } } + private void upgradeStatusIfNeededLocked() { + final File parcelStatus = new File(mSyncDir, LEGACY_STATUS_FILE_NAME); + if (parcelStatus.exists() && !mStatusFile.exists()) { + readStatusParcelLocked(parcelStatus); + writeStatusLocked(); + } + + // if upgrade to proto was successful, delete parcel file + if (DELETE_LEGACY_PARCEL_FILES && mStatusFile.exists()) { + parcelStatus.delete(); + } + } + + /** + * Read all sync status back in to the initial engine state. + */ + @VisibleForTesting + void readStatusLocked() { + upgradeStatusIfNeededLocked(); + + if (!mStatusFile.exists()) { + return; + } + try { + try (FileInputStream in = mStatusFile.openRead()) { + readStatusInfoLocked(in); + } + } catch (IOException e) { + Slog.e(TAG, "Unable to read status info file.", e); + } + } + + private void readStatusInfoLocked(InputStream in) throws IOException { + final ProtoInputStream proto = new ProtoInputStream(in); + while (true) { + switch (proto.nextField()) { + case (int) SyncStatusProto.STATUS: + final long token = proto.start(SyncStatusProto.STATUS); + final SyncStatusInfo status = readSyncStatusInfoLocked(proto); + proto.end(token); + if (mAuthorities.indexOfKey(status.authorityId) >= 0) { + status.pending = false; + mSyncStatus.put(status.authorityId, status); + } + break; + case ProtoInputStream.NO_MORE_FIELDS: + return; + } + } + } + + private SyncStatusInfo readSyncStatusInfoLocked(ProtoInputStream proto) throws IOException { + SyncStatusInfo status; + if (proto.nextField(SyncStatusProto.StatusInfo.AUTHORITY_ID)) { + //fast-path; this should work for most cases since the authority id is written first + status = new SyncStatusInfo(proto.readInt(SyncStatusProto.StatusInfo.AUTHORITY_ID)); + } else { + // placeholder to read other data; assume the default authority id as 0 + status = new SyncStatusInfo(0); + } + + int successTimesCount = 0; + int failureTimesCount = 0; + ArrayList> lastEventInformation = new ArrayList<>(); + while (true) { + switch (proto.nextField()) { + case (int) SyncStatusProto.StatusInfo.AUTHORITY_ID: + // fast-path failed for some reason, rebuild the status from placeholder object + Slog.w(TAG, "Failed to read the authority id via fast-path; " + + "some data might not have been read."); + status = new SyncStatusInfo( + proto.readInt(SyncStatusProto.StatusInfo.AUTHORITY_ID), status); + break; + case (int) SyncStatusProto.StatusInfo.LAST_SUCCESS_TIME: + status.lastSuccessTime = proto.readLong( + SyncStatusProto.StatusInfo.LAST_SUCCESS_TIME); + break; + case (int) SyncStatusProto.StatusInfo.LAST_SUCCESS_SOURCE: + status.lastSuccessSource = proto.readInt( + SyncStatusProto.StatusInfo.LAST_SUCCESS_SOURCE); + break; + case (int) SyncStatusProto.StatusInfo.LAST_FAILURE_TIME: + status.lastFailureTime = proto.readLong( + SyncStatusProto.StatusInfo.LAST_FAILURE_TIME); + break; + case (int) SyncStatusProto.StatusInfo.LAST_FAILURE_SOURCE: + status.lastFailureSource = proto.readInt( + SyncStatusProto.StatusInfo.LAST_FAILURE_SOURCE); + break; + case (int) SyncStatusProto.StatusInfo.LAST_FAILURE_MESSAGE: + status.lastFailureMesg = proto.readString( + SyncStatusProto.StatusInfo.LAST_FAILURE_MESSAGE); + break; + case (int) SyncStatusProto.StatusInfo.INITIAL_FAILURE_TIME: + status.initialFailureTime = proto.readLong( + SyncStatusProto.StatusInfo.INITIAL_FAILURE_TIME); + break; + case (int) SyncStatusProto.StatusInfo.PENDING: + status.pending = proto.readBoolean(SyncStatusProto.StatusInfo.PENDING); + break; + case (int) SyncStatusProto.StatusInfo.INITIALIZE: + status.initialize = proto.readBoolean(SyncStatusProto.StatusInfo.INITIALIZE); + break; + case (int) SyncStatusProto.StatusInfo.PERIODIC_SYNC_TIMES: + status.addPeriodicSyncTime( + proto.readLong(SyncStatusProto.StatusInfo.PERIODIC_SYNC_TIMES)); + break; + case (int) SyncStatusProto.StatusInfo.LAST_EVENT_INFO: + final long eventToken = proto.start(SyncStatusProto.StatusInfo.LAST_EVENT_INFO); + final Pair lastEventInfo = parseLastEventInfoLocked(proto); + if (lastEventInfo != null) { + lastEventInformation.add(lastEventInfo); + } + proto.end(eventToken); + break; + case (int) SyncStatusProto.StatusInfo.LAST_TODAY_RESET_TIME: + status.lastTodayResetTime = proto.readLong( + SyncStatusProto.StatusInfo.LAST_TODAY_RESET_TIME); + break; + case (int) SyncStatusProto.StatusInfo.TOTAL_STATS: + final long totalStatsToken = proto.start( + SyncStatusProto.StatusInfo.TOTAL_STATS); + readSyncStatusStatsLocked(proto, status.totalStats); + proto.end(totalStatsToken); + break; + case (int) SyncStatusProto.StatusInfo.TODAY_STATS: + final long todayStatsToken = proto.start( + SyncStatusProto.StatusInfo.TODAY_STATS); + readSyncStatusStatsLocked(proto, status.todayStats); + proto.end(todayStatsToken); + break; + case (int) SyncStatusProto.StatusInfo.YESTERDAY_STATS: + final long yesterdayStatsToken = proto.start( + SyncStatusProto.StatusInfo.YESTERDAY_STATS); + readSyncStatusStatsLocked(proto, status.yesterdayStats); + proto.end(yesterdayStatsToken); + break; + case (int) SyncStatusProto.StatusInfo.PER_SOURCE_LAST_SUCCESS_TIMES: + final long successTime = proto.readLong( + SyncStatusProto.StatusInfo.PER_SOURCE_LAST_SUCCESS_TIMES); + if (successTimesCount == status.perSourceLastSuccessTimes.length) { + Slog.w(TAG, "Attempted to read more per source last success times " + + "than expected; data might be corrupted."); + break; + } + status.perSourceLastSuccessTimes[successTimesCount] = successTime; + successTimesCount++; + break; + case (int) SyncStatusProto.StatusInfo.PER_SOURCE_LAST_FAILURE_TIMES: + final long failureTime = proto.readLong( + SyncStatusProto.StatusInfo.PER_SOURCE_LAST_FAILURE_TIMES); + if (failureTimesCount == status.perSourceLastFailureTimes.length) { + Slog.w(TAG, "Attempted to read more per source last failure times " + + "than expected; data might be corrupted."); + break; + } + status.perSourceLastFailureTimes[failureTimesCount] = failureTime; + failureTimesCount++; + break; + case ProtoInputStream.NO_MORE_FIELDS: + status.populateLastEventsInformation(lastEventInformation); + return status; + } + } + } + + private Pair parseLastEventInfoLocked(ProtoInputStream proto) throws IOException { + long time = 0; + String message = null; + while (true) { + switch (proto.nextField()) { + case (int) SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT_TIME: + time = proto.readLong(SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT_TIME); + break; + case (int) SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT: + message = proto.readString(SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT); + break; + case ProtoInputStream.NO_MORE_FIELDS: + return message == null ? null : new Pair<>(time, message); + } + } + } + + private void readSyncStatusStatsLocked(ProtoInputStream proto, SyncStatusInfo.Stats stats) + throws IOException { + while (true) { + switch (proto.nextField()) { + case (int) SyncStatusProto.StatusInfo.Stats.TOTAL_ELAPSED_TIME: + stats.totalElapsedTime = proto.readLong( + SyncStatusProto.StatusInfo.Stats.TOTAL_ELAPSED_TIME); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_SYNCS: + stats.numSyncs = proto.readInt(SyncStatusProto.StatusInfo.Stats.NUM_SYNCS); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_FAILURES: + stats.numFailures = proto.readInt( + SyncStatusProto.StatusInfo.Stats.NUM_FAILURES); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_CANCELS: + stats.numCancels = proto.readInt(SyncStatusProto.StatusInfo.Stats.NUM_CANCELS); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_OTHER: + stats.numSourceOther = proto.readInt( + SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_OTHER); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_LOCAL: + stats.numSourceLocal = proto.readInt( + SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_LOCAL); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_POLL: + stats.numSourcePoll = proto.readInt( + SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_POLL); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_USER: + stats.numSourceUser = proto.readInt( + SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_USER); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_PERIODIC: + stats.numSourcePeriodic = proto.readInt( + SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_PERIODIC); + break; + case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_FEED: + stats.numSourceFeed = proto.readInt( + SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_FEED); + break; + case ProtoInputStream.NO_MORE_FIELDS: + return; + } + } + } + /** * Write all sync status to the sync status file. */ - private void writeStatusLocked() { + @VisibleForTesting + void writeStatusLocked() { if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { Slog.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile()); } @@ -2070,26 +2315,87 @@ public class SyncStorageEngine { FileOutputStream fos = null; try { fos = mStatusFile.startWrite(); - Parcel out = Parcel.obtain(); - final int N = mSyncStatus.size(); - for (int i=0; i syncStatus) { + final Random r = new Random(1); + for (int i = 0; i < NUM_SYNC_STATUS; i++) { + final SyncStatusInfo ss = new SyncStatusInfo(i); + ss.lastSuccessTime = r.nextLong(); + ss.lastSuccessSource = r.nextInt(); + ss.lastFailureTime = r.nextLong(); + ss.lastFailureSource = r.nextInt(); + ss.lastFailureMesg = "fail_msg_" + r.nextInt(); + ss.initialFailureTime = r.nextLong(); + ss.initialize = r.nextBoolean(); + for (int j = 0; j < NUM_PERIODIC_SYNC_TIMES; j++) { + ss.addPeriodicSyncTime(r.nextLong()); + } + final ArrayList> lastEventInfos = new ArrayList<>(); + for (int j = 0; j < NUM_EVENTS; j++) { + lastEventInfos.add(new Pair<>(r.nextLong(), "event_" + r.nextInt())); + } + ss.populateLastEventsInformation(lastEventInfos); + ss.lastTodayResetTime = r.nextLong(); + populateStats(ss.totalStats, r); + populateStats(ss.todayStats, r); + populateStats(ss.yesterdayStats, r); + for (int j = 0; j < NUM_SOURCES; j++) { + ss.perSourceLastSuccessTimes[j] = r.nextLong(); + } + for (int j = 0; j < NUM_SOURCES; j++) { + ss.perSourceLastFailureTimes[j] = r.nextLong(); + } + syncStatus.put(i, ss); + } + } + + private void populateStats(SyncStatusInfo.Stats stats, Random r) { + stats.totalElapsedTime = r.nextLong(); + stats.numSyncs = r.nextInt(); + stats.numFailures = r.nextInt(); + stats.numCancels = r.nextInt(); + stats.numSourceOther = r.nextInt(); + stats.numSourceLocal = r.nextInt(); + stats.numSourcePoll = r.nextInt(); + stats.numSourceUser = r.nextInt(); + stats.numSourcePeriodic = r.nextInt(); + stats.numSourceFeed = r.nextInt(); + } + + private void verifyStatus(SparseArray syncStatus, + SparseArray syncStatusOther) { + assertEquals(syncStatusOther.size(), syncStatus.size()); + for (int i = 0; i < NUM_SYNC_STATUS; i++) { + final SyncStatusInfo ss = syncStatus.valueAt(i); + final SyncStatusInfo ssOther = syncStatusOther.valueAt(i); + assertEquals(ssOther.authorityId, ss.authorityId); + assertEquals(ssOther.lastSuccessTime, ss.lastSuccessTime); + assertEquals(ssOther.lastSuccessSource, ss.lastSuccessSource); + assertEquals(ssOther.lastFailureTime, ss.lastFailureTime); + assertEquals(ssOther.lastFailureSource, ss.lastFailureSource); + assertEquals(ssOther.lastFailureMesg, ss.lastFailureMesg); + assertFalse(ssOther.pending); // pending is always set to false when read + assertEquals(ssOther.initialize, ss.initialize); + assertEquals(ssOther.getPeriodicSyncTimesSize(), NUM_PERIODIC_SYNC_TIMES); + for (int j = 0; j < NUM_PERIODIC_SYNC_TIMES; j++) { + assertEquals(ssOther.getPeriodicSyncTime(j), ss.getPeriodicSyncTime(j)); + } + assertEquals(ssOther.getEventCount(), NUM_EVENTS); + for (int j = 0; j < NUM_EVENTS; j++) { + assertEquals(ssOther.getEventTime(j), ss.getEventTime(j)); + assertEquals(ssOther.getEvent(j), ss.getEvent(j)); + } + assertEquals(ssOther.lastTodayResetTime, ss.lastTodayResetTime); + verifyStats(ss.totalStats, ssOther.totalStats); + verifyStats(ss.todayStats, ssOther.todayStats); + verifyStats(ss.yesterdayStats, ssOther.yesterdayStats); + assertEquals(ssOther.perSourceLastSuccessTimes.length, NUM_SOURCES); + for (int j = 0; j < NUM_SOURCES; j++) { + assertEquals(ssOther.perSourceLastSuccessTimes[j], ss.perSourceLastSuccessTimes[j]); + } + assertEquals(ssOther.perSourceLastFailureTimes.length, NUM_SOURCES); + for (int j = 0; j < NUM_SOURCES; j++) { + assertEquals(ssOther.perSourceLastFailureTimes[j], ss.perSourceLastFailureTimes[j]); + } + } + } + + private void verifyStats(SyncStatusInfo.Stats stats, SyncStatusInfo.Stats statsOther) { + assertEquals(statsOther.totalElapsedTime, stats.totalElapsedTime); + assertEquals(statsOther.numSyncs, stats.numSyncs); + assertEquals(statsOther.numFailures, stats.numFailures); + assertEquals(statsOther.numCancels, stats.numCancels); + assertEquals(statsOther.numSourceOther, stats.numSourceOther); + assertEquals(statsOther.numSourceLocal, stats.numSourceLocal); + assertEquals(statsOther.numSourcePoll, stats.numSourcePoll); + assertEquals(statsOther.numSourceUser, stats.numSourceUser); + assertEquals(statsOther.numSourcePeriodic, stats.numSourcePeriodic); + assertEquals(statsOther.numSourceFeed, stats.numSourceFeed); + } +}