blob: 3985069145e36dfedcddf8b3a603b0bf29b101f2 [file] [log] [blame]
/*
* 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.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.Uri;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Status;
import android.telecom.PhoneAccountHandle;
import android.telecom.Voicemail;
import android.text.TextUtils;
import android.util.Log;
import com.android.phone.VoicemailUtils;
import com.android.phone.settings.VisualVoicemailSettingsUtil;
import com.android.phone.vvm.omtp.LocalLogHelper;
import com.android.phone.vvm.omtp.imap.ImapHelper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Sync OMTP visual voicemail.
*/
public class OmtpVvmSyncService extends IntentService {
private static final String TAG = OmtpVvmSyncService.class.getSimpleName();
// Number of retries
private static final int NETWORK_RETRY_COUNT = 3;
/** 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";
/** Only download single voicemail transcription. */
public static final String SYNC_DOWNLOAD_ONE_TRANSCRIPTION =
"download_one_transcription";
/** The account to sync. */
public static final String EXTRA_PHONE_ACCOUNT = "phone_account";
/** The voicemail to fetch. */
public static final String EXTRA_VOICEMAIL = "voicemail";
// Minimum time allowed between full syncs
private static final int MINIMUM_FULL_SYNC_INTERVAL_MILLIS = 60 * 1000;
private VoicemailsQueryHelper mQueryHelper;
public OmtpVvmSyncService() {
super("OmtpVvmSyncService");
}
public static Intent getSyncIntent(Context context, String action,
PhoneAccountHandle phoneAccount, boolean firstAttempt) {
return getSyncIntent(context, action, phoneAccount, null, firstAttempt);
}
public static Intent getSyncIntent(Context context, String action,
PhoneAccountHandle phoneAccount, Voicemail voicemail, 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);
}
if (voicemail != null) {
serviceIntent.putExtra(EXTRA_VOICEMAIL, voicemail);
}
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();
mQueryHelper = new VoicemailsQueryHelper(this);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
Log.d(TAG, "onHandleIntent: could not handle null intent");
return;
}
String action = intent.getAction();
PhoneAccountHandle phoneAccount = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT);
LocalLogHelper.log(TAG, "Sync requested: " + action +
" for all accounts: " + String.valueOf(phoneAccount == null));
Voicemail voicemail = intent.getParcelableExtra(EXTRA_VOICEMAIL);
if (phoneAccount != null) {
Log.v(TAG, "Sync requested: " + action + " - for account: " + phoneAccount);
setupAndSendRequest(phoneAccount, voicemail, 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) {
setupAndSendRequest(source, null, action);
}
}
}
private void setupAndSendRequest(PhoneAccountHandle phoneAccount, Voicemail voicemail,
String action) {
if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(this, phoneAccount)) {
Log.v(TAG, "Sync requested for disabled account");
return;
}
if (SYNC_FULL_SYNC.equals(action)) {
long lastSyncTime = VisualVoicemailSettingsUtil.getVisualVoicemailLastFullSyncTime(
this, phoneAccount);
long currentTime = System.currentTimeMillis();
if (currentTime - lastSyncTime < MINIMUM_FULL_SYNC_INTERVAL_MILLIS) {
// If it's been less than a minute since the last sync, bail.
Log.v(TAG, "Avoiding duplicate full sync: synced recently for "
+ phoneAccount.getId());
return;
}
VisualVoicemailSettingsUtil.setVisualVoicemailLastFullSyncTime(
this, phoneAccount, currentTime);
}
VvmNetworkRequestCallback networkCallback = new SyncNetworkRequestCallback(this,
phoneAccount, voicemail, action);
networkCallback.requestNetwork();
}
private void doSync(Network network, VvmNetworkRequestCallback callback,
PhoneAccountHandle phoneAccount, Voicemail voicemail, String action) {
int retryCount = NETWORK_RETRY_COUNT;
try {
while (retryCount > 0) {
ImapHelper imapHelper = new ImapHelper(this, phoneAccount, network);
if (!imapHelper.isSuccessfullyInitialized()) {
Log.w(TAG, "Can't retrieve Imap credentials.");
VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this,
phoneAccount);
return;
}
boolean success = true;
if (voicemail == null) {
success = syncAll(action, imapHelper);
} else {
success = syncOne(imapHelper, voicemail);
}
imapHelper.updateQuota();
// 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(this, phoneAccount) &&
!success) {
retryCount--;
Log.v(TAG, "Retrying " + action);
} else {
// Nothing more to do here, just exit.
VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this,
phoneAccount);
VoicemailUtils.setDataChannelState(
this, phoneAccount, Status.DATA_CHANNEL_STATE_OK);
return;
}
}
} finally {
if (callback != null) {
callback.releaseNetwork();
}
}
}
private boolean syncAll(String action, ImapHelper imapHelper) {
boolean uploadSuccess = true;
boolean downloadSuccess = true;
if (SYNC_FULL_SYNC.equals(action) || SYNC_UPLOAD_ONLY.equals(action)) {
uploadSuccess = upload(imapHelper);
}
if (SYNC_FULL_SYNC.equals(action) || SYNC_DOWNLOAD_ONLY.equals(action)) {
downloadSuccess = download(imapHelper);
}
Log.v(TAG, "upload succeeded: [" + String.valueOf(uploadSuccess)
+ "] download succeeded: [" + String.valueOf(downloadSuccess) + "]");
boolean success = uploadSuccess && downloadSuccess;
if (!uploadSuccess || !downloadSuccess) {
if (uploadSuccess) {
action = SYNC_DOWNLOAD_ONLY;
} else if (downloadSuccess) {
action = SYNC_UPLOAD_ONLY;
}
}
return success;
}
private boolean syncOne(ImapHelper imapHelper, Voicemail voicemail) {
return imapHelper.fetchTranscription(
new TranscriptionFetchedCallback(this, voicemail),
voicemail.getSourceData());
}
private class SyncNetworkRequestCallback extends VvmNetworkRequestCallback {
Voicemail mVoicemail;
private String mAction;
public SyncNetworkRequestCallback(Context context, PhoneAccountHandle phoneAccount,
Voicemail voicemail, String action) {
super(context, phoneAccount);
mAction = action;
mVoicemail = voicemail;
}
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
NetworkInfo info = getConnectivityManager().getNetworkInfo(network);
Log.d(TAG, "Network Type: " + info.getTypeName());
doSync(network, this, mPhoneAccount, mVoicemail, mAction);
}
}
private boolean upload(ImapHelper imapHelper) {
List<Voicemail> readVoicemails = mQueryHelper.getReadVoicemails();
List<Voicemail> deletedVoicemails = mQueryHelper.getDeletedVoicemails();
boolean success = true;
if (deletedVoicemails.size() > 0) {
if (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);
} else {
success = false;
}
}
if (readVoicemails.size() > 0) {
if (imapHelper.markMessagesAsRead(readVoicemails)) {
mQueryHelper.markReadInDatabase(readVoicemails);
} else {
success = false;
}
}
return success;
}
private boolean 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 false;
}
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);
}
if (!TextUtils.isEmpty(remoteVoicemail.getTranscription()) &&
TextUtils.isEmpty(localVoicemail.getTranscription())) {
mQueryHelper.updateWithTranscription(localVoicemail,
remoteVoicemail.getTranscription());
}
}
}
// The leftover messages are messages that exist on the server but not locally.
for (Voicemail remoteVoicemail : remoteMap.values()) {
VoicemailContract.Voicemails.insert(this, remoteVoicemail);
}
return true;
}
protected void setRetryAlarm(PhoneAccountHandle phoneAccount, String action) {
Intent serviceIntent = new Intent(this, OmtpVvmSyncService.class);
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.RTC, System.currentTimeMillis() + retryInterval,
pendingIntent);
VisualVoicemailSettingsUtil.setVisualVoicemailRetryInterval(this, phoneAccount,
retryInterval * 2);
}
/**
* 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;
}
public class TranscriptionFetchedCallback {
private Context mContext;
private Voicemail mVoicemail;
public TranscriptionFetchedCallback(Context context, Voicemail voicemail) {
mContext = context;
mVoicemail = voicemail;
}
public void setVoicemailTranscription(String transcription) {
VoicemailsQueryHelper queryHelper = new VoicemailsQueryHelper(mContext);
queryHelper.updateWithTranscription(mVoicemail, transcription);
}
}
}