Home | History | Annotate | Download | only in fetch
      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.voicemail.impl.fetch;
     17 
     18 import android.annotation.TargetApi;
     19 import android.content.BroadcastReceiver;
     20 import android.content.ComponentName;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.database.Cursor;
     25 import android.net.Network;
     26 import android.net.Uri;
     27 import android.os.Build.VERSION_CODES;
     28 import android.provider.VoicemailContract;
     29 import android.provider.VoicemailContract.Voicemails;
     30 import android.support.annotation.NonNull;
     31 import android.support.annotation.Nullable;
     32 import android.support.v4.os.BuildCompat;
     33 import android.telecom.PhoneAccountHandle;
     34 import android.telecom.TelecomManager;
     35 import android.telephony.TelephonyManager;
     36 import android.text.TextUtils;
     37 import com.android.voicemail.VoicemailComponent;
     38 import com.android.voicemail.impl.VoicemailStatus;
     39 import com.android.voicemail.impl.VvmLog;
     40 import com.android.voicemail.impl.imap.ImapHelper;
     41 import com.android.voicemail.impl.imap.ImapHelper.InitializingException;
     42 import com.android.voicemail.impl.sync.VvmAccountManager;
     43 import com.android.voicemail.impl.sync.VvmNetworkRequestCallback;
     44 import java.util.concurrent.Executor;
     45 import java.util.concurrent.Executors;
     46 
     47 /** handles {@link VoicemailContract#ACTION_FETCH_VOICEMAIL} */
     48 @TargetApi(VERSION_CODES.O)
     49 public class FetchVoicemailReceiver extends BroadcastReceiver {
     50 
     51   private static final String TAG = "FetchVoicemailReceiver";
     52 
     53   static final String[] PROJECTION =
     54       new String[] {
     55         Voicemails.SOURCE_DATA, // 0
     56         Voicemails.PHONE_ACCOUNT_ID, // 1
     57         Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, // 2
     58       };
     59 
     60   public static final int SOURCE_DATA = 0;
     61   public static final int PHONE_ACCOUNT_ID = 1;
     62   public static final int PHONE_ACCOUNT_COMPONENT_NAME = 2;
     63 
     64   // Number of retries
     65   private static final int NETWORK_RETRY_COUNT = 3;
     66 
     67   private ContentResolver mContentResolver;
     68   private Uri mUri;
     69   private VvmNetworkRequestCallback mNetworkCallback;
     70   private Context mContext;
     71   private String mUid;
     72   private PhoneAccountHandle mPhoneAccount;
     73   private int mRetryCount = NETWORK_RETRY_COUNT;
     74 
     75   @Override
     76   public void onReceive(final Context context, Intent intent) {
     77     if (!VoicemailComponent.get(context).getVoicemailClient().isVoicemailModuleEnabled()) {
     78       return;
     79     }
     80     if (VoicemailContract.ACTION_FETCH_VOICEMAIL.equals(intent.getAction())) {
     81       VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL received");
     82       mContext = context;
     83       mContentResolver = context.getContentResolver();
     84       mUri = intent.getData();
     85 
     86       if (mUri == null) {
     87         VvmLog.w(TAG, VoicemailContract.ACTION_FETCH_VOICEMAIL + " intent sent with no data");
     88         return;
     89       }
     90 
     91       if (!context
     92           .getPackageName()
     93           .equals(mUri.getQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE))) {
     94         // Ignore if the fetch request is for a voicemail not from this package.
     95         VvmLog.e(TAG, "ACTION_FETCH_VOICEMAIL from foreign pacakge " + context.getPackageName());
     96         return;
     97       }
     98 
     99       Cursor cursor = mContentResolver.query(mUri, PROJECTION, null, null, null);
    100       if (cursor == null) {
    101         VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL query returned null");
    102         return;
    103       }
    104       try {
    105         if (cursor.moveToFirst()) {
    106           mUid = cursor.getString(SOURCE_DATA);
    107           String accountId = cursor.getString(PHONE_ACCOUNT_ID);
    108           if (TextUtils.isEmpty(accountId)) {
    109             TelephonyManager telephonyManager =
    110                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    111             accountId = telephonyManager.getSimSerialNumber();
    112 
    113             if (TextUtils.isEmpty(accountId)) {
    114               VvmLog.e(TAG, "Account null and no default sim found.");
    115               return;
    116             }
    117           }
    118 
    119           mPhoneAccount =
    120               new PhoneAccountHandle(
    121                   ComponentName.unflattenFromString(cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME)),
    122                   cursor.getString(PHONE_ACCOUNT_ID));
    123           TelephonyManager telephonyManager =
    124               context
    125                   .getSystemService(TelephonyManager.class)
    126                   .createForPhoneAccountHandle(mPhoneAccount);
    127           if (telephonyManager == null) {
    128             // can happen when trying to fetch voicemails from a SIM that is no longer on the
    129             // device
    130             VvmLog.e(TAG, "account no longer valid, cannot retrieve message");
    131             return;
    132           }
    133           if (!VvmAccountManager.isAccountActivated(context, mPhoneAccount)) {
    134             mPhoneAccount = getAccountFromMarshmallowAccount(context, mPhoneAccount);
    135             if (mPhoneAccount == null) {
    136               VvmLog.w(TAG, "Account not registered - cannot retrieve message.");
    137               return;
    138             }
    139             VvmLog.i(TAG, "Fetching voicemail with Marshmallow PhoneAccountHandle");
    140           }
    141           VvmLog.i(TAG, "Requesting network to fetch voicemail");
    142           mNetworkCallback = new fetchVoicemailNetworkRequestCallback(context, mPhoneAccount);
    143           mNetworkCallback.requestNetwork();
    144         }
    145       } finally {
    146         cursor.close();
    147       }
    148     }
    149   }
    150 
    151   /**
    152    * In ag/930496 the format of PhoneAccountHandle has changed between Marshmallow and Nougat. This
    153    * method attempts to search the account from the old database in registered sources using the old
    154    * format. There's a chance of M phone account collisions on multi-SIM devices, but visual
    155    * voicemail is not supported on M multi-SIM.
    156    */
    157   @Nullable
    158   private static PhoneAccountHandle getAccountFromMarshmallowAccount(
    159       Context context, PhoneAccountHandle oldAccount) {
    160     if (!BuildCompat.isAtLeastN()) {
    161       return null;
    162     }
    163     for (PhoneAccountHandle handle :
    164         context.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()) {
    165       if (getIccSerialNumberFromFullIccSerialNumber(handle.getId()).equals(oldAccount.getId())) {
    166         return handle;
    167       }
    168     }
    169     return null;
    170   }
    171 
    172   /**
    173    * getIccSerialNumber() is used for ID before N, and getFullIccSerialNumber() after.
    174    * getIccSerialNumber() stops at the first hex char.
    175    */
    176   @NonNull
    177   private static String getIccSerialNumberFromFullIccSerialNumber(@NonNull String id) {
    178     for (int i = 0; i < id.length(); i++) {
    179       if (!Character.isDigit(id.charAt(i))) {
    180         return id.substring(0, i);
    181       }
    182     }
    183     return id;
    184   }
    185 
    186   private class fetchVoicemailNetworkRequestCallback extends VvmNetworkRequestCallback {
    187 
    188     public fetchVoicemailNetworkRequestCallback(Context context, PhoneAccountHandle phoneAccount) {
    189       super(context, phoneAccount, VoicemailStatus.edit(context, phoneAccount));
    190     }
    191 
    192     @Override
    193     public void onAvailable(final Network network) {
    194       super.onAvailable(network);
    195       fetchVoicemail(network, getVoicemailStatusEditor());
    196     }
    197   }
    198 
    199   private void fetchVoicemail(final Network network, final VoicemailStatus.Editor status) {
    200     Executor executor = Executors.newCachedThreadPool();
    201     executor.execute(
    202         new Runnable() {
    203           @Override
    204           public void run() {
    205             try {
    206               while (mRetryCount > 0) {
    207                 VvmLog.i(TAG, "fetching voicemail, retry count=" + mRetryCount);
    208                 try (ImapHelper imapHelper =
    209                     new ImapHelper(mContext, mPhoneAccount, network, status)) {
    210                   boolean success =
    211                       imapHelper.fetchVoicemailPayload(
    212                           new VoicemailFetchedCallback(mContext, mUri, mPhoneAccount), mUid);
    213                   if (!success && mRetryCount > 0) {
    214                     VvmLog.i(TAG, "fetch voicemail failed, retrying");
    215                     mRetryCount--;
    216                   } else {
    217                     return;
    218                   }
    219                 } catch (InitializingException e) {
    220                   VvmLog.w(TAG, "Can't retrieve Imap credentials ", e);
    221                   return;
    222                 }
    223               }
    224             } finally {
    225               if (mNetworkCallback != null) {
    226                 mNetworkCallback.releaseNetwork();
    227               }
    228             }
    229           }
    230         });
    231   }
    232 }
    233