diff options
16 files changed, 329 insertions, 201 deletions
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<String> userHomeCountryCode(); + @SuppressWarnings("AndroidApiChecker") // Use of optional @TargetApi(VERSION_CODES.N) public Optional<TransformationInfo> 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<String> 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<String> userHomeCountryCode() { + return Optional.empty(); + } + @Override public boolean isPlatformEligible() { return false; 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 @@ <string name="assisted_dialing_setting_title">Assisted dialing</string> <!-- Label for a setting enabling assisted dialing switch preference--> - <string name="assisted_dialing_setting_summary">Automatically correct the phone number prefix when traveling and calling international numbers</string> + <string name="assisted_dialing_setting_summary">Predict and add a country code when you call while traveling abroad</string> <!-- Key for the assisted dialing setting toggle--> <string name="assisted_dialing_setting_toggle_key" translatable="false">assisted_dialing_setting_toggle_key</string> @@ -531,4 +531,4 @@ <item>ZM</item> <item>ZW</item> </string-array> -</resources>
\ No newline at end of file +</resources> 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<RecyclerView.ViewHolder private final ArrayMap<ContactViewHolder, Integer> 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<RecyclerView.ViewHolder "ContactsAdapter", "Count sum (%d) != cursor count (%d).", sum, cursor.getCount()); } } + notifyDataSetChanged(); } @Override diff --git a/java/com/android/dialer/contactsfragment/ContactsFragment.java b/java/com/android/dialer/contactsfragment/ContactsFragment.java index a8daa546a..82b68b8ee 100644 --- a/java/com/android/dialer/contactsfragment/ContactsFragment.java +++ b/java/com/android/dialer/contactsfragment/ContactsFragment.java @@ -16,8 +16,13 @@ package com.android.dialer.contactsfragment; +import static android.Manifest.permission.READ_CONTACTS; + import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; @@ -80,6 +85,18 @@ public class ContactsFragment extends Fragment private static final String EXTRA_HEADER = "extra_header"; private static final String EXTRA_CLICK_ACTION = "extra_click_action"; + /** + * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS + * permission is granted via the UI in another fragment. + */ + private final BroadcastReceiver readContactsPermissionGrantedReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + getLoaderManager().initLoader(0, null, ContactsFragment.this); + } + }; + private FastScroller fastScroller; private TextView anchoredHeader; private RecyclerView recyclerView; @@ -146,6 +163,23 @@ public class ContactsFragment extends Fragment fastScroller = view.findViewById(R.id.fast_scroller); anchoredHeader = view.findViewById(R.id.header); recyclerView = view.findViewById(R.id.recycler_view); + adapter = new ContactsAdapter(getContext(), header, clickAction); + recyclerView.setAdapter(adapter); + 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); + } + } + }; + 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<Cursor> 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(); + } } 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<PhoneAccountHandle> 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/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 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/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 @@ <resources> <!-- Title of the dialog to select which SIM to call with before making a call, if the device has multiple SIMs [CHAR LIMIT=40]--> - <string name="pre_call_select_phone_account">Call with</string> + <string name="pre_call_select_phone_account">Choose SIM for this call</string> <!-- Checkbox label when selecting a SIM when calling a contact, to use the selected SIM for the same contact and never ask again [CHAR LIMIT=40]--> @@ -25,10 +25,10 @@ <!-- Hint text under a SIM when selecting SIM to call, indicating the SIM is on the same carrier as the outgoing call.[CHAR LIMIT=40]--> - <string name="pre_call_select_phone_account_hint_intra_carrier">Same carrier</string> + <string name="pre_call_select_phone_account_hint_intra_carrier">Uses your carrier</string> <!-- Hint text under a SIM when selecting SIM to call, indicating user often use the SIM to call the contact.[CHAR LIMIT=40]--> - <string name="pre_call_select_phone_account_hint_frequent">You use often</string> + <string name="pre_call_select_phone_account_hint_frequent">Recently used</string> </resources>
\ No newline at end of file diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java index bd3ba6dc6..c20642b59 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(), @@ -466,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; 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<DialpadPresenter, DialpadUi> }; 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<DialpadPresenter, DialpadUi> 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<DialpadPresenter, DialpadUi> @Override public void onDestroyView() { - mDialerKeyListener = null; + mDtmfKeyListener = null; super.onDestroyView(); } @@ -236,8 +236,8 @@ public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadUi> /** 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<DialpadPresenter, DialpadUi> /** 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<DialpadPresenter, DialpadUi> 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. - * - * <p>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. + * + * <p>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 <b>hardware keyboard</b> events. + * + * <p>From legacy documentation: + * + * <ul> + * <li>Ignores the backspace since it is irrelevant. + * <li>Allow ONLY valid DTMF characters to generate a tone and be sent as a DTMF code. + * <li>All other remaining characters are handled by the superclass. + * <li>This code is purely here to handle events from the hardware keyboard while the DTMF dialpad + * is up. + * </ul> + */ +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. + * + * <p>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}. + * + * <p>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; + } +} |