diff options
Diffstat (limited to 'java')
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; |