summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java5
-rw-r--r--java/com/android/dialer/precall/externalreceiver/AndroidManifest.xml40
-rw-r--r--java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java60
-rw-r--r--java/com/android/dialer/precall/impl/CallingAccountSelector.java241
-rw-r--r--java/com/android/dialer/precall/impl/PreferredAccountUtil.java94
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorMainMenu.java19
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java10
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java4
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java87
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java221
-rw-r--r--java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java11
-rw-r--r--java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry.xml2
12 files changed, 686 insertions, 108 deletions
diff --git a/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
index 6c6aebc0b..e21fded97 100644
--- a/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
+++ b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
@@ -123,6 +123,11 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment {
return mListener;
}
+ @VisibleForTesting
+ public boolean canSetDefault() {
+ return getArguments().getBoolean(ARG_CAN_SET_DEFAULT);
+ }
+
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
diff --git a/java/com/android/dialer/precall/externalreceiver/AndroidManifest.xml b/java/com/android/dialer/precall/externalreceiver/AndroidManifest.xml
new file mode 100644
index 000000000..b1c625a0f
--- /dev/null
+++ b/java/com/android/dialer/precall/externalreceiver/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.dialer.precall.externalreceiver">
+
+ <application>
+ <activity
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:name="com.android.dialer.precall.externalreceiver.LaunchPreCallActivity"
+ android:noHistory="true"
+ android:permission="android.permission.CALL_PHONE"
+ android:theme="@style/Theme.PreCall.DialogHolder">
+ <intent-filter>
+ <action android:name="com.android.dialer.LAUNCH_PRE_CALL"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.dialer.LAUNCH_PRE_CALL"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="voicemail" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java b/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java
new file mode 100644
index 000000000..121e6a6c9
--- /dev/null
+++ b/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.dialer.precall.externalreceiver;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import com.android.dialer.callintent.CallInitiationType.Type;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.precall.PreCall;
+
+/**
+ * Activity that forwards to {@link PreCall#start(Context, CallIntentBuilder)} so the pre-call flow
+ * can be initiated by external apps. This activity is exported but can only be started by apps with
+ * {@link android.Manifest.permission#CALL_PHONE}. Keyguard will be triggered if phone is locked.
+ *
+ * @see CallIntentBuilder
+ */
+public class LaunchPreCallActivity extends Activity {
+
+ public static final String ACTION_LAUNCH_PRE_CALL = "com.android.dialer.LAUNCH_PRE_CALL";
+
+ public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "phone_account_handle";
+
+ public static final String EXTRA_IS_VIDEO_CALL = "is_video_call";
+
+ public static final String EXTRA_CALL_SUBJECT = "call_subject";
+
+ public static final String EXTRA_ALLOW_ASSISTED_DIAL = "allow_assisted_dial";
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ CallIntentBuilder builder = new CallIntentBuilder(intent.getData(), Type.EXTERNAL_INITIATION);
+ builder
+ .setPhoneAccountHandle(intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE))
+ .setIsVideoCall(intent.getBooleanExtra(EXTRA_IS_VIDEO_CALL, false))
+ .setCallSubject(intent.getStringExtra(EXTRA_CALL_SUBJECT))
+ .setAllowAssistedDial(intent.getBooleanExtra(EXTRA_ALLOW_ASSISTED_DIAL, false));
+ PreCall.start(this, builder);
+ finish();
+ }
+}
diff --git a/java/com/android/dialer/precall/impl/CallingAccountSelector.java b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
index ca8798c5d..ca74bef08 100644
--- a/java/com/android/dialer/precall/impl/CallingAccountSelector.java
+++ b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
@@ -17,10 +17,18 @@
package com.android.dialer.precall.impl;
import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.PhoneLookup;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.support.v4.util.ArraySet;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -28,9 +36,17 @@ import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.precall.PreCallAction;
import com.android.dialer.precall.PreCallCoordinator;
+import com.android.dialer.precall.PreCallCoordinator.PendingAction;
+import com.android.dialer.preferredsim.PreferredSimFallbackContract;
+import com.android.dialer.preferredsim.PreferredSimFallbackContract.PreferredSim;
+import com.google.common.base.Optional;
import java.util.List;
+import java.util.Set;
/** PreCallAction to select which phone account to call with. Ignored if there's only one account */
@SuppressWarnings("MissingPermission")
@@ -43,6 +59,7 @@ public class CallingAccountSelector implements PreCallAction {
private boolean isDiscarding;
@Override
+ @MainThread
public void run(PreCallCoordinator coordinator) {
CallIntentBuilder builder = coordinator.getBuilder();
if (builder.getPhoneAccountHandle() != null) {
@@ -54,43 +71,189 @@ public class CallingAccountSelector implements PreCallAction {
if (accounts.size() <= 1) {
return;
}
- boolean isVoicemail = builder.getUri().getScheme().equals(PhoneAccount.SCHEME_VOICEMAIL);
-
- if (!isVoicemail) {
- PhoneAccountHandle defaultPhoneAccount =
- telecomManager.getDefaultOutgoingPhoneAccount(builder.getUri().getScheme());
- if (defaultPhoneAccount != null) {
- builder.setPhoneAccountHandle(defaultPhoneAccount);
- return;
- }
+ switch (builder.getUri().getScheme()) {
+ case PhoneAccount.SCHEME_VOICEMAIL:
+ showDialog(coordinator, coordinator.startPendingAction(), null);
+ break;
+ case PhoneAccount.SCHEME_TEL:
+ processPreferredAccount(coordinator);
+ break;
+ default:
+ // might be PhoneAccount.SCHEME_SIP
+ LogUtil.e(
+ "CallingAccountSelector.run",
+ "unable to process scheme " + builder.getUri().getScheme());
+ break;
}
+ }
+ /** Initiates a background worker to find if there's any preferred account. */
+ @MainThread
+ private void processPreferredAccount(PreCallCoordinator coordinator) {
+ Assert.isMainThread();
+ CallIntentBuilder builder = coordinator.getBuilder();
+ Activity activity = coordinator.getActivity();
+ String phoneNumber = builder.getUri().getSchemeSpecificPart();
+ PendingAction pendingAction = coordinator.startPendingAction();
+ DialerExecutorComponent.get(coordinator.getActivity())
+ .dialerExecutorFactory()
+ .createUiTaskBuilder(
+ activity.getFragmentManager(),
+ "PreferredAccountWorker",
+ new PreferredAccountWorker(phoneNumber))
+ .onSuccess(
+ (result -> {
+ if (result.phoneAccountHandle.isPresent()) {
+ coordinator.getBuilder().setPhoneAccountHandle(result.phoneAccountHandle.get());
+ pendingAction.finish();
+ return;
+ }
+ PhoneAccountHandle defaultPhoneAccount =
+ activity
+ .getSystemService(TelecomManager.class)
+ .getDefaultOutgoingPhoneAccount(builder.getUri().getScheme());
+ if (defaultPhoneAccount != null) {
+ builder.setPhoneAccountHandle(defaultPhoneAccount);
+ pendingAction.finish();
+ return;
+ }
+ showDialog(coordinator, pendingAction, result.dataId.orNull());
+ }))
+ .build()
+ .executeParallel(activity);
+ }
+
+ @MainThread
+ private void showDialog(
+ PreCallCoordinator coordinator, PendingAction pendingAction, @Nullable String dataId) {
+ Assert.isMainThread();
selectPhoneAccountDialogFragment =
SelectPhoneAccountDialogFragment.newInstance(
R.string.pre_call_select_phone_account,
- false /* canSetDefault */, // TODO(twyen): per contact defaults
- accounts,
- new SelectedListener(coordinator, coordinator.startPendingAction()),
+ dataId != null /* canSetDefault */,
+ coordinator
+ .getActivity()
+ .getSystemService(TelecomManager.class)
+ .getCallCapablePhoneAccounts(),
+ new SelectedListener(coordinator, pendingAction, dataId),
null /* call ID */);
selectPhoneAccountDialogFragment.show(
- activity.getFragmentManager(), TAG_CALLING_ACCOUNT_SELECTOR);
+ coordinator.getActivity().getFragmentManager(), TAG_CALLING_ACCOUNT_SELECTOR);
}
+ @MainThread
@Override
public void onDiscard() {
isDiscarding = true;
selectPhoneAccountDialogFragment.dismiss();
}
+ private static class PreferredAccountWorkerResult {
+
+ /** The preferred phone account for the number. Absent if not set or invalid. */
+ Optional<PhoneAccountHandle> phoneAccountHandle = Optional.absent();
+
+ /**
+ * {@link android.provider.ContactsContract.Data#_ID} of the row matching the number. If the
+ * preferred account is to be set it should be stored in this row
+ */
+ Optional<String> dataId = Optional.absent();
+ }
+
+ private static class PreferredAccountWorker
+ implements Worker<Context, PreferredAccountWorkerResult> {
+
+ private final String phoneNumber;
+
+ public PreferredAccountWorker(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+ @NonNull
+ @Override
+ @WorkerThread
+ public PreferredAccountWorkerResult doInBackground(Context context) throws Throwable {
+ PreferredAccountWorkerResult result = new PreferredAccountWorkerResult();
+ result.dataId = getDataId(context.getContentResolver(), phoneNumber);
+ if (result.dataId.isPresent()) {
+ result.phoneAccountHandle = getPreferredAccount(context, result.dataId.get());
+ }
+ return result;
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ private static Optional<String> getDataId(
+ @NonNull ContentResolver contentResolver, @Nullable String phoneNumber) {
+ Assert.isWorkerThread();
+ try (Cursor cursor =
+ contentResolver.query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)),
+ new String[] {PhoneLookup.DATA_ID},
+ null,
+ null,
+ null)) {
+ if (cursor == null) {
+ return Optional.absent();
+ }
+ Set<String> result = new ArraySet<>();
+ while (cursor.moveToNext()) {
+ result.add(cursor.getString(0));
+ }
+ // TODO(twyen): if there are multiples attempt to grab from the contact that initiated the
+ // call.
+ if (result.size() == 1) {
+ return Optional.of(result.iterator().next());
+ } else {
+ LogUtil.i("CallingAccountSelector.getDataId", "lookup result not unique, ignoring");
+ return Optional.absent();
+ }
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ private static Optional<PhoneAccountHandle> getPreferredAccount(
+ @NonNull Context context, @NonNull String dataId) {
+ Assert.isWorkerThread();
+ Assert.isNotNull(dataId);
+ try (Cursor cursor =
+ context
+ .getContentResolver()
+ .query(
+ PreferredSimFallbackContract.CONTENT_URI,
+ new String[] {
+ PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PreferredSim.PREFERRED_PHONE_ACCOUNT_ID
+ },
+ PreferredSim.DATA_ID + " = ?",
+ new String[] {dataId},
+ null)) {
+ if (cursor == null) {
+ return Optional.absent();
+ }
+ if (!cursor.moveToFirst()) {
+ return Optional.absent();
+ }
+ return PreferredAccountUtil.getValidPhoneAccount(
+ context, cursor.getString(0), cursor.getString(1));
+ }
+ }
+
private class SelectedListener extends SelectPhoneAccountListener {
private final PreCallCoordinator coordinator;
private final PreCallCoordinator.PendingAction listener;
+ private final String dataId;
public SelectedListener(
- @NonNull PreCallCoordinator builder, @NonNull PreCallCoordinator.PendingAction listener) {
+ @NonNull PreCallCoordinator builder,
+ @NonNull PreCallCoordinator.PendingAction listener,
+ @Nullable String dataId) {
this.coordinator = Assert.isNotNull(builder);
this.listener = Assert.isNotNull(listener);
+ this.dataId = dataId;
}
@MainThread
@@ -98,6 +261,17 @@ public class CallingAccountSelector implements PreCallAction {
public void onPhoneAccountSelected(
PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId) {
coordinator.getBuilder().setPhoneAccountHandle(selectedAccountHandle);
+
+ if (dataId != null && setDefault) {
+ DialerExecutorComponent.get(coordinator.getActivity())
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new WritePreferredAccountWorker())
+ .build()
+ .executeParallel(
+ new WritePreferredAccountWorkerInput(
+ coordinator.getActivity(), dataId, selectedAccountHandle));
+ }
+
listener.finish();
}
@@ -111,4 +285,43 @@ public class CallingAccountSelector implements PreCallAction {
listener.finish();
}
}
+
+ private static class WritePreferredAccountWorkerInput {
+ private final Context context;
+ private final String dataId;
+ private final PhoneAccountHandle phoneAccountHandle;
+
+ WritePreferredAccountWorkerInput(
+ @NonNull Context context,
+ @NonNull String dataId,
+ @NonNull PhoneAccountHandle phoneAccountHandle) {
+ this.context = Assert.isNotNull(context);
+ this.dataId = Assert.isNotNull(dataId);
+ this.phoneAccountHandle = Assert.isNotNull(phoneAccountHandle);
+ }
+ }
+
+ private static class WritePreferredAccountWorker
+ implements Worker<WritePreferredAccountWorkerInput, Void> {
+
+ @Nullable
+ @Override
+ @WorkerThread
+ public Void doInBackground(WritePreferredAccountWorkerInput input) throws Throwable {
+ ContentValues values = new ContentValues();
+ values.put(
+ PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ input.phoneAccountHandle.getComponentName().flattenToString());
+ values.put(PreferredSim.PREFERRED_PHONE_ACCOUNT_ID, input.phoneAccountHandle.getId());
+ input
+ .context
+ .getContentResolver()
+ .update(
+ PreferredSimFallbackContract.CONTENT_URI,
+ values,
+ PreferredSim.DATA_ID + " = ?",
+ new String[] {String.valueOf(input.dataId)});
+ return null;
+ }
+ }
}
diff --git a/java/com/android/dialer/precall/impl/PreferredAccountUtil.java b/java/com/android/dialer/precall/impl/PreferredAccountUtil.java
new file mode 100644
index 000000000..a41cb6e78
--- /dev/null
+++ b/java/com/android/dialer/precall/impl/PreferredAccountUtil.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 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.dialer.precall.impl;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.dialer.common.LogUtil;
+import com.google.common.base.Optional;
+
+/**
+ * Utilities for looking up and validating preferred {@link PhoneAccountHandle}. Contacts should
+ * follow the same logic.
+ */
+public class PreferredAccountUtil {
+
+ /**
+ * Validates {@code componentNameString} and {@code idString} maps to SIM that is present on the
+ * device.
+ */
+ @NonNull
+ public static Optional<PhoneAccountHandle> getValidPhoneAccount(
+ @NonNull Context context, @Nullable String componentNameString, @Nullable String idString) {
+ if (TextUtils.isEmpty(componentNameString) || TextUtils.isEmpty(idString)) {
+ LogUtil.i("PreferredAccountUtil.getValidPhoneAccount", "empty componentName or id");
+ return Optional.absent();
+ }
+ ComponentName componentName = ComponentName.unflattenFromString(componentNameString);
+ if (componentName == null) {
+ LogUtil.e("PreferredAccountUtil.getValidPhoneAccount", "cannot parse component name");
+ return Optional.absent();
+ }
+ PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(componentName, idString);
+
+ if (isPhoneAccountValid(context, phoneAccountHandle)) {
+ return Optional.of(phoneAccountHandle);
+ }
+ return Optional.absent();
+ }
+
+ private static boolean isPhoneAccountValid(
+ Context context, PhoneAccountHandle phoneAccountHandle) {
+ if (VERSION.SDK_INT >= VERSION_CODES.O) {
+ return context
+ .getSystemService(TelephonyManager.class)
+ .createForPhoneAccountHandle(phoneAccountHandle)
+ != null;
+ }
+
+ PhoneAccount phoneAccount =
+ context.getSystemService(TelecomManager.class).getPhoneAccount(phoneAccountHandle);
+ if (phoneAccount == null) {
+ LogUtil.e("PreferredAccountUtil.isPhoneAccountValid", "invalid phone account");
+ return false;
+ }
+
+ if (!phoneAccount.isEnabled()) {
+ LogUtil.e("PreferredAccountUtil.isPhoneAccountValid", "disabled phone account");
+ return false;
+ }
+ for (SubscriptionInfo info :
+ SubscriptionManager.from(context).getActiveSubscriptionInfoList()) {
+ if (phoneAccountHandle.getId().startsWith(info.getIccId())) {
+ LogUtil.i("PreferredAccountUtil.isPhoneAccountValid", "sim found");
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
index f4b1916c5..e2082105b 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
@@ -29,6 +29,7 @@ import com.android.dialer.databasepopulator.ContactsPopulator;
import com.android.dialer.databasepopulator.VoicemailPopulator;
import com.android.dialer.enrichedcall.simulator.EnrichedCallSimulatorActivity;
import com.android.dialer.persistentlog.PersistentLogger;
+import com.android.dialer.preferredsim.PreferredSimFallbackContract;
/** Implements the top level simulator menu. */
final class SimulatorMainMenu {
@@ -40,6 +41,7 @@ final class SimulatorMainMenu {
.addItem("Notifications", SimulatorNotifications.getActionProvider(context))
.addItem("Populate database", () -> populateDatabase(context))
.addItem("Clean database", () -> cleanDatabase(context))
+ .addItem("clear preferred SIM", () -> clearPreferredSim(context))
.addItem("Sync voicemail", () -> syncVoicemail(context))
.addItem("Share persistent log", () -> sharePersistentLog(context))
.addItem(
@@ -63,6 +65,14 @@ final class SimulatorMainMenu {
.executeSerial(context);
}
+ private static void clearPreferredSim(Context context) {
+ DialerExecutorComponent.get(context)
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new ClearPreferredSimWorker())
+ .build()
+ .executeSerial(context);
+ }
+
private static void syncVoicemail(@NonNull Context context) {
Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
context.sendBroadcast(intent);
@@ -109,6 +119,15 @@ final class SimulatorMainMenu {
}
}
+ private static class ClearPreferredSimWorker implements Worker<Context, Void> {
+ @Nullable
+ @Override
+ public Void doInBackground(Context context) {
+ context.getContentResolver().delete(PreferredSimFallbackContract.CONTENT_URI, null, null);
+ return null;
+ }
+ }
+
private static class ShareLogWorker implements Worker<Void, String> {
@Nullable
@Override
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
index d04143f59..6f6a87cef 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
@@ -15,6 +15,7 @@
*/
package com.android.dialer.voicemail.listui;
+import android.app.FragmentManager;
import android.database.Cursor;
import android.support.v7.widget.RecyclerView;
import android.util.ArraySet;
@@ -33,6 +34,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHol
private final Cursor cursor;
private final Clock clock;
+ private final FragmentManager fragmentManager;
/** A valid id for {@link VoicemailEntry} is greater than 0 */
private int currentlyExpandedViewHolderId = -1;
@@ -40,13 +42,16 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHol
private final Set<NewVoicemailViewHolder> newVoicemailViewHolderSet = new ArraySet<>();
/** @param cursor whose projection is {@link VoicemailCursorLoader.VOICEMAIL_COLUMNS} */
- NewVoicemailAdapter(Cursor cursor, Clock clock) {
+ NewVoicemailAdapter(Cursor cursor, Clock clock, FragmentManager fragmentManager) {
+ LogUtil.enterBlock("NewVoicemailAdapter");
this.cursor = cursor;
this.clock = clock;
+ this.fragmentManager = fragmentManager;
}
@Override
public NewVoicemailViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
+ LogUtil.enterBlock("NewVoicemailAdapter.onCreateViewHolder");
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
View view = inflater.inflate(R.layout.new_voicemail_entry, viewGroup, false);
NewVoicemailViewHolder newVoicemailViewHolder = new NewVoicemailViewHolder(view, clock, this);
@@ -56,9 +61,8 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHol
@Override
public void onBindViewHolder(NewVoicemailViewHolder viewHolder, int position) {
- LogUtil.i("onBindViewHolder", "position" + position);
cursor.moveToPosition(position);
- viewHolder.bind(cursor);
+ viewHolder.bind(cursor, fragmentManager);
expandOrCollapseViewHolder(viewHolder);
}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
index 9c1fd8b85..9a89dbe3e 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
@@ -54,7 +54,9 @@ public final class NewVoicemailFragment extends Fragment implements LoaderCallba
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
LogUtil.i("NewVoicemailFragment.onCreateLoader", "cursor size is %d", data.getCount());
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- recyclerView.setAdapter(new NewVoicemailAdapter(data, System::currentTimeMillis));
+ recyclerView.setAdapter(
+ new NewVoicemailAdapter(
+ data, System::currentTimeMillis, getActivity().getFragmentManager()));
}
@Override
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
deleted file mode 100644
index 11aa9ac2e..000000000
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 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.dialer.voicemail.listui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import com.android.dialer.common.LogUtil;
-
-/**
- * The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded.
- */
-public class NewVoicemailMediaPlayer extends LinearLayout {
-
- private Button playButton;
- private Button speakerButton;
- private Button deleteButton;
-
- public NewVoicemailMediaPlayer(Context context, AttributeSet attrs) {
- super(context, attrs);
- LogUtil.enterBlock("NewVoicemailMediaPlayer");
- LayoutInflater inflater =
- (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.new_voicemail_media_player_layout, this);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- LogUtil.enterBlock("NewVoicemailMediaPlayer.onFinishInflate");
- initializeMediaPlayerButtons();
- setupListenersForMediaPlayerButtons();
- }
-
- private void initializeMediaPlayerButtons() {
- playButton = findViewById(R.id.playButton);
- speakerButton = findViewById(R.id.speakerButton);
- deleteButton = findViewById(R.id.deleteButton);
- }
-
- private void setupListenersForMediaPlayerButtons() {
- playButton.setOnClickListener(playButtonListener);
- speakerButton.setOnClickListener(speakerButtonListener);
- deleteButton.setOnClickListener(deleteButtonListener);
- }
-
- private final View.OnClickListener playButtonListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- LogUtil.i("NewVoicemailMediaPlayer.playButtonListener", "onClick");
- }
- };
-
- private final View.OnClickListener speakerButtonListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- LogUtil.i("NewVoicemailMediaPlayer.speakerButtonListener", "onClick");
- }
- };
-
- private final View.OnClickListener deleteButtonListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- LogUtil.i("NewVoicemailMediaPlayer.deleteButtonListener", "onClick");
- }
- };
-}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
new file mode 100644
index 000000000..1e56a8189
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2017 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.dialer.voicemail.listui;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.provider.VoicemailContract;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.Pair;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
+
+/**
+ * The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded.
+ */
+public class NewVoicemailMediaPlayerView extends LinearLayout {
+
+ private Button playButton;
+ private Button speakerButton;
+ private Button deleteButton;
+ private Uri voicemailUri;
+ private FragmentManager fragmentManager;
+ private MediaPlayer mediaPlayer;
+
+ public NewVoicemailMediaPlayerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ LogUtil.enterBlock("NewVoicemailMediaPlayer");
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.new_voicemail_media_player_layout, this);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ LogUtil.enterBlock("NewVoicemailMediaPlayer.onFinishInflate");
+ initializeMediaPlayerButtons();
+ setupListenersForMediaPlayerButtons();
+ }
+
+ private void initializeMediaPlayerButtons() {
+ playButton = findViewById(R.id.playButton);
+ speakerButton = findViewById(R.id.speakerButton);
+ deleteButton = findViewById(R.id.deleteButton);
+ }
+
+ private void setupListenersForMediaPlayerButtons() {
+ playButton.setOnClickListener(playButtonListener);
+ speakerButton.setOnClickListener(speakerButtonListener);
+ deleteButton.setOnClickListener(deleteButtonListener);
+ }
+
+ private final View.OnClickListener playButtonListener =
+ view -> playVoicemailWhenAvailableLocally();
+
+ /**
+ * Plays the voicemail when we are able to play the voicemail locally from the device. This
+ * involves checking if the voicemail is available to play locally, if it is, then we setup the
+ * Media Player to play the voicemail. If the voicemail is not available, then we need download
+ * the voicemail from the voicemail server to the device, and then have the Media player play it.
+ */
+ private void playVoicemailWhenAvailableLocally() {
+ LogUtil.enterBlock("playVoicemailWhenAvailableLocally");
+ Worker<Pair<Context, Uri>, Pair<Boolean, Uri>> checkVoicemailHasContent =
+ this::queryVoicemailHasContent;
+ SuccessListener<Pair<Boolean, Uri>> checkVoicemailHasContentCallBack = this::prepareMediaPlayer;
+
+ DialerExecutorComponent.get(getContext())
+ .dialerExecutorFactory()
+ .createUiTaskBuilder(fragmentManager, "lookup_voicemail_content", checkVoicemailHasContent)
+ .onSuccess(checkVoicemailHasContentCallBack)
+ .build()
+ .executeSerial(new Pair<>(getContext(), voicemailUri));
+ }
+
+ private Pair<Boolean, Uri> queryVoicemailHasContent(Pair<Context, Uri> contextUriPair) {
+ Context context = contextUriPair.first;
+ Uri uri = contextUriPair.second;
+
+ try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
+ if (cursor != null && cursor.moveToNext()) {
+ return new Pair<>(
+ cursor.getInt(cursor.getColumnIndex(VoicemailContract.Voicemails.HAS_CONTENT)) == 1,
+ uri);
+ }
+ return new Pair<>(false, uri);
+ }
+ }
+
+ /**
+ * If the voicemail is available to play locally, setup the media player to play it. Otherwise
+ * send a request to download the voicemail and then play it.
+ */
+ private void prepareMediaPlayer(Pair<Boolean, Uri> booleanUriPair) {
+ boolean voicemailAvailableLocally = booleanUriPair.first;
+ Uri uri = booleanUriPair.second;
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.prepareMediaPlayer",
+ "voicemail available locally: %b for voicemailUri: %s",
+ voicemailAvailableLocally,
+ uri.toString());
+
+ if (voicemailAvailableLocally) {
+ try {
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setOnPreparedListener(onPreparedListener);
+ mediaPlayer.setOnErrorListener(onErrorListener);
+ mediaPlayer.setOnCompletionListener(onCompletionListener);
+
+ mediaPlayer.reset();
+ mediaPlayer.setDataSource(getContext(), uri);
+
+ mediaPlayer.prepareAsync();
+ } catch (Exception e) {
+ LogUtil.e("NewVoicemailMediaPlayer.prepareMediaPlayer", "IOException " + e);
+ }
+ } else {
+ // TODO(a bug): Add logic for downloading voicemail content from the server.
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.prepareVoicemailForMediaPlayer", "need to download content");
+ }
+ }
+
+ private final View.OnClickListener speakerButtonListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.speakerButtonListener",
+ "speaker request for voicemailUri: %s",
+ voicemailUri.toString());
+ }
+ };
+
+ private final View.OnClickListener deleteButtonListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.deleteButtonListener",
+ "delete voicemailUri %s",
+ voicemailUri.toString());
+ }
+ };
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ OnCompletionListener onCompletionListener =
+ new OnCompletionListener() {
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.onCompletionListener",
+ "completed playing voicemailUri: %s",
+ voicemailUri.toString());
+ }
+ };
+
+ private final OnPreparedListener onPreparedListener =
+ new OnPreparedListener() {
+
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.onPreparedListener",
+ "about to play voicemailUri: %s",
+ voicemailUri.toString());
+ mediaPlayer.start();
+ }
+ };
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ OnErrorListener onErrorListener =
+ new OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ LogUtil.i(
+ "NewVoicemailMediaPlayer.onErrorListener",
+ "error playing voicemailUri: %s",
+ voicemailUri.toString());
+ return false;
+ }
+ };
+
+ public void setVoicemailUri(Uri voicemailUri) {
+ Assert.isNotNull(voicemailUri);
+ this.voicemailUri = voicemailUri;
+ }
+
+ public void setFragmentManager(FragmentManager fragmentManager) {
+ this.fragmentManager = fragmentManager;
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
index d4bfefd22..078a029c9 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -15,6 +15,7 @@
*/
package com.android.dialer.voicemail.listui;
+import android.app.FragmentManager;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
@@ -25,6 +26,7 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.QuickContactBadge;
import android.widget.TextView;
+import com.android.dialer.common.LogUtil;
import com.android.dialer.contactphoto.ContactPhotoManager;
import com.android.dialer.lettertile.LetterTileDrawable;
import com.android.dialer.time.Clock;
@@ -38,7 +40,7 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
private final TextView secondaryTextView;
private final TextView transcriptionTextView;
private final QuickContactBadge quickContactBadge;
- private final View mediaPlayerView;
+ private final NewVoicemailMediaPlayerView mediaPlayerView;
private final Clock clock;
private boolean isViewHolderExpanded;
private int viewHolderId;
@@ -47,6 +49,7 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
NewVoicemailViewHolder(
View view, Clock clock, NewVoicemailViewHolderListener newVoicemailViewHolderListener) {
super(view);
+ LogUtil.enterBlock("NewVoicemailViewHolder");
this.context = view.getContext();
primaryTextView = view.findViewById(R.id.primary_text);
secondaryTextView = view.findViewById(R.id.secondary_text);
@@ -57,7 +60,7 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
voicemailViewHolderListener = newVoicemailViewHolderListener;
}
- void bind(Cursor cursor) {
+ void bind(Cursor cursor, FragmentManager fragmentManager) {
VoicemailEntry voicemailEntry = VoicemailCursorLoader.toVoicemailEntry(cursor);
viewHolderId = voicemailEntry.id();
primaryTextView.setText(VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntry));
@@ -76,6 +79,8 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
itemView.setOnClickListener(this);
setPhoto(voicemailEntry);
+ mediaPlayerView.setVoicemailUri(Uri.parse(voicemailEntry.voicemailUri()));
+ mediaPlayerView.setFragmentManager(fragmentManager);
}
// TODO(uabdullah): Consider/Implement TYPE (e.g Spam, TYPE_VOICEMAIL)
@@ -97,6 +102,7 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
}
void expandViewHolder() {
+ LogUtil.i("NewVoicemailViewHolder.expandViewHolder", "voicemail id: %d", viewHolderId);
transcriptionTextView.setMaxLines(999);
isViewHolderExpanded = true;
mediaPlayerView.setVisibility(View.VISIBLE);
@@ -117,6 +123,7 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On
@Override
public void onClick(View v) {
+ LogUtil.i("NewVoicemailViewHolder.onClick", "voicemail id: %d", viewHolderId);
if (isViewHolderExpanded) {
collapseViewHolder();
} else {
diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry.xml
index 80bb1b593..78d2785e9 100644
--- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry.xml
+++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry.xml
@@ -82,7 +82,7 @@
android:layout_gravity="center_vertical"
android:visibility="gone"/>
- <com.android.dialer.voicemail.listui.NewVoicemailMediaPlayer
+ <com.android.dialer.voicemail.listui.NewVoicemailMediaPlayerView
android:id="@+id/new_voicemail_media_player"
android:layout_width="match_parent"
android:layout_height="wrap_content"