diff options
Diffstat (limited to 'java/com/android/voicemail/impl/fetch')
-rw-r--r-- | java/com/android/voicemail/impl/fetch/FetchVoicemailReceiver.java | 218 | ||||
-rw-r--r-- | java/com/android/voicemail/impl/fetch/VoicemailFetchedCallback.java | 102 |
2 files changed, 320 insertions, 0 deletions
diff --git a/java/com/android/voicemail/impl/fetch/FetchVoicemailReceiver.java b/java/com/android/voicemail/impl/fetch/FetchVoicemailReceiver.java new file mode 100644 index 000000000..07e800836 --- /dev/null +++ b/java/com/android/voicemail/impl/fetch/FetchVoicemailReceiver.java @@ -0,0 +1,218 @@ +/* + * 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.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 mContentResolver; + private Uri mUri; + private VvmNetworkRequestCallback mNetworkCallback; + private Context mContext; + private String mUid; + private PhoneAccountHandle mPhoneAccount; + private int mRetryCount = NETWORK_RETRY_COUNT; + + @Override + public void onReceive(final Context context, Intent intent) { + if (VoicemailContract.ACTION_FETCH_VOICEMAIL.equals(intent.getAction())) { + VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL received"); + mContext = context; + mContentResolver = context.getContentResolver(); + mUri = intent.getData(); + + if (mUri == null) { + VvmLog.w(TAG, VoicemailContract.ACTION_FETCH_VOICEMAIL + " intent sent with no data"); + return; + } + + if (!context + .getPackageName() + .equals(mUri.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 = mContentResolver.query(mUri, PROJECTION, null, null, null); + if (cursor == null) { + VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL query returned null"); + return; + } + try { + if (cursor.moveToFirst()) { + mUid = 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; + } + } + + mPhoneAccount = + new PhoneAccountHandle( + ComponentName.unflattenFromString(cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME)), + cursor.getString(PHONE_ACCOUNT_ID)); + if (!VvmAccountManager.isAccountActivated(context, mPhoneAccount)) { + mPhoneAccount = getAccountFromMarshmallowAccount(context, mPhoneAccount); + if (mPhoneAccount == 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"); + mNetworkCallback = new fetchVoicemailNetworkRequestCallback(context, mPhoneAccount); + mNetworkCallback.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 (mRetryCount > 0) { + VvmLog.i(TAG, "fetching voicemail, retry count=" + mRetryCount); + try (ImapHelper imapHelper = + new ImapHelper(mContext, mPhoneAccount, network, status)) { + boolean success = + imapHelper.fetchVoicemailPayload( + new VoicemailFetchedCallback(mContext, mUri, mPhoneAccount), mUid); + if (!success && mRetryCount > 0) { + VvmLog.i(TAG, "fetch voicemail failed, retrying"); + mRetryCount--; + } else { + return; + } + } catch (InitializingException e) { + VvmLog.w(TAG, "Can't retrieve Imap credentials ", e); + return; + } + } + } finally { + if (mNetworkCallback != null) { + mNetworkCallback.releaseNetwork(); + } + } + } + }); + } +} diff --git a/java/com/android/voicemail/impl/fetch/VoicemailFetchedCallback.java b/java/com/android/voicemail/impl/fetch/VoicemailFetchedCallback.java new file mode 100644 index 000000000..f386fce0e --- /dev/null +++ b/java/com/android/voicemail/impl/fetch/VoicemailFetchedCallback.java @@ -0,0 +1,102 @@ +/* + * 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.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.provider.VoicemailContract.Voicemails; +import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import com.android.voicemail.impl.R; +import com.android.voicemail.impl.VvmLog; +import com.android.voicemail.impl.imap.VoicemailPayload; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.commons.io.IOUtils; + +/** + * Callback for when a voicemail payload is fetched. It copies the returned stream to the data file + * corresponding to the voicemail. + */ +public class VoicemailFetchedCallback { + private static final String TAG = "VoicemailFetchedCallback"; + + private final Context mContext; + private final ContentResolver mContentResolver; + private final Uri mUri; + private final PhoneAccountHandle mPhoneAccountHandle; + + public VoicemailFetchedCallback(Context context, Uri uri, PhoneAccountHandle phoneAccountHandle) { + mContext = context; + mContentResolver = context.getContentResolver(); + mUri = uri; + mPhoneAccountHandle = phoneAccountHandle; + } + + /** + * Saves the voicemail payload data into the voicemail provider then sets the "has_content" bit of + * the voicemail to "1". + * + * @param voicemailPayload The object containing the content data for the voicemail + */ + public void setVoicemailContent(@Nullable VoicemailPayload voicemailPayload) { + if (voicemailPayload == null) { + VvmLog.i(TAG, "Payload not found, message has unsupported format"); + ContentValues values = new ContentValues(); + values.put( + Voicemails.TRANSCRIPTION, + mContext.getString( + R.string.vvm_unsupported_message_format, + mContext + .getSystemService(TelecomManager.class) + .getVoiceMailNumber(mPhoneAccountHandle))); + updateVoicemail(values); + return; + } + + VvmLog.d(TAG, String.format("Writing new voicemail content: %s", mUri)); + OutputStream outputStream = null; + + try { + outputStream = mContentResolver.openOutputStream(mUri); + byte[] inputBytes = voicemailPayload.getBytes(); + if (inputBytes != null) { + outputStream.write(inputBytes); + } + } catch (IOException e) { + VvmLog.w(TAG, String.format("File not found for %s", mUri)); + return; + } finally { + IOUtils.closeQuietly(outputStream); + } + + // Update mime_type & has_content after we are done with file update. + ContentValues values = new ContentValues(); + values.put(Voicemails.MIME_TYPE, voicemailPayload.getMimeType()); + values.put(Voicemails.HAS_CONTENT, true); + updateVoicemail(values); + } + + private void updateVoicemail(ContentValues values) { + int updatedCount = mContentResolver.update(mUri, values, null, null); + if (updatedCount != 1) { + VvmLog.e(TAG, "Updating voicemail should have updated 1 row, was: " + updatedCount); + } + } +} |