summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2017-11-18 09:16:18 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2017-11-18 09:16:18 +0000
commit3b450ceb4290364df58a541c60cc690cc36ba4c8 (patch)
treec9bb61716fc202d23c4966a1d2407cfa09744791
parenta8b9f7de79559caef0aaab7dc05f585314024790 (diff)
parent7f1076d08b39467ac8c550f60a7fbbad604ab4fa (diff)
Merge changes Iee4e3db8,I6e74c7fe,Ibe722477,Ia22751f0,Ic28fb197, ...
* changes: DialpadView cleanup. Merge the following methods in InCallActivityCommon into InCallActivity: Add contact source options. Automated rollback of changelist 174944384 Refactoring and adding TOS check before transcribing Adding exponential backoff utilities Implement suggested SIM Fix dialer simulator for conference calling funcitonality. Updated the following contents: 1.Fix the order of spawning connections for GSM conference. 2.Make VOLTE conference call more realistic. 3.Fix minor bugs about simulator. 4.Add SimulatorConnectionsBank class to store connection tags created by simulator. 5.Fix tests influenced by SimulatorConnectionsBank.
-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;