summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlinyuh <linyuh@google.com>2017-11-18 09:20:05 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-11-18 09:20:05 +0000
commitd9bfc5a4f5cc22e0d57f449975a814726827bfbb (patch)
treec9bb61716fc202d23c4966a1d2407cfa09744791
parente97e594f85fc371a121ca31838ed64f13b7cce05 (diff)
parent3b450ceb4290364df58a541c60cc690cc36ba4c8 (diff)
Merge changes Iee4e3db8,I6e74c7fe,Ibe722477,Ia22751f0,Ic28fb197, ...
am: 3b450ceb42 Change-Id: Iaa54002139eac04ca0a646d1802bada6e69a70ad
-rw-r--r--java/com/android/dialer/app/voicemail/error/AndroidManifest.xml8
-rw-r--r--java/com/android/dialer/app/voicemail/error/PackageReplacedReceiver.java109
-rw-r--r--java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java46
-rw-r--r--java/com/android/dialer/binary/aosp/AospDialerRootComponent.java4
-rw-r--r--java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java2
-rw-r--r--java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java2
-rw-r--r--java/com/android/dialer/common/backoff/ExponentialBackoff.java96
-rw-r--r--java/com/android/dialer/common/backoff/ExponentialBaseCalculator.java126
-rw-r--r--java/com/android/dialer/dialpadview/DialpadView.java48
-rw-r--r--java/com/android/dialer/logging/contact_lookup_result.proto35
-rw-r--r--java/com/android/dialer/logging/contact_source.proto17
-rw-r--r--java/com/android/dialer/precall/impl/CallingAccountSelector.java64
-rw-r--r--java/com/android/dialer/preferredsim/suggestion/SimSuggestionComponent.java41
-rw-r--r--java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java58
-rw-r--r--java/com/android/dialer/preferredsim/suggestion/stub/StubSimSuggestionModule.java32
-rw-r--r--java/com/android/dialer/preferredsim/suggestion/stub/StubSuggestionProvider.java44
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchAdapter.java36
-rw-r--r--java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java17
-rw-r--r--java/com/android/dialer/simulator/Simulator.java13
-rw-r--r--java/com/android/dialer/simulator/SimulatorComponent.java2
-rw-r--r--java/com/android/dialer/simulator/SimulatorConnectionsBank.java55
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java119
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorConnection.java6
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorConnectionService.java20
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorConnectionsBankImpl.java147
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorModule.java6
-rw-r--r--java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java2
-rw-r--r--java/com/android/incallui/InCallActivity.java217
-rw-r--r--java/com/android/incallui/InCallActivityCommon.java226
-rw-r--r--java/com/android/voicemail/VoicemailClient.java2
-rw-r--r--java/com/android/voicemail/VoicemailVersionConstants.java38
-rw-r--r--java/com/android/voicemail/impl/PackageReplacedReceiver.java84
-rw-r--r--java/com/android/voicemail/impl/VoicemailClientImpl.java18
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionService.java30
-rw-r--r--java/com/android/voicemail/stub/StubVoicemailClient.java5
35 files changed, 1204 insertions, 571 deletions
diff --git a/java/com/android/dialer/app/voicemail/error/AndroidManifest.xml b/java/com/android/dialer/app/voicemail/error/AndroidManifest.xml
index bb6c55f5c..07cb77f58 100644
--- a/java/com/android/dialer/app/voicemail/error/AndroidManifest.xml
+++ b/java/com/android/dialer/app/voicemail/error/AndroidManifest.xml
@@ -18,12 +18,4 @@
<uses-permission android:name="android.permission.CALL_PHONE"/>
- <application>
- <receiver android:name=".PackageReplacedReceiver" android:exported="false">
- <intent-filter>
- <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
- </intent-filter>
- </receiver>
- </application>
-
</manifest>
diff --git a/java/com/android/dialer/app/voicemail/error/PackageReplacedReceiver.java b/java/com/android/dialer/app/voicemail/error/PackageReplacedReceiver.java
deleted file mode 100644
index 64d72b18f..000000000
--- a/java/com/android/dialer/app/voicemail/error/PackageReplacedReceiver.java
+++ /dev/null
@@ -1,109 +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.app.voicemail.error;
-
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.preference.PreferenceManager;
-import android.provider.CallLog.Calls;
-import android.provider.VoicemailContract.Voicemails;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.DialerExecutor.Worker;
-import com.android.dialer.common.concurrent.DialerExecutorComponent;
-
-/** Receives MY_PACKAGE_REPLACED to check for legacy voicemail users. */
-public class PackageReplacedReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- LogUtil.enterBlock("PackageReplacedReceiver.onReceive");
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- if (!prefs.contains(VoicemailTosMessageCreator.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY)) {
- setVoicemailFeatureVersionAsync(context);
- }
- }
-
- private void setVoicemailFeatureVersionAsync(Context context) {
- LogUtil.enterBlock("PackageReplacedReceiver.setVoicemailFeatureVersionAsync");
-
- // Check if user is already using voicemail (ie do they have any voicemails), and set the
- // acknowledged feature value accordingly.
- PendingResult pendingResult = goAsync();
- DialerExecutorComponent.get(context)
- .dialerExecutorFactory()
- .createNonUiTaskBuilder(new ExistingVoicemailCheck(context))
- .onSuccess(
- output -> {
- LogUtil.i("PackageReplacedReceiver.setVoicemailFeatureVersionAsync", "success");
- pendingResult.finish();
- })
- .onFailure(
- throwable -> {
- LogUtil.i("PackageReplacedReceiver.setVoicemailFeatureVersionAsync", "failure");
- pendingResult.finish();
- })
- .build()
- .executeParallel(null);
- }
-
- private static class ExistingVoicemailCheck implements Worker<Void, Void> {
- private static final String[] PROJECTION = new String[] {Voicemails._ID};
-
- private final Context context;
-
- ExistingVoicemailCheck(Context context) {
- this.context = context;
- }
-
- @TargetApi(android.os.Build.VERSION_CODES.M) // used for try with resources
- @Override
- public Void doInBackground(Void arg) throws Throwable {
- LogUtil.i("PackageReplacedReceiver.ExistingVoicemailCheck.doInBackground", "");
-
- // Check the database for existing voicemails.
- boolean hasVoicemails = false;
- Uri uri = Voicemails.buildSourceUri(context.getPackageName());
- String whereClause = Calls.TYPE + " = " + Calls.VOICEMAIL_TYPE;
- try (Cursor cursor =
- context.getContentResolver().query(uri, PROJECTION, whereClause, null, null)) {
- if (cursor == null) {
- LogUtil.e(
- "PackageReplacedReceiver.ExistingVoicemailCheck.doInBackground",
- "failed to check for existing voicemails");
- } else if (cursor.moveToNext()) {
- hasVoicemails = true;
- }
- }
-
- LogUtil.i(
- "PackageReplacedReceiver.ExistingVoicemailCheck.doInBackground",
- "has voicemails: " + hasVoicemails);
- int version = hasVoicemails ? VoicemailTosMessageCreator.LEGACY_VOICEMAIL_FEATURE_VERSION : 0;
- PreferenceManager.getDefaultSharedPreferences(context)
- .edit()
- .putInt(VoicemailTosMessageCreator.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, version)
- .apply();
- return null;
- }
- }
-}
diff --git a/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java b/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
index 3e4321309..b7c8ed721 100644
--- a/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
+++ b/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
@@ -48,6 +48,7 @@ import com.android.dialer.voicemail.settings.VoicemailSettingsFragment;
import com.android.voicemail.VisualVoicemailTypeExtensions;
import com.android.voicemail.VoicemailClient;
import com.android.voicemail.VoicemailComponent;
+import com.android.voicemail.VoicemailVersionConstants;
import java.util.Locale;
/**
@@ -55,22 +56,6 @@ import java.util.Locale;
* terms of service for Verizon and for other carriers.
*/
public class VoicemailTosMessageCreator {
- // Preference key to check which version of the Verizon ToS that the user has accepted.
- static final String PREF_VVM3_TOS_VERSION_ACCEPTED_KEY = "vvm3_tos_version_accepted";
-
- // Preference key to check which version of the Google Dialer ToS that the user has accepted.
- static final String PREF_DIALER_TOS_VERSION_ACCEPTED_KEY = "dialer_tos_version_accepted";
-
- // Preference key to check which feature version the user has acknowledged
- static final String PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY =
- "dialer_feature_version_acknowledged";
-
- static final int CURRENT_VVM3_TOS_VERSION = 2;
- static final int CURRENT_DIALER_TOS_VERSION = 1;
- static final int LEGACY_VOICEMAIL_FEATURE_VERSION = 1; // original visual voicemail
- static final int TRANSCRIPTION_VOICEMAIL_FEATURE_VERSION = 2; // adds voicemail transcription
- static final int CURRENT_VOICEMAIL_FEATURE_VERSION = TRANSCRIPTION_VOICEMAIL_FEATURE_VERSION;
-
private static final String ISO639_SPANISH = "es";
private final Context context;
@@ -328,10 +313,11 @@ public class VoicemailTosMessageCreator {
private boolean hasAcceptedTos() {
if (isVvm3()) {
- return preferences.getInt(PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, 0) >= CURRENT_VVM3_TOS_VERSION;
+ return preferences.getInt(VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION;
} else {
- return preferences.getInt(PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, 0)
- >= CURRENT_DIALER_TOS_VERSION;
+ return preferences.getInt(VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION;
}
}
@@ -339,12 +325,16 @@ public class VoicemailTosMessageCreator {
if (isVvm3()) {
preferences
.edit()
- .putInt(PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, CURRENT_VVM3_TOS_VERSION)
+ .putInt(
+ VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY,
+ VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION)
.apply();
} else {
preferences
.edit()
- .putInt(PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, CURRENT_DIALER_TOS_VERSION)
+ .putInt(
+ VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY,
+ VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION)
.apply();
}
@@ -360,20 +350,24 @@ public class VoicemailTosMessageCreator {
return true;
}
- return preferences.getInt(PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
- >= CURRENT_VOICEMAIL_FEATURE_VERSION;
+ return preferences.getInt(
+ VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION;
}
private void recordFeatureAcknowledgement() {
preferences
.edit()
- .putInt(PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, CURRENT_VOICEMAIL_FEATURE_VERSION)
+ .putInt(
+ VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY,
+ VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION)
.apply();
}
private boolean isLegacyVoicemailUser() {
- return preferences.getInt(PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
- == LEGACY_VOICEMAIL_FEATURE_VERSION;
+ return preferences.getInt(
+ VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
+ == VoicemailVersionConstants.LEGACY_VOICEMAIL_FEATURE_VERSION;
}
private void logTosCreatedImpression() {
diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
index a02727ea8..a0fb604cf 100644
--- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
+++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
@@ -26,6 +26,7 @@ import com.android.dialer.inject.ContextModule;
import com.android.dialer.phonelookup.PhoneLookupModule;
import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
import com.android.dialer.precall.impl.PreCallModule;
+import com.android.dialer.preferredsim.suggestion.stub.StubSimSuggestionModule;
import com.android.dialer.simulator.impl.SimulatorModule;
import com.android.dialer.storage.StorageModule;
import com.android.dialer.strictmode.impl.SystemStrictModeModule;
@@ -53,7 +54,8 @@ import javax.inject.Singleton;
StubDuoModule.class,
StubEnrichedCallModule.class,
StubMapsModule.class,
- VoicemailModule.class
+ VoicemailModule.class,
+ StubSimSuggestionModule.class
}
)
public interface AospDialerRootComponent extends BaseDialerRootComponent {}
diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
index 387fca530..d5c91c90a 100644
--- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
+++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
@@ -26,6 +26,7 @@ import com.android.dialer.main.MainComponent;
import com.android.dialer.phonelookup.PhoneLookupComponent;
import com.android.dialer.phonenumbergeoutil.PhoneNumberGeoUtilComponent;
import com.android.dialer.precall.PreCallComponent;
+import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
import com.android.dialer.simulator.SimulatorComponent;
import com.android.dialer.storage.StorageComponent;
import com.android.dialer.strictmode.StrictModeComponent;
@@ -50,6 +51,7 @@ public interface BaseDialerRootComponent
PhoneLookupComponent.HasComponent,
PhoneNumberGeoUtilComponent.HasComponent,
PreCallComponent.HasComponent,
+ SimSuggestionComponent.HasComponent,
SimulatorComponent.HasComponent,
StorageComponent.HasComponent,
StrictModeComponent.HasComponent,
diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
index 273d1e4fc..1ae80f4aa 100644
--- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
+++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
@@ -26,6 +26,7 @@ import com.android.dialer.inject.ContextModule;
import com.android.dialer.phonelookup.PhoneLookupModule;
import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
import com.android.dialer.precall.impl.PreCallModule;
+import com.android.dialer.preferredsim.suggestion.stub.StubSimSuggestionModule;
import com.android.dialer.simulator.impl.SimulatorModule;
import com.android.dialer.storage.StorageModule;
import com.android.dialer.strictmode.impl.SystemStrictModeModule;
@@ -49,6 +50,7 @@ import javax.inject.Singleton;
PhoneLookupModule.class, // TODO(zachh): Module which uses APDL?
PhoneNumberGeoUtilModule.class,
PreCallModule.class,
+ StubSimSuggestionModule.class,
SharedPrefConfigProviderModule.class,
SimulatorModule.class,
StorageModule.class,
diff --git a/java/com/android/dialer/common/backoff/ExponentialBackoff.java b/java/com/android/dialer/common/backoff/ExponentialBackoff.java
new file mode 100644
index 000000000..67727d8c2
--- /dev/null
+++ b/java/com/android/dialer/common/backoff/ExponentialBackoff.java
@@ -0,0 +1,96 @@
+/*
+ * 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.common.backoff;
+
+import com.android.dialer.common.Assert;
+
+/**
+ * Given an initial backoff delay, D, a base multiplier, B, and a total number of backoffs, N, this
+ * class returns values in the exponential sequence, D, D*B, D*B^2, ... D*B^(N-1), ...
+ *
+ * <p>Example usage:
+ *
+ * <pre>
+ * long initialDelayMillis = 1000;
+ * double multiplier = 1.2;
+ * int backoffs = 10;
+ * ExponentialBackoff backoff = new ExponentialBackoff(initialDelayMillis, multiplier, backoffs);
+ * while (backoff.isInRange()) {
+ * ...
+ * sleep(backoff.getNextBackoff());
+ * }
+ * </pre>
+ *
+ * <p>Note: the base multiplier can be calculated using {@code ExponentialBaseCalculator}
+ */
+public final class ExponentialBackoff {
+ public final long initialDelayMillis;
+ public final double baseMultiplier;
+ public final int maximumBackoffs;
+ private double nextBackoff;
+ private int backoffCount;
+
+ /**
+ * Setup an exponential backoff with an initial delay, a base multiplier and a maximum number of
+ * backoff steps.
+ *
+ * @throws IllegalArgumentException for negative argument values
+ */
+ public ExponentialBackoff(long initialDelayMillis, double baseMultiplier, int maximumBackoffs) {
+ Assert.checkArgument(initialDelayMillis > 0);
+ Assert.checkArgument(baseMultiplier > 0);
+ Assert.checkArgument(maximumBackoffs > 0);
+ this.initialDelayMillis = initialDelayMillis;
+ this.baseMultiplier = baseMultiplier;
+ this.maximumBackoffs = maximumBackoffs;
+ reset();
+ }
+
+ /**
+ * @return the next backoff time in the exponential sequence. Specifically, if D is the initial
+ * delay, B is the base multiplier and N is the total number of backoffs, then the return
+ * values will be: D, D*B, D*B^2, ... D*B^(N-1), ...
+ */
+ public long getNextBackoff() {
+ long backoff = Math.round(nextBackoff);
+ backoffCount++;
+ nextBackoff *= baseMultiplier;
+ return backoff;
+ }
+
+ /** @return the number of times getNextBackoff() has been called */
+ public int getBackoffCount() {
+ return backoffCount;
+ }
+
+ /**
+ * @return {@code true} if getNextBackoff() has been called less than the maximumBackoffs value
+ * specified in the constructor.
+ */
+ public boolean isInRange() {
+ return backoffCount < maximumBackoffs;
+ }
+
+ /**
+ * Reset the sequence of backoff values so the next call to getNextBackoff() will return the
+ * initial delay and getBackoffCount() will return 0
+ */
+ public void reset() {
+ nextBackoff = initialDelayMillis;
+ backoffCount = 0;
+ }
+}
diff --git a/java/com/android/dialer/common/backoff/ExponentialBaseCalculator.java b/java/com/android/dialer/common/backoff/ExponentialBaseCalculator.java
new file mode 100644
index 000000000..a1ae8a053
--- /dev/null
+++ b/java/com/android/dialer/common/backoff/ExponentialBaseCalculator.java
@@ -0,0 +1,126 @@
+/*
+ * 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.common.backoff;
+
+import com.android.dialer.common.Assert;
+
+/**
+ * Given an initial delay, D, a maximum total backoff time, T, and a maximum number of backoffs, N,
+ * this class calculates a base multiplier, b, and a scaling factor, g, such that the initial
+ * backoff is g*b = D and the sum of the scaled backoffs is T = g*b + g*b^2 + g*b^3 + ... g*b^N
+ *
+ * <p>T/D = (1 - b^N)/(1 - b) but this cannot be written as a simple equation for b in terms of T, N
+ * and D so instead use Newton's method (https://en.wikipedia.org/wiki/Newton%27s_method) to find an
+ * approximate value for b.
+ *
+ * <p>Example usage using the {@code ExponentialBackoff} would be:
+ *
+ * <pre>
+ * // Retry with exponential backoff for up to 2 minutes, with an initial delay of 100 millis
+ * // and a maximum of 10 retries
+ * long initialDelayMillis = 100;
+ * int maxTries = 10;
+ * double base = ExponentialBaseCalculator.findBase(
+ * initialDelayMillis,
+ * TimeUnit.MINUTES.toMillis(2),
+ * maxTries);
+ * ExponentialBackoff backoff = new ExponentialBackoff(initialDelayMillis, base, maxTries);
+ * while (backoff.isInRange()) {
+ * ...
+ * long delay = backoff.getNextBackoff();
+ * // Wait for the indicated time...
+ * }
+ * </pre>
+ */
+public final class ExponentialBaseCalculator {
+ private static final int MAX_STEPS = 1000;
+ private static final double DEFAULT_TOLERANCE_MILLIS = 1;
+
+ /**
+ * Calculate an exponential backoff base multiplier such that the first backoff delay will be as
+ * specified and the sum of the delays after doing the indicated maximum number of backoffs will
+ * be as specified.
+ *
+ * @throws IllegalArgumentException if the initial delay is greater than the total backoff time
+ * @throws IllegalArgumentException if the maximum number of backoffs is not greater than 1
+ * @throws IllegalStateException if it fails to find an acceptable base multiplier
+ */
+ public static double findBase(
+ long initialDelayMillis, long totalBackoffTimeMillis, int maximumBackoffs) {
+ Assert.checkArgument(initialDelayMillis < totalBackoffTimeMillis);
+ Assert.checkArgument(maximumBackoffs > 1);
+ long scaledTotalTime = Math.round(((double) totalBackoffTimeMillis) / initialDelayMillis);
+ double scaledTolerance = DEFAULT_TOLERANCE_MILLIS / initialDelayMillis;
+ return getBaseImpl(scaledTotalTime, maximumBackoffs, scaledTolerance);
+ }
+
+ /**
+ * T/D = (1 - b^N)/(1 - b) but this cannot be written as a simple equation for b in terms of T, D
+ * and N so instead we use Newtons method to find an approximate value for b.
+ *
+ * <p>Let f(b) = (1 - b^N)/(1 - b) - T/D then we want to find b* such that f(b*) = 0, or more
+ * precisely |f(b*)| < tolerance
+ *
+ * <p>Using Newton's method we can interatively find b* as follows: b1 = b0 - f(b0)/f'(b0), where
+ * b0 is the current best guess for b* and b1 is the next best guess.
+ *
+ * <p>f'(b) = (f(b) + T/D - N*b^(N - 1))/(1 - b)
+ *
+ * <p>so
+ *
+ * <p>b1 = b0 - f(b0)(1 - b0)/(f(b0) + T/D - N*b0^(N - 1))
+ */
+ private static double getBaseImpl(long t, int n, double tolerance) {
+ double b0 = 2; // Initial guess for b*
+ double b0n = Math.pow(b0, n);
+ double fb0 = f(b0, t, b0n);
+ if (Math.abs(fb0) < tolerance) {
+ // Initial guess was pretty good
+ return b0;
+ }
+
+ for (int i = 0; i < MAX_STEPS; i++) {
+ double fpb0 = fp(b0, t, n, fb0, b0n);
+ double b1 = b0 - fb0 / fpb0;
+ double b1n = Math.pow(b1, n);
+ double fb1 = f(b1, t, b1n);
+
+ if (Math.abs(fb1) < tolerance) {
+ // Found an acceptable value
+ return b1;
+ }
+
+ b0 = b1;
+ b0n = b1n;
+ fb0 = fb1;
+ }
+
+ throw new IllegalStateException("Failed to find base. Too many iterations.");
+ }
+
+ // Evaluate f(b), the function we are trying to find the zero for.
+ // Note: passing b^N as a parameter so it only has to be calculated once
+ private static double f(double b, long t, double bn) {
+ return (1 - bn) / (1 - b) - t;
+ }
+
+ // Evaluate f'(b), the derivative of the function we are trying to find the zero for.
+ // Note: passing f(b) and b^N as parameters for efficiency
+ private static double fp(double b, long t, int n, double fb, double bn) {
+ return (fb + t - n * bn / b) / (1 - b);
+ }
+}
diff --git a/java/com/android/dialer/dialpadview/DialpadView.java b/java/com/android/dialer/dialpadview/DialpadView.java
index 4cbf42f2e..1d48066ad 100644
--- a/java/com/android/dialer/dialpadview/DialpadView.java
+++ b/java/com/android/dialer/dialpadview/DialpadView.java
@@ -22,9 +22,7 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
-import android.os.Build;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.style.TtsSpan;
@@ -33,7 +31,6 @@ import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.accessibility.AccessibilityManager;
@@ -90,7 +87,6 @@ public class DialpadView extends LinearLayout {
private ViewGroup mRateContainer;
private TextView mIldCountry;
private TextView mIldRate;
- private boolean mCanDigitsBeEdited;
public DialpadView(Context context) {
this(context, null);
@@ -154,16 +150,7 @@ public class DialpadView extends LinearLayout {
private void setupKeypad() {
final Resources resources = getContext().getResources();
-
- final Locale currentLocale = resources.getConfiguration().locale;
- final NumberFormat nf;
- // We translate dialpad numbers only for "fa" and not any other locale
- // ("ar" anybody ?).
- if ("fa".equals(currentLocale.getLanguage())) {
- nf = DecimalFormat.getInstance(CompatUtils.getLocale(getContext()));
- } else {
- nf = DecimalFormat.getInstance(Locale.ENGLISH);
- }
+ final NumberFormat numberFormat = getNumberFormat();
for (int i = 0; i < BUTTON_IDS.length; i++) {
DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]);
@@ -178,7 +165,7 @@ public class DialpadView extends LinearLayout {
numberString = resources.getString(R.string.dialpad_star_number);
numberContentDescription = numberString;
} else {
- numberString = nf.format(i);
+ numberString = numberFormat.format(i);
// The content description is used for Talkback key presses. The number is
// separated by a "," to introduce a slight delay. Convert letters into a verbatim
// span so that they are read as letters instead of as one word.
@@ -194,7 +181,7 @@ public class DialpadView extends LinearLayout {
}
final RippleDrawable rippleBackground =
- (RippleDrawable) getDrawableCompat(getContext(), R.drawable.btn_dialpad_key);
+ (RippleDrawable) getContext().getDrawable(R.drawable.btn_dialpad_key);
if (mRippleColor != null) {
rippleBackground.setColor(mRippleColor);
}
@@ -239,26 +226,19 @@ public class DialpadView extends LinearLayout {
zero.setLongHoverContentDescription(resources.getText(R.string.description_image_button_plus));
}
- private Drawable getDrawableCompat(Context context, int id) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- return context.getDrawable(id);
- } else {
- return context.getResources().getDrawable(id);
- }
- }
+ private NumberFormat getNumberFormat() {
+ Locale locale = CompatUtils.getLocale(getContext());
- public void setShowVoicemailButton(boolean show) {
- View view = findViewById(R.id.dialpad_key_voicemail);
- if (view != null) {
- view.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
- }
+ // Return the Persian number format if the current language is Persian.
+ return "fas".equals(locale.getISO3Language())
+ ? DecimalFormat.getInstance(locale)
+ : DecimalFormat.getInstance(Locale.ENGLISH);
}
/**
- * Whether or not the digits above the dialer can be edited.
+ * Configure whether or not the digits above the dialpad can be edited.
*
- * @param canBeEdited If true, the backspace button will be shown and the digits EditText will be
- * configured to allow text manipulation.
+ * <p>If we allow editing digits, the backspace button will be shown.
*/
public void setCanDigitsBeEdited(boolean canBeEdited) {
View deleteButton = findViewById(R.id.deleteButton);
@@ -271,8 +251,6 @@ public class DialpadView extends LinearLayout {
digits.setLongClickable(canBeEdited);
digits.setFocusableInTouchMode(canBeEdited);
digits.setCursorVisible(false);
-
- mCanDigitsBeEdited = canBeEdited;
}
public void setCallRateInformation(String countryName, String displayRate) {
@@ -285,10 +263,6 @@ public class DialpadView extends LinearLayout {
mIldRate.setText(displayRate);
}
- public boolean canDigitsBeEdited() {
- return mCanDigitsBeEdited;
- }
-
/**
* Always returns true for onHoverEvent callbacks, to fix problems with accessibility due to the
* dialpad overlaying other fragments.
diff --git a/java/com/android/dialer/logging/contact_lookup_result.proto b/java/com/android/dialer/logging/contact_lookup_result.proto
index 6c83908b9..673ade760 100644
--- a/java/com/android/dialer/logging/contact_lookup_result.proto
+++ b/java/com/android/dialer/logging/contact_lookup_result.proto
@@ -65,5 +65,40 @@ message ContactLookupResult {
// Number was found in Dialer's local cache and was originally identified
// via Cequint caller ID.
LOCAL_CACHE_CEQUINT = 15;
+
+ // Number was identified by a remote data source not listed below
+ REMOTE_OTHER = 16;
+
+ // Number was found in Dialer's local cache and was originally identified
+ // as REMOTE_OTHER
+ LOCAL_CACHE_REMOTE_OTHER = 17;
+
+ // Number was identified by manually-entered caller ID data
+ REMOTE_MANUAL = 18;
+
+ // Number was found in Dialer's local cache and was originally identified
+ // as REMOTE_MANUAL
+ LOCAL_CACHE_REMOTE_MANUAL = 19;
+
+ // Number was identified by Google Voice data
+ REMOTE_GOOGLE_VOICE = 20;
+
+ // Number was found in Dialer's local cache and was originally identified
+ // as REMOTE_GOOGLE_VOICE
+ LOCAL_CACHE_REMOTE_GOOGLE_VOICE = 21;
+
+ // Number was identified by Customer Service Apps data
+ REMOTE_CSA = 22;
+
+ // Number was found in Dialer's local cache and was originally identified
+ // as REMOTE_CSA
+ LOCAL_CACHE_REMOTE_CSA = 23;
+
+ // Number was identified by Knowledge Graph data
+ REMOTE_KNOWLEDGE_GRAPH = 24;
+
+ // Number was found in Dialer's local cache and was originally identified
+ // as REMOTE_KNOWLEDGE_GRAPH
+ LOCAL_CACHE_REMOTE_KNOWLEDGE_GRAPH = 25;
}
}
diff --git a/java/com/android/dialer/logging/contact_source.proto b/java/com/android/dialer/logging/contact_source.proto
index 96ef9e18e..7b90730ab 100644
--- a/java/com/android/dialer/logging/contact_source.proto
+++ b/java/com/android/dialer/logging/contact_source.proto
@@ -16,6 +16,8 @@ message ContactSource {
// number's status at the time they made or received the call.
// Type definitions are from the CachedContactInfo interface in
// CachedNumberLookupService.java
+ // When adding new sources here, consider updating the isPeopleApiSource() and
+ // isBusiness() methods in LookupSourceUtils.java if needed.
enum Type {
UNKNOWN_SOURCE_TYPE = 0;
@@ -36,5 +38,20 @@ message ContactSource {
SOURCE_TYPE_CNAP = 5;
SOURCE_TYPE_CEQUINT_CALLER_ID = 6;
+
+ // A remote source not listed below
+ SOURCE_TYPE_REMOTE_OTHER = 7;
+
+ // Manually-entered caller ID data
+ SOURCE_TYPE_REMOTE_MANUAL = 8;
+
+ // Google Voice short code data
+ SOURCE_TYPE_REMOTE_GOOGLE_VOICE = 9;
+
+ // Customer Service Applications data
+ SOURCE_TYPE_REMOTE_CSA = 10;
+
+ // Knowledge Graph data
+ SOURCE_TYPE_REMOTE_KNOWLEDGE_GRAPH = 11;
}
}
diff --git a/java/com/android/dialer/precall/impl/CallingAccountSelector.java b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
index d763c7a5f..e0fe0c488 100644
--- a/java/com/android/dialer/precall/impl/CallingAccountSelector.java
+++ b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
@@ -44,6 +44,8 @@ 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.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
+import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion;
import com.google.common.base.Optional;
import java.util.List;
import java.util.Set;
@@ -84,7 +86,7 @@ public class CallingAccountSelector implements PreCallAction {
}
switch (builder.getUri().getScheme()) {
case PhoneAccount.SCHEME_VOICEMAIL:
- showDialog(coordinator, coordinator.startPendingAction(), null);
+ showDialog(coordinator, coordinator.startPendingAction(), null, null, null);
break;
case PhoneAccount.SCHEME_TEL:
processPreferredAccount(coordinator);
@@ -128,7 +130,17 @@ public class CallingAccountSelector implements PreCallAction {
pendingAction.finish();
return;
}
- showDialog(coordinator, pendingAction, result.dataId.orNull());
+ if (result.suggestion.isPresent()) {
+ LogUtil.i(
+ "CallingAccountSelector.processPreferredAccount",
+ "SIM suggested: " + result.suggestion.get().reason);
+ }
+ showDialog(
+ coordinator,
+ pendingAction,
+ result.dataId.orNull(),
+ phoneNumber,
+ result.suggestion.orNull());
}))
.build()
.executeParallel(activity);
@@ -136,7 +148,11 @@ public class CallingAccountSelector implements PreCallAction {
@MainThread
private void showDialog(
- PreCallCoordinator coordinator, PendingAction pendingAction, @Nullable String dataId) {
+ PreCallCoordinator coordinator,
+ PendingAction pendingAction,
+ @Nullable String dataId,
+ @Nullable String number,
+ @Nullable Suggestion unusedSuggestion) { // TODO(twyen): incoporate suggestion in dialog
Assert.isMainThread();
selectPhoneAccountDialogFragment =
SelectPhoneAccountDialogFragment.newInstance(
@@ -146,7 +162,7 @@ public class CallingAccountSelector implements PreCallAction {
.getActivity()
.getSystemService(TelecomManager.class)
.getCallCapablePhoneAccounts(),
- new SelectedListener(coordinator, pendingAction, dataId),
+ new SelectedListener(coordinator, pendingAction, dataId, number),
null /* call ID */);
selectPhoneAccountDialogFragment.show(
coordinator.getActivity().getFragmentManager(), TAG_CALLING_ACCOUNT_SELECTOR);
@@ -169,6 +185,8 @@ public class CallingAccountSelector implements PreCallAction {
* preferred account is to be set it should be stored in this row
*/
Optional<String> dataId = Optional.absent();
+
+ Optional<Suggestion> suggestion = Optional.absent();
}
private static class PreferredAccountWorker
@@ -189,6 +207,12 @@ public class CallingAccountSelector implements PreCallAction {
if (result.dataId.isPresent()) {
result.phoneAccountHandle = getPreferredAccount(context, result.dataId.get());
}
+ if (!result.phoneAccountHandle.isPresent()) {
+ result.suggestion =
+ SimSuggestionComponent.get(context)
+ .getSuggestionProvider()
+ .getSuggestion(context, phoneNumber);
+ }
return result;
}
}
@@ -257,14 +281,17 @@ public class CallingAccountSelector implements PreCallAction {
private final PreCallCoordinator coordinator;
private final PreCallCoordinator.PendingAction listener;
private final String dataId;
+ private final String number;
public SelectedListener(
@NonNull PreCallCoordinator builder,
@NonNull PreCallCoordinator.PendingAction listener,
- @Nullable String dataId) {
+ @Nullable String dataId,
+ @Nullable String number) {
this.coordinator = Assert.isNotNull(builder);
this.listener = Assert.isNotNull(listener);
this.dataId = dataId;
+ this.number = number;
}
@MainThread
@@ -282,7 +309,11 @@ public class CallingAccountSelector implements PreCallAction {
new WritePreferredAccountWorkerInput(
coordinator.getActivity(), dataId, selectedAccountHandle));
}
-
+ DialerExecutorComponent.get(coordinator.getActivity())
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new UserSelectionReporter(selectedAccountHandle, number))
+ .build()
+ .executeParallel(coordinator.getActivity());
listener.finish();
}
@@ -297,6 +328,27 @@ public class CallingAccountSelector implements PreCallAction {
}
}
+ private static class UserSelectionReporter implements Worker<Context, Void> {
+
+ private final String number;
+ private final PhoneAccountHandle phoneAccountHandle;
+
+ public UserSelectionReporter(
+ @NonNull PhoneAccountHandle phoneAccountHandle, @Nullable String number) {
+ this.phoneAccountHandle = Assert.isNotNull(phoneAccountHandle);
+ this.number = Assert.isNotNull(number);
+ }
+
+ @Nullable
+ @Override
+ public Void doInBackground(@NonNull Context context) throws Throwable {
+ SimSuggestionComponent.get(context)
+ .getSuggestionProvider()
+ .reportUserSelection(context, number, phoneAccountHandle);
+ return null;
+ }
+ }
+
private static class WritePreferredAccountWorkerInput {
private final Context context;
private final String dataId;
diff --git a/java/com/android/dialer/preferredsim/suggestion/SimSuggestionComponent.java b/java/com/android/dialer/preferredsim/suggestion/SimSuggestionComponent.java
new file mode 100644
index 000000000..4b3f7b26f
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/suggestion/SimSuggestionComponent.java
@@ -0,0 +1,41 @@
+/*
+ * 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.preferredsim.suggestion;
+
+import android.content.Context;
+import android.support.annotation.WorkerThread;
+import com.android.dialer.common.Assert;
+import com.android.dialer.inject.HasRootComponent;
+import dagger.Subcomponent;
+
+/** Dagger component for {@link SuggestionProvider} */
+@Subcomponent
+public abstract class SimSuggestionComponent {
+ public abstract SuggestionProvider getSuggestionProvider();
+
+ @WorkerThread
+ public static SimSuggestionComponent get(Context context) {
+ Assert.isWorkerThread();
+ return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
+ .simSuggestionComponent();
+ }
+
+ /** Used to refer to the root application component. */
+ public interface HasComponent {
+ SimSuggestionComponent simSuggestionComponent();
+ }
+}
diff --git a/java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java b/java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java
new file mode 100644
index 000000000..61a831b5e
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/suggestion/SuggestionProvider.java
@@ -0,0 +1,58 @@
+/*
+ * 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.preferredsim.suggestion;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+import android.telecom.PhoneAccountHandle;
+import com.android.dialer.common.Assert;
+import com.google.common.base.Optional;
+
+/** Provides hints to the user when selecting a SIM to make a call. */
+public interface SuggestionProvider {
+
+ /** The reason the suggestion is made. */
+ enum Reason {
+ UNKNOWN,
+ // The SIM has the same carrier as the callee.
+ INTRA_CARRIER,
+ // The user has selected the SIM for the callee multiple times.
+ FREQUENT
+ }
+
+ /** The suggestion. */
+ class Suggestion {
+ @NonNull public final PhoneAccountHandle phoneAccountHandle;
+ @NonNull public final Reason reason;
+
+ public Suggestion(@NonNull PhoneAccountHandle phoneAccountHandle, @NonNull Reason reason) {
+ this.phoneAccountHandle = Assert.isNotNull(phoneAccountHandle);
+ this.reason = Assert.isNotNull(reason);
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ Optional<Suggestion> getSuggestion(@NonNull Context context, @NonNull String number);
+
+ @WorkerThread
+ void reportUserSelection(
+ @NonNull Context context,
+ @NonNull String number,
+ @NonNull PhoneAccountHandle phoneAccountHandle);
+}
diff --git a/java/com/android/dialer/preferredsim/suggestion/stub/StubSimSuggestionModule.java b/java/com/android/dialer/preferredsim/suggestion/stub/StubSimSuggestionModule.java
new file mode 100644
index 000000000..2f0e9b238
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/suggestion/stub/StubSimSuggestionModule.java
@@ -0,0 +1,32 @@
+/*
+ * 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.preferredsim.suggestion.stub;
+
+import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
+import dagger.Binds;
+import dagger.Module;
+import javax.inject.Singleton;
+
+/** Stub module for {@link com.android.dialer.preferredsim.suggestion.SimSuggestionComponent} */
+@Module
+public abstract class StubSimSuggestionModule {
+
+ @Binds
+ @Singleton
+ public abstract SuggestionProvider bindSuggestionProvider(
+ StubSuggestionProvider suggestionProvider);
+}
diff --git a/java/com/android/dialer/preferredsim/suggestion/stub/StubSuggestionProvider.java b/java/com/android/dialer/preferredsim/suggestion/stub/StubSuggestionProvider.java
new file mode 100644
index 000000000..e3240448d
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/suggestion/stub/StubSuggestionProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.preferredsim.suggestion.stub;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+import android.telecom.PhoneAccountHandle;
+import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
+import com.google.common.base.Optional;
+import javax.inject.Inject;
+
+/** {@link SuggestionProvider} that does nothing. */
+public class StubSuggestionProvider implements SuggestionProvider {
+
+ @Inject
+ public StubSuggestionProvider() {}
+
+ @WorkerThread
+ @Override
+ public Optional<Suggestion> getSuggestion(Context context, String number) {
+ return Optional.absent();
+ }
+
+ @Override
+ public void reportUserSelection(
+ @NonNull Context context,
+ @NonNull String number,
+ @NonNull PhoneAccountHandle phoneAccountHandle) {}
+}
diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
index 4254baece..17cab6db1 100644
--- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java
+++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
@@ -17,7 +17,6 @@
package com.android.dialer.searchfragment.list;
import android.content.Context;
-import android.database.Cursor;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
@@ -28,15 +27,12 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.android.dialer.common.Assert;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.searchfragment.common.Projections;
import com.android.dialer.searchfragment.common.RowClickListener;
import com.android.dialer.searchfragment.common.SearchCursor;
import com.android.dialer.searchfragment.cp2.SearchContactViewHolder;
import com.android.dialer.searchfragment.list.SearchCursorManager.RowType;
import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder;
import com.android.dialer.searchfragment.remote.RemoteContactViewHolder;
-import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader;
import java.util.List;
/** RecyclerView adapter for {@link NewSearchFragment}. */
@@ -107,23 +103,6 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
} else if (holder instanceof NearbyPlaceViewHolder) {
((NearbyPlaceViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
} else if (holder instanceof RemoteContactViewHolder) {
- Cursor cursor = searchCursorManager.getCursor(position);
- // Temporary logging to identify cause of a bug:
- if (cursor.getString(Projections.PHONE_NUMBER) == null) {
- LogUtil.e(
- "SearchAdapter.onBindViewHolder", "cursor class: %s", cursor.getClass().getName());
- LogUtil.e("SearchAdapter.onBindViewHolder", "position: %d", position);
- LogUtil.e(
- "SearchAdapter.onBindViewHolder",
- "query length: %s",
- query == null ? "null" : query.length());
- logDirectories();
- LogUtil.e(
- "SearchAdapter.onBindViewHolder",
- "directory id: %d",
- ((SearchCursor) cursor).getDirectoryId());
- throw new IllegalStateException("Null phone number reading remote contact");
- }
((RemoteContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
} else if (holder instanceof HeaderViewHolder) {
String header =
@@ -142,21 +121,6 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
}
}
- private void logDirectories() {
- try (Cursor directories = new RemoteDirectoriesCursorLoader(context).loadInBackground()) {
- if (directories.moveToFirst()) {
- do {
- LogUtil.e(
- "SearchAdapter.logDirectories",
- "directory: %s",
- RemoteDirectoriesCursorLoader.readDirectory(directories));
- } while (directories.moveToNext());
- } else {
- LogUtil.e("SearchAdapter.logDirectories", "no directories found");
- }
- }
- }
-
public void setContactsCursor(SearchCursor cursor) {
if (searchCursorManager.setContactsCursor(cursor)) {
// Since this is a new contacts cursor, we need to reapply the filter.
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java b/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
index 3d16c4351..de71025cd 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
@@ -36,14 +36,12 @@ public final class RemoteDirectoriesCursorLoader extends CursorLoader {
private static final int DISPLAY_NAME = 1;
private static final int PHOTO_SUPPORT = 2;
- private static final int AUTHORITY = 3;
@VisibleForTesting
static final String[] PROJECTION = {
ContactsContract.Directory._ID,
ContactsContract.Directory.DISPLAY_NAME,
ContactsContract.Directory.PHOTO_SUPPORT,
- ContactsContract.Directory.DIRECTORY_AUTHORITY,
};
public RemoteDirectoriesCursorLoader(Context context) {
@@ -53,10 +51,7 @@ public final class RemoteDirectoriesCursorLoader extends CursorLoader {
/** @return current cursor row represented as a {@link Directory}. */
public static Directory readDirectory(Cursor cursor) {
return Directory.create(
- cursor.getInt(ID),
- cursor.getString(DISPLAY_NAME),
- cursor.getInt(PHOTO_SUPPORT) != 0,
- cursor.getString(AUTHORITY));
+ cursor.getInt(ID), cursor.getString(DISPLAY_NAME), cursor.getInt(PHOTO_SUPPORT) != 0);
}
private static Uri getContentUri() {
@@ -68,14 +63,8 @@ public final class RemoteDirectoriesCursorLoader extends CursorLoader {
/** POJO representing the results returned from {@link RemoteDirectoriesCursorLoader}. */
@AutoValue
public abstract static class Directory {
- public static Directory create(
- int id, @Nullable String displayName, boolean supportsPhotos, @Nullable String authority) {
- return new AutoValue_RemoteDirectoriesCursorLoader_Directory(
- id, displayName, supportsPhotos, authority);
- }
-
public static Directory create(int id, @Nullable String displayName, boolean supportsPhotos) {
- return create(id, displayName, supportsPhotos, null);
+ return new AutoValue_RemoteDirectoriesCursorLoader_Directory(id, displayName, supportsPhotos);
}
public abstract int getId();
@@ -84,7 +73,5 @@ public final class RemoteDirectoriesCursorLoader extends CursorLoader {
abstract @Nullable String getDisplayName();
abstract boolean supportsPhotos();
-
- abstract @Nullable String authority();
}
}
diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java
index 2094b420e..d75d10e82 100644
--- a/java/com/android/dialer/simulator/Simulator.java
+++ b/java/com/android/dialer/simulator/Simulator.java
@@ -42,6 +42,19 @@ public interface Simulator {
static final int CONFERENCE_TYPE_GSM = 1;
static final int CONFERENCE_TYPE_VOLTE = 2;
+ /** The types of connection service listener events */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ON_NEW_OUTGOING_CONNECTION,
+ ON_NEW_INCOMING_CONNECTION,
+ ON_CONFERENCE,
+ })
+ @interface ConnectionServiceEventType {}
+
+ static final int ON_NEW_OUTGOING_CONNECTION = 1;
+ static final int ON_NEW_INCOMING_CONNECTION = 2;
+ static final int ON_CONFERENCE = 3;
+
/** Information about a connection event. */
public static class Event {
/** The type of connection event. */
diff --git a/java/com/android/dialer/simulator/SimulatorComponent.java b/java/com/android/dialer/simulator/SimulatorComponent.java
index f14496b80..dee188281 100644
--- a/java/com/android/dialer/simulator/SimulatorComponent.java
+++ b/java/com/android/dialer/simulator/SimulatorComponent.java
@@ -26,6 +26,8 @@ public abstract class SimulatorComponent {
public abstract Simulator getSimulator();
+ public abstract SimulatorConnectionsBank getSimulatorConnectionsBank();
+
public static SimulatorComponent get(Context context) {
return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
.simulatorComponent();
diff --git a/java/com/android/dialer/simulator/SimulatorConnectionsBank.java b/java/com/android/dialer/simulator/SimulatorConnectionsBank.java
new file mode 100644
index 000000000..23c00424f
--- /dev/null
+++ b/java/com/android/dialer/simulator/SimulatorConnectionsBank.java
@@ -0,0 +1,55 @@
+/*
+ * 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.simulator;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.telecom.Connection;
+import com.android.dialer.simulator.Simulator.ConferenceType;
+import java.util.List;
+
+/**
+ * Used to create a shared connections bank which contains methods to manipulate connections. This
+ * is used mainly for conference calling.
+ */
+public interface SimulatorConnectionsBank {
+
+ /** Add a connection into bank. */
+ void add(Connection connection);
+
+ /** Remove a connection from bank. */
+ void remove(Connection connection);
+
+ /** Merge all existing connections created by simulator into a conference. */
+ void mergeAllConnections(@ConferenceType int conferenceType, Context context);
+
+ /** Set all connections created by simulator to disconnected. */
+ void disconnectAllConnections();
+
+ /**
+ * Update conferenceable connections for all connections in bank (usually after adding a new
+ * connection). Before calling this method, make sure all connections are returned by
+ * ConnectionService.
+ */
+ void updateConferenceableConnections();
+
+ /** Determine whether a connection is created by simulator. */
+ boolean isSimulatorConnection(@NonNull Connection connection);
+
+ /** Get all connections tags from bank. */
+ List<String> getConnectionTags();
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java
index d0249938a..36c19956a 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java
@@ -19,8 +19,6 @@ package com.android.dialer.simulator.impl;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.telecom.Conferenceable;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import com.android.dialer.common.Assert;
@@ -28,8 +26,9 @@ import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.simulator.Simulator;
import com.android.dialer.simulator.Simulator.Event;
+import com.android.dialer.simulator.SimulatorComponent;
+import com.android.dialer.simulator.SimulatorConnectionsBank;
import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
/** Creates a conference with a given number of participants. */
@@ -38,32 +37,59 @@ final class SimulatorConferenceCreator
SimulatorConnection.Listener,
SimulatorConference.Listener {
private static final String EXTRA_CALL_COUNT = "call_count";
-
+ private static final String RECONNECT = "reconnect";
@NonNull private final Context context;
- @NonNull private final List<String> connectionTags = new ArrayList<>();
+
+ private final SimulatorConnectionsBank simulatorConnectionsBank;
+
+ private boolean onNewIncomingConnectionEnabled = false;
+
@Simulator.ConferenceType private final int conferenceType;
public SimulatorConferenceCreator(
@NonNull Context context, @Simulator.ConferenceType int conferenceType) {
this.context = Assert.isNotNull(context);
this.conferenceType = conferenceType;
+ simulatorConnectionsBank = SimulatorComponent.get(context).getSimulatorConnectionsBank();
}
void start(int callCount) {
+ onNewIncomingConnectionEnabled = true;
SimulatorConnectionService.addListener(this);
- addNextCall(callCount);
+ if (conferenceType == Simulator.CONFERENCE_TYPE_VOLTE) {
+ addNextCall(callCount, true);
+ } else if (conferenceType == Simulator.CONFERENCE_TYPE_GSM) {
+ addNextCall(callCount, false);
+ }
}
-
- private void addNextCall(int callCount) {
+ /**
+ * Add a call in a process of making a conference.
+ *
+ * @param callCount the remaining number of calls to make
+ * @param reconnect whether all connections should reconnect once (connections are reconnected
+ * once in making VoLTE conference)
+ */
+ private void addNextCall(int callCount, boolean reconnect) {
LogUtil.i("SimulatorConferenceCreator.addNextIncomingCall", "callCount: " + callCount);
if (callCount <= 0) {
LogUtil.i("SimulatorConferenceCreator.addNextCall", "done adding calls");
+ if (reconnect) {
+ simulatorConnectionsBank.disconnectAllConnections();
+ addNextCall(simulatorConnectionsBank.getConnectionTags().size(), false);
+ } else {
+ simulatorConnectionsBank.mergeAllConnections(conferenceType, context);
+ SimulatorConnectionService.removeListener(this);
+ }
return;
}
-
String callerId = String.format(Locale.US, "+1-650-234%04d", callCount);
Bundle extras = new Bundle();
extras.putInt(EXTRA_CALL_COUNT, callCount - 1);
+ extras.putBoolean(RECONNECT, reconnect);
+ addConferenceCall(callerId, extras);
+ }
+
+ private void addConferenceCall(String number, Bundle extras) {
switch (conferenceType) {
case Simulator.CONFERENCE_TYPE_VOLTE:
extras.putBoolean("ISVOLTE", true);
@@ -71,35 +97,25 @@ final class SimulatorConferenceCreator
default:
break;
}
- connectionTags.add(
- SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras));
+ SimulatorSimCallManager.addNewIncomingCall(context, number, false /* isVideo */, extras);
}
@Override
public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
- if (!isMyConnection(connection)) {
+ if (!onNewIncomingConnectionEnabled) {
+ return;
+ }
+ if (!simulatorConnectionsBank.isSimulatorConnection(connection)) {
LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "unknown connection");
return;
}
-
LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "connection created");
connection.addListener(this);
-
// Once the connection is active, go ahead and conference it and add the next call.
ThreadUtil.postDelayedOnUiThread(
() -> {
- SimulatorConference conference = findCurrentConference();
- if (conference == null) {
- conference =
- SimulatorConference.newGsmConference(
- SimulatorSimCallManager.getSystemPhoneAccountHandle(context));
- conference.addListener(this);
- SimulatorConnectionService.getInstance().addConference(conference);
- }
- updateConferenceableConnections();
connection.setActive();
- conference.addConnection(connection);
- addNextCall(getCallCount(connection));
+ addNextCall(getCallCount(connection), shouldReconnect(connection));
},
1000);
}
@@ -115,7 +131,8 @@ final class SimulatorConferenceCreator
public void onConference(
@NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {
LogUtil.enterBlock("SimulatorConferenceCreator.onConference");
- if (!isMyConnection(connection1) || !isMyConnection(connection2)) {
+ if (!simulatorConnectionsBank.isSimulatorConnection(connection1)
+ || !simulatorConnectionsBank.isSimulatorConnection(connection2)) {
LogUtil.i("SimulatorConferenceCreator.onConference", "unknown connections, ignoring");
return;
}
@@ -125,7 +142,6 @@ final class SimulatorConferenceCreator
} else if (connection2.getConference() != null) {
connection2.getConference().addConnection(connection1);
} else {
- Assert.checkArgument(conferenceType == Simulator.CONFERENCE_TYPE_GSM);
SimulatorConference conference =
SimulatorConference.newGsmConference(
SimulatorSimCallManager.getSystemPhoneAccountHandle(context));
@@ -136,54 +152,14 @@ final class SimulatorConferenceCreator
}
}
- private boolean isMyConnection(@NonNull Connection connection) {
- for (String connectionTag : connectionTags) {
- if (connection.getExtras().getBoolean(connectionTag)) {
- return true;
- }
- }
- return false;
- }
-
- private void updateConferenceableConnections() {
- LogUtil.enterBlock("SimulatorConferenceCreator.updateConferenceableConnections");
- for (String connectionTag : connectionTags) {
- SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag);
- List<Conferenceable> conferenceables = getMyConferenceables();
- conferenceables.remove(connection);
- conferenceables.remove(connection.getConference());
- connection.setConferenceables(conferenceables);
- }
- }
-
- private List<Conferenceable> getMyConferenceables() {
- List<Conferenceable> conferenceables = new ArrayList<>();
- for (String connectionTag : connectionTags) {
- SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag);
- conferenceables.add(connection);
- if (connection.getConference() != null
- && !conferenceables.contains(connection.getConference())) {
- conferenceables.add(connection.getConference());
- }
- }
- return conferenceables;
- }
-
- @Nullable
- private SimulatorConference findCurrentConference() {
- for (String connectionTag : connectionTags) {
- SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag);
- if (connection.getConference() != null) {
- return (SimulatorConference) connection.getConference();
- }
- }
- return null;
- }
-
private static int getCallCount(@NonNull Connection connection) {
return connection.getExtras().getInt(EXTRA_CALL_COUNT);
}
+ private static boolean shouldReconnect(@NonNull Connection connection) {
+ return connection.getExtras().getBoolean(RECONNECT);
+ }
+
@Override
public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
switch (event.type) {
@@ -222,6 +198,7 @@ final class SimulatorConferenceCreator
for (Connection connection : new ArrayList<>(conference.getConnections())) {
connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
}
+ conference.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
break;
default:
LogUtil.i(
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
index 168f5db98..2a24d8f37 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
@@ -24,6 +24,8 @@ import android.telecom.VideoProfile;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.simulator.Simulator.Event;
+import com.android.dialer.simulator.SimulatorComponent;
+import com.android.dialer.simulator.SimulatorConnectionsBank;
import java.util.ArrayList;
import java.util.List;
@@ -31,6 +33,7 @@ import java.util.List;
public final class SimulatorConnection extends Connection {
private final List<Listener> listeners = new ArrayList<>();
private final List<Event> events = new ArrayList<>();
+ private final SimulatorConnectionsBank simulatorConnectionsBank;
private int currentState = STATE_NEW;
SimulatorConnection(@NonNull Context context, @NonNull ConnectionRequest request) {
@@ -47,6 +50,7 @@ public final class SimulatorConnection extends Connection {
setConnectionCapabilities(getConnectionCapabilities() | CAPABILITY_SEPARATE_FROM_CONFERENCE);
}
setVideoProvider(new SimulatorVideoProvider(context, this));
+ simulatorConnectionsBank = SimulatorComponent.get(context).getSimulatorConnectionsBank();
}
public void addListener(@NonNull Listener listener) {
@@ -71,6 +75,7 @@ public final class SimulatorConnection extends Connection {
@Override
public void onReject() {
LogUtil.enterBlock("SimulatorConnection.onReject");
+ simulatorConnectionsBank.remove(this);
onEvent(new Event(Event.REJECT));
}
@@ -89,6 +94,7 @@ public final class SimulatorConnection extends Connection {
@Override
public void onDisconnect() {
LogUtil.enterBlock("SimulatorConnection.onDisconnect");
+ simulatorConnectionsBank.remove(this);
onEvent(new Event(Event.DISCONNECT));
}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
index 465890cf0..e6bf99f3a 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
@@ -28,6 +28,9 @@ import android.telephony.TelephonyManager;
import android.widget.Toast;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.simulator.SimulatorComponent;
+import com.android.dialer.simulator.SimulatorConnectionsBank;
import java.util.ArrayList;
import java.util.List;
@@ -35,6 +38,7 @@ import java.util.List;
public class SimulatorConnectionService extends ConnectionService {
private static final List<Listener> listeners = new ArrayList<>();
private static SimulatorConnectionService instance;
+ private SimulatorConnectionsBank simulatorConnectionsBank;
public static SimulatorConnectionService getInstance() {
return instance;
@@ -52,12 +56,14 @@ public class SimulatorConnectionService extends ConnectionService {
public void onCreate() {
super.onCreate();
instance = this;
+ simulatorConnectionsBank = SimulatorComponent.get(this).getSimulatorConnectionsBank();
}
@Override
public void onDestroy() {
LogUtil.enterBlock("SimulatorConnectionService.onDestroy");
instance = null;
+ simulatorConnectionsBank = null;
super.onDestroy();
}
@@ -78,7 +84,12 @@ public class SimulatorConnectionService extends ConnectionService {
SimulatorConnection connection = new SimulatorConnection(this, request);
connection.setDialing();
connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
-
+ simulatorConnectionsBank.add(connection);
+ ThreadUtil.postOnUiThread(
+ () ->
+ SimulatorComponent.get(instance)
+ .getSimulatorConnectionsBank()
+ .updateConferenceableConnections());
for (Listener listener : listeners) {
listener.onNewOutgoingConnection(connection);
}
@@ -102,7 +113,12 @@ public class SimulatorConnectionService extends ConnectionService {
SimulatorConnection connection = new SimulatorConnection(this, request);
connection.setRinging();
connection.setAddress(getPhoneNumber(request), TelecomManager.PRESENTATION_ALLOWED);
-
+ simulatorConnectionsBank.add(connection);
+ ThreadUtil.postOnUiThread(
+ () ->
+ SimulatorComponent.get(instance)
+ .getSimulatorConnectionsBank()
+ .updateConferenceableConnections());
for (Listener listener : listeners) {
listener.onNewIncomingConnection(connection);
}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionsBankImpl.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionsBankImpl.java
new file mode 100644
index 000000000..75f144fde
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionsBankImpl.java
@@ -0,0 +1,147 @@
+/*
+ * 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.simulator.impl;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.telecom.Conferenceable;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.simulator.Simulator;
+import com.android.dialer.simulator.Simulator.ConferenceType;
+import com.android.dialer.simulator.Simulator.Event;
+import com.android.dialer.simulator.SimulatorConnectionsBank;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.inject.Inject;
+
+/** Wraps a list of connection tags and common methods around the connection tags list. */
+public class SimulatorConnectionsBankImpl
+ implements SimulatorConnectionsBank, SimulatorConference.Listener {
+ private final List<String> connectionTags = new ArrayList<>();
+
+ @Inject
+ public SimulatorConnectionsBankImpl() {}
+
+ @Override
+ public List<String> getConnectionTags() {
+ return connectionTags;
+ }
+
+ @Override
+ public void add(Connection connection) {
+ connectionTags.add(SimulatorSimCallManager.getConnectionTag(connection));
+ }
+
+ @Override
+ public void remove(Connection connection) {
+ connectionTags.remove(SimulatorSimCallManager.getConnectionTag(connection));
+ }
+
+ @Override
+ public void mergeAllConnections(@ConferenceType int conferenceType, Context context) {
+ SimulatorConference simulatorConference = null;
+ if (conferenceType == Simulator.CONFERENCE_TYPE_GSM) {
+ simulatorConference =
+ SimulatorConference.newGsmConference(
+ SimulatorSimCallManager.getSystemPhoneAccountHandle(context));
+ } else if (conferenceType == Simulator.CONFERENCE_TYPE_VOLTE) {
+ simulatorConference =
+ SimulatorConference.newVoLteConference(
+ SimulatorSimCallManager.getSystemPhoneAccountHandle(context));
+ }
+ Collection<Connection> connections =
+ SimulatorConnectionService.getInstance().getAllConnections();
+ for (Connection connection : connections) {
+ simulatorConference.addConnection(connection);
+ }
+ simulatorConference.addListener(this);
+ SimulatorConnectionService.getInstance().addConference(simulatorConference);
+ }
+
+ @Override
+ public void disconnectAllConnections() {
+ Collection<Connection> connections =
+ SimulatorConnectionService.getInstance().getAllConnections();
+ for (Connection connection : connections) {
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ }
+ }
+
+ @Override
+ public void updateConferenceableConnections() {
+ LogUtil.enterBlock("SimulatorConferenceCreator.updateConferenceableConnections");
+ for (String connectionTag : connectionTags) {
+ SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag);
+ List<Conferenceable> conferenceables = getSimulatorConferenceables();
+ conferenceables.remove(connection);
+ conferenceables.remove(connection.getConference());
+ connection.setConferenceables(conferenceables);
+ }
+ }
+
+ private List<Conferenceable> getSimulatorConferenceables() {
+ List<Conferenceable> conferenceables = new ArrayList<>();
+ for (String connectionTag : connectionTags) {
+ SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag);
+ conferenceables.add(connection);
+ if (connection.getConference() != null
+ && !conferenceables.contains(connection.getConference())) {
+ conferenceables.add(connection.getConference());
+ }
+ }
+ return conferenceables;
+ }
+
+ @Override
+ public boolean isSimulatorConnection(@NonNull Connection connection) {
+ for (String connectionTag : connectionTags) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onEvent(@NonNull SimulatorConference conference, @NonNull Event event) {
+ switch (event.type) {
+ case Event.MERGE:
+ int capabilities = conference.getConnectionCapabilities();
+ capabilities |= Connection.CAPABILITY_SWAP_CONFERENCE;
+ conference.setConnectionCapabilities(capabilities);
+ break;
+ case Event.SEPARATE:
+ SimulatorConnection connectionToRemove =
+ SimulatorSimCallManager.findConnectionByTag(event.data1);
+ conference.removeConnection(connectionToRemove);
+ break;
+ case Event.DISCONNECT:
+ for (Connection connection : new ArrayList<>(conference.getConnections())) {
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ }
+ conference.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ break;
+ default:
+ LogUtil.i(
+ "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type);
+ break;
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorModule.java b/java/com/android/dialer/simulator/impl/SimulatorModule.java
index c0cca271b..2bc72c956 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorModule.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorModule.java
@@ -17,6 +17,7 @@
package com.android.dialer.simulator.impl;
import com.android.dialer.simulator.Simulator;
+import com.android.dialer.simulator.SimulatorConnectionsBank;
import dagger.Binds;
import dagger.Module;
import javax.inject.Singleton;
@@ -27,4 +28,9 @@ public abstract class SimulatorModule {
@Binds
@Singleton
public abstract Simulator bindsSimulator(SimulatorImpl simulator);
+
+ @Binds
+ @Singleton
+ public abstract SimulatorConnectionsBank bindsSimulatorConnectionsBank(
+ SimulatorConnectionsBankImpl simulatorConnectionsBank);
}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
index d2eba6b03..451896b73 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
@@ -52,6 +52,8 @@ final class SimulatorVoiceCall
private SimulatorVoiceCall(@NonNull Context context) {
this.context = Assert.isNotNull(context);
SimulatorConnectionService.addListener(this);
+ SimulatorConnectionService.addListener(
+ new SimulatorConferenceCreator(context, Simulator.CONFERENCE_TYPE_GSM));
}
private void addNewIncomingCall(boolean isSpam) {
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index ed10ed0bb..1e5a5fc02 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -16,8 +16,10 @@
package com.android.incallui;
+import android.app.ActivityManager.TaskDescription;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.GradientDrawable;
@@ -30,6 +32,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.graphics.ColorUtils;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
@@ -54,9 +57,11 @@ import com.android.incallui.answer.protocol.AnswerScreen;
import com.android.incallui.answer.protocol.AnswerScreenDelegate;
import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
import com.android.incallui.answerproximitysensor.PseudoScreenState;
+import com.android.incallui.audiomode.AudioModeProvider;
import com.android.incallui.call.CallList;
import com.android.incallui.call.DialerCall;
import com.android.incallui.call.DialerCall.State;
+import com.android.incallui.call.TelecomAdapter;
import com.android.incallui.callpending.CallPendingActivity;
import com.android.incallui.disconnectdialog.DisconnectMessage;
import com.android.incallui.incall.bindings.InCallBindings;
@@ -84,6 +89,10 @@ public class InCallActivity extends TransactionSafeFragmentActivity
public static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
public static final int PENDING_INTENT_REQUEST_CODE_BUBBLE = 2;
+ private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
+
+ private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
+
private static final String TAG_ANSWER_SCREEN = "tag_answer_screen";
private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
@@ -168,11 +177,19 @@ public class InCallActivity extends TransactionSafeFragmentActivity
@Override
protected void onSaveInstanceState(Bundle out) {
- LogUtil.i("InCallActivity.onSaveInstanceState", "");
- common.onSaveInstanceState(out);
+ LogUtil.enterBlock("InCallActivity.onSaveInstanceState");
+
+ // TODO: DialpadFragment should handle this as part of its own state
+ out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible());
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null) {
+ out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
+ }
+
out.putBoolean(DID_SHOW_ANSWER_SCREEN_KEY, didShowAnswerScreen);
out.putBoolean(DID_SHOW_IN_CALL_SCREEN_KEY, didShowInCallScreen);
out.putBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY, didShowVideoCallScreen);
+
super.onSaveInstanceState(out);
isVisible = false;
}
@@ -180,18 +197,23 @@ public class InCallActivity extends TransactionSafeFragmentActivity
@Override
protected void onStart() {
Trace.beginSection("InCallActivity.onStart");
- LogUtil.i("InCallActivity.onStart", "");
- Trace.beginSection("call super");
super.onStart();
- Trace.endSection();
+
isVisible = true;
showMainInCallFragment();
- common.onStart();
+
+ InCallPresenter.getInstance().setActivity(this);
+ enableInCallOrientationEventListener(
+ getRequestedOrientation()
+ == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
+ InCallPresenter.getInstance().onActivityStarted();
+
if (ActivityCompat.isInMultiWindowMode(this)
&& !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
// Hide the dialpad because there may not be enough room
showDialpadFragment(false, false);
}
+
Trace.endSection();
}
@@ -213,13 +235,21 @@ public class InCallActivity extends TransactionSafeFragmentActivity
1000);
}
- /** onPause is guaranteed to be called when the InCallActivity goes in the background. */
@Override
protected void onPause() {
Trace.beginSection("InCallActivity.onPause");
- LogUtil.i("InCallActivity.onPause", "");
super.onPause();
- common.onPause();
+
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null) {
+ dialpadFragment.onDialerKeyUp(null);
+ }
+
+ InCallPresenter.getInstance().onUiShowing(false);
+ if (isFinishing()) {
+ InCallPresenter.getInstance().unsetActivity(this);
+ }
+
InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Trace.endSection();
}
@@ -227,19 +257,42 @@ public class InCallActivity extends TransactionSafeFragmentActivity
@Override
protected void onStop() {
Trace.beginSection("InCallActivity.onStop");
- LogUtil.i("InCallActivity.onStop", "");
isVisible = false;
super.onStop();
- common.onStop();
+
+ // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the
+ // user presses the home button).
+ // Without this the pending call will get stuck on phone account selection and new calls can't
+ // be created.
+ // Skip this when the screen is locked since the activity may complete its current life cycle
+ // and restart.
+ if (!common.getIsRecreating() && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
+ DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
+ if (waitingForAccountCall != null) {
+ waitingForAccountCall.disconnect();
+ }
+ }
+
+ enableInCallOrientationEventListener(false);
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
+ InCallPresenter.getInstance().onActivityStopped();
+ if (!common.getIsRecreating()) {
+ Dialog errorDialog = common.getErrorDialog();
+ if (errorDialog != null) {
+ errorDialog.dismiss();
+ }
+ }
+
Trace.endSection();
}
@Override
protected void onDestroy() {
Trace.beginSection("InCallActivity.onDestroy");
- LogUtil.i("InCallActivity.onDestroy", "");
super.onDestroy();
- common.onDestroy();
+
+ InCallPresenter.getInstance().unsetActivity(this);
+ InCallPresenter.getInstance().updateIsChangingConfigurations();
Trace.endSection();
}
@@ -306,10 +359,31 @@ public class InCallActivity extends TransactionSafeFragmentActivity
@Override
public void onBackPressed() {
- LogUtil.i("InCallActivity.onBackPressed", "");
- if (!common.onBackPressed(didShowInCallScreen || didShowVideoCallScreen)) {
- super.onBackPressed();
+ LogUtil.enterBlock("InCallActivity.onBackPressed");
+
+ if (!isVisible()) {
+ return;
+ }
+
+ if (!getCallCardFragmentVisible()) {
+ return;
+ }
+
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null && dialpadFragment.isVisible()) {
+ showDialpadFragment(false /* show */, true /* animate */);
+ return;
}
+
+ if (CallList.getInstance().getIncomingCall() != null) {
+ LogUtil.i(
+ "InCallActivity.onBackPressed",
+ "Ignore the press of the back key when an incoming call is ringing");
+ return;
+ }
+
+ // Nothing special to do. Fall back to the default behavior.
+ super.onBackPressed();
}
@Override
@@ -324,12 +398,79 @@ public class InCallActivity extends TransactionSafeFragmentActivity
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- return common.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null
+ && dialpadFragment.isVisible()
+ && dialpadFragment.onDialerKeyUp(event)) {
+ return true;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_CALL) {
+ // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
+ return true;
+ }
+
+ return super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- return common.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CALL:
+ if (!InCallPresenter.getInstance().handleCallKey()) {
+ LogUtil.e(
+ "InCallActivity.onKeyDown",
+ "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
+ }
+ // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
+ return true;
+
+ // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it
+ // is exactly what's needed, namely
+ // (1) "hang up" if there's an active call, or
+ // (2) "don't answer" if there's an incoming call.
+ // (See PhoneWindowManager for implementation details.)
+
+ case KeyEvent.KEYCODE_CAMERA:
+ // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button.
+ return true;
+
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ // Ringer silencing handled by PhoneWindowManager.
+ break;
+
+ case KeyEvent.KEYCODE_MUTE:
+ TelecomAdapter.getInstance()
+ .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
+ return true;
+
+ case KeyEvent.KEYCODE_SLASH:
+ // When verbose logging is enabled, dump the view for debugging/testing purposes.
+ if (LogUtil.isVerboseEnabled()) {
+ View decorView = getWindow().getDecorView();
+ LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView);
+ return true;
+ }
+ break;
+
+ case KeyEvent.KEYCODE_EQUALS:
+ break;
+
+ default: // fall out
+ }
+
+ // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types
+ // in DTMF (Dual-tone multi-frequency signaling) code.
+ DialpadFragment dialpadFragment = getDialpadFragment();
+ if (dialpadFragment != null
+ && dialpadFragment.isVisible()
+ && dialpadFragment.onDialerKeyDown(event)) {
+ return true;
+ }
+
+ return super.onKeyDown(keyCode, event);
}
public boolean isInCallScreenAnimating() {
@@ -371,20 +512,26 @@ public class InCallActivity extends TransactionSafeFragmentActivity
}
public void onForegroundCallChanged(DialerCall newForegroundCall) {
- common.updateTaskDescription();
- if (didShowAnswerScreen && newForegroundCall != null) {
- if (newForegroundCall.getState() == State.DISCONNECTED
- || newForegroundCall.getState() == State.IDLE) {
- LogUtil.i(
- "InCallActivity.onForegroundCallChanged",
- "rejecting incoming call, not updating " + "window background color");
- }
- } else {
+ updateTaskDescription();
+
+ if (newForegroundCall == null || !didShowAnswerScreen) {
LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
- updateWindowBackgroundColor(0);
+ updateWindowBackgroundColor(0 /* progress */);
}
}
+ // TODO(a bug): Make this method private after InCallActivityCommon is deleted.
+ void updateTaskDescription() {
+ int color =
+ getResources().getBoolean(R.bool.is_layout_landscape)
+ ? ResourcesCompat.getColor(
+ getResources(), R.color.statusbar_background_color, getTheme())
+ : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
+ setTaskDescription(
+ new TaskDescription(
+ getResources().getString(R.string.notification_ongoing_call), null /* icon */, color));
+ }
+
public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
@ColorInt int top;
@@ -680,8 +827,18 @@ public class InCallActivity extends TransactionSafeFragmentActivity
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
- if (!isInMultiWindowMode) {
- common.updateNavigationBar(isDialpadVisible());
+ updateNavigationBar(isDialpadVisible());
+ }
+
+ // TODO(a bug): Make this method private after InCallActivityCommon is deleted.
+ void updateNavigationBar(boolean isDialpadVisible) {
+ if (ActivityCompat.isInMultiWindowMode(this)) {
+ return;
+ }
+
+ View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background);
+ if (navigationBarBackground != null) {
+ navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
}
}
diff --git a/java/com/android/incallui/InCallActivityCommon.java b/java/com/android/incallui/InCallActivityCommon.java
index 8f82295ed..e8588a67a 100644
--- a/java/com/android/incallui/InCallActivityCommon.java
+++ b/java/com/android/incallui/InCallActivityCommon.java
@@ -18,12 +18,9 @@ package com.android.incallui;
import android.app.ActivityManager;
import android.app.ActivityManager.AppTask;
-import android.app.ActivityManager.TaskDescription;
import android.app.Dialog;
-import android.app.KeyguardManager;
import android.content.Intent;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.os.Bundle;
import android.os.Trace;
import android.support.annotation.IntDef;
@@ -33,12 +30,8 @@ import android.support.annotation.VisibleForTesting;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
-import android.support.v4.content.res.ResourcesCompat;
import android.telecom.CallAudioState;
import android.telecom.PhoneAccountHandle;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -47,7 +40,6 @@ import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.Selec
import com.android.dialer.animation.AnimUtils;
import com.android.dialer.animation.AnimationListenerAdapter;
import com.android.dialer.common.LogUtil;
-import com.android.dialer.compat.ActivityCompat;
import com.android.dialer.compat.CompatUtils;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.ScreenEvent;
@@ -56,7 +48,6 @@ import com.android.incallui.audiomode.AudioModeProvider;
import com.android.incallui.call.CallList;
import com.android.incallui.call.DialerCall;
import com.android.incallui.call.DialerCall.State;
-import com.android.incallui.call.TelecomAdapter;
import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
import com.google.common.base.Optional;
@@ -235,27 +226,6 @@ public class InCallActivityCommon {
}
}
- public void onSaveInstanceState(Bundle out) {
- // TODO: The dialpad fragment should handle this as part of its own state
- out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, inCallActivity.isDialpadVisible());
- DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
- if (dialpadFragment != null) {
- out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
- }
- }
-
- public void onStart() {
- Trace.beginSection("InCallActivityCommon.onStart");
- // setting activity should be last thing in setup process
- InCallPresenter.getInstance().setActivity(inCallActivity);
- inCallActivity.enableInCallOrientationEventListener(
- inCallActivity.getRequestedOrientation()
- == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
-
- InCallPresenter.getInstance().onActivityStarted();
- Trace.endSection();
- }
-
public void onResume() {
Trace.beginSection("InCallActivityCommon.onResume");
if (InCallPresenter.getInstance().isReadyForTearDown()) {
@@ -263,7 +233,7 @@ public class InCallActivityCommon {
"InCallActivityCommon.onResume",
"InCallPresenter is ready for tear down, not sending updates");
} else {
- updateTaskDescription();
+ inCallActivity.updateTaskDescription();
InCallPresenter.getInstance().onUiShowing(true);
}
@@ -290,7 +260,7 @@ public class InCallActivityCommon {
}
showDialpadRequest = DIALPAD_REQUEST_NONE;
}
- updateNavigationBar(inCallActivity.isDialpadVisible());
+ inCallActivity.updateNavigationBar(inCallActivity.isDialpadVisible());
if (showPostCharWaitDialogOnResume) {
showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
@@ -302,48 +272,6 @@ public class InCallActivityCommon {
Trace.endSection();
}
- // onPause is guaranteed to be called when the InCallActivity goes
- // in the background.
- public void onPause() {
- DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
- if (dialpadFragment != null) {
- dialpadFragment.onDialerKeyUp(null);
- }
-
- InCallPresenter.getInstance().onUiShowing(false);
- if (inCallActivity.isFinishing()) {
- InCallPresenter.getInstance().unsetActivity(inCallActivity);
- }
- }
-
- public void onStop() {
- // Disconnects call waiting for account when activity is hidden e.g. user press home button.
- // This is necessary otherwise the pending call will stuck on account choose and no new call
- // will be able to create. See a bug for more details.
- // Skip this on locked screen since the activity may go over life cycle and start again.
- if (!isRecreating
- && !inCallActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
- DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
- if (waitingForAccountCall != null) {
- waitingForAccountCall.disconnect();
- }
- }
-
- inCallActivity.enableInCallOrientationEventListener(false);
- InCallPresenter.getInstance().updateIsChangingConfigurations();
- InCallPresenter.getInstance().onActivityStopped();
- if (!isRecreating) {
- if (errorDialog != null) {
- errorDialog.dismiss();
- }
- }
- }
-
- public void onDestroy() {
- InCallPresenter.getInstance().unsetActivity(inCallActivity);
- InCallPresenter.getInstance().updateIsChangingConfigurations();
- }
-
void onNewIntent(Intent intent, boolean isRecreating) {
LogUtil.i("InCallActivityCommon.onNewIntent", "");
this.isRecreating = isRecreating;
@@ -368,106 +296,6 @@ public class InCallActivityCommon {
}
}
- public boolean onBackPressed(boolean isInCallScreenVisible) {
- LogUtil.i("InCallActivityCommon.onBackPressed", "");
-
- // BACK is also used to exit out of any "special modes" of the
- // in-call UI:
- if (!inCallActivity.isVisible()) {
- return true;
- }
-
- if (!isInCallScreenVisible) {
- return true;
- }
-
- DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
- if (dialpadFragment != null && dialpadFragment.isVisible()) {
- inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
- return true;
- }
-
- // Always disable the Back key while an incoming call is ringing
- DialerCall call = CallList.getInstance().getIncomingCall();
- if (call != null) {
- LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
- return true;
- }
-
- // Nothing special to do. Fall back to the default behavior.
- return false;
- }
-
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
- // push input to the dialer.
- if (dialpadFragment != null
- && (dialpadFragment.isVisible())
- && (dialpadFragment.onDialerKeyUp(event))) {
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_CALL) {
- // Always consume CALL to be sure the PhoneWindow won't do anything with it
- return true;
- }
- return false;
- }
-
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_CALL:
- boolean handled = InCallPresenter.getInstance().handleCallKey();
- if (!handled) {
- LogUtil.e(
- "InCallActivityCommon.onKeyDown",
- "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
- }
- // Always consume CALL to be sure the PhoneWindow won't do anything with it
- return true;
-
- // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
- // The standard system-wide handling of the ENDCALL key
- // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
- // already implements exactly what the UI spec wants,
- // namely (1) "hang up" if there's a current active call,
- // or (2) "don't answer" if there's a current ringing call.
-
- case KeyEvent.KEYCODE_CAMERA:
- // Disable the CAMERA button while in-call since it's too
- // easy to press accidentally.
- return true;
-
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- // Ringer silencing handled by PhoneWindowManager.
- break;
-
- case KeyEvent.KEYCODE_MUTE:
- TelecomAdapter.getInstance()
- .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
- return true;
-
- // Various testing/debugging features, enabled ONLY when VERBOSE == true.
- case KeyEvent.KEYCODE_SLASH:
- if (LogUtil.isVerboseEnabled()) {
- LogUtil.v(
- "InCallActivityCommon.onKeyDown",
- "----------- InCallActivity View dump --------------");
- // Dump starting from the top-level view of the entire activity:
- Window w = inCallActivity.getWindow();
- View decorView = w.getDecorView();
- LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
- return true;
- }
- break;
- case KeyEvent.KEYCODE_EQUALS:
- break;
- default: // fall out
- }
-
- return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
- }
-
private void setWindowFlags() {
// Allow the activity to be shown when the screen is locked and filter out touch events that are
// "too fat".
@@ -498,20 +326,6 @@ public class InCallActivityCommon {
audioRouteForTesting = Optional.of(audioRoute);
}
- private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
- LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
-
- // As soon as the user starts typing valid dialable keys on the
- // keyboard (presumably to type DTMF tones) we start passing the
- // key events to the DTMFDialer's onDialerKeyDown.
- DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
- if (dialpadFragment != null && dialpadFragment.isVisible()) {
- return dialpadFragment.onDialerKeyDown(event);
- }
-
- return false;
- }
-
public void showPostCharWaitDialog(String callId, String chars) {
if (inCallActivity.isVisible()) {
PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
@@ -566,16 +380,6 @@ public class InCallActivityCommon {
}
}
- void updateNavigationBar(boolean isDialpadVisible) {
- if (!ActivityCompat.isInMultiWindowMode(inCallActivity)) {
- View navigationBarBackground =
- inCallActivity.getWindow().findViewById(R.id.navigation_bar_background);
- if (navigationBarBackground != null) {
- navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
- }
- }
- }
-
public boolean showDialpadFragment(boolean show, boolean animate) {
// If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
boolean isDialpadVisible = inCallActivity.isDialpadVisible();
@@ -637,7 +441,7 @@ public class InCallActivityCommon {
dialpadFragmentManager.executePendingTransactions();
Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
- updateNavigationBar(true /* isDialpadVisible */);
+ inCallActivity.updateNavigationBar(true /* isDialpadVisible */);
}
private void performHideDialpadFragment() {
@@ -655,23 +459,7 @@ public class InCallActivityCommon {
transaction.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
- updateNavigationBar(false /* isDialpadVisible */);
- }
-
- public void updateTaskDescription() {
- Resources resources = inCallActivity.getResources();
- int color;
- if (resources.getBoolean(R.bool.is_layout_landscape)) {
- color =
- ResourcesCompat.getColor(
- resources, R.color.statusbar_background_color, inCallActivity.getTheme());
- } else {
- color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
- }
-
- TaskDescription td =
- new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
- inCallActivity.setTaskDescription(td);
+ inCallActivity.updateNavigationBar(false /* isDialpadVisible */);
}
private void internalResolveIntent(Intent intent) {
@@ -759,6 +547,12 @@ public class InCallActivityCommon {
/** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
@Deprecated
+ boolean getIsRecreating() {
+ return isRecreating;
+ }
+
+ /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
+ @Deprecated
@Nullable
SelectPhoneAccountDialogFragment getSelectPhoneAccountDialogFragment() {
return selectPhoneAccountDialogFragment;
diff --git a/java/com/android/voicemail/VoicemailClient.java b/java/com/android/voicemail/VoicemailClient.java
index db5d74555..1ce7ef75a 100644
--- a/java/com/android/voicemail/VoicemailClient.java
+++ b/java/com/android/voicemail/VoicemailClient.java
@@ -179,6 +179,8 @@ public interface VoicemailClient {
void onTosAccepted(Context context, PhoneAccountHandle phoneAccountHandle);
+ boolean hasAcceptedTos(Context context, PhoneAccountHandle phoneAccountHandle);
+
/**
* @return arbitrary carrier configuration String value associate with the indicated key. See
* {@code CarrierConfigKeys.java}
diff --git a/java/com/android/voicemail/VoicemailVersionConstants.java b/java/com/android/voicemail/VoicemailVersionConstants.java
new file mode 100644
index 000000000..44ef661d7
--- /dev/null
+++ b/java/com/android/voicemail/VoicemailVersionConstants.java
@@ -0,0 +1,38 @@
+/*
+ * 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.voicemail;
+
+/**
+ * Shared preference keys and values relating to the voicemail version that the user has accepted.
+ * Note: these can be carrier dependent.
+ */
+public interface VoicemailVersionConstants {
+ // Preference key to check which version of the Verizon ToS that the user has accepted.
+ String PREF_VVM3_TOS_VERSION_ACCEPTED_KEY = "vvm3_tos_version_accepted";
+
+ // Preference key to check which version of the Google Dialer ToS that the user has accepted.
+ String PREF_DIALER_TOS_VERSION_ACCEPTED_KEY = "dialer_tos_version_accepted";
+
+ // Preference key to check which feature version the user has acknowledged
+ String PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY = "dialer_feature_version_acknowledged";
+
+ int CURRENT_VVM3_TOS_VERSION = 2;
+ int CURRENT_DIALER_TOS_VERSION = 1;
+ int LEGACY_VOICEMAIL_FEATURE_VERSION = 1; // original visual voicemail
+ int TRANSCRIPTION_VOICEMAIL_FEATURE_VERSION = 2;
+ int CURRENT_VOICEMAIL_FEATURE_VERSION = TRANSCRIPTION_VOICEMAIL_FEATURE_VERSION;
+}
diff --git a/java/com/android/voicemail/impl/PackageReplacedReceiver.java b/java/com/android/voicemail/impl/PackageReplacedReceiver.java
index 6a7ca4a7b..bc56286fb 100644
--- a/java/com/android/voicemail/impl/PackageReplacedReceiver.java
+++ b/java/com/android/voicemail/impl/PackageReplacedReceiver.java
@@ -16,14 +16,27 @@
package com.android.voicemail.impl;
+import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import android.provider.CallLog.Calls;
+import android.provider.VoicemailContract.Voicemails;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.voicemail.VoicemailComponent;
+import com.android.voicemail.VoicemailVersionConstants;
-/** Receives MY_PACKAGE_REPLACED to trigger VVM activation. */
+/**
+ * Receives MY_PACKAGE_REPLACED to trigger VVM activation and to check for legacy voicemail users.
+ */
public class PackageReplacedReceiver extends BroadcastReceiver {
@Override
@@ -39,5 +52,74 @@ public class PackageReplacedReceiver extends BroadcastReceiver {
context.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()) {
ActivationTask.start(context, phoneAccountHandle, null);
}
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ if (!prefs.contains(VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY)) {
+ setVoicemailFeatureVersionAsync(context);
+ }
+ }
+
+ private void setVoicemailFeatureVersionAsync(Context context) {
+ LogUtil.enterBlock("PackageReplacedReceiver.setVoicemailFeatureVersionAsync");
+
+ // Check if user is already using voicemail (ie do they have any voicemails), and set the
+ // acknowledged feature value accordingly.
+ PendingResult pendingResult = goAsync();
+ DialerExecutorComponent.get(context)
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new ExistingVoicemailCheck(context))
+ .onSuccess(
+ output -> {
+ LogUtil.i("PackageReplacedReceiver.setVoicemailFeatureVersionAsync", "success");
+ pendingResult.finish();
+ })
+ .onFailure(
+ throwable -> {
+ LogUtil.i("PackageReplacedReceiver.setVoicemailFeatureVersionAsync", "failure");
+ pendingResult.finish();
+ })
+ .build()
+ .executeParallel(null);
+ }
+
+ private static class ExistingVoicemailCheck implements Worker<Void, Void> {
+ private static final String[] PROJECTION = new String[] {Voicemails._ID};
+
+ private final Context context;
+
+ ExistingVoicemailCheck(Context context) {
+ this.context = context;
+ }
+
+ @TargetApi(android.os.Build.VERSION_CODES.M) // used for try with resources
+ @Override
+ public Void doInBackground(Void arg) throws Throwable {
+ LogUtil.i("PackageReplacedReceiver.ExistingVoicemailCheck.doInBackground", "");
+
+ // Check the database for existing voicemails.
+ boolean hasVoicemails = false;
+ Uri uri = Voicemails.buildSourceUri(context.getPackageName());
+ String whereClause = Calls.TYPE + " = " + Calls.VOICEMAIL_TYPE;
+ try (Cursor cursor =
+ context.getContentResolver().query(uri, PROJECTION, whereClause, null, null)) {
+ if (cursor == null) {
+ LogUtil.e(
+ "PackageReplacedReceiver.ExistingVoicemailCheck.doInBackground",
+ "failed to check for existing voicemails");
+ } else if (cursor.moveToFirst()) {
+ hasVoicemails = true;
+ }
+ }
+
+ LogUtil.i(
+ "PackageReplacedReceiver.ExistingVoicemailCheck.doInBackground",
+ "has voicemails: " + hasVoicemails);
+ int version = hasVoicemails ? VoicemailVersionConstants.LEGACY_VOICEMAIL_FEATURE_VERSION : 0;
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putInt(VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, version)
+ .apply();
+ return null;
+ }
}
}
diff --git a/java/com/android/voicemail/impl/VoicemailClientImpl.java b/java/com/android/voicemail/impl/VoicemailClientImpl.java
index 330543837..60fc80692 100644
--- a/java/com/android/voicemail/impl/VoicemailClientImpl.java
+++ b/java/com/android/voicemail/impl/VoicemailClientImpl.java
@@ -16,8 +16,10 @@ package com.android.voicemail.impl;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Build.VERSION_CODES;
import android.os.PersistableBundle;
+import android.preference.PreferenceManager;
import android.provider.VoicemailContract.Status;
import android.provider.VoicemailContract.Voicemails;
import android.support.annotation.MainThread;
@@ -32,6 +34,7 @@ import com.android.dialer.configprovider.ConfigProviderBindings;
import com.android.voicemail.PinChanger;
import com.android.voicemail.VisualVoicemailTypeExtensions;
import com.android.voicemail.VoicemailClient;
+import com.android.voicemail.VoicemailVersionConstants;
import com.android.voicemail.impl.configui.VoicemailSecretCodeActivity;
import com.android.voicemail.impl.settings.VisualVoicemailSettingsUtil;
import com.android.voicemail.impl.sync.VvmAccountManager;
@@ -292,6 +295,21 @@ public class VoicemailClientImpl implements VoicemailClient {
}
@Override
+ public boolean hasAcceptedTos(Context context, PhoneAccountHandle phoneAccountHandle) {
+ SharedPreferences preferences =
+ PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
+ OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, phoneAccountHandle);
+ boolean isVvm3 = VisualVoicemailTypeExtensions.VVM_TYPE_VVM3.equals(helper.getVvmType());
+ if (isVvm3) {
+ return preferences.getInt(VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION;
+ } else {
+ return preferences.getInt(VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION;
+ }
+ }
+
+ @Override
@Nullable
public String getCarrierConfigString(Context context, PhoneAccountHandle account, String key) {
OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, account);
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
index 33c9676a4..a19ab6208 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
@@ -69,15 +69,7 @@ public class TranscriptionService extends JobService {
public static boolean scheduleNewVoicemailTranscriptionJob(
Context context, Uri voicemailUri, PhoneAccountHandle account, boolean highPriority) {
Assert.isMainThread();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- LogUtil.i(
- "TranscriptionService.scheduleNewVoicemailTranscriptionJob", "not supported by sdk");
- return false;
- }
- if (!carrierAllowsOttTranscription(context, account)) {
- LogUtil.i(
- "TranscriptionService.scheduleNewVoicemailTranscriptionJob",
- "carrier doesn't allow transcription");
+ if (!canTranscribeVoicemail(context, account)) {
return false;
}
@@ -101,12 +93,24 @@ public class TranscriptionService extends JobService {
return scheduler.enqueue(builder.build(), workItem) == JobScheduler.RESULT_SUCCESS;
}
- private static boolean carrierAllowsOttTranscription(
- Context context, PhoneAccountHandle account) {
+ private static boolean canTranscribeVoicemail(Context context, PhoneAccountHandle account) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ LogUtil.i("TranscriptionService.canTranscribeVoicemail", "not supported by sdk");
+ return false;
+ }
VoicemailClient client = VoicemailComponent.get(context).getVoicemailClient();
- return Boolean.parseBoolean(
+ if (!client.hasAcceptedTos(context, account)) {
+ LogUtil.i("TranscriptionService.canTranscribeVoicemail", "hasn't accepted TOS");
+ return false;
+ }
+ if (!Boolean.parseBoolean(
client.getCarrierConfigString(
- context, account, CarrierConfigKeys.VVM_CARRIER_ALLOWS_OTT_TRANSCRIPTION_STRING));
+ context, account, CarrierConfigKeys.VVM_CARRIER_ALLOWS_OTT_TRANSCRIPTION_STRING))) {
+ LogUtil.i(
+ "TranscriptionService.canTranscribeVoicemail", "carrier doesn't allow transcription");
+ return false;
+ }
+ return true;
}
// Cancel all transcription tasks
diff --git a/java/com/android/voicemail/stub/StubVoicemailClient.java b/java/com/android/voicemail/stub/StubVoicemailClient.java
index cfbb3ec00..3069ea4ba 100644
--- a/java/com/android/voicemail/stub/StubVoicemailClient.java
+++ b/java/com/android/voicemail/stub/StubVoicemailClient.java
@@ -133,6 +133,11 @@ public final class StubVoicemailClient implements VoicemailClient {
public void onTosAccepted(Context context, PhoneAccountHandle account) {}
@Override
+ public boolean hasAcceptedTos(Context context, PhoneAccountHandle phoneAccountHandle) {
+ return false;
+ }
+
+ @Override
@Nullable
public String getCarrierConfigString(Context context, PhoneAccountHandle account, String key) {
return null;