/* * 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.voicemail.impl.fetch; import android.annotation.TargetApi; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Network; import android.net.Uri; import android.os.Build.VERSION_CODES; import android.provider.VoicemailContract; import android.provider.VoicemailContract.Voicemails; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.os.BuildCompat; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import com.android.voicemail.VoicemailComponent; import com.android.voicemail.impl.VoicemailStatus; import com.android.voicemail.impl.VvmLog; import com.android.voicemail.impl.imap.ImapHelper; import com.android.voicemail.impl.imap.ImapHelper.InitializingException; import com.android.voicemail.impl.sync.VvmAccountManager; import com.android.voicemail.impl.sync.VvmNetworkRequestCallback; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** handles {@link VoicemailContract#ACTION_FETCH_VOICEMAIL} */ @TargetApi(VERSION_CODES.O) public class FetchVoicemailReceiver extends BroadcastReceiver { private static final String TAG = "FetchVoicemailReceiver"; static final String[] PROJECTION = new String[] { Voicemails.SOURCE_DATA, // 0 Voicemails.PHONE_ACCOUNT_ID, // 1 Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, // 2 }; public static final int SOURCE_DATA = 0; public static final int PHONE_ACCOUNT_ID = 1; public static final int PHONE_ACCOUNT_COMPONENT_NAME = 2; // Number of retries private static final int NETWORK_RETRY_COUNT = 3; private ContentResolver contentResolver; private Uri uri; private VvmNetworkRequestCallback networkCallback; private Context context; private String uid; private PhoneAccountHandle phoneAccount; private int retryCount = NETWORK_RETRY_COUNT; @Override public void onReceive(final Context context, Intent intent) { if (!VoicemailComponent.get(context).getVoicemailClient().isVoicemailModuleEnabled()) { return; } if (VoicemailContract.ACTION_FETCH_VOICEMAIL.equals(intent.getAction())) { VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL received"); this.context = context; contentResolver = context.getContentResolver(); uri = intent.getData(); if (uri == null) { VvmLog.w(TAG, VoicemailContract.ACTION_FETCH_VOICEMAIL + " intent sent with no data"); return; } if (!context .getPackageName() .equals(uri.getQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE))) { // Ignore if the fetch request is for a voicemail not from this package. VvmLog.e(TAG, "ACTION_FETCH_VOICEMAIL from foreign pacakge " + context.getPackageName()); return; } Cursor cursor = contentResolver.query(uri, PROJECTION, null, null, null); if (cursor == null) { VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL query returned null"); return; } try { if (cursor.moveToFirst()) { uid = cursor.getString(SOURCE_DATA); String accountId = cursor.getString(PHONE_ACCOUNT_ID); if (TextUtils.isEmpty(accountId)) { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); accountId = telephonyManager.getSimSerialNumber(); if (TextUtils.isEmpty(accountId)) { VvmLog.e(TAG, "Account null and no default sim found."); return; } } phoneAccount = new PhoneAccountHandle( ComponentName.unflattenFromString(cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME)), cursor.getString(PHONE_ACCOUNT_ID)); TelephonyManager telephonyManager = context .getSystemService(TelephonyManager.class) .createForPhoneAccountHandle(phoneAccount); if (telephonyManager == null) { // can happen when trying to fetch voicemails from a SIM that is no longer on the // device VvmLog.e(TAG, "account no longer valid, cannot retrieve message"); return; } if (!VvmAccountManager.isAccountActivated(context, phoneAccount)) { phoneAccount = getAccountFromMarshmallowAccount(context, phoneAccount); if (phoneAccount == null) { VvmLog.w(TAG, "Account not registered - cannot retrieve message."); return; } VvmLog.i(TAG, "Fetching voicemail with Marshmallow PhoneAccountHandle"); } VvmLog.i(TAG, "Requesting network to fetch voicemail"); networkCallback = new fetchVoicemailNetworkRequestCallback(context, phoneAccount); networkCallback.requestNetwork(); } } finally { cursor.close(); } } } /** * In ag/930496 the format of PhoneAccountHandle has changed between Marshmallow and Nougat. This * method attempts to search the account from the old database in registered sources using the old * format. There's a chance of M phone account collisions on multi-SIM devices, but visual * voicemail is not supported on M multi-SIM. */ @Nullable private static PhoneAccountHandle getAccountFromMarshmallowAccount( Context context, PhoneAccountHandle oldAccount) { if (!BuildCompat.isAtLeastN()) { return null; } for (PhoneAccountHandle handle : context.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()) { if (getIccSerialNumberFromFullIccSerialNumber(handle.getId()).equals(oldAccount.getId())) { return handle; } } return null; } /** * getIccSerialNumber() is used for ID before N, and getFullIccSerialNumber() after. * getIccSerialNumber() stops at the first hex char. */ @NonNull private static String getIccSerialNumberFromFullIccSerialNumber(@NonNull String id) { for (int i = 0; i < id.length(); i++) { if (!Character.isDigit(id.charAt(i))) { return id.substring(0, i); } } return id; } private class fetchVoicemailNetworkRequestCallback extends VvmNetworkRequestCallback { public fetchVoicemailNetworkRequestCallback(Context context, PhoneAccountHandle phoneAccount) { super(context, phoneAccount, VoicemailStatus.edit(context, phoneAccount)); } @Override public void onAvailable(final Network network) { super.onAvailable(network); fetchVoicemail(network, getVoicemailStatusEditor()); } } private void fetchVoicemail(final Network network, final VoicemailStatus.Editor status) { Executor executor = Executors.newCachedThreadPool(); executor.execute( new Runnable() { @Override public void run() { try { while (retryCount > 0) { VvmLog.i(TAG, "fetching voicemail, retry count=" + retryCount); try (ImapHelper imapHelper = new ImapHelper(context, phoneAccount, network, status)) { boolean success = imapHelper.fetchVoicemailPayload( new VoicemailFetchedCallback(context, uri, phoneAccount), uid); if (!success && retryCount > 0) { VvmLog.i(TAG, "fetch voicemail failed, retrying"); retryCount--; } else { return; } } catch (InitializingException e) { VvmLog.w(TAG, "Can't retrieve Imap credentials ", e); return; } } } finally { if (networkCallback != null) { networkCallback.releaseNetwork(); } } } }); } }