Fill in details for VVM sync and activation.

- Save new voicemail locally
- Query for local changes to be synced to the server
- Save IMAP credentials to sync account "userData" bundle

At this point, the activation stage except for the activate SMS
and all data sync links except for IMAP will be complete.

Bug: 19236241

Change-Id: I51fbd88c244948fb48f9626c4265a1d308bcac3a
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
index a4cf4e8..b891c70 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import com.android.phone.PhoneUtils;
+import com.android.phone.vvm.omtp.sms.StatusMessage;
 
 /**
  * A singleton class designed to assist in OMTP visual voicemail sync behavior.
@@ -118,4 +119,20 @@
         }
         return false;
     }
+
+    /**
+     * Set the IMAP credentials as extra fields in the account.
+     *
+     * @param account The account to add credentials to.
+     * @param message The status message to extract the fields from.
+     */
+    public void setAccountCredentialsFromStatusMessage(Account account, StatusMessage message) {
+        mAccountManager.setUserData(account, OmtpConstants.IMAP_PORT, message.getImapPort());
+        mAccountManager.setUserData(account, OmtpConstants.SERVER_ADDRESS,
+                message.getServerAddress());
+        mAccountManager.setUserData(account, OmtpConstants.IMAP_USER_NAME,
+                message.getImapUserName());
+        mAccountManager.setUserData(account, OmtpConstants.IMAP_PASSWORD,
+                message.getImapPassword());
+    }
 }
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
index 811e7e6..471baab 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
@@ -25,11 +25,19 @@
 import android.app.Service;
 import android.content.AbstractThreadedSyncAdapter;
 import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SyncResult;
+import android.database.Cursor;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.telecom.Voicemail;
+
+import com.android.phone.vvm.omtp.sync.DirtyVoicemailQuery;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A service to run the VvmSyncAdapter.
@@ -40,6 +48,8 @@
     // Object to use as a thread-safe lock
     private static final Object sSyncAdapterLock = new Object();
 
+    private Context mContext;
+
     @Override
     public void onCreate() {
         synchronized (sSyncAdapterLock) {
@@ -57,12 +67,40 @@
     public class OmtpVvmSyncAdapter extends AbstractThreadedSyncAdapter {
         public OmtpVvmSyncAdapter(Context context, boolean autoInitialize) {
             super(context, autoInitialize);
+            mContext = context;
         }
 
         @Override
         public void onPerformSync(Account account, Bundle extras, String authority,
                 ContentProviderClient provider, SyncResult syncResult) {
-            // TODO: Write code necessary for syncing.
+            if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+                List<Voicemail> readVoicemails = new ArrayList<Voicemail>();
+                List<Voicemail> deletedVoicemails = new ArrayList<Voicemail>();
+
+                Cursor cursor = DirtyVoicemailQuery.getDirtyVoicemails(mContext);
+                if (cursor == null) {
+                    return;
+                }
+                try {
+                    while (cursor.moveToNext()) {
+                        final long id = cursor.getLong(DirtyVoicemailQuery._ID);
+                        final String sourceData = cursor.getString(DirtyVoicemailQuery.SOURCE_DATA);
+                        final boolean isRead = cursor.getInt(DirtyVoicemailQuery.IS_READ) == 1;
+                        final boolean deleted = cursor.getInt(DirtyVoicemailQuery.DELETED) == 1;
+                        Voicemail voicemail = Voicemail.createForUpdate(id, sourceData).build();
+                        if (deleted) {
+                            // Check deleted first because if the voicemail is deleted, there's no
+                            // need to mark as read.
+                            deletedVoicemails.add(voicemail);
+                        } else if (isRead) {
+                            readVoicemails.add(voicemail);
+                        }
+                    }
+                } finally {
+                    cursor.close();
+                }
+                //TODO: send to server via IMAP
+            }
         }
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index c00441a..9181e50 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -24,6 +24,7 @@
 import android.provider.Telephony;
 import android.provider.VoicemailContract;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.Voicemail;
 import android.telephony.SmsMessage;
 import android.util.Log;
 
@@ -62,10 +63,10 @@
         if (messageData != null) {
             if (messageData.getPrefix() == OmtpConstants.SYNC_SMS_PREFIX) {
                 SyncMessage message = new SyncMessage(messageData);
-                //TODO: handle message
+                processSync(message);
             } else if (messageData.getPrefix() == OmtpConstants.STATUS_SMS_PREFIX) {
                 StatusMessage message = new StatusMessage(messageData);
-                handleStatusMessage(message);
+                updateAccount(message);
             } else {
                 Log.e(TAG, "This should never have happened");
             }
@@ -83,20 +84,61 @@
         }
     }
 
-    private void handleStatusMessage(StatusMessage message) {
-        OmtpVvmSyncAccountManager vvmSyncManager = OmtpVvmSyncAccountManager.getInstance(mContext);
+    /**
+     * A sync message has two purposes: to signal a new voicemail message, and to indicate the
+     * voicemails on the server have changed remotely (usually through the TUI). Save the new
+     * message to the voicemail provider if it is the former case and perform a full sync in the
+     * latter case.
+     *
+     * @param message The sync message to extract data from.
+     */
+    private void processSync(SyncMessage message) {
+        switch (message.getSyncTriggerEvent()) {
+            case OmtpConstants.NEW_MESSAGE:
+                Voicemail voicemail = Voicemail.createForInsertion(
+                        message.getTimestampMillis(), message.getSender())
+                        .setSourceData(message.getId())
+                        .setDuration(message.getLength())
+                        .setSourcePackage(mContext.getPackageName())
+                        .build();
+
+                VoicemailContract.Voicemails.insert(mContext, voicemail);
+                break;
+            case OmtpConstants.MAILBOX_UPDATE:
+                // Needs a total resync
+                ContentResolver.requestSync(
+                        new Account(mPhoneAccount.getId(), OmtpVvmSyncAccountManager.ACCOUNT_TYPE),
+                        VoicemailContract.AUTHORITY, new Bundle());
+                break;
+            case OmtpConstants.GREETINGS_UPDATE:
+                // Not implemented in V1
+                break;
+           default:
+               Log.e(TAG, "Unrecognized sync trigger event: "+message.getSyncTriggerEvent());
+               break;
+        }
+    }
+
+    private void updateAccount(StatusMessage message) {
+        OmtpVvmSyncAccountManager vvmAccountSyncManager =
+                OmtpVvmSyncAccountManager.getInstance(mContext);
         Account account = new Account(mPhoneAccount.getId(),
                 OmtpVvmSyncAccountManager.ACCOUNT_TYPE);
 
-        if (!vvmSyncManager.isAccountRegistered(account)) {
+        if (!vvmAccountSyncManager.isAccountRegistered(account)) {
             // If the account has not been previously registered, it means that this STATUS sms
             // is a result of the ACTIVATE sms, so register the voicemail source.
-            vvmSyncManager.createSyncAccount(account);
+            vvmAccountSyncManager.createSyncAccount(account);
             VoicemailContract.Status.setStatus(mContext, mPhoneAccount,
                     VoicemailContract.Status.CONFIGURATION_STATE_OK,
                     VoicemailContract.Status.DATA_CHANNEL_STATE_OK,
                     VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK);
         }
+
+        // Save the IMAP credentials in the corresponding account object so they are
+        // persistent and can be retrieved.
+        vvmAccountSyncManager.setAccountCredentialsFromStatusMessage(account, message);
+
         ContentResolver.requestSync(account, VoicemailContract.AUTHORITY, new Bundle());
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/sync/DirtyVoicemailQuery.java b/src/com/android/phone/vvm/omtp/sync/DirtyVoicemailQuery.java
new file mode 100644
index 0000000..965b36b
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sync/DirtyVoicemailQuery.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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
+ */
+package com.android.phone.vvm.omtp.sync;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Voicemails;
+
+/**
+ * Construct a query to get dirty voicemails.
+ */
+public class DirtyVoicemailQuery {
+    final static String[] PROJECTION = new String[] {
+            Voicemails._ID,              // 0
+            Voicemails.SOURCE_DATA,      // 1
+            Voicemails.IS_READ,          // 2
+            Voicemails.DELETED,          // 3
+    };
+
+    public static final int _ID = 0;
+    public static final int SOURCE_DATA = 1;
+    public static final int IS_READ = 2;
+    public static final int DELETED = 3;
+
+    final static String SELECTION = Voicemails.DIRTY + "=1";
+
+    /**
+     * Get all the locally modified voicemails that have not been synced to the server.
+     *
+     * @param context The context from the package calling the method. This will be the source.
+     * @return A list of all locally modified voicemails.
+     */
+    public static Cursor getDirtyVoicemails(Context context) {
+        ContentResolver contentResolver = context.getContentResolver();
+        Uri statusUri = VoicemailContract.Voicemails.buildSourceUri(context.getPackageName());
+        return contentResolver.query(statusUri, PROJECTION, SELECTION, null, null);
+    }
+}