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