• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 package com.android.phone.vvm.omtp.sync;
17 
18 import android.app.AlarmManager;
19 import android.app.IntentService;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.net.ConnectivityManager;
24 import android.net.Network;
25 import android.net.ConnectivityManager.NetworkCallback;
26 import android.net.NetworkCapabilities;
27 import android.net.NetworkRequest;
28 import android.provider.VoicemailContract;
29 import android.telecom.PhoneAccountHandle;
30 import android.telecom.Voicemail;
31 import android.telephony.TelephonyManager;
32 import android.util.Log;
33 
34 import com.android.phone.PhoneUtils;
35 import com.android.phone.settings.VisualVoicemailSettingsUtil;
36 import com.android.phone.vvm.omtp.LocalLogHelper;
37 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
38 import com.android.phone.vvm.omtp.imap.ImapHelper;
39 
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 
45 /**
46  * Sync OMTP visual voicemail.
47  */
48 public class OmtpVvmSyncService extends IntentService {
49     private static final String TAG = OmtpVvmSyncService.class.getSimpleName();
50 
51     /** Signifies a sync with both uploading to the server and downloading from the server. */
52     public static final String SYNC_FULL_SYNC = "full_sync";
53     /** Only upload to the server. */
54     public static final String SYNC_UPLOAD_ONLY = "upload_only";
55     /** Only download from the server. */
56     public static final String SYNC_DOWNLOAD_ONLY = "download_only";
57     /** The account to sync. */
58     public static final String EXTRA_PHONE_ACCOUNT = "phone_account";
59 
60     // Timeout used to call ConnectivityManager.requestNetwork
61     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
62 
63     // Minimum time allowed between full syncs
64     private static final int MINIMUM_FULL_SYNC_INTERVAL_MILLIS = 60 * 1000;
65 
66     // Number of retries
67     private static final int NETWORK_RETRY_COUNT = 6;
68 
69     private VoicemailsQueryHelper mQueryHelper;
70     private ConnectivityManager mConnectivityManager;
71 
OmtpVvmSyncService()72     public OmtpVvmSyncService() {
73         super("OmtpVvmSyncService");
74     }
75 
getSyncIntent(Context context, String action, PhoneAccountHandle phoneAccount, boolean firstAttempt)76     public static Intent getSyncIntent(Context context, String action,
77             PhoneAccountHandle phoneAccount, boolean firstAttempt) {
78         if (firstAttempt) {
79             if (phoneAccount != null) {
80                 VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(context,
81                         phoneAccount);
82             } else {
83                 OmtpVvmSourceManager vvmSourceManager =
84                         OmtpVvmSourceManager.getInstance(context);
85                 Set<PhoneAccountHandle> sources = vvmSourceManager.getOmtpVvmSources();
86                 for (PhoneAccountHandle source : sources) {
87                     VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(context, source);
88                 }
89             }
90         }
91 
92         Intent serviceIntent = new Intent(context, OmtpVvmSyncService.class);
93         serviceIntent.setAction(action);
94         if (phoneAccount != null) {
95             serviceIntent.putExtra(EXTRA_PHONE_ACCOUNT, phoneAccount);
96         }
97 
98         cancelRetriesForIntent(context, serviceIntent);
99         return serviceIntent;
100     }
101 
102     /**
103      * Cancel all retry syncs for an account.
104      * @param context The context the service runs in.
105      * @param phoneAccount The phone account for which to cancel syncs.
106      */
cancelAllRetries(Context context, PhoneAccountHandle phoneAccount)107     public static void cancelAllRetries(Context context, PhoneAccountHandle phoneAccount) {
108         cancelRetriesForIntent(context, getSyncIntent(context, SYNC_FULL_SYNC, phoneAccount,
109                 false));
110     }
111 
112     /**
113      * A helper method to cancel all pending alarms for intents that would be identical to the given
114      * intent.
115      * @param context The context the service runs in.
116      * @param intent The intent to search and cancel.
117      */
cancelRetriesForIntent(Context context, Intent intent)118     private static void cancelRetriesForIntent(Context context, Intent intent) {
119         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
120         alarmManager.cancel(PendingIntent.getService(context, 0, intent, 0));
121 
122         Intent copyIntent = new Intent(intent);
123         if (SYNC_FULL_SYNC.equals(copyIntent.getAction())) {
124             // A full sync action should also cancel both of the other types of syncs
125             copyIntent.setAction(SYNC_DOWNLOAD_ONLY);
126             alarmManager.cancel(PendingIntent.getService(context, 0, copyIntent, 0));
127             copyIntent.setAction(SYNC_UPLOAD_ONLY);
128             alarmManager.cancel(PendingIntent.getService(context, 0, copyIntent, 0));
129         }
130     }
131 
132     @Override
onCreate()133     public void onCreate() {
134         super.onCreate();
135         mQueryHelper = new VoicemailsQueryHelper(this);
136     }
137 
138     @Override
onHandleIntent(Intent intent)139     protected void onHandleIntent(Intent intent) {
140         if (intent == null) {
141             Log.d(TAG, "onHandleIntent: could not handle null intent");
142             return;
143         }
144 
145         String action = intent.getAction();
146 
147         PhoneAccountHandle phoneAccount = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT);
148 
149         LocalLogHelper.log(TAG, "Sync requested: " + action +
150                 " for all accounts: " + String.valueOf(phoneAccount == null));
151 
152         if (phoneAccount != null) {
153             Log.v(TAG, "Sync requested: " + action + " - for account: " + phoneAccount);
154             setupAndSendRequest(phoneAccount, action);
155         } else {
156             Log.v(TAG, "Sync requested: " + action + " - for all accounts");
157             OmtpVvmSourceManager vvmSourceManager =
158                     OmtpVvmSourceManager.getInstance(this);
159             Set<PhoneAccountHandle> sources = vvmSourceManager.getOmtpVvmSources();
160             for (PhoneAccountHandle source : sources) {
161                 setupAndSendRequest(source, action);
162             }
163         }
164     }
165 
setupAndSendRequest(PhoneAccountHandle phoneAccount, String action)166     private void setupAndSendRequest(PhoneAccountHandle phoneAccount, String action) {
167         if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(this, phoneAccount)) {
168             Log.v(TAG, "Sync requested for disabled account");
169             return;
170         }
171 
172         if (SYNC_FULL_SYNC.equals(action)) {
173             long lastSyncTime = VisualVoicemailSettingsUtil.getVisualVoicemailLastFullSyncTime(
174                     this, phoneAccount);
175             long currentTime = System.currentTimeMillis();
176             if (currentTime - lastSyncTime < MINIMUM_FULL_SYNC_INTERVAL_MILLIS) {
177                 // If it's been less than a minute since the last sync, bail.
178                 Log.v(TAG, "Avoiding duplicate full sync: synced recently for "
179                         + phoneAccount.getId());
180                 return;
181             }
182             VisualVoicemailSettingsUtil.setVisualVoicemailLastFullSyncTime(
183                     this, phoneAccount, currentTime);
184         }
185 
186         int subId = PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount);
187         OmtpVvmCarrierConfigHelper carrierConfigHelper =
188                 new OmtpVvmCarrierConfigHelper(this, subId);
189 
190         if (TelephonyManager.VVM_TYPE_CVVM.equals(carrierConfigHelper.getVvmType())) {
191             doSync(null, null, phoneAccount, action);
192         } else {
193             OmtpVvmNetworkRequestCallback networkCallback = new OmtpVvmNetworkRequestCallback(
194                     phoneAccount, action);
195             requestNetwork(networkCallback);
196         }
197     }
198 
199     private class OmtpVvmNetworkRequestCallback extends ConnectivityManager.NetworkCallback {
200         PhoneAccountHandle mPhoneAccount;
201         String mAction;
202         NetworkRequest mNetworkRequest;
203 
OmtpVvmNetworkRequestCallback(PhoneAccountHandle phoneAccount, String action)204         public OmtpVvmNetworkRequestCallback(PhoneAccountHandle phoneAccount,
205                 String action) {
206             mPhoneAccount = phoneAccount;
207             mAction = action;
208             mNetworkRequest = new NetworkRequest.Builder()
209                     .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
210                     .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
211                     .setNetworkSpecifier(
212                             Integer.toString(
213                                     PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount)))
214                     .build();
215         }
216 
getNetworkRequest()217         public NetworkRequest getNetworkRequest() {
218             return mNetworkRequest;
219         }
220 
221         @Override
onAvailable(final Network network)222         public void onAvailable(final Network network) {
223             doSync(network, this, mPhoneAccount, mAction);
224         }
225 
226         @Override
onLost(Network network)227         public void onLost(Network network) {
228             releaseNetwork(this);
229         }
230 
231         @Override
onUnavailable()232         public void onUnavailable() {
233             releaseNetwork(this);
234         }
235     }
236 
doSync(Network network, OmtpVvmNetworkRequestCallback callback, PhoneAccountHandle phoneAccount, String action)237     private void doSync(Network network, OmtpVvmNetworkRequestCallback callback,
238             PhoneAccountHandle phoneAccount, String action) {
239         int retryCount = NETWORK_RETRY_COUNT;
240 
241         boolean uploadSuccess;
242         boolean downloadSuccess;
243 
244         while (retryCount > 0) {
245             uploadSuccess = true;
246             downloadSuccess = true;
247 
248             ImapHelper imapHelper = new ImapHelper(this, phoneAccount, network);
249             if (!imapHelper.isSuccessfullyInitialized()) {
250                 Log.w(TAG, "Can't retrieve Imap credentials.");
251                 releaseNetwork(callback);
252                 VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this,
253                         phoneAccount);
254                 return;
255             }
256 
257             if (SYNC_FULL_SYNC.equals(action) || SYNC_UPLOAD_ONLY.equals(action)) {
258                 uploadSuccess = upload(imapHelper);
259             }
260             if (SYNC_FULL_SYNC.equals(action) || SYNC_DOWNLOAD_ONLY.equals(action)) {
261                 downloadSuccess = download(imapHelper);
262             }
263 
264             Log.v(TAG, "upload succeeded: ["+  String.valueOf(uploadSuccess)
265                     + "] download succeeded: [" + String.valueOf(downloadSuccess) + "]");
266 
267             // Need to check again for whether visual voicemail is enabled because it could have
268             // been disabled while waiting for the response from the network.
269             if (VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(this, phoneAccount) &&
270                     (!uploadSuccess || !downloadSuccess)) {
271                 retryCount--;
272                 // Re-adjust so that only the unsuccessful action needs to be retried.
273                 // No need to re-adjust if both are unsuccessful. It means the full sync
274                 // failed so the action remains unchanged.
275                 if (uploadSuccess) {
276                     action = SYNC_DOWNLOAD_ONLY;
277                 } else if (downloadSuccess) {
278                     action = SYNC_UPLOAD_ONLY;
279                 }
280 
281                 Log.v(TAG, "Retrying " + action);
282                 LocalLogHelper.log(TAG, "Immediately retrying " + action);
283             } else {
284                 // Nothing more to do here, just exit.
285                 releaseNetwork(callback);
286 
287                 VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this, phoneAccount);
288                 return;
289             }
290         }
291 
292         releaseNetwork(callback);
293         setRetryAlarm(phoneAccount, action);
294     }
295 
requestNetwork(OmtpVvmNetworkRequestCallback networkCallback)296     private void requestNetwork(OmtpVvmNetworkRequestCallback networkCallback) {
297         getConnectivityManager().requestNetwork(networkCallback.getNetworkRequest(),
298                 networkCallback, NETWORK_REQUEST_TIMEOUT_MILLIS);
299     }
300 
releaseNetwork(NetworkCallback networkCallback)301     private void releaseNetwork(NetworkCallback networkCallback) {
302         if (networkCallback != null) {
303             getConnectivityManager().unregisterNetworkCallback(networkCallback);
304         }
305     }
306 
getConnectivityManager()307     private ConnectivityManager getConnectivityManager() {
308         if (mConnectivityManager == null) {
309             mConnectivityManager = (ConnectivityManager) this.getSystemService(
310                     Context.CONNECTIVITY_SERVICE);
311         }
312         return mConnectivityManager;
313     }
314 
setRetryAlarm(PhoneAccountHandle phoneAccount, String action)315     private void setRetryAlarm(PhoneAccountHandle phoneAccount, String action) {
316         Intent serviceIntent = new Intent(this, OmtpVvmSyncService.class);
317         serviceIntent.setAction(action);
318         serviceIntent.putExtra(OmtpVvmSyncService.EXTRA_PHONE_ACCOUNT, phoneAccount);
319         PendingIntent pendingIntent = PendingIntent.getService(this, 0, serviceIntent, 0);
320         long retryInterval = VisualVoicemailSettingsUtil.getVisualVoicemailRetryInterval(this,
321                 phoneAccount);
322 
323         Log.v(TAG, "Retrying "+ action + " in " + retryInterval + "ms");
324         LocalLogHelper.log(TAG, "Retrying "+ action + " in " + retryInterval + "ms");
325 
326         AlarmManager alarmManager = (AlarmManager)
327                 this.getSystemService(Context.ALARM_SERVICE);
328         alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + retryInterval,
329                 pendingIntent);
330 
331         VisualVoicemailSettingsUtil.setVisualVoicemailRetryInterval(this, phoneAccount,
332                 retryInterval * 2);
333     }
334 
upload(ImapHelper imapHelper)335     private boolean upload(ImapHelper imapHelper) {
336         List<Voicemail> readVoicemails = mQueryHelper.getReadVoicemails();
337         List<Voicemail> deletedVoicemails = mQueryHelper.getDeletedVoicemails();
338 
339         boolean success = true;
340 
341         if (deletedVoicemails.size() > 0) {
342             if (imapHelper.markMessagesAsDeleted(deletedVoicemails)) {
343                 // We want to delete selectively instead of all the voicemails for this provider
344                 // in case the state changed since the IMAP query was completed.
345                 mQueryHelper.deleteFromDatabase(deletedVoicemails);
346             } else {
347                 success = false;
348             }
349         }
350 
351         if (readVoicemails.size() > 0) {
352             if (imapHelper.markMessagesAsRead(readVoicemails)) {
353                 mQueryHelper.markReadInDatabase(readVoicemails);
354             } else {
355                 success = false;
356             }
357         }
358 
359         return success;
360     }
361 
download(ImapHelper imapHelper)362     private boolean download(ImapHelper imapHelper) {
363         List<Voicemail> serverVoicemails = imapHelper.fetchAllVoicemails();
364         List<Voicemail> localVoicemails = mQueryHelper.getAllVoicemails();
365 
366         if (localVoicemails == null || serverVoicemails == null) {
367             // Null value means the query failed.
368             return false;
369         }
370 
371         Map<String, Voicemail> remoteMap = buildMap(serverVoicemails);
372 
373         // Go through all the local voicemails and check if they are on the server.
374         // They may be read or deleted on the server but not locally. Perform the
375         // appropriate local operation if the status differs from the server. Remove
376         // the messages that exist both locally and on the server to know which server
377         // messages to insert locally.
378         for (int i = 0; i < localVoicemails.size(); i++) {
379             Voicemail localVoicemail = localVoicemails.get(i);
380             Voicemail remoteVoicemail = remoteMap.remove(localVoicemail.getSourceData());
381             if (remoteVoicemail == null) {
382                 mQueryHelper.deleteFromDatabase(localVoicemail);
383             } else {
384                 if (remoteVoicemail.isRead() != localVoicemail.isRead()) {
385                     mQueryHelper.markReadInDatabase(localVoicemail);
386                 }
387             }
388         }
389 
390         // The leftover messages are messages that exist on the server but not locally.
391         for (Voicemail remoteVoicemail : remoteMap.values()) {
392             VoicemailContract.Voicemails.insert(this, remoteVoicemail);
393         }
394 
395         return true;
396     }
397 
398     /**
399      * Builds a map from provider data to message for the given collection of voicemails.
400      */
buildMap(List<Voicemail> messages)401     private Map<String, Voicemail> buildMap(List<Voicemail> messages) {
402         Map<String, Voicemail> map = new HashMap<String, Voicemail>();
403         for (Voicemail message : messages) {
404             map.put(message.getSourceData(), message);
405         }
406         return map;
407     }
408 }
409