Perform syncs with the voicemail server through data connection only.
This change covers anytime the voicemail provider data changes, initial
download of voicemails and syncing when receiving a "Mailbox Update"
sync request.
Bug: 20345518
Change-Id: Ib0ca46e4a70312a581d8f99dfa6a3e389e440162
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f25b90f..3c5e5d5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -641,16 +641,6 @@
</intent-filter>
</receiver>
<service
- android:name="com.android.phone.vvm.omtp.sync.OmtpVvmSyncService"
- android:exported="true"
- android:process=":sync">
- <intent-filter>
- <action android:name="android.content.SyncAdapter"/>
- </intent-filter>
- <meta-data android:name="android.content.SyncAdapter"
- android:resource="@xml/syncadapter" />
- </service>
- <service
android:name="android.telecom.AuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/>
@@ -677,5 +667,20 @@
android:mimeType="vnd.android.cursor.item/voicemail" />
</intent-filter>
</receiver>
+ <receiver
+ android:name="com.android.phone.vvm.omtp.sync.VoicemailProviderChangeReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.PROVIDER_CHANGED" />
+ <data
+ android:scheme="content"
+ android:host="com.android.voicemail"
+ android:mimeType="vnd.android.cursor.dir/voicemails"/>
+ </intent-filter>
+ </receiver>
+ <service
+ android:name="com.android.phone.vvm.omtp.sync.OmtpVvmSyncService"
+ android:exported="false"
+ />
</application>
</manifest>
diff --git a/res/xml/syncadapter.xml b/res/xml/syncadapter.xml
deleted file mode 100644
index b0484b1..0000000
--- a/res/xml/syncadapter.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<sync-adapter
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:contentAuthority="com.android.voicemail"
- android:accountType="com.android.phone.vvm.omtp"
- android:userVisible="false"
- android:supportsUploading="true"
- android:allowParallelSyncs="false"
- android:isAlwaysSyncable="true"/>
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 13e9c74..cd7a694 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -108,9 +108,9 @@
}
Log.i(TAG, "Requesting VVM activation for subId: " + subId);
- SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
OmtpMessageSender messageSender = null;
+ SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
switch (vvmType) {
case TelephonyManager.VVM_TYPE_OMTP:
messageSender = new OmtpStandardMessageSender(smsManager, (short) applicationPort,
diff --git a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
index 179fec3..0110d65 100644
--- a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
+++ b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
@@ -128,8 +128,6 @@
private class OmtpVvmNetworkRequestCallback extends ConnectivityManager.NetworkCallback {
@Override
public void onAvailable(final Network network) {
- super.onAvailable(network);
-
Executor executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
@Override
@@ -143,13 +141,11 @@
@Override
public void onLost(Network network) {
- super.onLost(network);
releaseNetwork();
}
@Override
public void onUnavailable() {
- super.onUnavailable();
releaseNetwork();
}
}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 7a2f5ad..222ea26 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -17,10 +17,8 @@
import android.accounts.Account;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.os.Bundle;
import android.provider.Telephony;
import android.provider.VoicemailContract;
import android.telecom.PhoneAccountHandle;
@@ -32,7 +30,7 @@
import com.android.phone.PhoneUtils;
import com.android.phone.vvm.omtp.OmtpConstants;
import com.android.phone.vvm.omtp.sync.OmtpVvmSyncAccountManager;
-import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService.OmtpVvmSyncAdapter;
+import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
import com.android.phone.vvm.omtp.sync.VoicemailsQueryHelper;
/**
@@ -96,12 +94,12 @@
queryHelper.insertIfUnique(voicemail);
break;
case OmtpConstants.MAILBOX_UPDATE:
- // Needs a total resync
- Bundle bundle = new Bundle();
- bundle.putBoolean(OmtpVvmSyncAdapter.SYNC_EXTRAS_DOWNLOAD, true);
- ContentResolver.requestSync(
- new Account(mPhoneAccount.getId(), OmtpVvmSyncAccountManager.ACCOUNT_TYPE),
- VoicemailContract.AUTHORITY, bundle);
+ Account account = new Account(
+ mPhoneAccount.getId(), OmtpVvmSyncAccountManager.ACCOUNT_TYPE);
+ Intent serviceIntent = new Intent(mContext, OmtpVvmSyncService.class);
+ serviceIntent.setAction(OmtpVvmSyncService.SYNC_DOWNLOAD_ONLY);
+ serviceIntent.putExtra(OmtpVvmSyncService.EXTRA_ACCOUNT, account);
+ mContext.startService(serviceIntent);
break;
case OmtpConstants.GREETINGS_UPDATE:
// Not implemented in V1
@@ -135,9 +133,9 @@
// Add a phone state listener so that changes to the communication channels can be recorded.
vvmAccountSyncManager.addPhoneStateListener(account);
- Bundle bundle = new Bundle();
- bundle.putBoolean(OmtpVvmSyncAdapter.SYNC_EXTRAS_DOWNLOAD, true);
- bundle.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
- ContentResolver.requestSync(account, VoicemailContract.AUTHORITY, bundle);
+ Intent serviceIntent = new Intent(mContext, OmtpVvmSyncService.class);
+ serviceIntent.setAction(OmtpVvmSyncService.SYNC_FULL_SYNC);
+ serviceIntent.putExtra(OmtpVvmSyncService.EXTRA_ACCOUNT, account);
+ mContext.startService(serviceIntent);
}
}
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncAccountManager.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncAccountManager.java
index fcb284e..6554b71 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncAccountManager.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncAccountManager.java
@@ -133,6 +133,10 @@
mTelephonyManager.listen(phoneStateListener, 0);
}
+ public Account[] getOmtpAccounts() {
+ return mAccountManager.getAccountsByType(ACCOUNT_TYPE);
+ }
+
/**
* Check if a certain account is registered.
*
@@ -141,7 +145,7 @@
* accounts. {@code false} otherwise.
*/
public boolean isAccountRegistered(Account account) {
- Account[] accounts = mAccountManager.getAccountsByType(ACCOUNT_TYPE);
+ Account[] accounts = getOmtpAccounts();
for (int i = 0; i < accounts.length; i++) {
if (account.equals(accounts[i])) {
return true;
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index 7fb4df2..0a6b5c1 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -13,27 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
-
-/**
- * A {@link Service} which runs the internal implementation of {@link AbstractThreadedSyncAdapter},
- * syncing voicemails to and from a visual voicemail server.
- */
-
package com.android.phone.vvm.omtp.sync;
import android.accounts.Account;
-import android.app.Service;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
+import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
-import android.content.SyncResult;
-import android.os.Bundle;
-import android.os.IBinder;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.provider.VoicemailContract;
import android.telecom.Voicemail;
+import android.util.Log;
+import com.android.phone.PhoneUtils;
import com.android.phone.vvm.omtp.imap.ImapHelper;
import java.util.HashMap;
@@ -41,108 +36,177 @@
import java.util.Map;
/**
- * A service to run the VvmSyncAdapter.
+ * Sync OMTP visual voicemail.
*/
-public class OmtpVvmSyncService extends Service {
- // Storage for an instance of the sync adapter
- private static OmtpVvmSyncAdapter sSyncAdapter = null;
- // Object to use as a thread-safe lock
- private static final Object sSyncAdapterLock = new Object();
+public class OmtpVvmSyncService extends IntentService {
+ private static final String TAG = OmtpVvmSyncService.class.getSimpleName();
+
+ /** Signifies a sync with both uploading to the server and downloading from the server. */
+ public static final String SYNC_FULL_SYNC = "full_sync";
+ /** Only upload to the server. */
+ public static final String SYNC_UPLOAD_ONLY = "upload_only";
+ /** Only download from the server. */
+ public static final String SYNC_DOWNLOAD_ONLY = "download_only";
+ /** The account to sync. */
+ public static final String EXTRA_ACCOUNT = "account";
+
+ // Timeout used to call ConnectivityManager.requestNetwork
+ private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
+
+ private VoicemailsQueryHelper mQueryHelper;
+
+ private ConnectivityManager mConnectivityManager;
+
+ public OmtpVvmSyncService() {
+ super("OmtpVvmSyncService");
+ }
@Override
public void onCreate() {
- synchronized (sSyncAdapterLock) {
- if (sSyncAdapter == null) {
- sSyncAdapter = new OmtpVvmSyncAdapter(getApplicationContext(), true);
- }
- }
+ super.onCreate();
+ mQueryHelper = new VoicemailsQueryHelper(this);
}
@Override
- public IBinder onBind(Intent intent) {
- return sSyncAdapter.getSyncAdapterBinder();
+ protected void onHandleIntent(Intent intent) {
+ if (intent == null) {
+ Log.d(TAG, "onHandleIntent: could not handle null intent");
+ return;
+ }
+
+ String action = intent.getAction();
+ OmtpVvmSyncAccountManager syncAccountManager = OmtpVvmSyncAccountManager.getInstance(this);
+ Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ if (account != null && syncAccountManager.isAccountRegistered(account)) {
+ Log.v(TAG, "Sync requested: " + action + " - for account: " + account.name);
+ doSync(account, action);
+ } else {
+ Log.v(TAG, "Sync requested: " + action + " - for all accounts");
+ Account[] accounts = syncAccountManager.getOmtpAccounts();
+ for (int i = 0; i < accounts.length; i++) {
+ doSync(accounts[i], action);
+ }
+ }
}
- public class OmtpVvmSyncAdapter extends AbstractThreadedSyncAdapter {
- /**
- * Sync triggers should pass this extra to clear the database and freshly populate from the
- * server.
- */
- public static final String SYNC_EXTRAS_DOWNLOAD = "extra_download";
+ private void doSync(Account account, String action) {
+ int subId = PhoneUtils.getSubIdForPhoneAccountHandle(
+ PhoneUtils.makePstnPhoneAccountHandle(account.name));
- private Context mContext;
+ NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .setNetworkSpecifier(Integer.toString(subId))
+ .build();
+ NetworkCallback networkCallback = new OmtpVvmNetworkRequestCallback(this, account, action);
+ getConnectivityManager().requestNetwork(
+ networkRequest, networkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
+ }
- public OmtpVvmSyncAdapter(Context context, boolean autoInitialize) {
- super(context, autoInitialize);
+ private class OmtpVvmNetworkRequestCallback extends ConnectivityManager.NetworkCallback {
+ Context mContext;
+ Account mAccount;
+ String mAction;
+
+ public OmtpVvmNetworkRequestCallback(Context context, Account account, String action) {
mContext = context;
+ mAccount = account;
+ mAction = action;
}
@Override
- public void onPerformSync(Account account, Bundle extras, String authority,
- ContentProviderClient provider, SyncResult syncResult) {
- ImapHelper imapHelper = new ImapHelper(mContext, account, null);
- VoicemailsQueryHelper queryHelper = new VoicemailsQueryHelper(mContext);
-
- if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
- List<Voicemail> readVoicemails = queryHelper.getReadVoicemails();
- List<Voicemail> deletedVoicemails = queryHelper.getDeletedVoicemails();
-
- if (deletedVoicemails != null &&
- imapHelper.markMessagesAsDeleted(deletedVoicemails)) {
- // We want to delete selectively instead of all the voicemails for this provider
- // in case the state changed since the IMAP query was completed.
- queryHelper.deleteFromDatabase(deletedVoicemails);
- }
-
- if (readVoicemails != null && imapHelper.markMessagesAsRead(readVoicemails)) {
- queryHelper.markReadInDatabase(readVoicemails);
- }
+ public void onAvailable(final Network network) {
+ ImapHelper imapHelper = new ImapHelper(mContext, mAccount, network);
+ if (SYNC_FULL_SYNC.equals(mAction) || SYNC_UPLOAD_ONLY.equals(mAction)) {
+ upload(imapHelper);
}
+ if (SYNC_FULL_SYNC.equals(mAction) || SYNC_DOWNLOAD_ONLY.equals(mAction)) {
+ download(imapHelper);
+ }
+ releaseNetwork();
+ }
- if (extras.getBoolean(SYNC_EXTRAS_DOWNLOAD, false)) {
- List<Voicemail> serverVoicemails = imapHelper.fetchAllVoicemails();
- List<Voicemail> localVoicemails = queryHelper.getAllVoicemails();
+ @Override
+ public void onLost(Network network) {
+ releaseNetwork();
+ }
- if (localVoicemails == null || serverVoicemails == null) {
- // Null value means the query failed.
- return;
- }
+ @Override
+ public void onUnavailable() {
+ releaseNetwork();
+ }
- Map<String, Voicemail> remoteMap = buildMap(serverVoicemails);
+ private void releaseNetwork() {
+ getConnectivityManager().unregisterNetworkCallback(this);
+ }
+ }
- // Go through all the local voicemails and check if they are on the server.
- // They may be read or deleted on the server but not locally. Perform the
- // appropriate local operation if the status differs from the server. Remove
- // the messages that exist both locally and on the server to know which server
- // messages to insert locally.
- for (int i = 0; i < localVoicemails.size(); i++) {
- Voicemail localVoicemail = localVoicemails.get(i);
- Voicemail remoteVoicemail = remoteMap.remove(localVoicemail.getSourceData());
- if (remoteVoicemail == null) {
- queryHelper.deleteFromDatabase(localVoicemail);
- } else {
- if (remoteVoicemail.isRead() != localVoicemail.isRead()) {
- queryHelper.markReadInDatabase(localVoicemail);
- }
- }
- }
+ private ConnectivityManager getConnectivityManager() {
+ if (mConnectivityManager == null) {
+ mConnectivityManager = (ConnectivityManager) this.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ }
+ return mConnectivityManager;
+ }
- // The leftover messages are messages that exist on the server but not locally.
- for (Voicemail remoteVoicemail : remoteMap.values()) {
- VoicemailContract.Voicemails.insert(mContext, remoteVoicemail);
+ private void upload(ImapHelper imapHelper) {
+ List<Voicemail> readVoicemails = mQueryHelper.getReadVoicemails();
+ List<Voicemail> deletedVoicemails = mQueryHelper.getDeletedVoicemails();
+
+ if (deletedVoicemails != null &&
+ imapHelper.markMessagesAsDeleted(deletedVoicemails)) {
+ // We want to delete selectively instead of all the voicemails for this provider
+ // in case the state changed since the IMAP query was completed.
+ mQueryHelper.deleteFromDatabase(deletedVoicemails);
+ }
+
+ if (readVoicemails != null && imapHelper.markMessagesAsRead(readVoicemails)) {
+ mQueryHelper.markReadInDatabase(readVoicemails);
+ }
+ }
+
+ private void download(ImapHelper imapHelper) {
+ List<Voicemail> serverVoicemails = imapHelper.fetchAllVoicemails();
+ List<Voicemail> localVoicemails = mQueryHelper.getAllVoicemails();
+
+ if (localVoicemails == null || serverVoicemails == null) {
+ // Null value means the query failed.
+ return;
+ }
+
+ Map<String, Voicemail> remoteMap = buildMap(serverVoicemails);
+
+ // Go through all the local voicemails and check if they are on the server.
+ // They may be read or deleted on the server but not locally. Perform the
+ // appropriate local operation if the status differs from the server. Remove
+ // the messages that exist both locally and on the server to know which server
+ // messages to insert locally.
+ for (int i = 0; i < localVoicemails.size(); i++) {
+ Voicemail localVoicemail = localVoicemails.get(i);
+ Voicemail remoteVoicemail = remoteMap.remove(localVoicemail.getSourceData());
+ if (remoteVoicemail == null) {
+ mQueryHelper.deleteFromDatabase(localVoicemail);
+ } else {
+ if (remoteVoicemail.isRead() != localVoicemail.isRead()) {
+ mQueryHelper.markReadInDatabase(localVoicemail);
}
}
}
- /**
- * Builds a map from provider data to message for the given collection of voicemails.
- */
- private Map<String, Voicemail> buildMap(List<Voicemail> messages) {
- Map<String, Voicemail> map = new HashMap<String, Voicemail>();
- for (Voicemail message : messages) {
- map.put(message.getSourceData(), message);
- }
- return map;
+ // The leftover messages are messages that exist on the server but not locally.
+ for (Voicemail remoteVoicemail : remoteMap.values()) {
+ VoicemailContract.Voicemails.insert(this, remoteVoicemail);
}
}
+
+ /**
+ * Builds a map from provider data to message for the given collection of voicemails.
+ */
+ private Map<String, Voicemail> buildMap(List<Voicemail> messages) {
+ Map<String, Voicemail> map = new HashMap<String, Voicemail>();
+ for (Voicemail message : messages) {
+ map.put(message.getSourceData(), message);
+ }
+ return map;
+ }
}
diff --git a/src/com/android/phone/vvm/omtp/sync/VoicemailProviderChangeReceiver.java b/src/com/android/phone/vvm/omtp/sync/VoicemailProviderChangeReceiver.java
new file mode 100644
index 0000000..fc1823e
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sync/VoicemailProviderChangeReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Receives changes to the voicemail provider so they can be sent to the voicemail server.
+ */
+public class VoicemailProviderChangeReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ OmtpVvmSyncAccountManager syncAccountManager =
+ OmtpVvmSyncAccountManager.getInstance(context);
+ if (syncAccountManager.getOmtpAccounts().length > 0) {
+ Intent serviceIntent = new Intent(context, OmtpVvmSyncService.class);
+ serviceIntent.setAction(OmtpVvmSyncService.SYNC_UPLOAD_ONLY);
+ context.startService(serviceIntent);
+ }
+ }
+}