From fc1f9cfd47a3685c5a7b1425bc02c01371044cb5 Mon Sep 17 00:00:00 2001 From: twyen Date: Mon, 27 Nov 2017 13:22:48 -0800 Subject: Add impressions for dual sim features Bug: 69618691 Test: CallingAccountSelectorTest PiperOrigin-RevId: 177062349 Change-Id: I4bc9098d06c541b607cca293269cf3489077b6c2 --- .../android/dialer/logging/dialer_impression.proto | 19 ++++++++- .../externalreceiver/LaunchPreCallActivity.java | 3 ++ .../precall/impl/CallingAccountSelector.java | 45 +++++++++++++++++++++- .../precall/impl/PreCallCoordinatorImpl.java | 3 ++ .../android/dialer/precall/impl/PreCallImpl.java | 3 ++ java/com/android/incallui/CallButtonPresenter.java | 2 + 6 files changed, 72 insertions(+), 3 deletions(-) diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto index c2456d8ac..2bbce0d25 100644 --- a/java/com/android/dialer/logging/dialer_impression.proto +++ b/java/com/android/dialer/logging/dialer_impression.proto @@ -12,7 +12,7 @@ message DialerImpression { // Event enums to be used for Impression Logging in Dialer. // It's perfectly acceptable for this enum to be large // Values should be from 1000 to 100000. - // Next Tag: 1293 + // Next Tag: 1308 enum Type { UNKNOWN_AOSP_EVENT_TYPE = 1000; @@ -604,5 +604,22 @@ message DialerImpression { 1291 ; + + PRECALL_INITIATED = 1293; + PRECALL_INITIATED_EXTERNAL = 1294; + PRECALL_CANCELED = 1295; + + DUAL_SIM_CHANGE_SIM_PRESSED = 1296; + DUAL_SIM_SELECTION_SHOWN = 1297; + DUAL_SIM_SELECTION_VOICEMAIL = 1298; + DUAL_SIM_SELECTION_IN_CONTACTS = 1299; + DUAL_SIM_SELECTION_SUGGESTION_AVAILABLE = 1300; + DUAL_SIM_SELECTION_SUGGESTED_CARRIER = 1301; + DUAL_SIM_SELECTION_SUGGESTED_FREQUENCY = 1302; + DUAL_SIM_SELECTION_SUGGESTED_SIM_SELECTED = 1303; + DUAL_SIM_SELECTION_NON_SUGGESTED_SIM_SELECTED = 1304; + DUAL_SIM_SELECTION_PREFERRED_SET = 1305; + DUAL_SIM_SELECTION_PREFERRED_USED = 1306; + DUAL_SIM_SELECTION_GLOBAL_USED = 1307; } } diff --git a/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java b/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java index 121e6a6c9..a7811fe6a 100644 --- a/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java +++ b/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java @@ -23,6 +23,8 @@ import android.os.Bundle; import android.support.annotation.Nullable; import com.android.dialer.callintent.CallInitiationType.Type; import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; import com.android.dialer.precall.PreCall; /** @@ -47,6 +49,7 @@ public class LaunchPreCallActivity extends Activity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Logger.get(this).logImpression(DialerImpression.Type.PRECALL_INITIATED_EXTERNAL); Intent intent = getIntent(); CallIntentBuilder builder = new CallIntentBuilder(intent.getData(), Type.EXTERNAL_INITIATION); builder diff --git a/java/com/android/dialer/precall/impl/CallingAccountSelector.java b/java/com/android/dialer/precall/impl/CallingAccountSelector.java index 5d928b4fb..a61a3b1c6 100644 --- a/java/com/android/dialer/precall/impl/CallingAccountSelector.java +++ b/java/com/android/dialer/precall/impl/CallingAccountSelector.java @@ -47,6 +47,9 @@ import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.configprovider.ConfigProviderBindings; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.DialerImpression.Type; +import com.android.dialer.logging.Logger; import com.android.dialer.precall.PreCallAction; import com.android.dialer.precall.PreCallCoordinator; import com.android.dialer.precall.PreCallCoordinator.PendingAction; @@ -116,6 +119,7 @@ public class CallingAccountSelector implements PreCallAction { switch (builder.getUri().getScheme()) { case PhoneAccount.SCHEME_VOICEMAIL: showDialog(coordinator, coordinator.startPendingAction(), null, null, null); + Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_VOICEMAIL); break; case PhoneAccount.SCHEME_TEL: processPreferredAccount(coordinator); @@ -146,6 +150,8 @@ public class CallingAccountSelector implements PreCallAction { return; } if (result.phoneAccountHandle.isPresent()) { + Logger.get(coordinator.getActivity()) + .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_PREFERRED_USED); coordinator.getBuilder().setPhoneAccountHandle(result.phoneAccountHandle.get()); pendingAction.finish(); return; @@ -155,6 +161,8 @@ public class CallingAccountSelector implements PreCallAction { .getSystemService(TelecomManager.class) .getDefaultOutgoingPhoneAccount(builder.getUri().getScheme()); if (defaultPhoneAccount != null) { + Logger.get(coordinator.getActivity()) + .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_GLOBAL_USED); builder.setPhoneAccountHandle(defaultPhoneAccount); pendingAction.finish(); return; @@ -183,6 +191,25 @@ public class CallingAccountSelector implements PreCallAction { @Nullable String number, @Nullable Suggestion suggestion) { Assert.isMainThread(); + Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_SHOWN); + if (dataId != null) { + Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_IN_CONTACTS); + } + if (suggestion != null) { + Logger.get(coordinator.getActivity()) + .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AVAILABLE); + switch (suggestion.reason) { + case INTRA_CARRIER: + Logger.get(coordinator.getActivity()) + .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTED_CARRIER); + break; + case FREQUENT: + Logger.get(coordinator.getActivity()) + .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTED_FREQUENCY); + break; + default: + } + } List phoneAccountHandles = coordinator .getActivity() @@ -194,7 +221,7 @@ public class CallingAccountSelector implements PreCallAction { dataId != null /* canSetDefault */, R.string.pre_call_select_phone_account_remember, phoneAccountHandles, - new SelectedListener(coordinator, pendingAction, dataId, number), + new SelectedListener(coordinator, pendingAction, dataId, number, suggestion), null /* call ID */, buildHint(coordinator.getActivity(), phoneAccountHandles, suggestion)); selectPhoneAccountDialogFragment.show( @@ -351,25 +378,39 @@ public class CallingAccountSelector implements PreCallAction { private final PreCallCoordinator.PendingAction listener; private final String dataId; private final String number; + private final Suggestion suggestion; public SelectedListener( @NonNull PreCallCoordinator builder, @NonNull PreCallCoordinator.PendingAction listener, @Nullable String dataId, - @Nullable String number) { + @Nullable String number, + @Nullable Suggestion suggestion) { this.coordinator = Assert.isNotNull(builder); this.listener = Assert.isNotNull(listener); this.dataId = dataId; this.number = number; + this.suggestion = suggestion; } @MainThread @Override public void onPhoneAccountSelected( PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId) { + if (suggestion != null) { + if (suggestion.phoneAccountHandle.equals(selectedAccountHandle)) { + Logger.get(coordinator.getActivity()) + .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_SUGGESTED_SIM_SELECTED); + } else { + Logger.get(coordinator.getActivity()) + .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_NON_SUGGESTED_SIM_SELECTED); + } + } coordinator.getBuilder().setPhoneAccountHandle(selectedAccountHandle); if (dataId != null && setDefault) { + Logger.get(coordinator.getActivity()) + .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_PREFERRED_SET); DialerExecutorComponent.get(coordinator.getActivity()) .dialerExecutorFactory() .createNonUiTaskBuilder(new WritePreferredAccountWorker()) diff --git a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java index 94ba90d75..36af62245 100644 --- a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java +++ b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java @@ -24,6 +24,8 @@ import android.support.annotation.Nullable; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.logging.DialerImpression.Type; +import com.android.dialer.logging.Logger; import com.android.dialer.precall.PreCallAction; import com.android.dialer.precall.PreCallComponent; import com.android.dialer.precall.PreCallCoordinator; @@ -116,6 +118,7 @@ public class PreCallCoordinatorImpl implements PreCallCoordinator { public void abortCall() { Assert.checkState(currentAction != null); aborted = true; + Logger.get(getActivity()).logImpression(Type.PRECALL_CANCELED); } @NonNull diff --git a/java/com/android/dialer/precall/impl/PreCallImpl.java b/java/com/android/dialer/precall/impl/PreCallImpl.java index 1c78bb8b0..b9c9d8873 100644 --- a/java/com/android/dialer/precall/impl/PreCallImpl.java +++ b/java/com/android/dialer/precall/impl/PreCallImpl.java @@ -21,6 +21,8 @@ import android.content.Intent; import android.support.annotation.NonNull; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.LogUtil; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; import com.android.dialer.precall.PreCall; import com.android.dialer.precall.PreCallAction; import com.android.dialer.precall.PreCallComponent; @@ -42,6 +44,7 @@ public class PreCallImpl implements PreCall { @NonNull @Override public Intent buildIntent(Context context, CallIntentBuilder builder) { + Logger.get(context).logImpression(DialerImpression.Type.PRECALL_INITIATED); if (!requiresUi(context, builder)) { LogUtil.i("PreCallImpl.buildIntent", "No UI requested, running pre-call directly"); for (PreCallAction action : PreCallComponent.get(context).getPreCall().getActions()) { diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java index bd3ba6dc6..6b316bef8 100644 --- a/java/com/android/incallui/CallButtonPresenter.java +++ b/java/com/android/incallui/CallButtonPresenter.java @@ -28,6 +28,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.DialerImpression.Type; import com.android.dialer.logging.Logger; import com.android.dialer.telecom.TelecomUtil; import com.android.incallui.InCallCameraManager.Listener; @@ -318,6 +319,7 @@ public class CallButtonPresenter @Override public void swapSimClicked() { LogUtil.enterBlock("CallButtonPresenter.swapSimClicked"); + Logger.get(getContext()).logImpression(Type.DUAL_SIM_CHANGE_SIM_PRESSED); SwapSimWorker worker = new SwapSimWorker( getContext(), -- cgit v1.2.3 From badfdc429f77626fd7b28c7964de7cfd2981f219 Mon Sep 17 00:00:00 2001 From: twyen Date: Mon, 27 Nov 2017 14:53:49 -0800 Subject: Adjust strings for select SIM dialog Test: N/A PiperOrigin-RevId: 177075957 Change-Id: Idf4546c57aba72470e07614d2a20d828da8ee08d --- java/com/android/dialer/precall/impl/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com/android/dialer/precall/impl/res/values/strings.xml b/java/com/android/dialer/precall/impl/res/values/strings.xml index 0d30ac9e5..db7df947b 100644 --- a/java/com/android/dialer/precall/impl/res/values/strings.xml +++ b/java/com/android/dialer/precall/impl/res/values/strings.xml @@ -17,7 +17,7 @@ - Call with + Choose SIM for this call @@ -25,10 +25,10 @@ - Same carrier + Uses your carrier - You use often + Recently used \ No newline at end of file -- cgit v1.2.3 From 5e22e8009682c92b1b4bb44301da3c606b0090e8 Mon Sep 17 00:00:00 2001 From: maxwelb Date: Mon, 27 Nov 2017 17:01:07 -0800 Subject: Refactor DialpadFragment's DtmfKeyListener This change is the refactoring on DtmfKeyListener and adding tests. The class is an overridden version of DialerKeyListener, but it also supports direct onKeyDown and onKeyUp hooks from the DialpadFragment. There was a lot of duplicated code between these hooks and the overridden methods so the code was refactored to share more logic. The interesting part of this change is using EMPTY_SPANNABLE to call into super type methods instead of having to copy the implementation. This change may also help with OutOfMemoryExceptions because the DtmfKeyListener is promoted from a non-static inner class to a top level class. It no longer maintains an implicit reference to the DialpadFragment, holding onto the DialpadPresenter instead. To support testing this code, the ShadowCall had to be updated since it didn't support checking when a Dtmf tone was playing. Bug: 69133398 Test: DtmfKeyListenerTest, InCallActivityTest PiperOrigin-RevId: 177092841 Change-Id: I368083c71982e14db0ad936432a3a5bdf0dde5ee --- java/com/android/incallui/DialpadFragment.java | 178 ++----------------------- java/com/android/incallui/DtmfKeyListener.java | 156 ++++++++++++++++++++++ 2 files changed, 166 insertions(+), 168 deletions(-) create mode 100644 java/com/android/incallui/DtmfKeyListener.java diff --git a/java/com/android/incallui/DialpadFragment.java b/java/com/android/incallui/DialpadFragment.java index fbcd4078e..2f3a68cda 100644 --- a/java/com/android/incallui/DialpadFragment.java +++ b/java/com/android/incallui/DialpadFragment.java @@ -18,8 +18,6 @@ package com.android.incallui; import android.content.Context; import android.os.Bundle; -import android.text.Editable; -import android.text.method.DialerKeyListener; import android.util.ArrayMap; import android.util.AttributeSet; import android.view.KeyEvent; @@ -32,6 +30,7 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; +import com.android.dialer.common.LogUtil; import com.android.dialer.dialpadview.DialpadKeyButton; import com.android.dialer.dialpadview.DialpadKeyButton.OnPressedListener; import com.android.dialer.dialpadview.DialpadView; @@ -82,7 +81,7 @@ public class DialpadFragment extends BaseFragment }; private EditText mDtmfDialerField; // KeyListener used with the "dialpad digits" EditText widget. - private DTMFKeyListener mDialerKeyListener; + private DtmfKeyListener mDtmfKeyListener; private DialpadView mDialpadView; private int mCurrentTextColor; @@ -141,8 +140,9 @@ public class DialpadFragment extends BaseFragment mDialpadView.setBackgroundResource(R.color.incall_dialpad_background); mDtmfDialerField = (EditText) parent.findViewById(R.id.digits); if (mDtmfDialerField != null) { - mDialerKeyListener = new DTMFKeyListener(); - mDtmfDialerField.setKeyListener(mDialerKeyListener); + LogUtil.i("DialpadFragment.onCreateView", "creating dtmfKeyListener"); + mDtmfKeyListener = new DtmfKeyListener(getPresenter()); + mDtmfDialerField.setKeyListener(mDtmfKeyListener); // remove the long-press context menus that support // the edit (copy / paste / select) functions. mDtmfDialerField.setLongClickable(false); @@ -180,7 +180,7 @@ public class DialpadFragment extends BaseFragment @Override public void onDestroyView() { - mDialerKeyListener = null; + mDtmfKeyListener = null; super.onDestroyView(); } @@ -236,8 +236,8 @@ public class DialpadFragment extends BaseFragment /** Called externally (from InCallScreen) to play a DTMF Tone. */ /* package */ boolean onDialerKeyDown(KeyEvent event) { Log.d(this, "Notifying dtmf key down."); - if (mDialerKeyListener != null) { - return mDialerKeyListener.onKeyDown(event); + if (mDtmfKeyListener != null) { + return mDtmfKeyListener.onKeyDown(event); } else { return false; } @@ -246,8 +246,8 @@ public class DialpadFragment extends BaseFragment /** Called externally (from InCallScreen) to cancel the last DTMF Tone played. */ public boolean onDialerKeyUp(KeyEvent event) { Log.d(this, "Notifying dtmf key up."); - if (mDialerKeyListener != null) { - return mDialerKeyListener.onKeyUp(event); + if (mDtmfKeyListener != null) { + return mDtmfKeyListener.onKeyUp(event); } else { return false; } @@ -307,162 +307,4 @@ public class DialpadFragment extends BaseFragment setTranslationY(yFraction * getHeight()); } } - - /** - * Our own key listener, specialized for dealing with DTMF codes. 1. Ignore the backspace since it - * is irrelevant. 2. Allow ONLY valid DTMF characters to generate a tone and be sent as a DTMF - * code. 3. All other remaining characters are handled by the superclass. - * - *

This code is purely here to handle events from the hardware keyboard while the DTMF dialpad - * is up. - */ - private class DTMFKeyListener extends DialerKeyListener { - - /** - * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} These are the valid - * dtmf characters. - */ - public final char[] dtmfCharacters = - new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'}; - - private DTMFKeyListener() { - super(); - } - - /** Overriden to return correct DTMF-dialable characters. */ - @Override - protected char[] getAcceptedChars() { - return dtmfCharacters; - } - - /** special key listener ignores backspace. */ - @Override - public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) { - return false; - } - - /** - * Overriden so that with each valid button press, we start sending a dtmf code and play a local - * dtmf tone. - */ - @Override - public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { - // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view); - - // find the character - char c = (char) lookup(event, content); - - // if not a long press, and parent onKeyDown accepts the input - if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) { - - boolean keyOK = ok(getAcceptedChars(), c); - - // if the character is a valid dtmf code, start playing the tone and send the - // code. - if (keyOK) { - Log.d(this, "DTMFKeyListener reading '" + c + "' from input."); - getPresenter().processDtmf(c); - } else { - Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input."); - } - return true; - } - return false; - } - - /** - * Overriden so that with each valid button up, we stop sending a dtmf code and the dtmf tone. - */ - @Override - public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { - // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view); - - super.onKeyUp(view, content, keyCode, event); - - // find the character - char c = (char) lookup(event, content); - - boolean keyOK = ok(getAcceptedChars(), c); - - if (keyOK) { - Log.d(this, "Stopping the tone for '" + c + "'"); - getPresenter().stopDtmf(); - return true; - } - - return false; - } - - /** Handle individual keydown events when we DO NOT have an Editable handy. */ - public boolean onKeyDown(KeyEvent event) { - char c = lookup(event); - Log.d(this, "DTMFKeyListener.onKeyDown: event '" + c + "'"); - - // if not a long press, and parent onKeyDown accepts the input - if (event.getRepeatCount() == 0 && c != 0) { - // if the character is a valid dtmf code, start playing the tone and send the - // code. - if (ok(getAcceptedChars(), c)) { - Log.d(this, "DTMFKeyListener reading '" + c + "' from input."); - getPresenter().processDtmf(c); - return true; - } else { - Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input."); - } - } - return false; - } - - /** - * Handle individual keyup events. - * - * @param event is the event we are trying to stop. If this is null, then we just force-stop the - * last tone without checking if the event is an acceptable dialer event. - */ - public boolean onKeyUp(KeyEvent event) { - if (event == null) { - //the below piece of code sends stopDTMF event unnecessarily even when a null event - //is received, hence commenting it. - /*if (DBG) log("Stopping the last played tone."); - stopTone();*/ - return true; - } - - char c = lookup(event); - Log.d(this, "DTMFKeyListener.onKeyUp: event '" + c + "'"); - - // TODO: stopTone does not take in character input, we may want to - // consider checking for this ourselves. - if (ok(getAcceptedChars(), c)) { - Log.d(this, "Stopping the tone for '" + c + "'"); - getPresenter().stopDtmf(); - return true; - } - - return false; - } - - /** - * Find the Dialer Key mapped to this event. - * - * @return The char value of the input event, otherwise 0 if no matching character was found. - */ - private char lookup(KeyEvent event) { - // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup} - int meta = event.getMetaState(); - int number = event.getNumber(); - - if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) { - int match = event.getMatch(getAcceptedChars(), meta); - number = (match != 0) ? match : number; - } - - return (char) number; - } - - /** Check to see if the keyEvent is dialable. */ - boolean isKeyEventAcceptable(KeyEvent event) { - return (ok(getAcceptedChars(), lookup(event))); - } - } } diff --git a/java/com/android/incallui/DtmfKeyListener.java b/java/com/android/incallui/DtmfKeyListener.java new file mode 100644 index 000000000..a2a0de09e --- /dev/null +++ b/java/com/android/incallui/DtmfKeyListener.java @@ -0,0 +1,156 @@ +/* + * 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.incallui; + +import android.support.annotation.NonNull; +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.method.DialerKeyListener; +import android.view.KeyEvent; +import android.view.View; +import com.android.dialer.common.LogUtil; + +/** + * Key listener specialized to deal with Dtmf codes. + * + *

This listener will listen for valid Dtmf characters, and in response will inform the + * associated presenter of the character. As an implementation of {@link DialerKeyListener}, this + * class will listen for hardware keyboard events. + * + *

From legacy documentation: + * + *

    + *
  • Ignores the backspace since it is irrelevant. + *
  • Allow ONLY valid DTMF characters to generate a tone and be sent as a DTMF code. + *
  • All other remaining characters are handled by the superclass. + *
  • This code is purely here to handle events from the hardware keyboard while the DTMF dialpad + * is up. + *
+ */ +final class DtmfKeyListener extends DialerKeyListener { + /** + * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} These are the valid dtmf + * characters. + */ + private static final char[] VALID_DTMF_CHARACTERS = + new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'}; + + /** + * Spannable used to call {@link DialerKeyListener#lookup(KeyEvent, Spannable)}, so it's not + * necessary to copy the implementation. + * + *

The Spannable is only used to determine which meta keys are pressed, e.g. shift, alt, see + * {@link android.text.method.MetaKeyKeyListener#getMetaState(CharSequence)}, so using a dummy + * value is fine here. + */ + private static final Spannable EMPTY_SPANNABLE = new SpannableString(""); + + private final DialpadPresenter presenter; + + DtmfKeyListener(@NonNull DialpadPresenter presenter) { + this.presenter = presenter; + } + + @Override + protected char[] getAcceptedChars() { + return VALID_DTMF_CHARACTERS; + } + + @Override + public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) { + return false; + } + + /** + * Responds to keyDown events by firing a Dtmf tone, if the given event corresponds is a {@link + * #VALID_DTMF_CHARACTERS}. + * + * @return {@code true} if the event was handled. + */ + @Override + public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { + LogUtil.i("DtmfKeyListener.onKeyDown", "overload"); + if (!super.onKeyDown(view, content, keyCode, event)) { + LogUtil.i("DtmfKeyListener.onKeyDown", "parent type didn't support event"); + return false; + } + + return onKeyDown(event); + } + + /** + * Version of {@link #onKeyDown(View, Editable, int, KeyEvent)} used when a View/Editable isn't + * available. + */ + boolean onKeyDown(KeyEvent event) { + LogUtil.enterBlock("DtmfKeyListener.onKeyDown"); + if (event.getRepeatCount() != 0) { + LogUtil.i("DtmfKeyListener.onKeyDown", "long press, ignoring"); + return false; + } + + char c = (char) lookup(event, EMPTY_SPANNABLE); + + if (!ok(getAcceptedChars(), c)) { + LogUtil.i("DtmfKeyListener.onKeyDown", "not an accepted character"); + return false; + } + + presenter.processDtmf(c); + return true; + } + + /** + * Responds to keyUp events by stopping any playing Dtmf tone if the given event corresponds is a + * {@link #VALID_DTMF_CHARACTERS}. + * + *

Null events also stop the Dtmf tone. + * + * @return {@code true} if the event was handled + */ + @Override + public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { + LogUtil.i("DtmfKeyListener.onKeyUp", "overload"); + super.onKeyUp(view, content, keyCode, event); + + return onKeyUp(event); + } + + /** + * Handle individual keyup events. + * + * @param event is the event we are trying to stop. If this is null, then we just force-stop the + * last tone without checking if the event is an acceptable dialer event. + */ + boolean onKeyUp(KeyEvent event) { + LogUtil.enterBlock("DtmfKeyListener.onKeyUp"); + if (event == null) { + return true; + } + + char c = (char) lookup(event, EMPTY_SPANNABLE); + + if (!ok(getAcceptedChars(), c)) { + LogUtil.i("DtmfKeyListener.onKeyUp", "not an accepted character"); + return false; + } + + presenter.stopDtmf(); + return true; + } +} -- cgit v1.2.3 From af96139ce2bb384036f85654441b248b3b4ef77c Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Mon, 27 Nov 2017 19:39:32 -0800 Subject: Contact list no longer jumps to the top when the cp2 database updates. The contacts fragment recycler view was rebuilding the adapter and layout manager each time the Contacts.CONTENT_URI observered a content change. Now we simply update the adapter's cursor and notifyDataSetChanged. This keeps the recycler view in place through all changes (inserts, deletes and updates). This change also fixes another bug replated to contacts fragment not refreshing when the contacts permission was granted in another part of the UI. Bug: 64044576,67781478,68179085 Test: manual PiperOrigin-RevId: 177106761 Change-Id: Ib6a929efb047001d681cb008c3ede69860146836 --- .../dialer/contactsfragment/ContactsAdapter.java | 17 +++--- .../dialer/contactsfragment/ContactsFragment.java | 67 ++++++++++++++++------ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/java/com/android/dialer/contactsfragment/ContactsAdapter.java b/java/com/android/dialer/contactsfragment/ContactsAdapter.java index 481574e0c..8f2120cd7 100644 --- a/java/com/android/dialer/contactsfragment/ContactsAdapter.java +++ b/java/com/android/dialer/contactsfragment/ContactsAdapter.java @@ -49,22 +49,24 @@ final class ContactsAdapter extends RecyclerView.Adapter holderMap = new ArrayMap<>(); private final Context context; - private final Cursor cursor; private final @Header int header; private final @ClickAction int clickAction; // List of contact sublist headers - private final String[] headers; - + private String[] headers = new String[0]; // Number of contacts that correspond to each header in {@code headers}. - private final int[] counts; + private int[] counts = new int[0]; + // Cursor with list of contacts + private Cursor cursor; - ContactsAdapter( - Context context, Cursor cursor, @Header int header, @ClickAction int clickAction) { + ContactsAdapter(Context context, @Header int header, @ClickAction int clickAction) { this.context = context; - this.cursor = cursor; this.header = header; this.clickAction = clickAction; + } + + void updateCursor(Cursor cursor) { + this.cursor = cursor; headers = cursor.getExtras().getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); counts = cursor.getExtras().getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); if (counts != null) { @@ -78,6 +80,7 @@ final class ContactsAdapter extends RecyclerView.Adapter itemsShown) { + fastScroller.setVisibility(View.VISIBLE); + recyclerView.setOnScrollChangeListener(ContactsFragment.this); + } else { + fastScroller.setVisibility(View.GONE); + } + } + }; + recyclerView.setLayoutManager(manager); emptyContentView = view.findViewById(R.id.empty_list_view); emptyContentView.setImage(R.drawable.empty_contacts); @@ -187,6 +221,7 @@ public class ContactsFragment extends Fragment @Override public void onLoadFinished(Loader loader, Cursor cursor) { + LogUtil.enterBlock("ContactsFragment.onLoadFinished"); if (cursor == null || cursor.getCount() == 0) { emptyContentView.setDescription(R.string.all_contacts_empty); emptyContentView.setActionLabel(R.string.all_contacts_empty_add_contact_action); @@ -195,24 +230,8 @@ public class ContactsFragment extends Fragment } else { emptyContentView.setVisibility(View.GONE); recyclerView.setVisibility(View.VISIBLE); - adapter = new ContactsAdapter(getContext(), cursor, header, clickAction); - manager = - new LinearLayoutManager(getContext()) { - @Override - public void onLayoutChildren(Recycler recycler, State state) { - super.onLayoutChildren(recycler, state); - int itemsShown = findLastVisibleItemPosition() - findFirstVisibleItemPosition() + 1; - if (adapter.getItemCount() > itemsShown) { - fastScroller.setVisibility(View.VISIBLE); - recyclerView.setOnScrollChangeListener(ContactsFragment.this); - } else { - fastScroller.setVisibility(View.GONE); - } - } - }; + adapter.updateCursor(cursor); - recyclerView.setLayoutManager(manager); - recyclerView.setAdapter(adapter); PerformanceReport.logOnScrollStateChange(recyclerView); fastScroller.setup(adapter, manager); } @@ -311,4 +330,18 @@ public class ContactsFragment extends Fragment } } } + + @Override + public void onStart() { + super.onStart(); + PermissionsUtil.registerPermissionReceiver( + getActivity(), readContactsPermissionGrantedReceiver, READ_CONTACTS); + } + + @Override + public void onStop() { + PermissionsUtil.unregisterPermissionReceiver( + getActivity(), readContactsPermissionGrantedReceiver); + super.onStop(); + } } -- cgit v1.2.3 From b5bf47fa9e29fb659c55fbdd9b855f69bc3bfb4f Mon Sep 17 00:00:00 2001 From: erfanian Date: Tue, 28 Nov 2017 08:38:03 -0800 Subject: Update assisted dialing setting text. Bug: 69562852 Test: none PiperOrigin-RevId: 177165337 Change-Id: I297faa26d778ec39f2365e84e90398e7ab49f353 --- java/com/android/dialer/assisteddialing/ui/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml b/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml index 3b40817ed..1dc778f59 100644 --- a/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml +++ b/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml @@ -20,7 +20,7 @@ Assisted dialing - Automatically correct the phone number prefix when traveling and calling international numbers + Predict and add a country code when you call while traveling abroad assisted_dialing_setting_toggle_key @@ -531,4 +531,4 @@ ZM ZW - \ No newline at end of file + -- cgit v1.2.3 From fb06d3cd67e37500792b1b8ebb971e89821227cf Mon Sep 17 00:00:00 2001 From: erfanian Date: Tue, 28 Nov 2017 09:05:45 -0800 Subject: Expose user home country code as public method in Mediator Interface. This will be used in a subsequent change to show the user the default/predetected country code in the assisted dialing settings. Bug: 68775522 Test: unit tests PiperOrigin-RevId: 177168946 Change-Id: Ic738a6ddde8766962a725668f6db5056ab0bcafd --- .../android/dialer/assisteddialing/AssistedDialingMediator.java | 5 +++++ .../dialer/assisteddialing/AssistedDialingMediatorImpl.java | 8 ++++++++ .../dialer/assisteddialing/AssistedDialingMediatorStub.java | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/java/com/android/dialer/assisteddialing/AssistedDialingMediator.java b/java/com/android/dialer/assisteddialing/AssistedDialingMediator.java index 004d2f7da..756fd6125 100644 --- a/java/com/android/dialer/assisteddialing/AssistedDialingMediator.java +++ b/java/com/android/dialer/assisteddialing/AssistedDialingMediator.java @@ -27,6 +27,11 @@ public interface AssistedDialingMediator { /** Returns {@code true} if the current client platform supports Assisted Dialing. */ public boolean isPlatformEligible(); + /** Returns the country code in which the library thinks the user typically resides. */ + @SuppressWarnings("AndroidApiChecker") // Use of optional + @TargetApi(VERSION_CODES.N) + public Optional userHomeCountryCode(); + @SuppressWarnings("AndroidApiChecker") // Use of optional @TargetApi(VERSION_CODES.N) public Optional attemptAssistedDial(@NonNull String numberToTransform); diff --git a/java/com/android/dialer/assisteddialing/AssistedDialingMediatorImpl.java b/java/com/android/dialer/assisteddialing/AssistedDialingMediatorImpl.java index 1cc9732bb..7214437ab 100644 --- a/java/com/android/dialer/assisteddialing/AssistedDialingMediatorImpl.java +++ b/java/com/android/dialer/assisteddialing/AssistedDialingMediatorImpl.java @@ -56,6 +56,14 @@ final class AssistedDialingMediatorImpl implements AssistedDialingMediator { return true; } + /** Returns the country code in which the library thinks the user typically resides. */ + @Override + @SuppressWarnings("AndroidApiChecker") // Use of optional + @TargetApi(VERSION_CODES.N) + public Optional userHomeCountryCode() { + return locationDetector.getUpperCaseUserHomeCountry(); + } + /** * Returns an Optional of type String containing the transformed number that was provided. The * transformed number should be capable of dialing out of the User's current country and diff --git a/java/com/android/dialer/assisteddialing/AssistedDialingMediatorStub.java b/java/com/android/dialer/assisteddialing/AssistedDialingMediatorStub.java index fe32e2c9a..9a40e0576 100644 --- a/java/com/android/dialer/assisteddialing/AssistedDialingMediatorStub.java +++ b/java/com/android/dialer/assisteddialing/AssistedDialingMediatorStub.java @@ -32,6 +32,14 @@ public final class AssistedDialingMediatorStub implements AssistedDialingMediato return Optional.empty(); } + /** Always returns an empty Optional. */ + @Override + @SuppressWarnings("AndroidApiChecker") // Use of optional + @TargetApi(VERSION_CODES.N) + public Optional userHomeCountryCode() { + return Optional.empty(); + } + @Override public boolean isPlatformEligible() { return false; -- cgit v1.2.3 From 2c6803d4af5b3d5274857429e51cbc871ee814bd Mon Sep 17 00:00:00 2001 From: twyen Date: Tue, 28 Nov 2017 11:41:49 -0800 Subject: Prevent change SIM icon from showing during a voicemail call Calling the VM other number is to complicated for this release. Bug: 69803593 Test: CallButtonPresenterTest PiperOrigin-RevId: 177193486 Change-Id: I4149abe1d6eea48bb5399e12b9ce43a8e1ef4b37 --- java/com/android/incallui/CallButtonPresenter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java index 6b316bef8..c20642b59 100644 --- a/java/com/android/incallui/CallButtonPresenter.java +++ b/java/com/android/incallui/CallButtonPresenter.java @@ -468,6 +468,7 @@ public class CallButtonPresenter mOtherAccount = TelecomUtil.getOtherAccount(getContext(), call.getAccountHandle()); boolean showSwapSim = mOtherAccount != null + && !call.isVoiceMailNumber() && DialerCall.State.isDialing(call.getState()) // Most devices cannot make calls on 2 SIMs at the same time. && InCallPresenter.getInstance().getCallList().getAllCalls().size() == 1; -- cgit v1.2.3 From 9264b26505607dee9307c378fdd390c985d6be6c Mon Sep 17 00:00:00 2001 From: twyen Date: Tue, 28 Nov 2017 11:43:02 -0800 Subject: Prevent PreCallActivity from showing in lockscreen if it is not started in the lockscreen Bug: 69812359 Test: PreCallActivityTest PiperOrigin-RevId: 177193665 Change-Id: Ic61819b8456ee22c01228d6be2255b3f2c1e418a --- java/com/android/dialer/precall/impl/PreCallActivity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/com/android/dialer/precall/impl/PreCallActivity.java b/java/com/android/dialer/precall/impl/PreCallActivity.java index 48c2fbfc3..ee417b968 100644 --- a/java/com/android/dialer/precall/impl/PreCallActivity.java +++ b/java/com/android/dialer/precall/impl/PreCallActivity.java @@ -18,6 +18,7 @@ package com.android.dialer.precall.impl; import android.annotation.TargetApi; import android.app.Activity; +import android.app.KeyguardManager; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -34,8 +35,10 @@ public class PreCallActivity extends Activity { super.onCreate(savedInstanceState); preCallCoordinator = new PreCallCoordinatorImpl(this); preCallCoordinator.onCreate(getIntent(), savedInstanceState); - getWindow().addFlags(LayoutParams.FLAG_SHOW_WHEN_LOCKED); + if (getSystemService(KeyguardManager.class).isKeyguardLocked()) { + getWindow().addFlags(LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } } @Override -- cgit v1.2.3