Merge "Set alarm for retries with exponential backoff." into mnc-dev
diff --git a/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java b/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
index 95dded8..45ad1cb 100644
--- a/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
+++ b/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
@@ -40,6 +40,12 @@
     // however, the user can override this setting.
     private static final String IS_USER_SET = "is_user_set";
 
+    // Setting for how often retries should be done.
+    private static final String SYNC_RETRY_INTERVAL = "sync_retry_interval";
+    private static final long MAX_SYNC_RETRY_INTERVAL_MS = 86400000;   // 24 hours
+    private static final long DEFAULT_SYNC_RETRY_INTERVAL_MS = 900000; // 15 minutes
+
+
     public static void setVisualVoicemailEnabled(Phone phone, boolean isEnabled,
             boolean isUserSet) {
         setVisualVoicemailEnabled(phone.getContext(), PhoneUtils.makePstnPhoneAccountHandle(phone),
@@ -115,6 +121,27 @@
         return prefs.getString(getVisualVoicemailSharedPrefsKey(key, phoneAccount), null);
     }
 
+    public static long getVisualVoicemailRetryInterval(Context context,
+            PhoneAccountHandle phoneAccount) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        return prefs.getLong(getVisualVoicemailSharedPrefsKey(SYNC_RETRY_INTERVAL, phoneAccount),
+                DEFAULT_SYNC_RETRY_INTERVAL_MS);
+    }
+
+    public static void resetVisualVoicemailRetryInterval(Context context,
+            PhoneAccountHandle phoneAccount) {
+        setVisualVoicemailRetryInterval(context, phoneAccount, DEFAULT_SYNC_RETRY_INTERVAL_MS);
+    }
+
+    public static void setVisualVoicemailRetryInterval(Context context,
+            PhoneAccountHandle phoneAccount, long interval) {
+        SharedPreferences.Editor editor =
+                PreferenceManager.getDefaultSharedPreferences(context).edit();
+        editor.putLong(getVisualVoicemailSharedPrefsKey(SYNC_RETRY_INTERVAL, phoneAccount),
+                Math.min(interval, MAX_SYNC_RETRY_INTERVAL_MS));
+        editor.commit();
+    }
+
     private static String getVisualVoicemailSharedPrefsKey(String key,
             PhoneAccountHandle phoneAccount) {
         return VISUAL_VOICEMAIL_SHARED_PREFS_KEY_PREFIX + key + "_" + phoneAccount.getId();
diff --git a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
index 197c754..83e9633 100644
--- a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
+++ b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
@@ -133,15 +133,16 @@
             executor.execute(new Runnable() {
                 @Override
                 public void run() {
-                    ImapHelper imapHelper = new ImapHelper(mContext, mPhoneAccount, network);
-                    boolean success = imapHelper.fetchVoicemailPayload(
-                            new VoicemailFetchedCallback(mContext, mUri), mUid);
-
-                    releaseNetwork();
-
-                    if (!success && mRetryCount > 0) {
-                        mRetryCount--;
-                        requestNetwork();
+                    while (mRetryCount > 0) {
+                        ImapHelper imapHelper = new ImapHelper(mContext, mPhoneAccount, network);
+                        boolean success = imapHelper.fetchVoicemailPayload(
+                                new VoicemailFetchedCallback(mContext, mUri), mUid);
+                        if (!success && mRetryCount > 0) {
+                            mRetryCount--;
+                        } else {
+                            releaseNetwork();
+                            return;
+                        }
                     }
                 }
             });
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index fe4cf11..6c8106b 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -104,9 +104,9 @@
                 queryHelper.insertIfUnique(voicemail);
                 break;
             case OmtpConstants.MAILBOX_UPDATE:
-                Intent serviceIntent = new Intent(mContext, OmtpVvmSyncService.class);
-                serviceIntent.setAction(OmtpVvmSyncService.SYNC_DOWNLOAD_ONLY);
-                serviceIntent.putExtra(OmtpVvmSyncService.EXTRA_PHONE_ACCOUNT, mPhoneAccount);
+                Intent serviceIntent = OmtpVvmSyncService.getSyncIntent(
+                        mContext, OmtpVvmSyncService.SYNC_DOWNLOAD_ONLY, mPhoneAccount,
+                        true /* firstAttempt */);
                 mContext.startService(serviceIntent);
                 break;
             case OmtpConstants.GREETINGS_UPDATE:
@@ -135,9 +135,9 @@
         // Add the source to indicate that it is active.
         vvmSourceManager.addSource(mPhoneAccount);
 
-        Intent serviceIntent = new Intent(mContext, OmtpVvmSyncService.class);
-        serviceIntent.setAction(OmtpVvmSyncService.SYNC_FULL_SYNC);
-        serviceIntent.putExtra(OmtpVvmSyncService.EXTRA_PHONE_ACCOUNT, mPhoneAccount);
+        Intent serviceIntent = OmtpVvmSyncService.getSyncIntent(
+                mContext, OmtpVvmSyncService.SYNC_FULL_SYNC, mPhoneAccount,
+                true /* firstAttempt */);
         mContext.startService(serviceIntent);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
index 59230cc..63e9fee 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
@@ -101,6 +101,7 @@
                 VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
         removePhoneStateListener(phoneAccount);
         mActiveVvmSources.remove(phoneAccount);
+        OmtpVvmSyncService.cancelAllRetries(mContext, phoneAccount);
     }
 
     public void addPhoneStateListener(PhoneAccountHandle phoneAccount) {
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index 1b61cfe..d96f058 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -58,7 +58,7 @@
     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
 
     // Number of retries
-    private static final int NETWORK_RETRY_COUNT = 3;
+    private static final int NETWORK_RETRY_COUNT = 6;
 
     private VoicemailsQueryHelper mQueryHelper;
     private ConnectivityManager mConnectivityManager;
@@ -68,6 +68,62 @@
         super("OmtpVvmSyncService");
     }
 
+    public static Intent getSyncIntent(Context context, String action,
+            PhoneAccountHandle phoneAccount, boolean firstAttempt) {
+        if (firstAttempt) {
+            if (phoneAccount != null) {
+                VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(context,
+                        phoneAccount);
+            } else {
+                OmtpVvmSourceManager vvmSourceManager =
+                        OmtpVvmSourceManager.getInstance(context);
+                Set<PhoneAccountHandle> sources = vvmSourceManager.getOmtpVvmSources();
+                for (PhoneAccountHandle source : sources) {
+                    VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(context, source);
+                }
+            }
+        }
+
+        Intent serviceIntent = new Intent(context, OmtpVvmSyncService.class);
+        serviceIntent.setAction(action);
+        if (phoneAccount != null) {
+            serviceIntent.putExtra(EXTRA_PHONE_ACCOUNT, phoneAccount);
+        }
+
+        cancelRetriesForIntent(context, serviceIntent);
+        return serviceIntent;
+    }
+
+    /**
+     * Cancel all retry syncs for an account.
+     * @param context The context the service runs in.
+     * @param phoneAccount The phone account for which to cancel syncs.
+     */
+    public static void cancelAllRetries(Context context, PhoneAccountHandle phoneAccount) {
+        cancelRetriesForIntent(context, getSyncIntent(context, SYNC_FULL_SYNC, phoneAccount,
+                false));
+    }
+
+    /**
+     * A helper method to cancel all pending alarms for intents that would be identical to the given
+     * intent.
+     * @param context The context the service runs in.
+     * @param intent The intent to search and cancel.
+     */
+    private static void cancelRetriesForIntent(Context context, Intent intent) {
+        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        alarmManager.cancel(PendingIntent.getService(context, 0, intent, 0));
+
+        Intent copyIntent = new Intent(intent);
+        if (SYNC_FULL_SYNC.equals(copyIntent.getAction())) {
+            // A full sync action should also cancel both of the other types of syncs
+            copyIntent.setAction(SYNC_DOWNLOAD_ONLY);
+            alarmManager.cancel(PendingIntent.getService(context, 0, copyIntent, 0));
+            copyIntent.setAction(SYNC_UPLOAD_ONLY);
+            alarmManager.cancel(PendingIntent.getService(context, 0, copyIntent, 0));
+        }
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -87,19 +143,19 @@
         PhoneAccountHandle phoneAccount = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT);
         if (phoneAccount != null) {
             Log.v(TAG, "Sync requested: " + action + " - for account: " + phoneAccount);
-            doSync(phoneAccount, action);
+            setupAndSendNetworkRequest(phoneAccount, action);
         } else {
             Log.v(TAG, "Sync requested: " + action + " - for all accounts");
             OmtpVvmSourceManager vvmSourceManager =
                     OmtpVvmSourceManager.getInstance(this);
             Set<PhoneAccountHandle> sources = vvmSourceManager.getOmtpVvmSources();
             for (PhoneAccountHandle source : sources) {
-                doSync(source, action);
+                setupAndSendNetworkRequest(source, action);
             }
         }
     }
 
-    private void doSync(PhoneAccountHandle phoneAccount, String action) {
+    private void setupAndSendNetworkRequest(PhoneAccountHandle phoneAccount, String action) {
         if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(this, phoneAccount)) {
             Log.v(TAG, "Sync requested for disabled account");
             return;
@@ -136,28 +192,32 @@
 
         @Override
         public void onAvailable(final Network network) {
-            boolean uploadSuccess = true;
-            boolean downloadSuccess = true;
+            boolean uploadSuccess;
+            boolean downloadSuccess;
 
-            ImapHelper imapHelper = new ImapHelper(mContext, mPhoneAccount, network);
-            if (SYNC_FULL_SYNC.equals(mAction) || SYNC_UPLOAD_ONLY.equals(mAction)) {
-                uploadSuccess = upload(imapHelper);
-            }
-            if (SYNC_FULL_SYNC.equals(mAction) || SYNC_DOWNLOAD_ONLY.equals(mAction)) {
-                downloadSuccess = download(imapHelper);
-            }
+            while (mRetryCount > 0) {
+                uploadSuccess = true;
+                downloadSuccess = true;
 
-            releaseNetwork(this);
+                ImapHelper imapHelper = new ImapHelper(mContext, mPhoneAccount, network);
+                if (SYNC_FULL_SYNC.equals(mAction) || SYNC_UPLOAD_ONLY.equals(mAction)) {
+                    uploadSuccess = upload(imapHelper);
+                }
+                if (SYNC_FULL_SYNC.equals(mAction) || SYNC_DOWNLOAD_ONLY.equals(mAction)) {
+                    downloadSuccess = download(imapHelper);
+                }
 
-            Log.v(TAG, "upload succeeded: ["+  String.valueOf(uploadSuccess)
-                    + "] download succeeded: [" + String.valueOf(downloadSuccess) + "]");
+                Log.v(TAG, "upload succeeded: ["+  String.valueOf(uploadSuccess)
+                        + "] download succeeded: [" + String.valueOf(downloadSuccess) + "]");
 
-            if ((!uploadSuccess || !downloadSuccess)) {
-                if (mRetryCount > 0) {
+                // Need to check again for whether visual voicemail is enabled because it could have
+                // been disabled while waiting for the response from the network.
+                if (VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(mContext, mPhoneAccount) &&
+                        (!uploadSuccess || !downloadSuccess)) {
                     mRetryCount--;
                     // Re-adjust so that only the unsuccessful action needs to be retried.
-                    // No need to re-adjust if both are unsuccessful. It means the full sync failed
-                    // so the action remains unchanged.
+                    // No need to re-adjust if both are unsuccessful. It means the full sync
+                    // failed so the action remains unchanged.
                     if (uploadSuccess) {
                         mAction = SYNC_DOWNLOAD_ONLY;
                     } else if (downloadSuccess) {
@@ -165,12 +225,17 @@
                     }
 
                     Log.v(TAG, "Retrying " + mAction);
-
-                    requestNetwork(this);
                 } else {
-                    setRetryAlarm(mPhoneAccount);
+                    // Nothing more to do here, just exit.
+                    releaseNetwork(this);
+                    VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(mContext,
+                            mPhoneAccount);
+                    return;
                 }
             }
+
+            releaseNetwork(this);
+            setRetryAlarm(mPhoneAccount, mAction);
         }
 
         @Override
@@ -181,7 +246,7 @@
                 mRetryCount--;
                 requestNetwork(this);
             } else {
-                setRetryAlarm(mPhoneAccount);
+                setRetryAlarm(mPhoneAccount, mAction);
             }
         }
 
@@ -193,7 +258,7 @@
                 mRetryCount--;
                 requestNetwork(this);
             } else {
-                setRetryAlarm(mPhoneAccount);
+                setRetryAlarm(mPhoneAccount, mAction);
             }
         }
     }
@@ -216,15 +281,22 @@
         return mConnectivityManager;
     }
 
-    private void setRetryAlarm(PhoneAccountHandle phoneAccount) {
+    private void setRetryAlarm(PhoneAccountHandle phoneAccount, String action) {
         Intent serviceIntent = new Intent(this, OmtpVvmSyncService.class);
-        serviceIntent.setAction(OmtpVvmSyncService.SYNC_FULL_SYNC);
+        serviceIntent.setAction(action);
         serviceIntent.putExtra(OmtpVvmSyncService.EXTRA_PHONE_ACCOUNT, phoneAccount);
         PendingIntent pendingIntent = PendingIntent.getService(this, 0, serviceIntent, 0);
+        long retryInterval = VisualVoicemailSettingsUtil.getVisualVoicemailRetryInterval(this,
+                phoneAccount);
+
+        Log.v(TAG, "Retrying "+ action + " in " + retryInterval + "ms");
 
         AlarmManager alarmManager = (AlarmManager)
                 this.getSystemService(Context.ALARM_SERVICE);
-        alarmManager.set(AlarmManager.ELAPSED_REALTIME, 5000, pendingIntent);
+        alarmManager.set(AlarmManager.ELAPSED_REALTIME, retryInterval, pendingIntent);
+
+        VisualVoicemailSettingsUtil.setVisualVoicemailRetryInterval(this, phoneAccount,
+                retryInterval * 2);
     }
 
     private boolean upload(ImapHelper imapHelper) {
@@ -240,7 +312,6 @@
                 mQueryHelper.deleteFromDatabase(deletedVoicemails);
             } else {
                 success = false;
-                mQueryHelper.markUndeletedInDatabase(deletedVoicemails);
             }
         }
 
@@ -249,7 +320,6 @@
                 mQueryHelper.markReadInDatabase(readVoicemails);
             } else {
                 success = false;
-                mQueryHelper.markUnreadInDatabase(readVoicemails);
             }
         }
 
diff --git a/src/com/android/phone/vvm/omtp/sync/VoicemailProviderChangeReceiver.java b/src/com/android/phone/vvm/omtp/sync/VoicemailProviderChangeReceiver.java
index 0885d65..c2e6178 100644
--- a/src/com/android/phone/vvm/omtp/sync/VoicemailProviderChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sync/VoicemailProviderChangeReceiver.java
@@ -30,8 +30,8 @@
         OmtpVvmSourceManager vvmSourceManager =
                 OmtpVvmSourceManager.getInstance(context);
         if (vvmSourceManager.getOmtpVvmSources().size() > 0 && !isSelfChanged) {
-            Intent serviceIntent = new Intent(context, OmtpVvmSyncService.class);
-            serviceIntent.setAction(OmtpVvmSyncService.SYNC_UPLOAD_ONLY);
+            Intent serviceIntent = OmtpVvmSyncService.getSyncIntent(
+                    context, OmtpVvmSyncService.SYNC_UPLOAD_ONLY, null, true /* firstAttempt */);
             context.startService(serviceIntent);
         }
     }
diff --git a/src/com/android/phone/vvm/omtp/sync/VoicemailsQueryHelper.java b/src/com/android/phone/vvm/omtp/sync/VoicemailsQueryHelper.java
index 2a30216..b86351c 100644
--- a/src/com/android/phone/vvm/omtp/sync/VoicemailsQueryHelper.java
+++ b/src/com/android/phone/vvm/omtp/sync/VoicemailsQueryHelper.java
@@ -104,7 +104,10 @@
             while (cursor.moveToNext()) {
                 final long id = cursor.getLong(_ID);
                 final String sourceData = cursor.getString(SOURCE_DATA);
-                Voicemail voicemail = Voicemail.createForUpdate(id, sourceData).build();
+                final boolean isRead = cursor.getInt(IS_READ) == 1;
+                Voicemail voicemail = Voicemail
+                        .createForUpdate(id, sourceData)
+                        .setIsRead(isRead).build();
                 voicemails.add(voicemail);
             }
             return voicemails;
@@ -166,36 +169,9 @@
      * Utility method to mark single message as read.
      */
     public void markReadInDatabase(Voicemail voicemail) {
-        updateInDatabase(voicemail, Voicemails.IS_READ, "1");
-    }
-
-    /**
-     * Undelete in database. This will be called if sync to server fails.
-     */
-    public void markUndeletedInDatabase(List<Voicemail> voicemails) {
-        int count = voicemails.size();
-        for (int i = 0; i < count; i++) {
-            updateInDatabase(voicemails.get(i), Voicemails.DELETED, "0");
-        }
-    }
-
-    /**
-     * Unread in database. This will be called if sync to server fails.
-     */
-    public void markUnreadInDatabase(List<Voicemail> voicemails) {
-        int count = voicemails.size();
-        for (int i = 0; i < count; i++) {
-            updateInDatabase(voicemails.get(i), Voicemails.IS_READ, "0");
-        }
-    }
-
-    /**
-     * Make an update of the requested field in the database.
-     */
-    private void updateInDatabase(Voicemail voicemail, String field, String value) {
         Uri uri = ContentUris.withAppendedId(mSourceUri, voicemail.getId());
         ContentValues contentValues = new ContentValues();
-        contentValues.put(field, value);
+        contentValues.put(Voicemails.IS_READ, "1");
         mContentResolver.update(uri, contentValues, null, null);
     }
 
diff --git a/src/com/android/phone/vvm/omtp/sync/VvmPhoneStateListener.java b/src/com/android/phone/vvm/omtp/sync/VvmPhoneStateListener.java
index 30f5a45..d4b84d4 100644
--- a/src/com/android/phone/vvm/omtp/sync/VvmPhoneStateListener.java
+++ b/src/com/android/phone/vvm/omtp/sync/VvmPhoneStateListener.java
@@ -50,9 +50,9 @@
                             VoicemailContract.Status.DATA_CHANNEL_STATE_OK,
                             VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK);
                     // Run a full sync in case something was missed while signal was down.
-                    Intent serviceIntent = new Intent(mContext, OmtpVvmSyncService.class);
-                    serviceIntent.setAction(OmtpVvmSyncService.SYNC_FULL_SYNC);
-                    serviceIntent.putExtra(OmtpVvmSyncService.EXTRA_PHONE_ACCOUNT, mPhoneAccount);
+                    Intent serviceIntent = OmtpVvmSyncService.getSyncIntent(
+                            mContext, OmtpVvmSyncService.SYNC_FULL_SYNC, mPhoneAccount,
+                            true /* firstAttempt */);
                     mContext.startService(serviceIntent);
                 }
             } else {