From 8d407a0e0ac28c55e863f7f4f3105ec9d6dea956 Mon Sep 17 00:00:00 2001 From: wangqi Date: Thu, 15 Feb 2018 15:32:52 -0800 Subject: Annotate RTT as @TargetApi(28). Bug: 67596257 Test: lint tests PiperOrigin-RevId: 185912566 Change-Id: I281fbd62865c9de150c3e2a7a3ea79c3eb85c529 --- java/com/android/dialer/simulator/impl/RttChatBot.java | 2 ++ java/com/android/incallui/call/DialerCall.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/java/com/android/dialer/simulator/impl/RttChatBot.java b/java/com/android/dialer/simulator/impl/RttChatBot.java index 9c2989a07..5a7769f3e 100644 --- a/java/com/android/dialer/simulator/impl/RttChatBot.java +++ b/java/com/android/dialer/simulator/impl/RttChatBot.java @@ -16,6 +16,7 @@ package com.android.dialer.simulator.impl; +import android.annotation.TargetApi; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Random; /** Chat bot to generate remote RTT chat messages. */ +@TargetApi(28) public class RttChatBot { interface Callback { diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index cbe7c57a6..378806183 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -17,6 +17,7 @@ package com.android.incallui.call; import android.Manifest.permission; +import android.annotation.TargetApi; import android.content.Context; import android.hardware.camera2.CameraCharacteristics; import android.net.Uri; @@ -929,6 +930,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa return getVideoTech().isTransmittingOrReceiving() || VideoProfile.isVideo(getVideoState()); } + @TargetApi(28) public boolean isRttCall() { if (BuildCompat.isAtLeastP()) { return getTelecomCall().isRttActive(); -- cgit v1.2.3 From 3da989d320eff8bae7491d7ab528abd5582a3594 Mon Sep 17 00:00:00 2001 From: roldenburg Date: Thu, 15 Feb 2018 15:53:20 -0800 Subject: Add configuration for when we show decimal and barcode device id Bug: 69063060,71706655 Test: SpecialCharSequenceMgrTest PiperOrigin-RevId: 185915881 Change-Id: I7d82303979b381d1b0de2a8f30d7d577cccf65d9 --- .../dialer/dialpadview/SpecialCharSequenceMgr.java | 21 +++++++++++++++++---- .../dialer/dialpadview/res/layout/row_deviceid.xml | 1 + .../res/values-mcc310-mnc000-spnsprint/bools.xml | 19 +++++++++++++++++++ .../dialpadview/res/values-mcc310-mnc120/bools.xml | 19 +++++++++++++++++++ .../dialpadview/res/values-mcc311-mnc480/bools.xml | 19 +++++++++++++++++++ .../dialpadview/res/values-mcc311-mnc490/bools.xml | 19 +++++++++++++++++++ .../dialpadview/res/values-mcc311-mnc870/bools.xml | 19 +++++++++++++++++++ .../dialpadview/res/values-mcc312-mnc530/bools.xml | 19 +++++++++++++++++++ .../dialpadview/res/values-mcc316-mnc010/bools.xml | 19 +++++++++++++++++++ .../android/dialer/dialpadview/res/values/bools.xml | 20 ++++++++++++++++++++ 10 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc310-mnc000-spnsprint/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc310-mnc120/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc311-mnc480/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc311-mnc490/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc311-mnc870/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc312-mnc530/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc316-mnc010/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values/bools.xml diff --git a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java index c349d2235..3321d93f2 100644 --- a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java +++ b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java @@ -341,11 +341,22 @@ public class SpecialCharSequenceMgr { for (int slot = 0; slot < telephonyManager.getPhoneCount(); slot++) { String deviceId = telephonyManager.getDeviceId(slot); if (!TextUtils.isEmpty(deviceId)) { - addDeviceIdRow(holder, deviceId, /* showBarcode */ false); + addDeviceIdRow( + holder, + deviceId, + /* showDecimal */ + context.getResources().getBoolean(R.bool.show_device_id_in_hex_and_decimal), + /* showBarcode */ false); } } } else { - addDeviceIdRow(holder, telephonyManager.getDeviceId(), /* showBarcode */ true); + addDeviceIdRow( + holder, + telephonyManager.getDeviceId(), + /* showDecimal */ + context.getResources().getBoolean(R.bool.show_device_id_in_hex_and_decimal), + /* showBarcode */ + context.getResources().getBoolean(R.bool.show_device_id_as_barcode)); } new AlertDialog.Builder(context) @@ -361,7 +372,8 @@ public class SpecialCharSequenceMgr { return false; } - private static void addDeviceIdRow(ViewGroup holder, String deviceId, boolean showBarcode) { + private static void addDeviceIdRow( + ViewGroup holder, String deviceId, boolean showDecimal, boolean showBarcode) { if (TextUtils.isEmpty(deviceId)) { return; } @@ -378,11 +390,12 @@ public class SpecialCharSequenceMgr { // If this is the valid length IMEI or MEID (14 digits), show it in all formats, otherwise fall // back to just showing the raw hex - if (hex.length() == 14) { + if (hex.length() == 14 && showDecimal) { ((TextView) row.findViewById(R.id.deviceid_hex)).setText(hex); ((TextView) row.findViewById(R.id.deviceid_dec)).setText(getDecimalFromHex(hex)); row.findViewById(R.id.deviceid_dec_label).setVisibility(View.VISIBLE); } else { + row.findViewById(R.id.deviceid_hex_label).setVisibility(View.GONE); ((TextView) row.findViewById(R.id.deviceid_hex)).setText(deviceId); } diff --git a/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml b/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml index 53f3e5d0d..efeeb5f1e 100644 --- a/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml +++ b/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml @@ -19,6 +19,7 @@ android:layout_marginTop="?dialogPreferredPadding" android:orientation="vertical"> + + + true + diff --git a/java/com/android/dialer/dialpadview/res/values-mcc310-mnc120/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc120/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc120/bools.xml @@ -0,0 +1,19 @@ + + + + true + diff --git a/java/com/android/dialer/dialpadview/res/values-mcc311-mnc480/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc480/bools.xml new file mode 100644 index 000000000..7e549e825 --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc480/bools.xml @@ -0,0 +1,19 @@ + + + + true + diff --git a/java/com/android/dialer/dialpadview/res/values-mcc311-mnc490/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc490/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc490/bools.xml @@ -0,0 +1,19 @@ + + + + true + diff --git a/java/com/android/dialer/dialpadview/res/values-mcc311-mnc870/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc870/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc870/bools.xml @@ -0,0 +1,19 @@ + + + + true + diff --git a/java/com/android/dialer/dialpadview/res/values-mcc312-mnc530/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc312-mnc530/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc312-mnc530/bools.xml @@ -0,0 +1,19 @@ + + + + true + diff --git a/java/com/android/dialer/dialpadview/res/values-mcc316-mnc010/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc316-mnc010/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc316-mnc010/bools.xml @@ -0,0 +1,19 @@ + + + + true + diff --git a/java/com/android/dialer/dialpadview/res/values/bools.xml b/java/com/android/dialer/dialpadview/res/values/bools.xml new file mode 100644 index 000000000..b7fed07e7 --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values/bools.xml @@ -0,0 +1,20 @@ + + + + false + false + -- cgit v1.2.3 From 153af2febd64f989f5e67c51c0653489e3339a1a Mon Sep 17 00:00:00 2001 From: wangqi Date: Thu, 15 Feb 2018 16:21:49 -0800 Subject: Implement read/write text stream to RttCall. This change also: 1. Add simulator support of RTT request during call (always accept at this moment, will add random accept/decline in the future) 2. Fix bugs of putting RTT call in background and back to call Bug: 67596257 Test: Simulator PiperOrigin-RevId: 185920527 Change-Id: I51016fa6cf1ccc8a5a21335f9dacf286ae393706 --- .../android/dialer/simulator/impl/RttChatBot.java | 1 - .../dialer/simulator/impl/SimulatorConnection.java | 29 ++++- .../dialer/simulator/impl/SimulatorRttCall.java | 12 +- .../simulator/impl/SimulatorSimCallManager.java | 2 +- .../dialer/simulator/impl/SimulatorVoiceCall.java | 14 ++- java/com/android/incallui/InCallActivity.java | 3 + java/com/android/incallui/RttCallPresenter.java | 131 +++++++++++++++++++-- java/com/android/incallui/call/DialerCall.java | 9 +- .../android/incallui/rtt/impl/RttChatAdapter.java | 12 +- .../android/incallui/rtt/impl/RttChatFragment.java | 21 +++- .../incallui/rtt/protocol/RttCallScreen.java | 2 + .../rtt/protocol/RttCallScreenDelegate.java | 6 +- 12 files changed, 218 insertions(+), 24 deletions(-) diff --git a/java/com/android/dialer/simulator/impl/RttChatBot.java b/java/com/android/dialer/simulator/impl/RttChatBot.java index 5a7769f3e..b2860e387 100644 --- a/java/com/android/dialer/simulator/impl/RttChatBot.java +++ b/java/com/android/dialer/simulator/impl/RttChatBot.java @@ -97,7 +97,6 @@ public class RttChatBot { break; case SEND_MESSAGE: String message = (String) msg.obj; - LogUtil.w("test", "type: %s, to stream: %s", message, rttTextStream); try { rttTextStream.write(message); } catch (IOException e) { diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java index c832a5051..3aa3296ea 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java +++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java @@ -16,9 +16,12 @@ package com.android.dialer.simulator.impl; +import android.annotation.TargetApi; import android.content.Context; import android.support.annotation.NonNull; +import android.support.v4.os.BuildCompat; import android.telecom.Connection; +import android.telecom.Connection.RttTextStream; import android.telecom.ConnectionRequest; import android.telecom.VideoProfile; import com.android.dialer.common.Assert; @@ -31,11 +34,14 @@ import java.util.ArrayList; import java.util.List; /** Represents a single phone call on the device. */ +@TargetApi(28) public final class SimulatorConnection extends Connection { private final List listeners = new ArrayList<>(); private final List events = new ArrayList<>(); private final SimulatorConnectionsBank simulatorConnectionsBank; private int currentState = STATE_NEW; + private RttTextStream rttTextStream; + private RttChatBot rttChatBot; SimulatorConnection(@NonNull Context context, @NonNull ConnectionRequest request) { Assert.isNotNull(context); @@ -54,6 +60,9 @@ public final class SimulatorConnection extends Connection { getConnectionCapabilities() | CAPABILITY_SEPARATE_FROM_CONFERENCE); } } + if (BuildCompat.isAtLeastP()) { + rttTextStream = request.getRttTextStream(); + } setVideoProvider(new SimulatorVideoProvider(context, this)); simulatorConnectionsBank = SimulatorComponent.get(context).getSimulatorConnectionsBank(); } @@ -66,6 +75,10 @@ public final class SimulatorConnection extends Connection { listeners.remove(Assert.isNotNull(listener)); } + RttTextStream getRttTextStream() { + return rttTextStream; + } + @NonNull public List getEvents() { return events; @@ -101,6 +114,11 @@ public final class SimulatorConnection extends Connection { LogUtil.enterBlock("SimulatorConnection.onDisconnect"); simulatorConnectionsBank.remove(this); onEvent(new Event(Event.DISCONNECT)); + rttTextStream = null; + if (rttChatBot != null) { + rttChatBot.stop(); + rttChatBot = null; + } } @Override @@ -124,12 +142,21 @@ public final class SimulatorConnection extends Connection { @Override public void onStartRtt(@NonNull RttTextStream rttTextStream) { LogUtil.enterBlock("SimulatorConnection.onStartRtt"); + if (this.rttTextStream != null || rttChatBot != null) { + LogUtil.e("SimulatorConnection.onStartRtt", "rttTextStream or rttChatBot is not null!"); + } + this.rttTextStream = rttTextStream; + rttChatBot = new RttChatBot(rttTextStream); + rttChatBot.start(); onEvent(new Event(Event.START_RTT)); } @Override public void onStopRtt() { LogUtil.enterBlock("SimulatorConnection.onStopRtt"); + rttChatBot.stop(); + rttChatBot = null; + rttTextStream = null; onEvent(new Event(Event.STOP_RTT)); } @@ -159,6 +186,4 @@ public final class SimulatorConnection extends Connection { public interface Listener { void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event); } - - } diff --git a/java/com/android/dialer/simulator/impl/SimulatorRttCall.java b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java index 7b0066719..352b9e4ef 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorRttCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java @@ -34,6 +34,7 @@ final class SimulatorRttCall @NonNull private final Context context; @Nullable private String connectionTag; + private RttChatBot rttChatBot; static ActionProvider getActionProvider(@NonNull Context context) { return new SimulatorSubMenu(context) @@ -112,24 +113,29 @@ final class SimulatorRttCall switch (event.type) { case Event.NONE: throw Assert.createIllegalStateFailException(); - case Event.ANSWER: - connection.setActive(); - break; case Event.REJECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED)); break; case Event.HOLD: connection.setOnHold(); break; + case Event.ANSWER: case Event.UNHOLD: connection.setActive(); break; case Event.DISCONNECT: + rttChatBot.stop(); connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); break; case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; + case Event.STATE_CHANGE: + if (Connection.stateToString(Connection.STATE_ACTIVE).equals(event.data2)) { + rttChatBot = new RttChatBot(connection.getRttTextStream()); + rttChatBot.start(); + } + break; default: LogUtil.i("SimulatorRttCall.onEvent", "unexpected event: " + event.type); break; diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java index d51e06816..c56afb21f 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java @@ -115,7 +115,7 @@ public class SimulatorSimCallManager { TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, callType == CALL_TYPE_VIDEO ? getVideoProviderHandle(context) - : getSystemPhoneAccountHandle(context)); + : getSimCallManagerHandle(context)); if (callType == CALL_TYPE_RTT) { outgoingCallExtras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true); } diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java index d4c7ee458..e59cddd51 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -22,6 +22,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.telecom.Connection; +import android.telecom.Connection.RttModifyStatus; import android.telecom.DisconnectCause; import android.view.ActionProvider; import com.android.dialer.common.Assert; @@ -223,15 +224,13 @@ final class SimulatorVoiceCall switch (event.type) { case Event.NONE: throw Assert.createIllegalStateFailException(); - case Event.ANSWER: - connection.setActive(); - break; case Event.REJECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED)); break; case Event.HOLD: connection.setOnHold(); break; + case Event.ANSWER: case Event.UNHOLD: connection.setActive(); break; @@ -244,6 +243,15 @@ final class SimulatorVoiceCall case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; + case Event.START_RTT: + // TODO(wangqi): Add random accept/decline. + boolean accept = true; + if (accept) { + connection.sendRttInitiationSuccess(); + } else { + connection.sendRttInitiationFailure(RttModifyStatus.SESSION_MODIFY_REQUEST_FAIL); + } + break; default: LogUtil.i("SimulatorVoiceCall.onEvent", "unexpected event: " + event.type); break; diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index 67f5cfe4f..3fc7f6c76 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -182,6 +182,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN); didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN); didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN); + didShowRttCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN); } setWindowFlags(); @@ -387,6 +388,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen); out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen); out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen); + out.putBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN, didShowRttCallScreen); super.onSaveInstanceState(out); isVisible = false; @@ -1593,6 +1595,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen"; static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen"; static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen"; + static final String DID_SHOW_RTT_CALL_SCREEN = "did_show_rtt_call_screen"; } /** Request codes for pending intents. */ diff --git a/java/com/android/incallui/RttCallPresenter.java b/java/com/android/incallui/RttCallPresenter.java index b90d56b36..939c9d00b 100644 --- a/java/com/android/incallui/RttCallPresenter.java +++ b/java/com/android/incallui/RttCallPresenter.java @@ -16,28 +16,145 @@ package com.android.incallui; -import android.content.Context; +import android.annotation.TargetApi; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.telecom.Call.RttCall; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.incallui.InCallPresenter.InCallState; +import com.android.incallui.InCallPresenter.InCallStateListener; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; import com.android.incallui.rtt.protocol.RttCallScreen; import com.android.incallui.rtt.protocol.RttCallScreenDelegate; +import java.io.IOException; /** * Logic related to the {@link RttCallScreen} and for managing changes to the RTT calling surfaces * based on other user interface events and incoming events. */ -public class RttCallPresenter implements RttCallScreenDelegate { +@TargetApi(28) +public class RttCallPresenter implements RttCallScreenDelegate, InCallStateListener { - private Context appContext; private RttCallScreen rttCallScreen; + private RttCall rttCall; + private HandlerThread handlerThread; + private RemoteMessageHandler remoteMessageHandler; @Override - public void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen) { - this.appContext = context.getApplicationContext(); + public void initRttCallScreenDelegate(RttCallScreen rttCallScreen) { this.rttCallScreen = rttCallScreen; } @Override - public void onRttCallScreenUiReady() {} + public void onLocalMessage(String message) { + if (rttCall == null) { + LogUtil.w("RttCallPresenter.onLocalMessage", "Rtt Call is not started yet"); + return; + } + remoteMessageHandler.writeMessage(message); + } + + @Override + public void onRttCallScreenUiReady() { + LogUtil.enterBlock("RttCallPresenter.onRttCallScreenUiReady"); + InCallPresenter.getInstance().addListener(this); + startListenOnRemoteMessage(); + } + + @Override + public void onRttCallScreenUiUnready() { + LogUtil.enterBlock("RttCallPresenter.onRttCallScreenUiUnready"); + InCallPresenter.getInstance().removeListener(this); + stopListenOnRemoteMessage(); + } @Override - public void onRttCallScreenUiUnready() {} + public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { + LogUtil.enterBlock("RttCallPresenter.onStateChange"); + if (newState == InCallState.INCALL) { + startListenOnRemoteMessage(); + } + } + + private void startListenOnRemoteMessage() { + DialerCall call = CallList.getInstance().getActiveCall(); + if (call == null) { + LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "call is active yet"); + return; + } + rttCall = call.getRttCall(); + if (rttCall == null) { + LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "RTT Call is not started yet"); + return; + } + if (handlerThread != null && handlerThread.isAlive()) { + LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "already running"); + return; + } + handlerThread = new HandlerThread("RttCallRemoteMessageHandler"); + handlerThread.start(); + remoteMessageHandler = + new RemoteMessageHandler(handlerThread.getLooper(), rttCall, rttCallScreen); + remoteMessageHandler.start(); + } + + private void stopListenOnRemoteMessage() { + if (handlerThread != null && handlerThread.isAlive()) { + handlerThread.quit(); + } + } + + private static class RemoteMessageHandler extends Handler { + private static final int START = 1; + private static final int READ_MESSAGE = 2; + private static final int WRITE_MESSAGE = 3; + + private final RttCall rttCall; + private final RttCallScreen rttCallScreen; + + RemoteMessageHandler(Looper looper, RttCall rttCall, RttCallScreen rttCallScreen) { + super(looper); + this.rttCall = rttCall; + this.rttCallScreen = rttCallScreen; + } + + @Override + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case START: + sendEmptyMessage(READ_MESSAGE); + break; + case READ_MESSAGE: + try { + final String message = rttCall.readImmediately(); + if (message != null) { + ThreadUtil.postOnUiThread(() -> rttCallScreen.onRemoteMessage(message)); + } + } catch (IOException e) { + LogUtil.e("RttCallPresenter.RemoteMessageHandler.handleMessage", "read message", e); + } + sendEmptyMessageDelayed(READ_MESSAGE, 200); + break; + case WRITE_MESSAGE: + try { + rttCall.write((String) msg.obj); + } catch (IOException e) { + LogUtil.e("RttCallPresenter.RemoteMessageHandler.handleMessage", "write message", e); + } + break; + default: // fall out + } + } + + void start() { + sendEmptyMessage(START); + } + + void writeMessage(String message) { + sendMessage(obtainMessage(WRITE_MESSAGE, message)); + } + } } diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index 378806183..90a01401c 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -939,6 +939,14 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } } + @TargetApi(28) + public RttCall getRttCall() { + if (!isRttCall()) { + return null; + } + return getTelecomCall().getRttCall(); + } + public boolean hasReceivedVideoUpgradeRequest() { return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState()); } @@ -948,7 +956,6 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } public boolean hasSentRttUpgradeRequest() { - // TODO(wangqi): Implement this. return false; } diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java index 1ea7f31b1..69837188a 100644 --- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java +++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java @@ -110,7 +110,17 @@ public class RttChatAdapter extends RecyclerView.Adapter lastIndexOfLocalMessage) { + lastIndexOfRemoteMessage -= 1; + } + lastIndexOfLocalMessage = -1; + } else { + notifyItemChanged(lastIndexOfLocalMessage); + } } } diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index c7ee2ff67..ba99b2bf6 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -132,7 +132,7 @@ public class RttChatFragment extends Fragment FragmentUtils.getParentUnsafe(this, RttCallScreenDelegateFactory.class) .newRttCallScreenDelegate(this); - rttCallScreenDelegate.initRttCallScreenDelegate(getContext(), this); + rttCallScreenDelegate.initRttCallScreenDelegate(this); inCallScreenDelegate.onInCallScreenDelegateInit(this); inCallScreenDelegate.onInCallScreenReady(); @@ -193,7 +193,24 @@ public class RttChatFragment extends Fragment if (isClearingInput) { return; } - adapter.addLocalMessage(RttChatMessage.getChangedString(s, start, before, count)); + String messageToAppend = RttChatMessage.getChangedString(s, start, before, count); + if (!TextUtils.isEmpty(messageToAppend)) { + adapter.addLocalMessage(messageToAppend); + rttCallScreenDelegate.onLocalMessage(messageToAppend); + } + } + + @Override + public void onRemoteMessage(String message) { + adapter.addRemoteMessage(message); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + LogUtil.enterBlock("RttChatFragment.onDestroyView"); + inCallButtonUiDelegate.onInCallButtonUiUnready(); + inCallScreenDelegate.onInCallScreenUnready(); } @Override diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreen.java b/java/com/android/incallui/rtt/protocol/RttCallScreen.java index afacbae48..916dfb84d 100644 --- a/java/com/android/incallui/rtt/protocol/RttCallScreen.java +++ b/java/com/android/incallui/rtt/protocol/RttCallScreen.java @@ -25,6 +25,8 @@ public interface RttCallScreen { void onRttScreenStop(); + void onRemoteMessage(String message); + Fragment getRttCallScreenFragment(); String getCallId(); diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java index e29c43d70..8c484a844 100644 --- a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java +++ b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java @@ -16,14 +16,14 @@ package com.android.incallui.rtt.protocol; -import android.content.Context; - /** Callbacks from the module out to the container. */ public interface RttCallScreenDelegate { - void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen); + void initRttCallScreenDelegate(RttCallScreen rttCallScreen); void onRttCallScreenUiReady(); void onRttCallScreenUiUnready(); + + void onLocalMessage(String message); } -- cgit v1.2.3 From ddd30052026e85de5237d4ef4751a4c26c09cb9b Mon Sep 17 00:00:00 2001 From: linyuh Date: Fri, 16 Feb 2018 09:35:22 -0800 Subject: Include the primary callback action in the new call log's bottom sheet. Test: ModulesTest PiperOrigin-RevId: 186006639 Change-Id: I0c37d342d4a6da563b49b3ebe8f8ee2262efde60 --- .../android/dialer/calllog/ui/menu/Modules.java | 33 +++++++++------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java index c85a9fddd..a13f2e333 100644 --- a/java/com/android/dialer/calllog/ui/menu/Modules.java +++ b/java/com/android/dialer/calllog/ui/menu/Modules.java @@ -51,7 +51,7 @@ final class Modules { PhoneNumberHelper.canPlaceCallsTo(normalizedNumber, row.numberPresentation()); if (canPlaceCalls) { - addModuleForVideoOrAudioCall(context, modules, row, normalizedNumber); + addModuleForCalls(context, modules, row, normalizedNumber); SharedModules.maybeAddModuleForSendingTextMessage( context, modules, normalizedNumber, row.numberAttributes().getIsBlocked()); } @@ -90,12 +90,12 @@ final class Modules { return modules; } - private static void addModuleForVideoOrAudioCall( + private static void addModuleForCalls( Context context, List modules, CoalescedRow row, String normalizedNumber) { - // If a number is blocked, skip this menu item. + // Don't add call options if a number is blocked. if (row.numberAttributes().getIsBlocked()) { return; } @@ -104,28 +104,21 @@ final class Modules { TelecomUtil.composePhoneAccountHandle( row.phoneAccountComponentName(), row.phoneAccountId()); - // For a spam number, only audio calls are allowed. - if (row.numberAttributes().getIsSpam()) { - modules.add( - IntentModule.newCallModule( - context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); - return; - } + // Add an audio call item + modules.add( + IntentModule.newCallModule( + context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); - if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) { - // Add an audio call item for video calls. Clicking the top entry on the bottom sheet will - // trigger a video call. - modules.add( - IntentModule.newCallModule( - context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); - } else { - // Add a video call item for audio calls. Click the top entry on the bottom sheet will - // trigger an audio call. - // TODO(zachh): Only show video option if video capabilities present? + // Add a video item if (1) the call log entry is for a video call, and (2) the call is not spam. + if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO + && !row.numberAttributes().getIsSpam()) { modules.add( IntentModule.newVideoCallModule( context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); } + + // TODO(zachh): Also show video option if the call log entry is for an audio call but video + // capabilities are present? } private static void addModuleForAccessingCallDetails( -- cgit v1.2.3 From 8074af74464a162e01296cc2b0b707f1c9d12bb7 Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Fri, 16 Feb 2018 13:20:56 -0800 Subject: Fix a few UI issues based around multiselect in new nav. - No longer crashes when entering mw-mode while in multiselect - Configuration changes now properly restore bottom nav visibility - toolbar shadow is now only visible in search Bug: 72525324 Test: MainActivityOldPeerSearchIntegrationTest PiperOrigin-RevId: 186037379 Change-Id: I5fc00c8091e85fc67482b2131944fb776626d06f --- .../android/dialer/app/calllog/CallLogFragment.java | 15 +++++++++++++-- .../dialer/main/impl/MainSearchController.java | 16 +++++++++------- .../android/dialer/main/impl/OldMainActivityPeer.java | 19 +++++++++++++++---- .../dialer/main/impl/res/layout/main_activity.xml | 4 +++- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java index 7f635dbca..6b6b92297 100644 --- a/java/com/android/dialer/app/calllog/CallLogFragment.java +++ b/java/com/android/dialer/app/calllog/CallLogFragment.java @@ -661,8 +661,19 @@ public class CallLogFragment extends Fragment multiSelectUnSelectAllViewContent.setVisibility(show ? View.VISIBLE : View.GONE); multiSelectUnSelectAllViewContent.setAlpha(show ? 0 : 1); multiSelectUnSelectAllViewContent.animate().alpha(show ? 1 : 0).start(); - FragmentUtils.getParentUnsafe(this, CallLogFragmentListener.class) - .showMultiSelectRemoveView(show); + if (show) { + FragmentUtils.getParentUnsafe(this, CallLogFragmentListener.class) + .showMultiSelectRemoveView(true); + } else { + // This method is called after onDestroy. In DialtactsActivity, ListsFragment implements this + // interface and never goes away with configuration changes so this is safe. MainActivity + // removes that extra layer though, so we need to check if the parent is still there. + CallLogFragmentListener listener = + FragmentUtils.getParent(this, CallLogFragmentListener.class); + if (listener != null) { + listener.showMultiSelectRemoveView(false); + } + } } @Override diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java index 9b734f40c..8d9e784a7 100644 --- a/java/com/android/dialer/main/impl/MainSearchController.java +++ b/java/com/android/dialer/main/impl/MainSearchController.java @@ -68,8 +68,7 @@ import java.util.ArrayList; public class MainSearchController implements SearchBarListener { private static final String KEY_IS_FAB_HIDDEN = "is_fab_hidden"; - private static final String KEY_CURRENT_TAB = "current_tab"; - private static final String KEY_BOTTOM_NAV_VISIBILITY = "bottom_nav_visibility"; + private static final String KEY_TOOLBAR_SHADOW_VISIBILITY = "toolbar_shadow_visibility"; private static final String KEY_IS_TOOLBAR_EXPANDED = "is_toolbar_expanded"; private static final String KEY_IS_TOOLBAR_SLIDE_UP = "is_toolbar_slide_up"; @@ -80,16 +79,19 @@ public class MainSearchController implements SearchBarListener { private final BottomNavBar bottomNav; private final FloatingActionButton fab; private final MainToolbar toolbar; + private final View toolbarShadow; public MainSearchController( MainActivity mainActivity, BottomNavBar bottomNav, FloatingActionButton fab, - MainToolbar toolbar) { + MainToolbar toolbar, + View toolbarShadow) { this.mainActivity = mainActivity; this.bottomNav = bottomNav; this.fab = fab; this.toolbar = toolbar; + this.toolbarShadow = toolbarShadow; } /** Should be called if we're showing the dialpad because of a new ACTION_DIAL intent. */ @@ -266,6 +268,7 @@ public class MainSearchController implements SearchBarListener { } showBottomNav(); toolbar.collapse(animate); + toolbarShadow.setVisibility(View.GONE); mainActivity.getFragmentManager().beginTransaction().remove(getSearchFragment()).commit(); // Clear the dialpad so the phone number isn't persisted between search sessions. @@ -319,6 +322,7 @@ public class MainSearchController implements SearchBarListener { fab.hide(); toolbar.expand(/* animate=*/ true, query); toolbar.showKeyboard(); + toolbarShadow.setVisibility(View.VISIBLE); hideBottomNav(); FragmentTransaction transaction = mainActivity.getFragmentManager().beginTransaction(); @@ -396,15 +400,13 @@ public class MainSearchController implements SearchBarListener { public void onSaveInstanceState(Bundle bundle) { bundle.putBoolean(KEY_IS_FAB_HIDDEN, !fab.isShown()); - bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab()); - bundle.putInt(KEY_BOTTOM_NAV_VISIBILITY, bottomNav.getVisibility()); + bundle.putInt(KEY_TOOLBAR_SHADOW_VISIBILITY, toolbarShadow.getVisibility()); bundle.putBoolean(KEY_IS_TOOLBAR_EXPANDED, toolbar.isExpanded()); bundle.putBoolean(KEY_IS_TOOLBAR_SLIDE_UP, toolbar.isSlideUp()); } public void onRestoreInstanceState(Bundle savedInstanceState) { - bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); - bottomNav.setVisibility(savedInstanceState.getInt(KEY_BOTTOM_NAV_VISIBILITY)); + toolbarShadow.setVisibility(savedInstanceState.getInt(KEY_TOOLBAR_SHADOW_VISIBILITY)); if (savedInstanceState.getBoolean(KEY_IS_FAB_HIDDEN, false)) { fab.hide(); } diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 07c7185ae..7d9216c3f 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -196,7 +196,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen mainActivity, mainActivity.getContentResolver(), bottomNav, toolbar); bottomNav.addOnTabSelectedListener(callLogFragmentListener); - searchController = getNewMainSearchController(bottomNav, fab, toolbar); + searchController = + getNewMainSearchController( + bottomNav, fab, toolbar, mainActivity.findViewById(R.id.toolbar_shadow)); toolbar.setSearchBarListener(searchController); onDialpadQueryChangedListener = getNewOnDialpadQueryChangedListener(searchController); @@ -284,6 +286,13 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen .getDatabaseHelper(mainActivity) .startSmartDialUpdateThread(forceUpdate); showPostCallPrompt(); + + if (searchController.isInSearch() + || callLogAdapterOnActionModeStateChangedListener.isActionModeStateEnabled()) { + bottomNav.setVisibility(View.GONE); + } else { + bottomNav.setVisibility(View.VISIBLE); + } } @Override @@ -375,8 +384,11 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } public MainSearchController getNewMainSearchController( - BottomNavBar bottomNavBar, FloatingActionButton fab, MainToolbar mainToolbar) { - return new MainSearchController(mainActivity, bottomNavBar, fab, mainToolbar); + BottomNavBar bottomNavBar, + FloatingActionButton fab, + MainToolbar mainToolbar, + View toolbarShadow) { + return new MainSearchController(mainActivity, bottomNavBar, fab, mainToolbar, toolbarShadow); } public MainOnDialpadQueryChangedListener getNewOnDialpadQueryChangedListener( @@ -482,7 +494,6 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } /** @see CallLogAdapter.OnActionModeStateChangedListener */ - // TODO(calderwoodra): What is the purpose of this listener? private static final class MainCallLogAdapterOnActionModeStateChangedListener implements CallLogAdapter.OnActionModeStateChangedListener { diff --git a/java/com/android/dialer/main/impl/res/layout/main_activity.xml b/java/com/android/dialer/main/impl/res/layout/main_activity.xml index d8b13a6c2..4f0284126 100644 --- a/java/com/android/dialer/main/impl/res/layout/main_activity.xml +++ b/java/com/android/dialer/main/impl/res/layout/main_activity.xml @@ -68,11 +68,13 @@ layout="@layout/toolbar_layout"/> + android:layout_below="@+id/toolbar" + android:visibility="gone"/> -- cgit v1.2.3 From e2bf3e9d45aa8c1a6dbf39212f7ba32cbbd53185 Mon Sep 17 00:00:00 2001 From: roldenburg Date: Fri, 16 Feb 2018 15:10:40 -0800 Subject: Rename invalid resource folder Test: n/a PiperOrigin-RevId: 186053053 Change-Id: Ia250ee650025317cd72d0b773922ead7266aac6c --- .../res/values-mcc310-mnc000-spnsprint/bools.xml | 19 ------------------- .../dialpadview/res/values-mcc310-mnc000/bools.xml | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) delete mode 100644 java/com/android/dialer/dialpadview/res/values-mcc310-mnc000-spnsprint/bools.xml create mode 100644 java/com/android/dialer/dialpadview/res/values-mcc310-mnc000/bools.xml diff --git a/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000-spnsprint/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000-spnsprint/bools.xml deleted file mode 100644 index 99fa7da7f..000000000 --- a/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000-spnsprint/bools.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - true - diff --git a/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000/bools.xml @@ -0,0 +1,19 @@ + + + + true + -- cgit v1.2.3 From 43c978b616363bcd364693dde24209384b264319 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 20 Feb 2018 12:46:54 -0800 Subject: Use a broadcast receiver to refresh the annotated call log. Bug: 73347270 Test: Existing tests + RefreshAnnotatedCallLogNotifierTest PiperOrigin-RevId: 186347066 Change-Id: I5a530416bdaa9edc7131a0d5ced44f1b5ee1692b --- .../android/dialer/calllog/CallLogComponent.java | 3 + .../android/dialer/calllog/CallLogFramework.java | 70 +----------- .../calllog/RefreshAnnotatedCallLogReceiver.java | 121 +++++++++++++++++++++ .../calllog/RefreshAnnotatedCallLogWorker.java | 5 +- .../dialer/calllog/constants/IntentNames.java | 30 +++++ .../dialer/calllog/constants/SharedPrefKeys.java | 25 +++++ .../calllog/datasources/CallLogDataSource.java | 11 +- .../phonelookup/PhoneLookupDataSource.java | 18 +-- .../systemcalllog/SystemCallLogDataSource.java | 23 ++-- .../notifier/RefreshAnnotatedCallLogNotifier.java | 99 +++++++++++++++++ .../dialer/calllog/ui/NewCallLogFragment.java | 93 ++++------------ .../android/dialer/phonelookup/PhoneLookup.java | 11 +- .../DialerBlockedNumberPhoneLookup.java | 10 +- .../blockednumber/MarkDirtyObserver.java | 21 ++-- .../SystemBlockedNumberPhoneLookup.java | 10 +- .../composite/CompositePhoneLookup.java | 5 +- .../phonelookup/cp2/Cp2LocalPhoneLookup.java | 3 +- .../phonelookup/cp2/Cp2RemotePhoneLookup.java | 3 +- .../dialer/phonelookup/spam/SpamPhoneLookup.java | 6 +- .../voicemail/listui/NewVoicemailFragment.java | 107 +++++------------- 20 files changed, 387 insertions(+), 287 deletions(-) create mode 100644 java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java create mode 100644 java/com/android/dialer/calllog/constants/IntentNames.java create mode 100644 java/com/android/dialer/calllog/constants/SharedPrefKeys.java create mode 100644 java/com/android/dialer/calllog/notifier/RefreshAnnotatedCallLogNotifier.java diff --git a/java/com/android/dialer/calllog/CallLogComponent.java b/java/com/android/dialer/calllog/CallLogComponent.java index bb5bfee2a..4f147f1a6 100644 --- a/java/com/android/dialer/calllog/CallLogComponent.java +++ b/java/com/android/dialer/calllog/CallLogComponent.java @@ -16,6 +16,7 @@ package com.android.dialer.calllog; import android.content.Context; +import com.android.dialer.calllog.notifier.RefreshAnnotatedCallLogNotifier; import com.android.dialer.inject.HasRootComponent; import dagger.Subcomponent; @@ -25,6 +26,8 @@ public abstract class CallLogComponent { public abstract CallLogFramework callLogFramework(); + public abstract RefreshAnnotatedCallLogNotifier getRefreshAnnotatedCallLogNotifier(); + public abstract RefreshAnnotatedCallLogWorker getRefreshAnnotatedCallLogWorker(); public abstract ClearMissedCalls getClearMissedCalls(); diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java index 440055de6..7da8d9c0c 100644 --- a/java/com/android/dialer/calllog/CallLogFramework.java +++ b/java/com/android/dialer/calllog/CallLogFramework.java @@ -17,40 +17,26 @@ package com.android.dialer.calllog; import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.DataSources; -import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.storage.Unencrypted; import javax.inject.Inject; import javax.inject.Singleton; /** - * Coordinates work across CallLog data sources to detect if the annotated call log is out of date - * ("dirty") and update it if necessary. + * Coordinates work across {@link DataSources}. * *

All methods should be called on the main thread. */ @Singleton -public final class CallLogFramework implements CallLogDataSource.ContentObserverCallbacks { - - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String PREF_FORCE_REBUILD = "callLogFrameworkForceRebuild"; +public final class CallLogFramework { private final DataSources dataSources; - private final SharedPreferences sharedPreferences; - - @Nullable private CallLogUi ui; @Inject - CallLogFramework(DataSources dataSources, @Unencrypted SharedPreferences sharedPreferences) { + CallLogFramework(DataSources dataSources) { this.dataSources = dataSources; - this.sharedPreferences = sharedPreferences; } /** Registers the content observers for all data sources. */ @@ -63,58 +49,10 @@ public final class CallLogFramework implements CallLogDataSource.ContentObserver // TODO(zachh): Find a way to access Main#isNewUiEnabled without creating a circular dependency. if (ConfigProviderBindings.get(appContext).getBoolean("is_nui_shortcut_enabled", false)) { for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { - dataSource.registerContentObservers(appContext, this); + dataSource.registerContentObservers(appContext); } } else { LogUtil.i("CallLogFramework.registerContentObservers", "not registering content observers"); } } - - /** - * Attach a UI component to the framework so that it may be notified of changes to the annotated - * call log. - */ - public void attachUi(CallLogUi ui) { - LogUtil.enterBlock("CallLogFramework.attachUi"); - this.ui = ui; - } - - /** - * Detaches the UI from the framework. This should be called when the UI is hidden or destroyed - * and no longer needs to be notified of changes to the annotated call log. - */ - public void detachUi() { - LogUtil.enterBlock("CallLogFramework.detachUi"); - this.ui = null; - } - - /** - * Marks the call log as dirty and notifies any attached UI components. If there are no UI - * components currently attached, this is an efficient operation since it is just writing a shared - * pref. - * - *

We don't want to actually force a rebuild when there is no UI running because we don't want - * to be constantly rebuilding the database when the device is sitting on a desk and receiving a - * lot of calls, for example. - */ - @Override - @MainThread - public void markDirtyAndNotify(Context appContext) { - Assert.isMainThread(); - LogUtil.enterBlock("CallLogFramework.markDirtyAndNotify"); - - sharedPreferences.edit().putBoolean(PREF_FORCE_REBUILD, true).apply(); - - if (ui != null) { - ui.invalidateUi(); - } - } - - /** Callbacks invoked on listening UI components. */ - public interface CallLogUi { - - /** Notifies the call log UI that the annotated call log is out of date. */ - @MainThread - void invalidateUi(); - } } diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java new file mode 100644 index 000000000..e0bfcd8a3 --- /dev/null +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 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.calllog; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.support.annotation.Nullable; +import com.android.dialer.calllog.constants.IntentNames; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DefaultFutureCallback; +import com.android.dialer.common.concurrent.ThreadUtil; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * A {@link BroadcastReceiver} that starts/cancels refreshing the annotated call log when notified. + */ +public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { + + /** + * This is a reasonable time that it might take between related call log writes, that also + * shouldn't slow down single-writes too much. For example, when populating the database using the + * simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 call + * log entries. + */ + private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L; + + private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; + + @Nullable private Runnable refreshAnnotatedCallLogRunnable; + + /** Returns an {@link IntentFilter} containing all actions accepted by this broadcast receiver. */ + public static IntentFilter getIntentFilter() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG); + intentFilter.addAction(IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG); + return intentFilter; + } + + public RefreshAnnotatedCallLogReceiver(Context context) { + refreshAnnotatedCallLogWorker = + CallLogComponent.get(context).getRefreshAnnotatedCallLogWorker(); + } + + @Override + public void onReceive(Context context, Intent intent) { + LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.onReceive"); + + String action = intent.getAction(); + + if (IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG.equals(action)) { + boolean checkDirty = intent.getBooleanExtra(IntentNames.EXTRA_CHECK_DIRTY, false); + refreshAnnotatedCallLog(checkDirty); + } else if (IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG.equals(action)) { + cancelRefreshingAnnotatedCallLog(); + } + } + + /** + * Request a refresh of the annotated call log. + * + *

Note that the execution will be delayed by {@link #REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS}. + * Once the work begins, it can't be cancelled. + * + * @see #cancelRefreshingAnnotatedCallLog() + */ + private void refreshAnnotatedCallLog(boolean checkDirty) { + LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.refreshAnnotatedCallLog"); + + // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests + // in quick succession don't result in too much work. For example, if we get 10 requests in + // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait + // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests + // are serialized in RefreshAnnotatedCallLogWorker. + // + // We might get many requests in quick succession, for example, when the simulator inserts + // hundreds of rows into the system call log, or when the data for a new call is incrementally + // written to different columns as it becomes available. + ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); + + refreshAnnotatedCallLogRunnable = + () -> { + ListenableFuture future = + checkDirty + ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() + : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); + Futures.addCallback( + future, new DefaultFutureCallback<>(), MoreExecutors.directExecutor()); + }; + + ThreadUtil.getUiThreadHandler() + .postDelayed(refreshAnnotatedCallLogRunnable, REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS); + } + + /** + * When a refresh is requested, its execution is delayed (see {@link + * #refreshAnnotatedCallLog(boolean)}). This method only cancels the refresh if it hasn't started. + */ + private void cancelRefreshingAnnotatedCallLog() { + LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.cancelRefreshingAnnotatedCallLog"); + + ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); + } +} diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java index 4c5904ef1..a430d14a8 100644 --- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java @@ -18,6 +18,7 @@ package com.android.dialer.calllog; import android.content.Context; import android.content.SharedPreferences; +import com.android.dialer.calllog.constants.SharedPrefKeys; import com.android.dialer.calllog.database.CallLogDatabaseComponent; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.CallLogMutations; @@ -95,7 +96,7 @@ public class RefreshAnnotatedCallLogWorker { // Default to true. If the pref doesn't exist, the annotated call log hasn't been // created and we just skip isDirty checks and force a rebuild. boolean forceRebuildPrefValue = - sharedPreferences.getBoolean(CallLogFramework.PREF_FORCE_REBUILD, true); + sharedPreferences.getBoolean(SharedPrefKeys.FORCE_REBUILD, true); if (forceRebuildPrefValue) { LogUtil.i( "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", @@ -183,7 +184,7 @@ public class RefreshAnnotatedCallLogWorker { return Futures.transform( onSuccessfulFillFuture, unused -> { - sharedPreferences.edit().putBoolean(CallLogFramework.PREF_FORCE_REBUILD, false).apply(); + sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, false).apply(); return null; }, backgroundExecutorService); diff --git a/java/com/android/dialer/calllog/constants/IntentNames.java b/java/com/android/dialer/calllog/constants/IntentNames.java new file mode 100644 index 000000000..3912450a1 --- /dev/null +++ b/java/com/android/dialer/calllog/constants/IntentNames.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.calllog.constants; + +/** A class containing names for call log intents. */ +public final class IntentNames { + + public static final String ACTION_REFRESH_ANNOTATED_CALL_LOG = "refresh_annotated_call_log"; + + public static final String ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG = + "cancel_refreshing_annotated_call_log"; + + public static final String EXTRA_CHECK_DIRTY = "check_dirty"; + + private IntentNames() {} +} diff --git a/java/com/android/dialer/calllog/constants/SharedPrefKeys.java b/java/com/android/dialer/calllog/constants/SharedPrefKeys.java new file mode 100644 index 000000000..41e65bb53 --- /dev/null +++ b/java/com/android/dialer/calllog/constants/SharedPrefKeys.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.calllog.constants; + +/** Keys for shared preferences in the call log package. */ +public final class SharedPrefKeys { + + public static final String FORCE_REBUILD = "force_rebuild"; + + private SharedPrefKeys() {} +} diff --git a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java index 60654a81a..dbed1d81c 100644 --- a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java @@ -104,14 +104,5 @@ public interface CallLogDataSource { ContentValues coalesce(List individualRowsSortedByTimestampDesc); @MainThread - void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks); - - /** - * Methods which may optionally be called as a result of a data source's content observer firing. - */ - interface ContentObserverCallbacks { - @MainThread - void markDirtyAndNotify(Context appContext); - } + void registerContentObservers(Context appContext); } diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 8dec43759..40788f42a 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -61,8 +61,7 @@ import javax.inject.Inject; * Responsible for maintaining the columns in the annotated call log which are derived from phone * numbers. */ -public final class PhoneLookupDataSource - implements CallLogDataSource, PhoneLookup.ContentObserverCallbacks { +public final class PhoneLookupDataSource implements CallLogDataSource { private final PhoneLookup phoneLookup; private final ListeningExecutorService backgroundExecutorService; @@ -85,8 +84,6 @@ public final class PhoneLookupDataSource */ private final Set phoneLookupHistoryRowsToDelete = new ArraySet<>(); - private CallLogDataSource.ContentObserverCallbacks dataSourceContentObserverCallbacks; - @Inject PhoneLookupDataSource( PhoneLookup phoneLookup, @@ -288,17 +285,8 @@ public final class PhoneLookupDataSource @MainThread @Override - public void registerContentObservers( - Context appContext, CallLogDataSource.ContentObserverCallbacks contentObserverCallbacks) { - dataSourceContentObserverCallbacks = contentObserverCallbacks; - phoneLookup.registerContentObservers(appContext, this); - } - - @MainThread - @Override - public void markDirtyAndNotify(Context appContext) { - Assert.isMainThread(); - dataSourceContentObserverCallbacks.markDirtyAndNotify(appContext); + public void registerContentObservers(Context appContext) { + phoneLookup.registerContentObservers(appContext); } private static ImmutableSet diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index ee484d95e..e9f7c00bf 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -47,6 +47,7 @@ import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.Ann import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.CallLogMutations; import com.android.dialer.calllog.datasources.util.RowCombiner; +import com.android.dialer.calllog.notifier.RefreshAnnotatedCallLogNotifier; import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; @@ -79,18 +80,21 @@ public class SystemCallLogDataSource implements CallLogDataSource { static final String PREF_LAST_TIMESTAMP_PROCESSED = "systemCallLogLastTimestampProcessed"; private final ListeningExecutorService backgroundExecutorService; + private final RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier; @Nullable private Long lastTimestampProcessed; @Inject - SystemCallLogDataSource(@BackgroundExecutor ListeningExecutorService backgroundExecutorService) { + SystemCallLogDataSource( + @BackgroundExecutor ListeningExecutorService backgroundExecutorService, + RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier) { this.backgroundExecutorService = backgroundExecutorService; + this.refreshAnnotatedCallLogNotifier = refreshAnnotatedCallLogNotifier; } @MainThread @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { Assert.isMainThread(); LogUtil.enterBlock("SystemCallLogDataSource.registerContentObservers"); @@ -102,7 +106,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { // TODO(zachh): Need to somehow register observers if user enables permission after launch? CallLogObserver callLogObserver = - new CallLogObserver(ThreadUtil.getUiThreadHandler(), appContext, contentObserverCallbacks); + new CallLogObserver(ThreadUtil.getUiThreadHandler(), refreshAnnotatedCallLogNotifier); appContext .getContentResolver() @@ -524,15 +528,14 @@ public class SystemCallLogDataSource implements CallLogDataSource { return ids; } + // TODO(a bug): Consider replacing it with MarkDirtyObserver. private static class CallLogObserver extends ContentObserver { - private final Context appContext; - private final ContentObserverCallbacks contentObserverCallbacks; + private final RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier; CallLogObserver( - Handler handler, Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + Handler handler, RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier) { super(handler); - this.appContext = appContext; - this.contentObserverCallbacks = contentObserverCallbacks; + this.refreshAnnotatedCallLogNotifier = refreshAnnotatedCallLogNotifier; } @MainThread @@ -552,7 +555,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { * table, which would be too slow. So, we just rely on content observers to trigger rebuilds * when any change is made to the system call log. */ - contentObserverCallbacks.markDirtyAndNotify(appContext); + refreshAnnotatedCallLogNotifier.markDirtyAndNotify(); } } } diff --git a/java/com/android/dialer/calllog/notifier/RefreshAnnotatedCallLogNotifier.java b/java/com/android/dialer/calllog/notifier/RefreshAnnotatedCallLogNotifier.java new file mode 100644 index 000000000..5b73ad778 --- /dev/null +++ b/java/com/android/dialer/calllog/notifier/RefreshAnnotatedCallLogNotifier.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.calllog.notifier; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.support.v4.content.LocalBroadcastManager; +import com.android.dialer.calllog.constants.IntentNames; +import com.android.dialer.calllog.constants.SharedPrefKeys; +import com.android.dialer.common.LogUtil; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.storage.Unencrypted; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Notifies that a refresh of the annotated call log needs to be started/cancelled. + * + *

Methods in this class are usually invoked when the underlying data backing the annotated call + * log change. + * + *

For example, a {@link android.database.ContentObserver} for the system call log can use {@link + * #markDirtyAndNotify()} to force the annotated call log to be rebuilt. + */ +@Singleton +public class RefreshAnnotatedCallLogNotifier { + + private final Context appContext; + private final SharedPreferences sharedPreferences; + + @Inject + RefreshAnnotatedCallLogNotifier( + @ApplicationContext Context appContext, @Unencrypted SharedPreferences sharedPreferences) { + this.appContext = appContext; + this.sharedPreferences = sharedPreferences; + } + + /** + * Mark the annotated call log as "dirty" and notify that it needs to be refreshed. + * + *

This will force a rebuild by skip checking whether the annotated call log is "dirty". + */ + public void markDirtyAndNotify() { + LogUtil.enterBlock("RefreshAnnotatedCallLogNotifier.markDirtyAndNotify"); + + sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, true).apply(); + notify(/* checkDirty = */ false); + } + + /** + * Notifies that the annotated call log needs to be refreshed. + * + *

Note that the notification is sent as a broadcast, which means the annotated call log might + * not be refreshed if there is no corresponding receiver registered. + * + * @param checkDirty Whether to check if the annotated call log is "dirty" before proceeding to + * rebuild it. + */ + public void notify(boolean checkDirty) { + LogUtil.i("RefreshAnnotatedCallLogNotifier.notify", "checkDirty = %s", checkDirty); + + Intent intent = new Intent(); + intent.setAction(IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG); + intent.putExtra(IntentNames.EXTRA_CHECK_DIRTY, checkDirty); + + LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent); + } + + /** + * Notifies to cancel refreshing the annotated call log. + * + *

Note that this method does not guarantee the job to be cancelled. As the notification is + * sent as a broadcast, please see the corresponding receiver for details about cancelling the + * job. + */ + public void cancel() { + LogUtil.enterBlock("RefreshAnnotatedCallLogNotifier.cancel"); + + Intent intent = new Intent(); + intent.setAction(IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG); + + LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent); + } +} diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 5e676f072..8e8d55f3a 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -22,44 +22,29 @@ import android.support.annotation.VisibleForTesting; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; +import android.support.v4.content.LocalBroadcastManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.dialer.calllog.CallLogComponent; -import com.android.dialer.calllog.CallLogFramework; -import com.android.dialer.calllog.CallLogFramework.CallLogUi; -import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker; +import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DefaultFutureCallback; -import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; -import com.android.dialer.common.concurrent.UiListener; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.TimeUnit; /** The "new" call log fragment implementation, which is built on top of the annotated call log. */ -public final class NewCallLogFragment extends Fragment - implements CallLogUi, LoaderCallbacks { - - /* - * This is a reasonable time that it might take between related call log writes, that also - * shouldn't slow down single-writes too much. For example, when populating the database using - * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 - * call log entries. - */ - private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L; +public final class NewCallLogFragment extends Fragment implements LoaderCallbacks { @VisibleForTesting static final long MARK_ALL_CALLS_READ_WAIT_MILLIS = TimeUnit.SECONDS.toMillis(3); - private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; - private UiListener refreshAnnotatedCallLogListener; + private RefreshAnnotatedCallLogReceiver refreshAnnotatedCallLogReceiver; private RecyclerView recyclerView; - @Nullable private Runnable refreshAnnotatedCallLogRunnable; private boolean shouldMarkCallsRead = false; private final Runnable setShouldMarkCallsReadTrue = () -> shouldMarkCallsRead = true; @@ -74,16 +59,7 @@ public final class NewCallLogFragment extends Fragment LogUtil.enterBlock("NewCallLogFragment.onActivityCreated"); - CallLogComponent component = CallLogComponent.get(getContext()); - CallLogFramework callLogFramework = component.callLogFramework(); - callLogFramework.attachUi(this); - - // TODO(zachh): Use support fragment manager and add support for them in executors library. - refreshAnnotatedCallLogListener = - DialerExecutorComponent.get(getContext()) - .createUiListener( - getActivity().getFragmentManager(), "NewCallLogFragment.refreshAnnotatedCallLog"); - refreshAnnotatedCallLogWorker = component.getRefreshAnnotatedCallLogWorker(); + refreshAnnotatedCallLogReceiver = new RefreshAnnotatedCallLogReceiver(getContext()); } @Override @@ -99,11 +75,12 @@ public final class NewCallLogFragment extends Fragment LogUtil.enterBlock("NewCallLogFragment.onResume"); - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.attachUi(this); + registerRefreshAnnotatedCallLogReceiver(); // TODO(zachh): Consider doing this when fragment becomes visible. - refreshAnnotatedCallLog(true /* checkDirty */); + CallLogComponent.get(getContext()) + .getRefreshAnnotatedCallLogNotifier() + .notify(/* checkDirty = */ true); // There are some types of data that we show in the call log that are not represented in the // AnnotatedCallLog. For example, CP2 information for invalid numbers can sometimes only be @@ -130,11 +107,9 @@ public final class NewCallLogFragment extends Fragment LogUtil.enterBlock("NewCallLogFragment.onPause"); // This is pending work that we don't actually need to follow through with. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(setShouldMarkCallsReadTrue); - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.detachUi(); + unregisterRefreshAnnotatedCallLogReceiver(); if (shouldMarkCallsRead) { Futures.addCallback( @@ -157,42 +132,22 @@ public final class NewCallLogFragment extends Fragment return view; } - private void refreshAnnotatedCallLog(boolean checkDirty) { - LogUtil.enterBlock("NewCallLogFragment.refreshAnnotatedCallLog"); - - // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests - // in quick succession don't result in too much work. For example, if we get 10 requests in - // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait - // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests - // are serialized in RefreshAnnotatedCallLogWorker. - // - // We might get many requests in quick succession, for example, when the simulator inserts - // hundreds of rows into the system call log, or when the data for a new call is incrementally - // written to different columns as it becomes available. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); - - refreshAnnotatedCallLogRunnable = - () -> { - ListenableFuture future = - checkDirty - ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() - : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); - refreshAnnotatedCallLogListener.listen( - getContext(), - future, - unused -> {}, - throwable -> { - throw new RuntimeException(throwable); - }); - }; - ThreadUtil.getUiThreadHandler() - .postDelayed(refreshAnnotatedCallLogRunnable, REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS); + private void registerRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewCallLogFragment.registerRefreshAnnotatedCallLogReceiver"); + + LocalBroadcastManager.getInstance(getContext()) + .registerReceiver( + refreshAnnotatedCallLogReceiver, RefreshAnnotatedCallLogReceiver.getIntentFilter()); } - @Override - public void invalidateUi() { - LogUtil.enterBlock("NewCallLogFragment.invalidateUi"); - refreshAnnotatedCallLog(false /* checkDirty */); + private void unregisterRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewCallLogFragment.unregisterRefreshAnnotatedCallLogReceiver"); + + // Cancel pending work as we don't need it any more. + CallLogComponent.get(getContext()).getRefreshAnnotatedCallLogNotifier().cancel(); + + LocalBroadcastManager.getInstance(getContext()) + .unregisterReceiver(refreshAnnotatedCallLogReceiver); } @Override diff --git a/java/com/android/dialer/phonelookup/PhoneLookup.java b/java/com/android/dialer/phonelookup/PhoneLookup.java index 76ff98e7c..a7974ad10 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/PhoneLookup.java @@ -84,14 +84,5 @@ public interface PhoneLookup { ListenableFuture onSuccessfulBulkUpdate(); @MainThread - void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks); - - /** - * Methods which may optionally be called as a result of a phone lookup's content observer firing. - */ - interface ContentObserverCallbacks { - @MainThread - void markDirtyAndNotify(Context appContext); - } + void registerContentObservers(Context appContext); } diff --git a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java index 2271c7580..2d019c8c2 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java @@ -51,13 +51,16 @@ public final class DialerBlockedNumberPhoneLookup implements PhoneLookup @Override @MainThread - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { for (PhoneLookup phoneLookup : phoneLookups) { - phoneLookup.registerContentObservers(appContext, contentObserverCallbacks); + phoneLookup.registerContentObservers(appContext); } } } diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java index e051f473c..8db308892 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java @@ -619,8 +619,7 @@ public final class Cp2LocalPhoneLookup implements PhoneLookup { } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { // Do nothing since CP2 changes are too noisy. } diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java index cc4fbf19f..7efe039eb 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java @@ -237,8 +237,7 @@ public final class Cp2RemotePhoneLookup implements PhoneLookup { } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { // No content observer needed for remote contacts } } diff --git a/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java b/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java index 9f0b5cf52..7661a15da 100644 --- a/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java @@ -152,8 +152,8 @@ public final class SpamPhoneLookup implements PhoneLookup { } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { - // No content observer needed for spam info + public void registerContentObservers(Context appContext) { + // No content observer can be registered as Spam is not based on a content provider. + // Each Spam implementation should be responsible for notifying any data changes. } } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java index 8b6fcbc07..8d724afe3 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java @@ -24,18 +24,16 @@ import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; +import android.support.v4.content.LocalBroadcastManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.dialer.calllog.CallLogComponent; -import com.android.dialer.calllog.CallLogFramework; -import com.android.dialer.calllog.CallLogFramework.CallLogUi; -import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker; +import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; -import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.common.concurrent.UiListener; import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent; import com.android.dialer.voicemail.listui.error.VoicemailStatus; @@ -48,24 +46,11 @@ import java.util.List; // TODO(uabdullah): Register content observer for VoicemailContract.Status.CONTENT_URI in onStart /** Fragment for Dialer Voicemail Tab. */ -public final class NewVoicemailFragment extends Fragment - implements LoaderCallbacks, CallLogUi { - - /* - * This is a reasonable time that it might take between related call log writes, that also - * shouldn't slow down single-writes too much. For example, when populating the database using - * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 - * call log entries. - */ - private static final long WAIT_MILLIS = 100L; - - private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; - private UiListener refreshAnnotatedCallLogListener; - @Nullable private Runnable refreshAnnotatedCallLogRunnable; - - private UiListener> queryVoicemailStatusTableListener; +public final class NewVoicemailFragment extends Fragment implements LoaderCallbacks { private RecyclerView recyclerView; + private RefreshAnnotatedCallLogReceiver refreshAnnotatedCallLogReceiver; + private UiListener> queryVoicemailStatusTableListener; public NewVoicemailFragment() { LogUtil.enterBlock("NewVoicemailFragment.NewVoicemailFragment"); @@ -77,23 +62,12 @@ public final class NewVoicemailFragment extends Fragment LogUtil.enterBlock("NewVoicemailFragment.onActivityCreated"); - CallLogComponent component = CallLogComponent.get(getContext()); - CallLogFramework callLogFramework = component.callLogFramework(); - callLogFramework.attachUi(this); - - // TODO(zachh): Use support fragment manager and add support for them in executors library. - refreshAnnotatedCallLogListener = - DialerExecutorComponent.get(getContext()) - .createUiListener( - getActivity().getFragmentManager(), "NewVoicemailFragment.refreshAnnotatedCallLog"); - + refreshAnnotatedCallLogReceiver = new RefreshAnnotatedCallLogReceiver(getContext()); queryVoicemailStatusTableListener = DialerExecutorComponent.get(getContext()) .createUiListener( getActivity().getFragmentManager(), "NewVoicemailFragment.queryVoicemailStatusTable"); - - refreshAnnotatedCallLogWorker = component.getRefreshAnnotatedCallLogWorker(); } @Override @@ -108,11 +82,12 @@ public final class NewVoicemailFragment extends Fragment LogUtil.enterBlock("NewCallLogFragment.onResume"); - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.attachUi(this); + registerRefreshAnnotatedCallLogReceiver(); // TODO(zachh): Consider doing this when fragment becomes visible. - refreshAnnotatedCallLog(true /* checkDirty */); + CallLogComponent.get(getContext()) + .getRefreshAnnotatedCallLogNotifier() + .notify(/* checkDirty = */ true); } @Override @@ -121,14 +96,9 @@ public final class NewVoicemailFragment extends Fragment LogUtil.enterBlock("NewVoicemailFragment.onPause"); - // This is pending work that we don't actually need to follow through with. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); - - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.detachUi(); + unregisterRefreshAnnotatedCallLogReceiver(); } - @Nullable @Override public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -139,43 +109,6 @@ public final class NewVoicemailFragment extends Fragment return view; } - private void refreshAnnotatedCallLog(boolean checkDirty) { - LogUtil.enterBlock("NewVoicemailFragment.refreshAnnotatedCallLog"); - - // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests - // in quick succession don't result in too much work. For example, if we get 10 requests in - // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait - // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests - // are serialized in RefreshAnnotatedCallLogWorker. - // - // We might get many requests in quick succession, for example, when the simulator inserts - // hundreds of rows into the system call log, or when the data for a new call is incrementally - // written to different columns as it becomes available. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); - - refreshAnnotatedCallLogRunnable = - () -> { - ListenableFuture future = - checkDirty - ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() - : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); - refreshAnnotatedCallLogListener.listen( - getContext(), - future, - unused -> {}, - throwable -> { - throw new RuntimeException(throwable); - }); - }; - ThreadUtil.getUiThreadHandler().postDelayed(refreshAnnotatedCallLogRunnable, WAIT_MILLIS); - } - - @Override - public void invalidateUi() { - LogUtil.enterBlock("NewVoicemailFragment.invalidateUi"); - refreshAnnotatedCallLog(false /* checkDirty */); - } - @Override public Loader onCreateLoader(int id, Bundle args) { LogUtil.enterBlock("NewVoicemailFragment.onCreateLoader"); @@ -210,6 +143,24 @@ public final class NewVoicemailFragment extends Fragment queryAndUpdateVoicemailStatusAlert(); } + private void registerRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewVoicemailFragment.registerRefreshAnnotatedCallLogReceiver"); + + LocalBroadcastManager.getInstance(getContext()) + .registerReceiver( + refreshAnnotatedCallLogReceiver, RefreshAnnotatedCallLogReceiver.getIntentFilter()); + } + + private void unregisterRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewVoicemailFragment.unregisterRefreshAnnotatedCallLogReceiver"); + + // Cancel pending work as we don't need it any more. + CallLogComponent.get(getContext()).getRefreshAnnotatedCallLogNotifier().cancel(); + + LocalBroadcastManager.getInstance(getContext()) + .unregisterReceiver(refreshAnnotatedCallLogReceiver); + } + private void queryAndUpdateVoicemailStatusAlert() { queryVoicemailStatusTableListener.listen( getContext(), -- cgit v1.2.3 From 7e3f39706c2d8ae74ee5613f582daa2946dcc5d4 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 20 Feb 2018 13:07:49 -0800 Subject: Initialize/Update the UI of New{CallLog|Voicemail}Fragment iff the fragment is truly visible. Bug: 73347270 Test: NewCallLogFragmentTest, NewVoicemailFragmentTest PiperOrigin-RevId: 186350076 Change-Id: Ib3e320f02a02795acb8b7d2017818b36df3dd49d --- .../dialer/calllog/ui/NewCallLogFragment.java | 60 +++++++++++++++++++--- .../voicemail/listui/NewVoicemailFragment.java | 60 +++++++++++++++++++--- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 8e8d55f3a..d0f42d335 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -73,11 +73,49 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback public void onResume() { super.onResume(); - LogUtil.enterBlock("NewCallLogFragment.onResume"); + boolean isHidden = isHidden(); + LogUtil.i("NewCallLogFragment.onResume", "isHidden = %s", isHidden); + + // As a fragment's onResume() is tied to the containing Activity's onResume(), being resumed is + // not equivalent to becoming visible. + // For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + // will be called but it is not visible. + if (!isHidden) { + onFragmentShown(); + } + } + + @Override + public void onPause() { + super.onPause(); + LogUtil.enterBlock("NewCallLogFragment.onPause"); + + onFragmentHidden(); + } + + @Override + public void onHiddenChanged(boolean hidden) { + super.onHiddenChanged(hidden); + LogUtil.i("NewCallLogFragment.onHiddenChanged", "hidden = %s", hidden); + if (hidden) { + onFragmentHidden(); + } else { + onFragmentShown(); + } + } + + /** + * To be called when the fragment becomes visible. + * + *

Note that for a fragment, being resumed is not equivalent to becoming visible. + * + *

For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + * will be called but it is not visible. + */ + private void onFragmentShown() { registerRefreshAnnotatedCallLogReceiver(); - // TODO(zachh): Consider doing this when fragment becomes visible. CallLogComponent.get(getContext()) .getRefreshAnnotatedCallLogNotifier() .notify(/* checkDirty = */ true); @@ -100,12 +138,18 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback .postDelayed(setShouldMarkCallsReadTrue, MARK_ALL_CALLS_READ_WAIT_MILLIS); } - @Override - public void onPause() { - super.onPause(); - - LogUtil.enterBlock("NewCallLogFragment.onPause"); - + /** + * To be called when the fragment becomes hidden. + * + *

This can happen in the following two cases: + * + *

    + *
  • hide the fragment but keep the parent activity visible (e.g., calling {@link + * android.support.v4.app.FragmentTransaction#hide(Fragment)} in an activity, or + *
  • the parent activity is paused. + *
+ */ + private void onFragmentHidden() { // This is pending work that we don't actually need to follow through with. ThreadUtil.getUiThreadHandler().removeCallbacks(setShouldMarkCallsReadTrue); diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java index 8d724afe3..9296d04e7 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java @@ -80,22 +80,66 @@ public final class NewVoicemailFragment extends Fragment implements LoaderCallba public void onResume() { super.onResume(); - LogUtil.enterBlock("NewCallLogFragment.onResume"); + boolean isHidden = isHidden(); + LogUtil.i("NewVoicemailFragment.onResume", "isHidden = %s", isHidden); + + // As a fragment's onResume() is tied to the containing Activity's onResume(), being resumed is + // not equivalent to becoming visible. + // For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + // will be called but it is not visible. + if (!isHidden) { + onFragmentShown(); + } + } + + @Override + public void onPause() { + super.onPause(); + LogUtil.enterBlock("NewVoicemailFragment.onPause"); + + onFragmentHidden(); + } + + @Override + public void onHiddenChanged(boolean hidden) { + super.onHiddenChanged(hidden); + LogUtil.i("NewVoicemailFragment.onHiddenChanged", "hidden = %s", hidden); + if (hidden) { + onFragmentHidden(); + } else { + onFragmentShown(); + } + } + + /** + * To be called when the fragment becomes visible. + * + *

Note that for a fragment, being resumed is not equivalent to becoming visible. + * + *

For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + * will be called but it is not visible. + */ + private void onFragmentShown() { registerRefreshAnnotatedCallLogReceiver(); - // TODO(zachh): Consider doing this when fragment becomes visible. CallLogComponent.get(getContext()) .getRefreshAnnotatedCallLogNotifier() .notify(/* checkDirty = */ true); } - @Override - public void onPause() { - super.onPause(); - - LogUtil.enterBlock("NewVoicemailFragment.onPause"); - + /** + * To be called when the fragment becomes hidden. + * + *

This can happen in the following two cases: + * + *

    + *
  • hide the fragment but keep the parent activity visible (e.g., calling {@link + * android.support.v4.app.FragmentTransaction#hide(Fragment)} in an activity, or + *
  • the parent activity is paused. + *
+ */ + private void onFragmentHidden() { unregisterRefreshAnnotatedCallLogReceiver(); } -- cgit v1.2.3 From 844dcad13ad494256dddfdc44f7ad90a5f93c530 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 20 Feb 2018 13:40:18 -0800 Subject: Merge searchfragment/remote into searchfragment/directories and renaming things accordingly. Test: Existing tests PiperOrigin-RevId: 186355284 Change-Id: I7c2a2917175ef13742ca3b2e628d8655dc668f60 --- .../directories/DirectoriesCursorLoader.java | 7 +- .../directories/DirectoryContactViewHolder.java | 145 ++++++++++++++++++ .../directories/DirectoryContactsCursor.java | 153 +++++++++++++++++++ .../directories/DirectoryContactsCursorLoader.java | 162 +++++++++++++++++++++ .../directories/res/values/strings.xml | 20 +++ .../searchfragment/list/NewSearchFragment.java | 71 +++++---- .../dialer/searchfragment/list/SearchAdapter.java | 12 +- .../searchfragment/remote/AndroidManifest.xml | 16 -- .../remote/RemoteContactViewHolder.java | 145 ------------------ .../remote/RemoteContactsCursor.java | 154 -------------------- .../remote/RemoteContactsCursorLoader.java | 162 --------------------- .../searchfragment/remote/res/values/strings.xml | 20 --- 12 files changed, 534 insertions(+), 533 deletions(-) create mode 100644 java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java create mode 100644 java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java create mode 100644 java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java create mode 100644 java/com/android/dialer/searchfragment/directories/res/values/strings.xml delete mode 100644 java/com/android/dialer/searchfragment/remote/AndroidManifest.xml delete mode 100644 java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java delete mode 100644 java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java delete mode 100644 java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java delete mode 100644 java/com/android/dialer/searchfragment/remote/res/values/strings.xml diff --git a/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java b/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java index 39c1187a4..dbe11dd96 100644 --- a/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java +++ b/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java @@ -31,7 +31,12 @@ import com.google.auto.value.AutoValue; import java.util.ArrayList; import java.util.List; -/** {@link CursorLoader} to load the list of all directories (local and remote). */ +/** + * {@link CursorLoader} to load information about all directories (local and remote). + * + *

Information about a directory includes its ID, display name, etc, but doesn't include the + * contacts in it. + */ public final class DirectoriesCursorLoader extends CursorLoader { public static final String[] PROJECTION = { diff --git a/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java new file mode 100644 index 000000000..ff321fc75 --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java @@ -0,0 +1,145 @@ +/* + * 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.searchfragment.directories; + +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.QuickContactBadge; +import android.widget.TextView; +import com.android.contacts.common.compat.DirectoryCompat; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.contactphoto.ContactPhotoManager; +import com.android.dialer.lettertile.LetterTileDrawable; +import com.android.dialer.precall.PreCall; +import com.android.dialer.searchfragment.common.Projections; +import com.android.dialer.searchfragment.common.QueryBoldingUtil; +import com.android.dialer.searchfragment.common.R; +import com.android.dialer.searchfragment.common.SearchCursor; + +/** ViewHolder for a directory contact row. */ +public final class DirectoryContactViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener { + + private final Context context; + private final TextView nameView; + private final TextView numberView; + private final QuickContactBadge photo; + private final ImageView workBadge; + + private String number; + + public DirectoryContactViewHolder(View view) { + super(view); + view.setOnClickListener(this); + photo = view.findViewById(R.id.photo); + nameView = view.findViewById(R.id.primary); + numberView = view.findViewById(R.id.secondary); + workBadge = view.findViewById(R.id.work_icon); + context = view.getContext(); + } + + /** + * Binds the ViewHolder with a cursor from {@link DirectoryContactsCursorLoader} with the data + * found at the cursors current position. + */ + public void bind(SearchCursor cursor, String query) { + number = cursor.getString(Projections.PHONE_NUMBER); + String name = cursor.getString(Projections.DISPLAY_NAME); + String label = getLabel(context.getResources(), cursor); + String secondaryInfo = + TextUtils.isEmpty(label) + ? number + : context.getString( + com.android.contacts.common.R.string.call_subject_type_and_number, label, number); + + nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); + numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo, context)); + workBadge.setVisibility( + DirectoryCompat.isOnlyEnterpriseDirectoryId(cursor.getDirectoryId()) + ? View.VISIBLE + : View.GONE); + + if (shouldShowPhoto(cursor)) { + nameView.setVisibility(View.VISIBLE); + photo.setVisibility(View.VISIBLE); + String photoUri = cursor.getString(Projections.PHOTO_URI); + ContactPhotoManager.getInstance(context) + .loadDialerThumbnailOrPhoto( + photo, + getContactUri(cursor), + cursor.getLong(Projections.PHOTO_ID), + photoUri == null ? null : Uri.parse(photoUri), + name, + LetterTileDrawable.TYPE_DEFAULT); + } else { + nameView.setVisibility(View.GONE); + photo.setVisibility(View.INVISIBLE); + } + } + + // Show the contact photo next to only the first number if a contact has multiple numbers + private boolean shouldShowPhoto(SearchCursor cursor) { + int currentPosition = cursor.getPosition(); + String currentLookupKey = cursor.getString(Projections.LOOKUP_KEY); + cursor.moveToPosition(currentPosition - 1); + + if (!cursor.isHeader() && !cursor.isBeforeFirst()) { + String previousLookupKey = cursor.getString(Projections.LOOKUP_KEY); + cursor.moveToPosition(currentPosition); + return !currentLookupKey.equals(previousLookupKey); + } + cursor.moveToPosition(currentPosition); + return true; + } + + // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType + private static String getLabel(Resources resources, Cursor cursor) { + int numberType = cursor.getInt(Projections.PHONE_TYPE); + String numberLabel = cursor.getString(Projections.PHONE_LABEL); + + // Returns empty label instead of "custom" if the custom label is empty. + if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) { + return ""; + } + return (String) Phone.getTypeLabel(resources, numberType, numberLabel); + } + + private static Uri getContactUri(SearchCursor cursor) { + long contactId = cursor.getLong(Projections.ID); + String lookupKey = cursor.getString(Projections.LOOKUP_KEY); + return Contacts.getLookupUri(contactId, lookupKey) + .buildUpon() + .appendQueryParameter( + ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(cursor.getDirectoryId())) + .build(); + } + + @Override + public void onClick(View v) { + PreCall.start(context, new CallIntentBuilder(number, CallInitiationType.Type.REGULAR_SEARCH)); + } +} diff --git a/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java new file mode 100644 index 000000000..bf0bdc057 --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java @@ -0,0 +1,153 @@ +/* + * 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.searchfragment.directories; + +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MergeCursor; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import com.android.contacts.common.compat.DirectoryCompat; +import com.android.dialer.common.Assert; +import com.android.dialer.searchfragment.common.SearchCursor; +import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * {@link MergeCursor} used for combining directory cursors into one cursor. + * + *

Usually a device with multiple Google accounts will have multiple directories returned by + * {@link DirectoriesCursorLoader}, each represented as a {@link Directory}. + * + *

This cursor merges them together with a header at the start of each cursor/list using {@link + * Directory#getDisplayName()} as the header text. + */ +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) +public final class DirectoryContactsCursor extends MergeCursor implements SearchCursor { + + /** + * {@link SearchCursor#HEADER_PROJECTION} with {@link #COLUMN_DIRECTORY_ID} appended on the end. + * + *

This is needed to get the directoryId associated with each contact. directoryIds are needed + * to load the correct quick contact card. + */ + private static final String[] PROJECTION = buildProjection(); + + private static final String COLUMN_DIRECTORY_ID = "directory_id"; + + /** + * Returns a single cursor with headers inserted between each non-empty cursor. If all cursors are + * empty, null or closed, this method returns null. + */ + @Nullable + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static DirectoryContactsCursor newInstance( + Context context, Cursor[] cursors, List directories) { + Assert.checkArgument( + cursors.length == directories.size(), + "Directories (%d) and cursors (%d) must be the same size.", + directories.size(), + cursors.length); + Cursor[] cursorsWithHeaders = insertHeaders(context, cursors, directories); + if (cursorsWithHeaders.length > 0) { + return new DirectoryContactsCursor(cursorsWithHeaders); + } + return null; + } + + private DirectoryContactsCursor(Cursor[] cursors) { + super(cursors); + } + + private static Cursor[] insertHeaders( + Context context, Cursor[] cursors, List directories) { + List cursorList = new ArrayList<>(); + for (int i = 0; i < cursors.length; i++) { + Cursor cursor = cursors[i]; + + if (cursor == null || cursor.isClosed()) { + continue; + } + + Directory directory = directories.get(i); + if (cursor.getCount() == 0) { + // Since the cursor isn't being merged in, we need to close it here. + cursor.close(); + continue; + } + + cursorList.add(createHeaderCursor(context, directory.getDisplayName(), directory.getId())); + cursorList.add(cursor); + } + return cursorList.toArray(new Cursor[cursorList.size()]); + } + + private static MatrixCursor createHeaderCursor(Context context, String name, long id) { + MatrixCursor headerCursor = new MatrixCursor(PROJECTION, 1); + if (DirectoryCompat.isOnlyEnterpriseDirectoryId(id)) { + headerCursor.addRow( + new Object[] {context.getString(R.string.directory_search_label_work), id}); + } else { + headerCursor.addRow(new Object[] {context.getString(R.string.directory, name), id}); + } + return headerCursor; + } + + private static String[] buildProjection() { + String[] projection = Arrays.copyOf(HEADER_PROJECTION, HEADER_PROJECTION.length + 1); + projection[projection.length - 1] = COLUMN_DIRECTORY_ID; + return projection; + } + + /** Returns true if the current position is a header row. */ + @Override + public boolean isHeader() { + return !isClosed() && getColumnIndex(HEADER_PROJECTION[HEADER_TEXT_POSITION]) != -1; + } + + @Override + public long getDirectoryId() { + int position = getPosition(); + // proceed backwards until we reach the header row, which contains the directory ID. + while (moveToPrevious()) { + int columnIndex = getColumnIndex(COLUMN_DIRECTORY_ID); + if (columnIndex == -1) { + continue; + } + + int id = getInt(columnIndex); + if (id == -1) { + continue; + } + + // return the cursor to it's original position/state + moveToPosition(position); + return id; + } + throw Assert.createIllegalStateFailException("No directory id for contact at: " + position); + } + + @Override + public boolean updateQuery(@Nullable String query) { + // When the query changes, a new network request is made for nearby places. Meaning this cursor + // will be closed and another created, so return false. + return false; + } +} diff --git a/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java new file mode 100644 index 000000000..fc36f59bb --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java @@ -0,0 +1,162 @@ +/* + * 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.searchfragment.directories; + +import android.content.Context; +import android.content.CursorLoader; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import com.android.contacts.common.compat.DirectoryCompat; +import com.android.dialer.searchfragment.common.Projections; +import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; +import java.util.ArrayList; +import java.util.List; + +/** + * Cursor loader to load extended contacts on device. + * + *

This loader performs several database queries in serial and merges the resulting cursors + * together into {@link DirectoryContactsCursor}. If there are no results, the loader will return a + * null cursor. + */ +public final class DirectoryContactsCursorLoader extends CursorLoader { + + private static final Uri ENTERPRISE_CONTENT_FILTER_URI = + Uri.withAppendedPath(Phone.CONTENT_URI, "filter_enterprise"); + + private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE = "length(" + Phone.NUMBER + ") < 1000"; + private static final String PHONE_NUMBER_NOT_NULL = Phone.NUMBER + " IS NOT NULL"; + private static final String MAX_RESULTS = "10"; + + private final String query; + private final List directories; + private final Cursor[] cursors; + + public DirectoryContactsCursorLoader(Context context, String query, List directories) { + super( + context, + null, + Projections.DATA_PROJECTION, + IGNORE_NUMBER_TOO_LONG_CLAUSE + " AND " + PHONE_NUMBER_NOT_NULL, + null, + Phone.SORT_KEY_PRIMARY); + this.query = query; + this.directories = new ArrayList<>(directories); + cursors = new Cursor[directories.size()]; + } + + @Override + public Cursor loadInBackground() { + for (int i = 0; i < directories.size(); i++) { + Directory directory = directories.get(i); + + // Only load contacts in the enterprise directory & remote directories. + if (!DirectoryCompat.isRemoteDirectoryId(directory.getId()) + && !DirectoryCompat.isEnterpriseDirectoryId(directory.getId())) { + cursors[i] = null; + continue; + } + + // Filter out invisible directories. + if (DirectoryCompat.isInvisibleDirectory(directory.getId())) { + cursors[i] = null; + continue; + } + + Cursor cursor = + getContext() + .getContentResolver() + .query( + getContentFilterUri(query, directory.getId()), + getProjection(), + getSelection(), + getSelectionArgs(), + getSortOrder()); + // Even though the cursor specifies "WHERE PHONE_NUMBER IS NOT NULL" the Blackberry Hub app's + // directory extension doesn't appear to respect it, and sometimes returns a null phone + // number. In this case just hide the row entirely. See a bug. + cursors[i] = createMatrixCursorFilteringNullNumbers(cursor); + } + return DirectoryContactsCursor.newInstance(getContext(), cursors, directories); + } + + private MatrixCursor createMatrixCursorFilteringNullNumbers(Cursor cursor) { + if (cursor == null) { + return null; + } + MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames()); + try { + if (cursor.moveToFirst()) { + do { + String number = cursor.getString(Projections.PHONE_NUMBER); + if (number == null) { + continue; + } + matrixCursor.addRow(objectArrayFromCursor(cursor)); + } while (cursor.moveToNext()); + } + } finally { + cursor.close(); + } + return matrixCursor; + } + + @NonNull + private static Object[] objectArrayFromCursor(@NonNull Cursor cursor) { + Object[] values = new Object[cursor.getColumnCount()]; + for (int i = 0; i < cursor.getColumnCount(); i++) { + int fieldType = cursor.getType(i); + if (fieldType == Cursor.FIELD_TYPE_BLOB) { + values[i] = cursor.getBlob(i); + } else if (fieldType == Cursor.FIELD_TYPE_FLOAT) { + values[i] = cursor.getDouble(i); + } else if (fieldType == Cursor.FIELD_TYPE_INTEGER) { + values[i] = cursor.getLong(i); + } else if (fieldType == Cursor.FIELD_TYPE_STRING) { + values[i] = cursor.getString(i); + } else if (fieldType == Cursor.FIELD_TYPE_NULL) { + values[i] = null; + } else { + throw new IllegalStateException("Unknown fieldType (" + fieldType + ") for column: " + i); + } + } + return values; + } + + @VisibleForTesting + static Uri getContentFilterUri(String query, long directoryId) { + Uri baseUri = + VERSION.SDK_INT >= VERSION_CODES.N + ? ENTERPRISE_CONTENT_FILTER_URI + : Phone.CONTENT_FILTER_URI; + + return baseUri + .buildUpon() + .appendPath(query) + .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)) + .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true") + .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, MAX_RESULTS) + .build(); + } +} diff --git a/java/com/android/dialer/searchfragment/directories/res/values/strings.xml b/java/com/android/dialer/searchfragment/directories/res/values/strings.xml new file mode 100644 index 000000000..beabba135 --- /dev/null +++ b/java/com/android/dialer/searchfragment/directories/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + + Directory %1$s + \ No newline at end of file diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index 2d45457d2..aff946206 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -65,9 +65,9 @@ import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.cp2.SearchContactsCursorLoader; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; +import com.android.dialer.searchfragment.directories.DirectoryContactsCursorLoader; import com.android.dialer.searchfragment.list.SearchActionViewHolder.Action; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader; -import com.android.dialer.searchfragment.remote.RemoteContactsCursorLoader; import com.android.dialer.storage.StorageComponent; import com.android.dialer.util.CallUtil; import com.android.dialer.util.DialerUtils; @@ -103,8 +103,11 @@ public final class NewSearchFragment extends Fragment private static final int CONTACTS_LOADER_ID = 0; private static final int NEARBY_PLACES_LOADER_ID = 1; - private static final int REMOTE_DIRECTORIES_LOADER_ID = 2; - private static final int REMOTE_CONTACTS_LOADER_ID = 3; + + // ID for the loader that loads info about all directories (local & remote). + private static final int DIRECTORIES_LOADER_ID = 2; + + private static final int DIRECTORY_CONTACTS_LOADER_ID = 3; private static final String KEY_QUERY = "key_query"; private static final String KEY_CALL_INITIATION_TYPE = "key_call_initiation_type"; @@ -117,15 +120,17 @@ public final class NewSearchFragment extends Fragment // for actions to add contact or send sms. private String rawNumber; private CallInitiationType.Type callInitiationType = CallInitiationType.Type.UNKNOWN_INITIATION; - private boolean remoteDirectoriesDisabledForTesting; + private boolean directoriesDisabledForTesting; + // Information about all local & remote directories (including ID, display name, etc, but not + // the contacts in them). private final List directories = new ArrayList<>(); private final Runnable loaderCp2ContactsRunnable = () -> getLoaderManager().restartLoader(CONTACTS_LOADER_ID, null, this); private final Runnable loadNearbyPlacesRunnable = () -> getLoaderManager().restartLoader(NEARBY_PLACES_LOADER_ID, null, this); - private final Runnable loadRemoteContactsRunnable = - () -> getLoaderManager().restartLoader(REMOTE_CONTACTS_LOADER_ID, null, this); + private final Runnable loadDirectoryContactsRunnable = + () -> getLoaderManager().restartLoader(DIRECTORY_CONTACTS_LOADER_ID, null, this); private final Runnable capabilitiesUpdatedRunnable = () -> adapter.notifyDataSetChanged(); private Runnable updatePositionRunnable; @@ -184,7 +189,7 @@ public final class NewSearchFragment extends Fragment private void initLoaders() { getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this); - loadRemoteDirectoriesCursor(); + loadDirectoriesCursor(); } @Override @@ -201,10 +206,10 @@ public final class NewSearchFragment extends Fragment directoryIds.add(directory.getId()); } return new NearbyPlacesCursorLoader(getContext(), query, directoryIds); - } else if (id == REMOTE_DIRECTORIES_LOADER_ID) { + } else if (id == DIRECTORIES_LOADER_ID) { return new DirectoriesCursorLoader(getContext()); - } else if (id == REMOTE_CONTACTS_LOADER_ID) { - return new RemoteContactsCursorLoader(getContext(), query, directories); + } else if (id == DIRECTORY_CONTACTS_LOADER_ID) { + return new DirectoryContactsCursorLoader(getContext(), query, directories); } else { throw new IllegalStateException("Invalid loader id: " + id); } @@ -225,14 +230,14 @@ public final class NewSearchFragment extends Fragment } else if (loader instanceof NearbyPlacesCursorLoader) { adapter.setNearbyPlacesCursor((SearchCursor) cursor); - } else if (loader instanceof RemoteContactsCursorLoader) { - adapter.setRemoteContactsCursor((SearchCursor) cursor); + } else if (loader instanceof DirectoryContactsCursorLoader) { + adapter.setDirectoryContactsCursor((SearchCursor) cursor); } else if (loader instanceof DirectoriesCursorLoader) { directories.clear(); directories.addAll(DirectoriesCursorLoader.toDirectories(cursor)); loadNearbyPlacesCursor(); - loadRemoteContactsCursors(); + loadDirectoryContactsCursors(); } else { throw new IllegalStateException("Invalid loader: " + loader); @@ -246,8 +251,8 @@ public final class NewSearchFragment extends Fragment adapter.setContactsCursor(null); } else if (loader instanceof NearbyPlacesCursorLoader) { adapter.setNearbyPlacesCursor(null); - } else if (loader instanceof RemoteContactsCursorLoader) { - adapter.setRemoteContactsCursor(null); + } else if (loader instanceof DirectoryContactsCursorLoader) { + adapter.setDirectoryContactsCursor(null); } } @@ -264,7 +269,7 @@ public final class NewSearchFragment extends Fragment adapter.setZeroSuggestVisible(isRegularSearch()); loadCp2ContactsCursor(); loadNearbyPlacesCursor(); - loadRemoteContactsCursors(); + loadDirectoryContactsCursors(); } } @@ -306,7 +311,7 @@ public final class NewSearchFragment extends Fragment super.onDestroy(); ThreadUtil.getUiThreadHandler().removeCallbacks(loaderCp2ContactsRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable); - ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(loadDirectoryContactsRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(capabilitiesUpdatedRunnable); } @@ -342,23 +347,27 @@ public final class NewSearchFragment extends Fragment } } - // Loads remote directories. - private void loadRemoteDirectoriesCursor() { - if (!remoteDirectoriesDisabledForTesting) { - getLoaderManager().initLoader(REMOTE_DIRECTORIES_LOADER_ID, null, this); + /** Loads info about all directories (local & remote). */ + private void loadDirectoriesCursor() { + if (!directoriesDisabledForTesting) { + getLoaderManager().initLoader(DIRECTORIES_LOADER_ID, null, this); } } - // Should not be called before remote directories have finished loading. - private void loadRemoteContactsCursors() { - if (remoteDirectoriesDisabledForTesting) { + /** + * Loads contacts stored in directories. + * + *

Should not be called before finishing loading info about all directories (local & remote). + */ + private void loadDirectoryContactsCursors() { + if (directoriesDisabledForTesting) { return; } // Cancel existing load if one exists. - ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(loadDirectoryContactsRunnable); ThreadUtil.getUiThreadHandler() - .postDelayed(loadRemoteContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); + .postDelayed(loadDirectoryContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); } private void loadCp2ContactsCursor() { @@ -368,7 +377,11 @@ public final class NewSearchFragment extends Fragment .postDelayed(loaderCp2ContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); } - // Should not be called before remote directories (not contacts) have finished loading. + /** + * Loads nearby places. + * + *

Should not be called before finishing loading info about all directories (local and remote). + */ private void loadNearbyPlacesCursor() { if (!PermissionsUtil.hasLocationPermissions(getContext()) && !StorageComponent.get(getContext()) @@ -443,8 +456,8 @@ public final class NewSearchFragment extends Fragment // being untestable while it can query multiple datasources. This is a temporary fix. // TODO(a bug): Remove this method and test this fragment with multiple data sources @VisibleForTesting - public void setRemoteDirectoriesDisabled(boolean disabled) { - remoteDirectoriesDisabledForTesting = disabled; + public void setDirectoriesDisabled(boolean disabled) { + directoriesDisabledForTesting = disabled; } /** diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java index 1681097bf..462426943 100644 --- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java +++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java @@ -31,9 +31,9 @@ import com.android.dialer.common.Assert; 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.directories.DirectoryContactViewHolder; import com.android.dialer.searchfragment.list.SearchCursorManager.RowType; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder; -import com.android.dialer.searchfragment.remote.RemoteContactViewHolder; import java.util.List; /** RecyclerView adapter for {@link NewSearchFragment}. */ @@ -77,7 +77,7 @@ public final class SearchAdapter extends RecyclerView.Adapter { return new HeaderViewHolder( LayoutInflater.from(context).inflate(R.layout.header_layout, root, false)); case RowType.DIRECTORY_ROW: - return new RemoteContactViewHolder( + return new DirectoryContactViewHolder( LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false)); case RowType.SEARCH_ACTION: return new SearchActionViewHolder( @@ -104,8 +104,8 @@ public final class SearchAdapter extends RecyclerView.Adapter { ((SearchContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query); } else if (holder instanceof NearbyPlaceViewHolder) { ((NearbyPlaceViewHolder) holder).bind(searchCursorManager.getCursor(position), query); - } else if (holder instanceof RemoteContactViewHolder) { - ((RemoteContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query); + } else if (holder instanceof DirectoryContactViewHolder) { + ((DirectoryContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query); } else if (holder instanceof HeaderViewHolder) { String header = searchCursorManager.getCursor(position).getString(SearchCursor.HEADER_TEXT_POSITION); @@ -200,8 +200,8 @@ public final class SearchAdapter extends RecyclerView.Adapter { } } - public void setRemoteContactsCursor(SearchCursor remoteContactsCursor) { - if (searchCursorManager.setCorpDirectoryCursor(remoteContactsCursor)) { + void setDirectoryContactsCursor(SearchCursor directoryContactsCursor) { + if (searchCursorManager.setCorpDirectoryCursor(directoryContactsCursor)) { notifyDataSetChanged(); } } diff --git a/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml b/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml deleted file mode 100644 index e52f5319e..000000000 --- a/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - \ No newline at end of file diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java deleted file mode 100644 index 4be96fe58..000000000 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java +++ /dev/null @@ -1,145 +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.searchfragment.remote; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Contacts; -import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; -import android.view.View; -import android.widget.ImageView; -import android.widget.QuickContactBadge; -import android.widget.TextView; -import com.android.contacts.common.compat.DirectoryCompat; -import com.android.dialer.callintent.CallInitiationType; -import com.android.dialer.callintent.CallIntentBuilder; -import com.android.dialer.contactphoto.ContactPhotoManager; -import com.android.dialer.lettertile.LetterTileDrawable; -import com.android.dialer.precall.PreCall; -import com.android.dialer.searchfragment.common.Projections; -import com.android.dialer.searchfragment.common.QueryBoldingUtil; -import com.android.dialer.searchfragment.common.R; -import com.android.dialer.searchfragment.common.SearchCursor; - -/** ViewHolder for a nearby place row. */ -public final class RemoteContactViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener { - - private final Context context; - private final TextView nameView; - private final TextView numberView; - private final QuickContactBadge photo; - private final ImageView workBadge; - - private String number; - - public RemoteContactViewHolder(View view) { - super(view); - view.setOnClickListener(this); - photo = view.findViewById(R.id.photo); - nameView = view.findViewById(R.id.primary); - numberView = view.findViewById(R.id.secondary); - workBadge = view.findViewById(R.id.work_icon); - context = view.getContext(); - } - - /** - * Binds the ViewHolder with a cursor from {@link RemoteContactsCursorLoader} with the data found - * at the cursors current position. - */ - public void bind(SearchCursor cursor, String query) { - number = cursor.getString(Projections.PHONE_NUMBER); - String name = cursor.getString(Projections.DISPLAY_NAME); - String label = getLabel(context.getResources(), cursor); - String secondaryInfo = - TextUtils.isEmpty(label) - ? number - : context.getString( - com.android.contacts.common.R.string.call_subject_type_and_number, label, number); - - nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); - numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo, context)); - workBadge.setVisibility( - DirectoryCompat.isOnlyEnterpriseDirectoryId(cursor.getDirectoryId()) - ? View.VISIBLE - : View.GONE); - - if (shouldShowPhoto(cursor)) { - nameView.setVisibility(View.VISIBLE); - photo.setVisibility(View.VISIBLE); - String photoUri = cursor.getString(Projections.PHOTO_URI); - ContactPhotoManager.getInstance(context) - .loadDialerThumbnailOrPhoto( - photo, - getContactUri(cursor), - cursor.getLong(Projections.PHOTO_ID), - photoUri == null ? null : Uri.parse(photoUri), - name, - LetterTileDrawable.TYPE_DEFAULT); - } else { - nameView.setVisibility(View.GONE); - photo.setVisibility(View.INVISIBLE); - } - } - - // Show the contact photo next to only the first number if a contact has multiple numbers - private boolean shouldShowPhoto(SearchCursor cursor) { - int currentPosition = cursor.getPosition(); - String currentLookupKey = cursor.getString(Projections.LOOKUP_KEY); - cursor.moveToPosition(currentPosition - 1); - - if (!cursor.isHeader() && !cursor.isBeforeFirst()) { - String previousLookupKey = cursor.getString(Projections.LOOKUP_KEY); - cursor.moveToPosition(currentPosition); - return !currentLookupKey.equals(previousLookupKey); - } - cursor.moveToPosition(currentPosition); - return true; - } - - // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType - private static String getLabel(Resources resources, Cursor cursor) { - int numberType = cursor.getInt(Projections.PHONE_TYPE); - String numberLabel = cursor.getString(Projections.PHONE_LABEL); - - // Returns empty label instead of "custom" if the custom label is empty. - if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) { - return ""; - } - return (String) Phone.getTypeLabel(resources, numberType, numberLabel); - } - - private static Uri getContactUri(SearchCursor cursor) { - long contactId = cursor.getLong(Projections.ID); - String lookupKey = cursor.getString(Projections.LOOKUP_KEY); - return Contacts.getLookupUri(contactId, lookupKey) - .buildUpon() - .appendQueryParameter( - ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(cursor.getDirectoryId())) - .build(); - } - - @Override - public void onClick(View v) { - PreCall.start(context, new CallIntentBuilder(number, CallInitiationType.Type.REGULAR_SEARCH)); - } -} diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java deleted file mode 100644 index 653c67041..000000000 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java +++ /dev/null @@ -1,154 +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.searchfragment.remote; - -import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MergeCursor; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import com.android.contacts.common.compat.DirectoryCompat; -import com.android.dialer.common.Assert; -import com.android.dialer.searchfragment.common.SearchCursor; -import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader; -import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * {@link MergeCursor} used for combining remote directory cursors into one cursor. - * - *

Usually a device with multiple Google accounts will have multiple remote directories returned - * by {@link DirectoriesCursorLoader}, each represented as a {@link Directory}. - * - *

This cursor merges them together with a header at the start of each cursor/list using {@link - * Directory#getDisplayName()} as the header text. - */ -@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) -public final class RemoteContactsCursor extends MergeCursor implements SearchCursor { - - /** - * {@link SearchCursor#HEADER_PROJECTION} with {@link #COLUMN_DIRECTORY_ID} appended on the end. - * - *

This is needed to get the directoryId associated with each contact. directoryIds are needed - * to load the correct quick contact card. - */ - private static final String[] PROJECTION = buildProjection(); - - private static final String COLUMN_DIRECTORY_ID = "directory_id"; - - /** - * Returns a single cursor with headers inserted between each non-empty cursor. If all cursors are - * empty, null or closed, this method returns null. - */ - @Nullable - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static RemoteContactsCursor newInstance( - Context context, Cursor[] cursors, List directories) { - Assert.checkArgument( - cursors.length == directories.size(), - "Directories (%d) and cursors (%d) must be the same size.", - directories.size(), - cursors.length); - Cursor[] cursorsWithHeaders = insertHeaders(context, cursors, directories); - if (cursorsWithHeaders.length > 0) { - return new RemoteContactsCursor(cursorsWithHeaders); - } - return null; - } - - private RemoteContactsCursor(Cursor[] cursors) { - super(cursors); - } - - private static Cursor[] insertHeaders( - Context context, Cursor[] cursors, List directories) { - List cursorList = new ArrayList<>(); - for (int i = 0; i < cursors.length; i++) { - Cursor cursor = cursors[i]; - - if (cursor == null || cursor.isClosed()) { - continue; - } - - Directory directory = directories.get(i); - if (cursor.getCount() == 0) { - // Since the cursor isn't being merged in, we need to close it here. - cursor.close(); - continue; - } - - cursorList.add(createHeaderCursor(context, directory.getDisplayName(), directory.getId())); - cursorList.add(cursor); - } - return cursorList.toArray(new Cursor[cursorList.size()]); - } - - private static MatrixCursor createHeaderCursor(Context context, String name, long id) { - MatrixCursor headerCursor = new MatrixCursor(PROJECTION, 1); - if (DirectoryCompat.isOnlyEnterpriseDirectoryId(id)) { - headerCursor.addRow( - new Object[] {context.getString(R.string.directory_search_label_work), id}); - } else { - headerCursor.addRow(new Object[] {context.getString(R.string.directory, name), id}); - } - return headerCursor; - } - - private static String[] buildProjection() { - String[] projection = Arrays.copyOf(HEADER_PROJECTION, HEADER_PROJECTION.length + 1); - projection[projection.length - 1] = COLUMN_DIRECTORY_ID; - return projection; - } - - /** Returns true if the current position is a header row. */ - @Override - public boolean isHeader() { - return !isClosed() && getColumnIndex(HEADER_PROJECTION[HEADER_TEXT_POSITION]) != -1; - } - - @Override - public long getDirectoryId() { - int position = getPosition(); - // proceed backwards until we reach the header row, which contains the directory ID. - while (moveToPrevious()) { - int columnIndex = getColumnIndex(COLUMN_DIRECTORY_ID); - if (columnIndex == -1) { - continue; - } - - int id = getInt(columnIndex); - if (id == -1) { - continue; - } - - // return the cursor to it's original position/state - moveToPosition(position); - return id; - } - throw Assert.createIllegalStateFailException("No directory id for contact at: " + position); - } - - @Override - public boolean updateQuery(@Nullable String query) { - // When the query changes, a new network request is made for nearby places. Meaning this cursor - // will be closed and another created, so return false. - return false; - } -} diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java deleted file mode 100644 index cf495e49c..000000000 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java +++ /dev/null @@ -1,162 +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.searchfragment.remote; - -import android.content.Context; -import android.content.CursorLoader; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import com.android.contacts.common.compat.DirectoryCompat; -import com.android.dialer.searchfragment.common.Projections; -import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; -import java.util.ArrayList; -import java.util.List; - -/** - * Cursor loader to load extended contacts on device. - * - *

This loader performs several database queries in serial and merges the resulting cursors - * together into {@link RemoteContactsCursor}. If there are no results, the loader will return a - * null cursor. - */ -public final class RemoteContactsCursorLoader extends CursorLoader { - - private static final Uri ENTERPRISE_CONTENT_FILTER_URI = - Uri.withAppendedPath(Phone.CONTENT_URI, "filter_enterprise"); - - private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE = "length(" + Phone.NUMBER + ") < 1000"; - private static final String PHONE_NUMBER_NOT_NULL = Phone.NUMBER + " IS NOT NULL"; - private static final String MAX_RESULTS = "10"; - - private final String query; - private final List directories; - private final Cursor[] cursors; - - public RemoteContactsCursorLoader(Context context, String query, List directories) { - super( - context, - null, - Projections.DATA_PROJECTION, - IGNORE_NUMBER_TOO_LONG_CLAUSE + " AND " + PHONE_NUMBER_NOT_NULL, - null, - Phone.SORT_KEY_PRIMARY); - this.query = query; - this.directories = new ArrayList<>(directories); - cursors = new Cursor[directories.size()]; - } - - @Override - public Cursor loadInBackground() { - for (int i = 0; i < directories.size(); i++) { - Directory directory = directories.get(i); - - // Filter out local directories - if (!DirectoryCompat.isRemoteDirectoryId(directory.getId()) - && !DirectoryCompat.isEnterpriseDirectoryId(directory.getId())) { - cursors[i] = null; - continue; - } - - // Filter out invisible directories - if (DirectoryCompat.isInvisibleDirectory(directory.getId())) { - cursors[i] = null; - continue; - } - - Cursor cursor = - getContext() - .getContentResolver() - .query( - getContentFilterUri(query, directory.getId()), - getProjection(), - getSelection(), - getSelectionArgs(), - getSortOrder()); - // Even though the cursor specifies "WHERE PHONE_NUMBER IS NOT NULL" the Blackberry Hub app's - // directory extension doesn't appear to respect it, and sometimes returns a null phone - // number. In this case just hide the row entirely. See a bug. - cursors[i] = createMatrixCursorFilteringNullNumbers(cursor); - } - return RemoteContactsCursor.newInstance(getContext(), cursors, directories); - } - - private MatrixCursor createMatrixCursorFilteringNullNumbers(Cursor cursor) { - if (cursor == null) { - return null; - } - MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames()); - try { - if (cursor.moveToFirst()) { - do { - String number = cursor.getString(Projections.PHONE_NUMBER); - if (number == null) { - continue; - } - matrixCursor.addRow(objectArrayFromCursor(cursor)); - } while (cursor.moveToNext()); - } - } finally { - cursor.close(); - } - return matrixCursor; - } - - @NonNull - private static Object[] objectArrayFromCursor(@NonNull Cursor cursor) { - Object[] values = new Object[cursor.getColumnCount()]; - for (int i = 0; i < cursor.getColumnCount(); i++) { - int fieldType = cursor.getType(i); - if (fieldType == Cursor.FIELD_TYPE_BLOB) { - values[i] = cursor.getBlob(i); - } else if (fieldType == Cursor.FIELD_TYPE_FLOAT) { - values[i] = cursor.getDouble(i); - } else if (fieldType == Cursor.FIELD_TYPE_INTEGER) { - values[i] = cursor.getLong(i); - } else if (fieldType == Cursor.FIELD_TYPE_STRING) { - values[i] = cursor.getString(i); - } else if (fieldType == Cursor.FIELD_TYPE_NULL) { - values[i] = null; - } else { - throw new IllegalStateException("Unknown fieldType (" + fieldType + ") for column: " + i); - } - } - return values; - } - - @VisibleForTesting - static Uri getContentFilterUri(String query, long directoryId) { - Uri baseUri = - VERSION.SDK_INT >= VERSION_CODES.N - ? ENTERPRISE_CONTENT_FILTER_URI - : Phone.CONTENT_FILTER_URI; - - return baseUri - .buildUpon() - .appendPath(query) - .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)) - .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true") - .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, MAX_RESULTS) - .build(); - } -} diff --git a/java/com/android/dialer/searchfragment/remote/res/values/strings.xml b/java/com/android/dialer/searchfragment/remote/res/values/strings.xml deleted file mode 100644 index beabba135..000000000 --- a/java/com/android/dialer/searchfragment/remote/res/values/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Directory %1$s - \ No newline at end of file -- cgit v1.2.3 From 078a8340a1e7077c25a78698804cc3ed3d90051f Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 20 Feb 2018 15:13:35 -0800 Subject: Fix an NPE when ContactInfoHelper tries to retrieve remote directories. Bug: 73593111 Test: None PiperOrigin-RevId: 186370203 Change-Id: I7b328dc546c9ae828f70c0467d03243305591ea9 --- java/com/android/dialer/phonenumbercache/ContactInfoHelper.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java index 04226552d..e99533a40 100644 --- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java +++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java @@ -15,7 +15,6 @@ package com.android.dialer.phonenumbercache; import android.annotation.TargetApi; -import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -299,12 +298,12 @@ public class ContactInfoHelper { VERSION.SDK_INT >= VERSION_CODES.N ? Directory.ENTERPRISE_CONTENT_URI : Directory.CONTENT_URI; - ContentResolver cr = context.getContentResolver(); - Cursor cursor = cr.query(uri, new String[] {Directory._ID}, null, null, null); - int idIndex = cursor.getColumnIndex(Directory._ID); + Cursor cursor = + context.getContentResolver().query(uri, new String[] {Directory._ID}, null, null, null); if (cursor == null) { return remoteDirectories; } + int idIndex = cursor.getColumnIndex(Directory._ID); try { while (cursor.moveToNext()) { long directoryId = cursor.getLong(idIndex); -- cgit v1.2.3 From 2d127ba4eb81066ee85688ed3cd1c3736dfbd017 Mon Sep 17 00:00:00 2001 From: twyen Date: Tue, 20 Feb 2018 15:57:36 -0800 Subject: Disable VVM for tracfone USA Bug: 72666573 Test: Updated unit test to match the real case PiperOrigin-RevId: 186376740 Change-Id: If1523963a03f89d6699ef89c8b82c4f22c04d039 --- java/com/android/voicemail/impl/res/xml/vvm_config.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/java/com/android/voicemail/impl/res/xml/vvm_config.xml b/java/com/android/voicemail/impl/res/xml/vvm_config.xml index ed7761abe..4c5efcc1c 100644 --- a/java/com/android/voicemail/impl/res/xml/vvm_config.xml +++ b/java/com/android/voicemail/impl/res/xml/vvm_config.xml @@ -122,6 +122,21 @@ true + + + vvm_carrier_flag_tracfone_usa + + + + + + + + + + vvm_type_disable> + + vvm_carrier_flag_302220 -- cgit v1.2.3 From 6bc46129b93069868d2425fb9d9c50e1dabe6502 Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Tue, 20 Feb 2018 16:14:22 -0800 Subject: Fixed some issues in NUI search bar. - search bar is now the correct height - search bar hint now updates for Google Dialer - search bar now properly slides up when in dialpad search Bug: 72525324 Test: GoogleMainActivityOldPeerIntegrationTest PiperOrigin-RevId: 186379302 Change-Id: If5ad570c89c6a784fa17df422d88e68c30356712 --- .../dialer/main/impl/toolbar/MainToolbar.java | 46 +++++++++++++++++----- .../dialer/main/impl/toolbar/SearchBarView.java | 16 ++++++-- .../toolbar/res/layout/expanded_search_bar.xml | 2 +- .../impl/toolbar/res/layout/toolbar_layout.xml | 9 +++-- .../dialer/main/impl/toolbar/res/values/dimens.xml | 4 +- 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java index 604422978..a129fca8b 100644 --- a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java +++ b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java @@ -16,13 +16,17 @@ package com.android.dialer.main.impl.toolbar; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; +import android.support.annotation.StringRes; import android.support.v7.widget.PopupMenu.OnMenuItemClickListener; import android.support.v7.widget.Toolbar; import android.util.AttributeSet; import android.view.MenuItem; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageButton; +import android.widget.RelativeLayout; import com.android.dialer.common.Assert; import com.android.dialer.util.ViewUtil; import com.google.common.base.Optional; @@ -74,22 +78,40 @@ public final class MainToolbar extends Toolbar implements OnMenuItemClickListene return; } isSlideUp = true; - animate() - .translationY(-getHeight()) - .setDuration(animate ? SLIDE_DURATION : 0) - .setInterpolator(SLIDE_INTERPOLATOR) - .start(); + ValueAnimator animator = ValueAnimator.ofFloat(0, -getHeight()); + animator.setDuration(animate ? SLIDE_DURATION : 0); + animator.setInterpolator(SLIDE_INTERPOLATOR); + animator.addUpdateListener( + new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int val = ((Float) animation.getAnimatedValue()).intValue(); + RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams(); + params.topMargin = val; + requestLayout(); + } + }); + animator.start(); } /** Slides the toolbar down and back onto the screen. */ public void slideDown(boolean animate) { Assert.checkArgument(isSlideUp); isSlideUp = false; - animate() - .translationY(0) - .setDuration(animate ? SLIDE_DURATION : 0) - .setInterpolator(SLIDE_INTERPOLATOR) - .start(); + ValueAnimator animator = ValueAnimator.ofFloat(-getHeight(), 0); + animator.setDuration(animate ? SLIDE_DURATION : 0); + animator.setInterpolator(SLIDE_INTERPOLATOR); + animator.addUpdateListener( + new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int val = ((Float) animation.getAnimatedValue()).intValue(); + RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams(); + params.topMargin = val; + requestLayout(); + } + }); + animator.start(); } /** @see SearchBarView#collapse(boolean) */ @@ -129,4 +151,8 @@ public final class MainToolbar extends Toolbar implements OnMenuItemClickListene public MainToolbarMenu getOverflowMenu() { return overflowMenu; } + + public void setHint(@StringRes int hint) { + searchBar.setHint(hint); + } } diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java index 95929383b..37ffb9778 100644 --- a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java +++ b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -29,6 +30,7 @@ import android.util.AttributeSet; import android.view.View; import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.TextView; import com.android.dialer.animation.AnimUtils; import com.android.dialer.common.UiUtil; import com.android.dialer.util.DialerUtils; @@ -42,14 +44,15 @@ final class SearchBarView extends FrameLayout { private final float margin; private final float animationEndHeight; + private final float animationStartHeight; private SearchBarListener listener; private EditText searchBox; + private TextView searchBoxTextView; // This useful for when the query didn't actually change. We want to avoid making excessive calls // where we can since IPCs can take a long time on slow networks. private boolean skipLatestTextChange; - private int initialHeight; private boolean isExpanded; private View searchBoxCollapsed; private View searchBoxExpanded; @@ -60,6 +63,8 @@ final class SearchBarView extends FrameLayout { margin = getContext().getResources().getDimension(R.dimen.search_bar_margin); animationEndHeight = getContext().getResources().getDimension(R.dimen.expanded_search_bar_height); + animationStartHeight = + getContext().getResources().getDimension(R.dimen.collapsed_search_bar_height); } @Override @@ -67,6 +72,7 @@ final class SearchBarView extends FrameLayout { super.onFinishInflate(); clearButton = findViewById(R.id.search_clear_button); searchBox = findViewById(R.id.search_view); + searchBoxTextView = findViewById(R.id.search_box_start_search); searchBoxCollapsed = findViewById(R.id.search_box_collapsed); searchBoxExpanded = findViewById(R.id.search_box_expanded); @@ -104,7 +110,6 @@ final class SearchBarView extends FrameLayout { if (isExpanded) { return; } - initialHeight = getHeight(); int duration = animate ? ANIMATION_DURATION : 0; searchBoxExpanded.setVisibility(VISIBLE); @@ -177,7 +182,7 @@ final class SearchBarView extends FrameLayout { params.leftMargin = margin; params.rightMargin = margin; searchBoxExpanded.getLayoutParams().height = - (int) (animationEndHeight - (animationEndHeight - initialHeight) * fraction); + (int) (animationEndHeight - (animationEndHeight - animationStartHeight) * fraction); requestLayout(); } @@ -207,6 +212,11 @@ final class SearchBarView extends FrameLayout { UiUtil.openKeyboardFrom(getContext(), searchBox); } + public void setHint(@StringRes int hint) { + searchBox.setHint(hint); + searchBoxTextView.setText(hint); + } + /** Handles logic for text changes in the search box. */ private class SearchBoxTextWatcher implements TextWatcher { diff --git a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml index 4e49accae..3bd71b63a 100644 --- a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml +++ b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml @@ -17,7 +17,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/search_box_expanded" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/expanded_search_bar_height" android:visibility="invisible"> @@ -27,6 +27,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/search_bar_margin" + android:minHeight="@dimen/collapsed_search_bar_height" android:background="@drawable/rounded_corner" android:elevation="4dp"> @@ -58,8 +59,8 @@ android:layout_marginStart="8dp" android:layout_centerVertical="true" android:fontFamily="sans-serif" - android:hint="@string/dialer_hint_find_contact" - android:textColorHint="@color/dialer_secondary_text_color" + android:text="@string/dialer_hint_find_contact" + android:textColor="@color/dialer_secondary_text_color" android:textSize="16dp"/> - \ No newline at end of file + diff --git a/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml index f54f053da..ed6f197fa 100644 --- a/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml +++ b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml @@ -16,5 +16,7 @@ --> 8dp - 60dp + 48dp + + 64dp \ No newline at end of file -- cgit v1.2.3 From f5326db368ba35faf861f690b09d0ea9c8d5080f Mon Sep 17 00:00:00 2001 From: roldenburg Date: Tue, 20 Feb 2018 17:14:12 -0800 Subject: Add Tracfone to Motorola menu, move existing menu to Sprint only Bug: 71707082 Test: MotorolaHiddenMenuKeySequenceTest PiperOrigin-RevId: 186387666 Change-Id: I3971604d717dcea8bfd1159b281a2dc5a0f3b0f7 --- .../dialer/oem/MotorolaHiddenMenuKeySequence.java | 111 +++++++++++++-------- java/com/android/dialer/oem/MotorolaUtils.java | 3 +- .../res/values-mcc310-mnc120/motorola_config.xml | 56 +++++++++++ .../dialer/oem/res/values/motorola_config.xml | 45 ++------- 4 files changed, 132 insertions(+), 83 deletions(-) diff --git a/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java b/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java index 79abff08e..81f6b607c 100644 --- a/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java +++ b/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java @@ -22,7 +22,11 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.support.annotation.VisibleForTesting; import com.android.dialer.common.LogUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.regex.Pattern; /** @@ -30,13 +34,14 @@ import java.util.regex.Pattern; */ public class MotorolaHiddenMenuKeySequence { private static final String EXTRA_HIDDEN_MENU_CODE = "HiddenMenuCode"; + private static MotorolaHiddenMenuKeySequence instance = null; - private static String[] hiddenKeySequenceArray = null; - private static String[] hiddenKeySequenceIntentArray = null; - private static String[] hiddenKeyPatternArray = null; - private static String[] hiddenKeyPatternIntentArray = null; - private static boolean featureHiddenMenuEnabled = false; + @VisibleForTesting final List hiddenKeySequences = new ArrayList<>(); + @VisibleForTesting final List hiddenKeySequenceIntents = new ArrayList<>(); + @VisibleForTesting final List hiddenKeyPatterns = new ArrayList<>(); + @VisibleForTesting final List hiddenKeyPatternIntents = new ArrayList<>(); + @VisibleForTesting boolean featureHiddenMenuEnabled = false; /** * Handle input char sequence. @@ -46,8 +51,7 @@ public class MotorolaHiddenMenuKeySequence { * @return true if the input matches any pattern */ static boolean handleCharSequence(Context context, String input) { - getInstance(context); - if (!featureHiddenMenuEnabled) { + if (!getInstance(context).featureHiddenMenuEnabled) { return false; } return handleKeySequence(context, input) || handleKeyPattern(context, input); @@ -66,60 +70,81 @@ public class MotorolaHiddenMenuKeySequence { return instance; } - private MotorolaHiddenMenuKeySequence(Context context) { - featureHiddenMenuEnabled = MotorolaUtils.isSupportingHiddenMenu(context); - // In case we do have a SPN from resource we need to match from service; otherwise we are - // free to go - if (featureHiddenMenuEnabled) { - - hiddenKeySequenceArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence); - hiddenKeySequenceIntentArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence_intents); - hiddenKeyPatternArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern); - hiddenKeyPatternIntentArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern_intents); - - if (hiddenKeySequenceArray.length != hiddenKeySequenceIntentArray.length - || hiddenKeyPatternArray.length != hiddenKeyPatternIntentArray.length - || (hiddenKeySequenceArray.length == 0 && hiddenKeyPatternArray.length == 0)) { - LogUtil.e( - "MotorolaHiddenMenuKeySequence", - "the key sequence array is not matching, turn off feature." - + "key sequence: %d != %d, key pattern %d != %d", - hiddenKeySequenceArray.length, - hiddenKeySequenceIntentArray.length, - hiddenKeyPatternArray.length, - hiddenKeyPatternIntentArray.length); - featureHiddenMenuEnabled = false; - } + @VisibleForTesting + MotorolaHiddenMenuKeySequence(Context context) { + if (MotorolaUtils.isSupportingHiddenMenu(context)) { + Collections.addAll( + hiddenKeySequences, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence)); + Collections.addAll( + hiddenKeySequenceIntents, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence_intents)); + Collections.addAll( + hiddenKeyPatterns, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern)); + Collections.addAll( + hiddenKeyPatternIntents, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern_intents)); + featureHiddenMenuEnabled = true; + } + + if ("tracfone".equals(System.getProperty("ro.carrier"))) { + addHiddenKeySequence("#83865625#", "com.motorola.extensions.TFUnlock"); + addHiddenKeySequence("#83782887#", "com.motorola.extensions.TFStatus"); + featureHiddenMenuEnabled = true; } + + if (hiddenKeySequences.size() != hiddenKeySequenceIntents.size() + || hiddenKeyPatterns.size() != hiddenKeyPatternIntents.size() + || (hiddenKeySequences.isEmpty() && hiddenKeyPatterns.isEmpty())) { + LogUtil.e( + "MotorolaHiddenMenuKeySequence", + "the key sequence array is not matching, turn off feature." + + "key sequence: %d != %d, key pattern %d != %d", + hiddenKeySequences.size(), + hiddenKeySequenceIntents.size(), + hiddenKeyPatterns.size(), + hiddenKeyPatternIntents.size()); + featureHiddenMenuEnabled = false; + } + } + + private void addHiddenKeySequence(String keySequence, String intentAction) { + hiddenKeySequences.add(keySequence); + hiddenKeySequenceIntents.add(intentAction); } private static boolean handleKeyPattern(Context context, String input) { + MotorolaHiddenMenuKeySequence instance = getInstance(context); + int len = input.length(); - if (len <= 3 || hiddenKeyPatternArray == null || hiddenKeyPatternIntentArray == null) { + if (len <= 3 + || instance.hiddenKeyPatterns == null + || instance.hiddenKeyPatternIntents == null) { return false; } - for (int i = 0; i < hiddenKeyPatternArray.length; i++) { - if ((Pattern.compile(hiddenKeyPatternArray[i])).matcher(input).matches()) { - return sendIntent(context, input, hiddenKeyPatternIntentArray[i]); + for (int i = 0; i < instance.hiddenKeyPatterns.size(); i++) { + if (Pattern.matches(instance.hiddenKeyPatterns.get(i), input)) { + return sendIntent(context, input, instance.hiddenKeyPatternIntents.get(i)); } } return false; } private static boolean handleKeySequence(Context context, String input) { + MotorolaHiddenMenuKeySequence instance = getInstance(context); + int len = input.length(); - if (len <= 3 || hiddenKeySequenceArray == null || hiddenKeySequenceIntentArray == null) { + if (len <= 3 + || instance.hiddenKeySequences == null + || instance.hiddenKeySequenceIntents == null) { return false; } - for (int i = 0; i < hiddenKeySequenceArray.length; i++) { - if (hiddenKeySequenceArray[i].equals(input)) { - return sendIntent(context, input, hiddenKeySequenceIntentArray[i]); + for (int i = 0; i < instance.hiddenKeySequences.size(); i++) { + if (instance.hiddenKeySequences.get(i).equals(input)) { + return sendIntent(context, input, instance.hiddenKeySequenceIntents.get(i)); } } return false; diff --git a/java/com/android/dialer/oem/MotorolaUtils.java b/java/com/android/dialer/oem/MotorolaUtils.java index c1e2da256..1446a0219 100644 --- a/java/com/android/dialer/oem/MotorolaUtils.java +++ b/java/com/android/dialer/oem/MotorolaUtils.java @@ -43,7 +43,8 @@ public class MotorolaUtils { // package is enabled. @VisibleForTesting public static final String WIFI_CALL_PACKAGE_NAME = "com.motorola.sprintwfc"; // Thi is used to check if a Motorola device supports hidden menu feature. - private static final String HIDDEN_MENU_FEATURE = "com.motorola.software.sprint.hidden_menu"; + @VisibleForTesting + static final String HIDDEN_MENU_FEATURE = "com.motorola.software.sprint.hidden_menu"; private static boolean hasCheckedSprintWifiCall; private static boolean supportSprintWifiCall; diff --git a/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml b/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml index c5cb0d1f7..417a4b845 100644 --- a/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml +++ b/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml @@ -17,4 +17,60 @@ true + + + + + ##66236# + ##2539# + ##786# + ##72786# + ##3282# + ##33284# + ##3424# + ##564# + ##4567257# + ##873283# + ##6343# + ##27263# + ##258# + ##8422# + ##4382# + + + com.motorola.intent.action.LAUNCH_HIDDEN_MENU + + + + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + com.motorola.android.intent.action.omadm.sprint.hfa + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + + + + + + ##[0-9]{6}# + + + + + @string/motorola_hidden_menu_intent + \ No newline at end of file diff --git a/java/com/android/dialer/oem/res/values/motorola_config.xml b/java/com/android/dialer/oem/res/values/motorola_config.xml index ba451e715..fd9cee0a9 100644 --- a/java/com/android/dialer/oem/res/values/motorola_config.xml +++ b/java/com/android/dialer/oem/res/values/motorola_config.xml @@ -20,59 +20,26 @@ false - - ##66236# - ##2539# - ##786# - ##72786# - ##3282# - ##33284# - ##3424# - ##564# - ##4567257# - ##873283# - ##6343# - ##27263# - ##258# - ##8422# - ##4382# - com.motorola.intent.action.LAUNCH_HIDDEN_MENU + - - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - com.motorola.android.intent.action.omadm.sprint.hfa - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - @string/motorola_hidden_menu_intent - - - ##[0-9]{6}# - - @string/motorola_hidden_menu_intent - Let Google review your voicemail messages to improve transcription quality. Your - voicemail messages will not be tied to your Google Account. + Let Google review your voicemail messages to improve transcription quality. + For voicemail transcription analysis, your voicemail messages are stored anonymously. diff --git a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java index 5ae26f5f7..7f5bb796a 100644 --- a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java +++ b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java @@ -32,12 +32,14 @@ import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; +import android.text.Html; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.notification.NotificationChannelManager; import com.android.dialer.telecom.TelecomUtil; +import com.android.dialer.widget.TextViewPreference; import com.android.voicemail.VoicemailClient; import com.android.voicemail.VoicemailClient.ActivationStateListener; import com.android.voicemail.VoicemailComponent; @@ -73,6 +75,7 @@ public class VoicemailSettingsFragment extends PreferenceFragment private SwitchPreference donateVoicemailSwitchPreference; private Preference voicemailChangePinPreference; private PreferenceScreen advancedSettings; + private TextViewPreference voicemailTranscriptionInstructionText; @Override public void onCreate(Bundle icicle) { @@ -209,6 +212,10 @@ public class VoicemailSettingsFragment extends PreferenceFragment return false; } }); + + voicemailTranscriptionInstructionText = + (TextViewPreference) findPreference(getString(R.string.voicemail_transcription_text_key)); + voicemailTranscriptionInstructionText.setTitle(getVoicemailTranscriptionInstructionsText()); } @Override @@ -348,4 +355,20 @@ public class VoicemailSettingsFragment extends PreferenceFragment builder.setCancelable(true); builder.show(); } + + /** + * Builds a spannable string containing the voicemail transcription instructions text containing + * the appropriate "Learn More" urls. + * + * @return The voicemail transcription instructions text. + */ + private CharSequence getVoicemailTranscriptionInstructionsText() { + String settingText = + getString( + R.string.voicemail_transcription_instruction_text, + getString(R.string.transcription_learn_more_url), + getString(R.string.donation_learn_more_url)); + CharSequence settingSeq = Html.fromHtml(settingText); + return settingSeq; + } } diff --git a/java/com/android/dialer/voicemail/settings/res/values/strings.xml b/java/com/android/dialer/voicemail/settings/res/values/strings.xml index 10fa459ff..47228b70b 100644 --- a/java/com/android/dialer/voicemail/settings/res/values/strings.xml +++ b/java/com/android/dialer/voicemail/settings/res/values/strings.xml @@ -44,7 +44,7 @@ voicemail_change_pin_key - Visual Voicemail + Visual voicemail Extra backup and storage @@ -125,4 +125,18 @@ TURN OFF + + voicemail_transcription_text_key + + + + Visual voicemail allows you to check voicemail messages without having to call voicemail. Transcripts provided by Google. <a href="%1$s">Learn more</a> + <br><br> + For voicemail transcription analysis, your voicemail messages are stored anonymously. <a href="%2$s">Learn more</a> + + + https://support.google.com/phoneapp/answer/2811844?hl=en%26ref_topic=7539039 + https://support.google.com/phoneapp/answer/2811844#voicemail_transcript + diff --git a/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml b/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml index 9b0391ad4..75c8cfe2b 100644 --- a/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml +++ b/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml @@ -43,4 +43,8 @@ android:key="@string/voicemail_advanced_settings_key" android:title="@string/voicemail_advanced_settings_title"> + + + diff --git a/java/com/android/dialer/widget/TextViewPreference.java b/java/com/android/dialer/widget/TextViewPreference.java new file mode 100644 index 000000000..2c1885c4d --- /dev/null +++ b/java/com/android/dialer/widget/TextViewPreference.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018 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.widget; + +import android.content.Context; +import android.preference.Preference; +import android.text.method.LinkMovementMethod; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +/** + * Provides a {@link TextView} inside a preference. Useful for displaying static text which may + * contain hyperlinks. + */ +public class TextViewPreference extends Preference { + + /** + * The resource ID of the text to be populated in the {@link TextView} when a resource ID is used. + */ + private int textResourceId = 0; + + /** The text to be populated in the {@link TextView} when a {@link CharSequence} is used. */ + private CharSequence text; + + /** The {@link TextView} containing the text. */ + private TextView textView; + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the preference. + * @param defStyleAttr An attribute in the current theme that contains a reference to a style + * resource that supplies default values for the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that supplies default values for + * the view, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not + * look for defaults. + */ + public TextViewPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + setLayoutResource(R.layout.text_view_preference); + } + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the preference. + * @param defStyleAttr An attribute in the current theme that contains a reference to a style + * resource that supplies default values for the view. Can be 0 to not look for defaults. + */ + public TextViewPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the preference. + */ + public TextViewPreference(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.preferenceStyle, 0); + } + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + */ + public TextViewPreference(Context context) { + super(context, null); + + setLayoutResource(R.layout.text_view_preference); + } + + /** + * Handles binding the preference. + * + * @param view The view. + */ + @Override + protected void onBindView(View view) { + super.onBindView(view); + textView = (TextView) view.findViewById(R.id.text); + if (textResourceId != 0) { + setTitle(textResourceId); + } else if (text != null) { + setTitle(text); + } + } + + /** + * Sets the preference title from a {@link CharSequence}. + * + * @param text The text. + */ + @Override + public void setTitle(CharSequence text) { + textResourceId = 0; + this.text = text; + if (textView == null) { + return; + } + + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setText(text); + } + + /** + * Sets the preference title from a resource id. + * + * @param textResId The string resource Id. + */ + @Override + public void setTitle(int textResId) { + textResourceId = textResId; + setTitle(getContext().getString(textResId)); + } +} diff --git a/java/com/android/dialer/widget/res/layout/text_view_preference.xml b/java/com/android/dialer/widget/res/layout/text_view_preference.xml new file mode 100644 index 000000000..39b550657 --- /dev/null +++ b/java/com/android/dialer/widget/res/layout/text_view_preference.xml @@ -0,0 +1,24 @@ + + + -- cgit v1.2.3 From 8d26b821c9754a9a81915ccb1b431efbb8550670 Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Tue, 20 Feb 2018 17:44:07 -0800 Subject: Fixed some bugs in NUI search. - Nearby places promo now displays properly - Dialpad now properly handles dial intents - placing calls from search now closes search - placing calls from search now closes the keyboard - toolbar shadow is now visible when search is started from the dialpad. Bug: 72525324 Test: MainActivityOldPeerSearchIntegrationTest PiperOrigin-RevId: 186390952 Change-Id: I7e10ef0499c787da2d1820cae915f2d42645303b --- .../contacts/common/model/ContactLoader.java | 2 +- java/com/android/dialer/main/MainActivityPeer.java | 2 + .../com/android/dialer/main/impl/MainActivity.java | 7 ++++ .../dialer/main/impl/MainSearchController.java | 49 ++++++++++++++++++---- .../dialer/main/impl/NewMainActivityPeer.java | 3 ++ .../dialer/main/impl/OldMainActivityPeer.java | 34 +++++++++------ .../main/impl/toolbar/SearchBarListener.java | 7 ++++ 7 files changed, 81 insertions(+), 23 deletions(-) diff --git a/java/com/android/contacts/common/model/ContactLoader.java b/java/com/android/contacts/common/model/ContactLoader.java index d2c757709..51b8e3efc 100644 --- a/java/com/android/contacts/common/model/ContactLoader.java +++ b/java/com/android/contacts/common/model/ContactLoader.java @@ -218,7 +218,7 @@ public class ContactLoader extends AsyncTaskLoader { @Override public Contact loadInBackground() { - LogUtil.e(TAG, "loadInBackground=" + mLookupUri); + LogUtil.v(TAG, "loadInBackground=" + mLookupUri); try { final ContentResolver resolver = getContext().getContentResolver(); final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(resolver, mLookupUri); diff --git a/java/com/android/dialer/main/MainActivityPeer.java b/java/com/android/dialer/main/MainActivityPeer.java index c1a328a65..9c5627be8 100644 --- a/java/com/android/dialer/main/MainActivityPeer.java +++ b/java/com/android/dialer/main/MainActivityPeer.java @@ -26,6 +26,8 @@ public interface MainActivityPeer { void onActivityResume(); + void onUserLeaveHint(); + void onActivityStop(); void onActivityDestroyed(); diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java index ac2cb389e..1646becf4 100644 --- a/java/com/android/dialer/main/impl/MainActivity.java +++ b/java/com/android/dialer/main/impl/MainActivity.java @@ -82,6 +82,7 @@ public class MainActivity extends TransactionSafeActivity @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); + setIntent(intent); activePeer.onNewIntent(intent); } @@ -91,6 +92,12 @@ public class MainActivity extends TransactionSafeActivity activePeer.onActivityResume(); } + @Override + protected void onUserLeaveHint() { + super.onUserLeaveHint(); + activePeer.onUserLeaveHint(); + } + @Override protected void onStop() { super.onStop(); diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java index 8d9e784a7..ccd7a4b49 100644 --- a/java/com/android/dialer/main/impl/MainSearchController.java +++ b/java/com/android/dialer/main/impl/MainSearchController.java @@ -95,12 +95,14 @@ public class MainSearchController implements SearchBarListener { } /** Should be called if we're showing the dialpad because of a new ACTION_DIAL intent. */ - public void showDialpadFromNewIntent(boolean animate) { - showDialpad(animate, true); + public void showDialpadFromNewIntent() { + LogUtil.enterBlock("MainSearchController.showDialpadFromNewIntent"); + showDialpad(/* animate=*/ false, /* fromNewIntent=*/ true); } /** Shows the dialpad, hides the FAB and slides the toolbar off screen. */ public void showDialpad(boolean animate) { + LogUtil.enterBlock("MainSearchController.showDialpad"); showDialpad(animate, false); } @@ -110,17 +112,22 @@ public class MainSearchController implements SearchBarListener { fab.hide(); toolbar.slideUp(animate); toolbar.expand(animate, Optional.absent()); + toolbarShadow.setVisibility(View.VISIBLE); mainActivity.setTitle(R.string.dialpad_activity_title); FragmentTransaction transaction = mainActivity.getFragmentManager().beginTransaction(); + NewSearchFragment searchFragment = getSearchFragment(); // Show Search - if (getSearchFragment() == null) { - NewSearchFragment searchFragment = NewSearchFragment.newInstance(false); + if (searchFragment == null) { + // TODO(a bug): zero suggest results aren't actually shown but this enabled the nearby + // places promo to be shown. + searchFragment = NewSearchFragment.newInstance(/* showZeroSuggest=*/ true); transaction.add(R.id.fragment_container, searchFragment, SEARCH_FRAGMENT_TAG); } else if (!isSearchVisible()) { - transaction.show(getSearchFragment()); + transaction.show(searchFragment); } + searchFragment.setQuery("", CallInitiationType.Type.DIALPAD); // Show Dialpad if (getDialpadFragment() == null) { @@ -145,6 +152,7 @@ public class MainSearchController implements SearchBarListener { * @see {@link #closeSearch(boolean)} to "remove" the dialpad. */ private void hideDialpad(boolean animate, boolean bottomNavVisible) { + LogUtil.enterBlock("MainSearchController.hideDialpad"); Assert.checkArgument(isDialpadVisible()); fab.show(); @@ -190,6 +198,7 @@ public class MainSearchController implements SearchBarListener { /** Should be called when {@link DialpadListener#onDialpadShown()} is called. */ public void onDialpadShown() { + LogUtil.enterBlock("MainSearchController.onDialpadShown"); getDialpadFragment().slideUp(true); hideBottomNav(); } @@ -205,6 +214,7 @@ public class MainSearchController implements SearchBarListener { * */ public void onSearchListTouch() { + LogUtil.enterBlock("MainSearchController.onSearchListTouched"); if (isDialpadVisible()) { if (TextUtils.isEmpty(getDialpadFragment().getQuery())) { Logger.get(mainActivity) @@ -236,13 +246,13 @@ public class MainSearchController implements SearchBarListener { */ public boolean onBackPressed() { if (isDialpadVisible() && !TextUtils.isEmpty(getDialpadFragment().getQuery())) { - LogUtil.i("MainSearchController#onBackPressed", "Dialpad visible with query"); + LogUtil.i("MainSearchController.onBackPressed", "Dialpad visible with query"); Logger.get(mainActivity) .logImpression(DialerImpression.Type.NUI_PRESS_BACK_BUTTON_TO_HIDE_DIALPAD); hideDialpad(/* animate=*/ true, /* bottomNavVisible=*/ false); return true; } else if (isSearchVisible()) { - LogUtil.i("MainSearchController#onBackPressed", "Search is visible"); + LogUtil.i("MainSearchController.onBackPressed", "Search is visible"); Logger.get(mainActivity) .logImpression( isDialpadVisible() @@ -260,6 +270,7 @@ public class MainSearchController implements SearchBarListener { * dialpad. */ private void closeSearch(boolean animate) { + LogUtil.enterBlock("MainSearchController.closeSearch"); Assert.checkArgument(isSearchVisible()); if (isDialpadVisible()) { hideDialpad(animate, /* bottomNavVisible=*/ true); @@ -314,11 +325,13 @@ public class MainSearchController implements SearchBarListener { */ @Override public void onSearchBarClicked() { + LogUtil.enterBlock("MainSearchController.onSearchBarClicked"); Logger.get(mainActivity).logImpression(DialerImpression.Type.NUI_CLICK_SEARCH_BAR); openSearch(Optional.absent()); } private void openSearch(Optional query) { + LogUtil.enterBlock("MainSearchController.openSearch"); fab.hide(); toolbar.expand(/* animate=*/ true, query); toolbar.showKeyboard(); @@ -326,20 +339,26 @@ public class MainSearchController implements SearchBarListener { hideBottomNav(); FragmentTransaction transaction = mainActivity.getFragmentManager().beginTransaction(); + NewSearchFragment searchFragment = getSearchFragment(); // Show Search - if (getSearchFragment() == null) { - NewSearchFragment searchFragment = NewSearchFragment.newInstance(false); + if (searchFragment == null) { + // TODO(a bug): zero suggest results aren't actually shown but this enabled the nearby + // places promo to be shown. + searchFragment = NewSearchFragment.newInstance(true); transaction.add(R.id.fragment_container, searchFragment, SEARCH_FRAGMENT_TAG); } else if (!isSearchVisible()) { transaction.show(getSearchFragment()); } + searchFragment.setQuery( + query.isPresent() ? query.get() : "", CallInitiationType.Type.REGULAR_SEARCH); transaction.commit(); } @Override public void onSearchBackButtonClicked() { + LogUtil.enterBlock("MainSearchController.onSearchBackButtonClicked"); closeSearch(true); } @@ -384,6 +403,18 @@ public class MainSearchController implements SearchBarListener { return false; } + @Override + public void onUserLeaveHint() { + if (isInSearch()) { + closeSearch(false); + } + } + + @Override + public void onCallPlacedFromSearch() { + closeSearch(false); + } + public void onVoiceResults(int resultCode, Intent data) { if (resultCode == AppCompatActivity.RESULT_OK) { ArrayList matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); diff --git a/java/com/android/dialer/main/impl/NewMainActivityPeer.java b/java/com/android/dialer/main/impl/NewMainActivityPeer.java index ed67df936..6f5c18623 100644 --- a/java/com/android/dialer/main/impl/NewMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/NewMainActivityPeer.java @@ -56,6 +56,9 @@ public class NewMainActivityPeer implements MainActivityPeer { @Override public void onActivityResume() {} + @Override + public void onUserLeaveHint() {} + @Override public void onActivityStop() {} diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 7d9216c3f..69d8032f2 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -226,34 +226,34 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen searchController.onRestoreInstanceState(savedInstanceState); bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); } else { - showTabOnIntent(mainActivity.getIntent()); + onHandleIntent(mainActivity.getIntent()); } } @Override public void onNewIntent(Intent intent) { LogUtil.enterBlock("OldMainActivityPeer.onNewIntent"); - showTabOnIntent(intent); + onHandleIntent(intent); } - private void showTabOnIntent(Intent intent) { + private void onHandleIntent(Intent intent) { + // Two important implementation notes: + // 1) If the intent contains extra data to open to a specific screen (e.g. DIAL intent), when + // the user leaves that screen, they will return here and add see a blank screen unless we + // select a tab here. + // 2) Don't return early here in case the intent does contain extra data. if (isShowTabIntent(intent)) { bottomNav.selectTab(getTabFromIntent(intent)); - return; + } else if (lastTabController.isEnabled) { + lastTabController.selectLastTab(); + } else { + bottomNav.selectTab(TabIndex.SPEED_DIAL); } if (isDialIntent(intent)) { - searchController.showDialpadFromNewIntent(false); // Dialpad will grab the intent and populate the number - return; - } - - if (lastTabController.isEnabled) { - lastTabController.selectLastTab(); - return; + searchController.showDialpadFromNewIntent(); } - - bottomNav.selectTab(TabIndex.SPEED_DIAL); } /** Returns true if the given intent contains a phone number to populate the dialer with */ @@ -295,6 +295,11 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } } + @Override + public void onUserLeaveHint() { + searchController.onUserLeaveHint(); + } + @Override public void onActivityStop() { lastTabController.onActivityStop(); @@ -344,6 +349,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @Override public boolean onBackPressed() { + LogUtil.enterBlock("OldMainActivityPeer.onBackPressed"); if (searchController.onBackPressed()) { return true; } @@ -460,6 +466,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @Override public void onCallPlacedFromDialpad() { // TODO(calderwoodra): logging + searchController.onCallPlacedFromSearch(); } } @@ -480,6 +487,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @Override public void onCallPlacedFromSearch() { // TODO(calderwoodra): logging + searchController.onCallPlacedFromSearch(); } } diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java index a074b5131..857c4b9c9 100644 --- a/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java +++ b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java @@ -16,6 +16,7 @@ package com.android.dialer.main.impl.toolbar; +import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; /** Useful callback for {@link SearchBarView} listeners. */ @@ -36,6 +37,12 @@ public interface SearchBarListener { /** Called when a toolbar menu item is clicked. */ boolean onMenuItemClicked(MenuItem menuItem); + /** Called when {@link AppCompatActivity#onUserLeaveHint()} is called. */ + void onUserLeaveHint(); + + /** Called when the user places a call from search (regular or dialpad). */ + void onCallPlacedFromSearch(); + /** Interface for returning voice results to the search bar. */ interface VoiceSearchResultCallback { -- cgit v1.2.3 From 2905f6f492c34db684d53bac6e438c1d81fc0314 Mon Sep 17 00:00:00 2001 From: zachh Date: Tue, 20 Feb 2018 21:50:53 -0800 Subject: Record jank metrics in old and new call logs. Bug: 70989667 Test: unit PiperOrigin-RevId: 186410938 Change-Id: I0671ab0bbbe957b8f034c673e6309204284756d2 --- .../dialer/app/calllog/CallLogFragment.java | 7 ++++ java/com/android/dialer/buildtype/BuildType.java | 20 ++++++---- .../buildtype/release/BuildTypeAccessorImpl.java | 3 +- .../dialer/calllog/ui/NewCallLogFragment.java | 7 ++++ .../dialer/commandline/CommandLineReceiver.java | 3 +- .../android/dialer/logging/LoggingBindings.java | 3 ++ java/com/android/dialer/metrics/Metrics.java | 6 +++ java/com/android/dialer/metrics/StubMetrics.java | 6 +++ .../metrics/jank/RecyclerViewJankLogger.java | 46 ++++++++++++++++++++++ .../dialer/simulator/impl/SimulatorImpl.java | 3 +- .../android/dialer/strictmode/StrictModeUtils.java | 3 +- 11 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 java/com/android/dialer/metrics/jank/RecyclerViewJankLogger.java diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java index 6b6b92297..6b6239da4 100644 --- a/java/com/android/dialer/app/calllog/CallLogFragment.java +++ b/java/com/android/dialer/app/calllog/CallLogFragment.java @@ -63,6 +63,9 @@ import com.android.dialer.database.CallLogQueryHandler.Listener; import com.android.dialer.location.GeoUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; +import com.android.dialer.logging.LoggingBindings; +import com.android.dialer.metrics.MetricsComponent; +import com.android.dialer.metrics.jank.RecyclerViewJankLogger; import com.android.dialer.oem.CequintCallerIdManager; import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.phonenumbercache.ContactInfoHelper; @@ -306,6 +309,10 @@ public class CallLogFragment extends Fragment protected void setupView(View view) { recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); recyclerView.setHasFixedSize(true); + recyclerView.addOnScrollListener( + new RecyclerViewJankLogger( + MetricsComponent.get(getContext()).metrics(), + LoggingBindings.OLD_CALL_LOG_JANK_EVENT_NAME)); layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); PerformanceReport.logOnScrollStateChange(recyclerView); diff --git a/java/com/android/dialer/buildtype/BuildType.java b/java/com/android/dialer/buildtype/BuildType.java index 6b6bc2906..c5c41d247 100644 --- a/java/com/android/dialer/buildtype/BuildType.java +++ b/java/com/android/dialer/buildtype/BuildType.java @@ -28,15 +28,19 @@ public class BuildType { /** The type of build. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ - BUGFOOD, FISHFOOD, DOGFOOD, RELEASE, TEST, + Type.BUGFOOD, + Type.FISHFOOD, + Type.DOGFOOD, + Type.RELEASE, + Type.TEST, }) - public @interface Type {} - - public static final int BUGFOOD = 1; - public static final int FISHFOOD = 2; - public static final int DOGFOOD = 3; - public static final int RELEASE = 4; - public static final int TEST = 5; + public @interface Type { + int BUGFOOD = 1; + int FISHFOOD = 2; + int DOGFOOD = 3; + int RELEASE = 4; + int TEST = 5; + } private static int cachedBuildType; private static boolean didInitializeBuildType; diff --git a/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java b/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java index 70b9f9e37..4019dd011 100644 --- a/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java +++ b/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java @@ -16,6 +16,7 @@ package com.android.dialer.buildtype; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.proguard.UsedByReflection; /** Gets the build type. */ @@ -25,6 +26,6 @@ public class BuildTypeAccessorImpl implements BuildTypeAccessor { @Override @BuildType.Type public int getBuildType() { - return BuildType.RELEASE; + return Type.RELEASE; } } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index d0f42d335..bbd16fbf1 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -33,6 +33,9 @@ import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.logging.LoggingBindings; +import com.android.dialer.metrics.MetricsComponent; +import com.android.dialer.metrics.jank.RecyclerViewJankLogger; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.TimeUnit; @@ -170,6 +173,10 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback View view = inflater.inflate(R.layout.new_call_log_fragment, container, false); recyclerView = view.findViewById(R.id.new_call_log_recycler_view); + recyclerView.addOnScrollListener( + new RecyclerViewJankLogger( + MetricsComponent.get(getContext()).metrics(), + LoggingBindings.NEW_CALL_LOG_JANK_EVENT_NAME)); getLoaderManager().restartLoader(0, null, this); diff --git a/java/com/android/dialer/commandline/CommandLineReceiver.java b/java/com/android/dialer/commandline/CommandLineReceiver.java index e5e78c46a..effca2e92 100644 --- a/java/com/android/dialer/commandline/CommandLineReceiver.java +++ b/java/com/android/dialer/commandline/CommandLineReceiver.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.text.TextUtils; import com.android.dialer.buildtype.BuildType; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.commandline.Command.IllegalCommandLineArgumentException; import com.android.dialer.common.LogUtil; import com.google.common.util.concurrent.FutureCallback; @@ -44,7 +45,7 @@ public class CommandLineReceiver extends BroadcastReceiver { LogUtil.e("CommandLineReceiver", "missing tag"); return; } - if (!LogUtil.isDebugEnabled() && BuildType.get() != BuildType.BUGFOOD) { + if (!LogUtil.isDebugEnabled() && BuildType.get() != Type.BUGFOOD) { LogUtil.i(outputTag, "DISABLED"); return; } diff --git a/java/com/android/dialer/logging/LoggingBindings.java b/java/com/android/dialer/logging/LoggingBindings.java index ec98ed124..a093c4f6b 100644 --- a/java/com/android/dialer/logging/LoggingBindings.java +++ b/java/com/android/dialer/logging/LoggingBindings.java @@ -29,6 +29,9 @@ public interface LoggingBindings { String ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "GoogleDialtactsActivity.onResume"; String INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "IncallActivity.OnResume"; String INCALL_ACTIVITY_ON_STOP_MEMORY_EVENT_NAME = "IncallActivity.OnStop"; + String OLD_CALL_LOG_JANK_EVENT_NAME = "OldCallLog.Jank"; + String NEW_CALL_LOG_JANK_EVENT_NAME = "NewCallLog.Jank"; + /** * Logs an DialerImpression event that's not associated with a specific call. * diff --git a/java/com/android/dialer/metrics/Metrics.java b/java/com/android/dialer/metrics/Metrics.java index 3922a8cfa..7cdfa220e 100644 --- a/java/com/android/dialer/metrics/Metrics.java +++ b/java/com/android/dialer/metrics/Metrics.java @@ -28,6 +28,12 @@ public interface Metrics { /** Stop a timer. */ void stopTimer(String timerEventName); + /** Start a jank recorder. */ + void startJankRecorder(String eventName); + + /** Stop a jank recorder. */ + void stopJankRecorder(String eventName); + /** Record memory. */ void recordMemory(String memoryEventName); diff --git a/java/com/android/dialer/metrics/StubMetrics.java b/java/com/android/dialer/metrics/StubMetrics.java index 114eb4308..f4d41e2d2 100644 --- a/java/com/android/dialer/metrics/StubMetrics.java +++ b/java/com/android/dialer/metrics/StubMetrics.java @@ -31,6 +31,12 @@ public final class StubMetrics implements Metrics { @Override public void stopTimer(String timerEventName) {} + @Override + public void startJankRecorder(String eventName) {} + + @Override + public void stopJankRecorder(String eventName) {} + @Override public void recordMemory(String memoryEventName) {} } diff --git a/java/com/android/dialer/metrics/jank/RecyclerViewJankLogger.java b/java/com/android/dialer/metrics/jank/RecyclerViewJankLogger.java new file mode 100644 index 000000000..c9f389285 --- /dev/null +++ b/java/com/android/dialer/metrics/jank/RecyclerViewJankLogger.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.metrics.jank; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.OnScrollListener; +import com.android.dialer.metrics.Metrics; + +/** Logs jank for {@link RecyclerView} scrolling events. */ +public final class RecyclerViewJankLogger extends OnScrollListener { + + private final Metrics metrics; + private final String eventName; + + private boolean isScrolling; + + public RecyclerViewJankLogger(Metrics metrics, String eventName) { + this.metrics = metrics; + this.eventName = eventName; + } + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (!isScrolling && newState == RecyclerView.SCROLL_STATE_DRAGGING) { + isScrolling = true; + metrics.startJankRecorder(eventName); + } else if (isScrolling && newState == RecyclerView.SCROLL_STATE_IDLE) { + isScrolling = false; + metrics.stopJankRecorder(eventName); + } + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorImpl.java b/java/com/android/dialer/simulator/impl/SimulatorImpl.java index 24f34102e..c8b8af92e 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorImpl.java +++ b/java/com/android/dialer/simulator/impl/SimulatorImpl.java @@ -19,6 +19,7 @@ package com.android.dialer.simulator.impl; import android.support.v7.app.AppCompatActivity; import android.view.ActionProvider; import com.android.dialer.buildtype.BuildType; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.common.LogUtil; import com.android.dialer.simulator.Simulator; import javax.inject.Inject; @@ -33,7 +34,7 @@ final class SimulatorImpl implements Simulator { @Override public boolean shouldShow() { - return BuildType.get() == BuildType.BUGFOOD || LogUtil.isDebugEnabled(); + return BuildType.get() == Type.BUGFOOD || LogUtil.isDebugEnabled(); } @Override diff --git a/java/com/android/dialer/strictmode/StrictModeUtils.java b/java/com/android/dialer/strictmode/StrictModeUtils.java index c07138f81..27f8142d8 100644 --- a/java/com/android/dialer/strictmode/StrictModeUtils.java +++ b/java/com/android/dialer/strictmode/StrictModeUtils.java @@ -25,6 +25,7 @@ import android.preference.PreferenceManager; import android.support.annotation.AnyThread; import android.support.v4.os.UserManagerCompat; import com.android.dialer.buildtype.BuildType; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.function.Supplier; import com.android.dialer.storage.StorageComponent; @@ -86,7 +87,7 @@ public final class StrictModeUtils { } public static boolean isStrictModeAllowed() { - return BuildType.get() == BuildType.BUGFOOD; + return BuildType.get() == Type.BUGFOOD; } private static boolean onMainThread() { -- cgit v1.2.3 From 7a96dc70bb94f25ffc1d84cea34f03f54cc89a00 Mon Sep 17 00:00:00 2001 From: zachh Date: Tue, 20 Feb 2018 22:16:03 -0800 Subject: Moved metrics related methods out of LoggingBindings. Test: existing PiperOrigin-RevId: 186413083 Change-Id: I96c88c46b0ecc01167b655fa30fc47aaa6a9e351 --- java/com/android/dialer/app/DialtactsActivity.java | 8 +++++--- .../android/dialer/app/calllog/CallLogFragment.java | 5 ++--- .../dialer/calllog/ui/NewCallLogFragment.java | 5 ++--- .../com/android/dialer/logging/LoggingBindings.java | 21 --------------------- .../android/dialer/logging/LoggingBindingsStub.java | 9 --------- java/com/android/dialer/metrics/Metrics.java | 15 +++++++++++++-- java/com/android/dialer/metrics/StubMetrics.java | 3 +-- java/com/android/incallui/InCallActivity.java | 18 +++++++++++------- java/com/android/incallui/call/CallList.java | 13 ++++++++----- 9 files changed, 42 insertions(+), 55 deletions(-) diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 293ebed87..ff64ba168 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -116,9 +116,10 @@ import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCo import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; -import com.android.dialer.logging.LoggingBindings; import com.android.dialer.logging.ScreenEvent; import com.android.dialer.logging.UiAction; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.p13n.inference.P13nRanking; import com.android.dialer.p13n.inference.protocol.P13nRanker; import com.android.dialer.p13n.inference.protocol.P13nRanker.P13nRefreshCompleteListener; @@ -612,8 +613,9 @@ public class DialtactsActivity extends TransactionSafeActivity // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. ThreadUtil.postDelayedOnUiThread( () -> - Logger.get(this) - .logRecordMemory(LoggingBindings.ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME), + MetricsComponent.get(this) + .metrics() + .recordMemory(Metrics.DIALTACTS_ON_RESUME_MEMORY_EVENT_NAME), 1000); } diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java index 6b6239da4..ee564e203 100644 --- a/java/com/android/dialer/app/calllog/CallLogFragment.java +++ b/java/com/android/dialer/app/calllog/CallLogFragment.java @@ -63,7 +63,7 @@ import com.android.dialer.database.CallLogQueryHandler.Listener; import com.android.dialer.location.GeoUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; -import com.android.dialer.logging.LoggingBindings; +import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.metrics.jank.RecyclerViewJankLogger; import com.android.dialer.oem.CequintCallerIdManager; @@ -311,8 +311,7 @@ public class CallLogFragment extends Fragment recyclerView.setHasFixedSize(true); recyclerView.addOnScrollListener( new RecyclerViewJankLogger( - MetricsComponent.get(getContext()).metrics(), - LoggingBindings.OLD_CALL_LOG_JANK_EVENT_NAME)); + MetricsComponent.get(getContext()).metrics(), Metrics.OLD_CALL_LOG_JANK_EVENT_NAME)); layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); PerformanceReport.logOnScrollStateChange(recyclerView); diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index bbd16fbf1..bb1a7303e 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -33,7 +33,7 @@ import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.ThreadUtil; -import com.android.dialer.logging.LoggingBindings; +import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.metrics.jank.RecyclerViewJankLogger; import com.google.common.util.concurrent.Futures; @@ -175,8 +175,7 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback recyclerView = view.findViewById(R.id.new_call_log_recycler_view); recyclerView.addOnScrollListener( new RecyclerViewJankLogger( - MetricsComponent.get(getContext()).metrics(), - LoggingBindings.NEW_CALL_LOG_JANK_EVENT_NAME)); + MetricsComponent.get(getContext()).metrics(), Metrics.NEW_CALL_LOG_JANK_EVENT_NAME)); getLoaderManager().restartLoader(0, null, this); diff --git a/java/com/android/dialer/logging/LoggingBindings.java b/java/com/android/dialer/logging/LoggingBindings.java index a093c4f6b..ca9a0533e 100644 --- a/java/com/android/dialer/logging/LoggingBindings.java +++ b/java/com/android/dialer/logging/LoggingBindings.java @@ -20,18 +20,6 @@ import android.widget.QuickContactBadge; /** Allows the container application to gather analytics. */ public interface LoggingBindings { - String ON_CREATE_PRIMES_EVENT_NAME = "Application.onCreate"; - String ACTIVITY_ON_CREATE_PRIMES_EVENT_NAME = "GoogleDialtactsActivity.onCreate"; - String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING = - "CallList.onCallAdded_To_InCallActivity.onCreate_Incoming"; - String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING = - "CallList.onCallAdded_To_InCallActivity.onCreate_Outgoing"; - String ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "GoogleDialtactsActivity.onResume"; - String INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "IncallActivity.OnResume"; - String INCALL_ACTIVITY_ON_STOP_MEMORY_EVENT_NAME = "IncallActivity.OnStop"; - String OLD_CALL_LOG_JANK_EVENT_NAME = "OldCallLog.Jank"; - String NEW_CALL_LOG_JANK_EVENT_NAME = "NewCallLog.Jank"; - /** * Logs an DialerImpression event that's not associated with a specific call. * @@ -99,13 +87,4 @@ public interface LoggingBindings { /** Logs successful People Api lookup result */ void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode); - - /** Log start a latency timer */ - void logStartLatencyTimer(String timerEventName); - - /** Log end a latency timer */ - void logStopLatencyTimer(String timerEventName); - - /** Log get a memory snapshot */ - void logRecordMemory(String memoryEventName); } diff --git a/java/com/android/dialer/logging/LoggingBindingsStub.java b/java/com/android/dialer/logging/LoggingBindingsStub.java index 74ac294fc..2dbcc3ffb 100644 --- a/java/com/android/dialer/logging/LoggingBindingsStub.java +++ b/java/com/android/dialer/logging/LoggingBindingsStub.java @@ -61,13 +61,4 @@ public class LoggingBindingsStub implements LoggingBindings { @Override public void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode) {} - - @Override - public void logStartLatencyTimer(String timerEventName) {} - - @Override - public void logStopLatencyTimer(String timerEventName) {} - - @Override - public void logRecordMemory(String memoryEventName) {} } diff --git a/java/com/android/dialer/metrics/Metrics.java b/java/com/android/dialer/metrics/Metrics.java index 7cdfa220e..9488f3068 100644 --- a/java/com/android/dialer/metrics/Metrics.java +++ b/java/com/android/dialer/metrics/Metrics.java @@ -17,13 +17,24 @@ package com.android.dialer.metrics; import android.app.Application; -import android.content.Context; /** Logs metrics. */ public interface Metrics { + String APPLICATION_ON_CREATE_EVENT_NAME = "Application.onCreate"; + String DIALTACTS_ON_CREATE_EVENT_NAME = "GoogleDialtactsActivity.onCreate"; + String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING = + "CallList.onCallAdded_To_InCallActivity.onCreate_Incoming"; + String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING = + "CallList.onCallAdded_To_InCallActivity.onCreate_Outgoing"; + String DIALTACTS_ON_RESUME_MEMORY_EVENT_NAME = "GoogleDialtactsActivity.onResume"; + String INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "IncallActivity.OnResume"; + String INCALL_ACTIVITY_ON_STOP_MEMORY_EVENT_NAME = "IncallActivity.OnStop"; + String OLD_CALL_LOG_JANK_EVENT_NAME = "OldCallLog.Jank"; + String NEW_CALL_LOG_JANK_EVENT_NAME = "NewCallLog.Jank"; + /** Start a timer. */ - void startTimer(Context context, String timerEventName); + void startTimer(String timerEventName); /** Stop a timer. */ void stopTimer(String timerEventName); diff --git a/java/com/android/dialer/metrics/StubMetrics.java b/java/com/android/dialer/metrics/StubMetrics.java index f4d41e2d2..99c3d7691 100644 --- a/java/com/android/dialer/metrics/StubMetrics.java +++ b/java/com/android/dialer/metrics/StubMetrics.java @@ -16,7 +16,6 @@ package com.android.dialer.metrics; -import android.content.Context; import javax.inject.Inject; /** Stub {@link Metrics}. */ @@ -26,7 +25,7 @@ public final class StubMetrics implements Metrics { StubMetrics() {} @Override - public void startTimer(Context context, String timerEventName) {} + public void startTimer(String timerEventName) {} @Override public void stopTimer(String timerEventName) {} diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index 3fc7f6c76..f842aedf7 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -61,8 +61,9 @@ import com.android.dialer.compat.ActivityCompat; import com.android.dialer.compat.CompatUtils; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.logging.Logger; -import com.android.dialer.logging.LoggingBindings; import com.android.dialer.logging.ScreenEvent; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.util.ViewUtil; import com.android.incallui.answer.bindings.AnswerBindings; import com.android.incallui.answer.protocol.AnswerScreen; @@ -249,10 +250,12 @@ public class InCallActivity extends TransactionSafeFragmentActivity pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay); sendBroadcast(CallPendingActivity.getFinishBroadcast()); Trace.endSection(); - Logger.get(this) - .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); - Logger.get(this) - .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); + MetricsComponent.get(this) + .metrics() + .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); + MetricsComponent.get(this) + .metrics() + .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); } private void setWindowFlags() { @@ -470,8 +473,9 @@ public class InCallActivity extends TransactionSafeFragmentActivity // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. ThreadUtil.postDelayedOnUiThread( () -> - Logger.get(this) - .logRecordMemory(LoggingBindings.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME), + MetricsComponent.get(this) + .metrics() + .recordMemory(Metrics.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME), 1000); } diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java index e5948084d..9a0902639 100644 --- a/java/com/android/incallui/call/CallList.java +++ b/java/com/android/incallui/call/CallList.java @@ -36,7 +36,8 @@ import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; -import com.android.dialer.logging.LoggingBindings; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.shortcuts.ShortcutUsageReporter; import com.android.dialer.spam.Spam; import com.android.dialer.spam.SpamComponent; @@ -119,11 +120,13 @@ public class CallList implements DialerCallDelegate { final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) { Trace.beginSection("CallList.onCallAdded"); if (telecomCall.getState() == Call.STATE_CONNECTING) { - Logger.get(context) - .logStartLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); + MetricsComponent.get(context) + .metrics() + .startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); } else if (telecomCall.getState() == Call.STATE_RINGING) { - Logger.get(context) - .logStartLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); + MetricsComponent.get(context) + .metrics() + .startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); } if (uiListeners != null) { uiListeners.onCallAdded(); -- cgit v1.2.3 From b0d678fd79ff07fccc766e73c9f77cd99f33ff48 Mon Sep 17 00:00:00 2001 From: mdooley Date: Tue, 20 Feb 2018 22:51:51 -0800 Subject: Changing the voicemail transcription font size UX wants the voicemail transcription font size to be 14sp Bug: 73668659 Test: manual PiperOrigin-RevId: 186415158 Change-Id: I3c5992efa4dace2f455c4de41d7d1cb51ef2a916 --- java/com/android/dialer/theme/res/values/dimens.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com/android/dialer/theme/res/values/dimens.xml b/java/com/android/dialer/theme/res/values/dimens.xml index e7c947782..88b8a0423 100644 --- a/java/com/android/dialer/theme/res/values/dimens.xml +++ b/java/com/android/dialer/theme/res/values/dimens.xml @@ -22,7 +22,7 @@ 16sp 12sp 14sp - 16sp + 14sp 48dp 15dp -- cgit v1.2.3 From 82133d92307bc26327be7609262e4684df3af669 Mon Sep 17 00:00:00 2001 From: twyen Date: Tue, 20 Feb 2018 23:47:14 -0800 Subject: Rebuild Call Log on first launch after NUI is enabled This improves the first time experience by populating the DB when the user is still in other tabs. Bug: 72119926 Test: Unit tests PiperOrigin-RevId: 186418788 Change-Id: If011d7191a09fd1aaca489c6e682ccdc643c2139 --- .../dialer/binary/common/DialerApplication.java | 7 ++ .../dialer/calllog/AnnotatedCallLogMigrator.java | 92 ++++++++++++++++++++++ .../android/dialer/calllog/CallLogComponent.java | 2 + 3 files changed, 101 insertions(+) create mode 100644 java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java diff --git a/java/com/android/dialer/binary/common/DialerApplication.java b/java/com/android/dialer/binary/common/DialerApplication.java index c0e6ae660..c23926021 100644 --- a/java/com/android/dialer/binary/common/DialerApplication.java +++ b/java/com/android/dialer/binary/common/DialerApplication.java @@ -23,11 +23,14 @@ import android.support.v4.os.BuildCompat; import com.android.dialer.blocking.BlockedNumbersAutoMigrator; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; import com.android.dialer.calllog.CallLogComponent; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.inject.HasRootComponent; import com.android.dialer.notification.NotificationChannelManager; import com.android.dialer.persistentlog.PersistentLogger; import com.android.dialer.strictmode.StrictModeComponent; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; /** A common application subclass for all Dialer build variants. */ public abstract class DialerApplication extends Application implements HasRootComponent { @@ -46,6 +49,10 @@ public abstract class DialerApplication extends Application implements HasRootCo DialerExecutorComponent.get(this).dialerExecutorFactory()) .asyncAutoMigrate(); CallLogComponent.get(this).callLogFramework().registerContentObservers(getApplicationContext()); + Futures.addCallback( + CallLogComponent.get(this).getAnnotatedCallLogMigrator().migrate(), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); PersistentLogger.initialize(this); if (BuildCompat.isAtLeastO()) { diff --git a/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java b/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java new file mode 100644 index 000000000..f8c6fcef1 --- /dev/null +++ b/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 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.calllog; + +import android.content.Context; +import android.content.SharedPreferences; +import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.configprovider.ConfigProviderBindings; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.storage.Unencrypted; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import javax.inject.Inject; + +/** + * Builds the annotated call log on application create once after the feature is enabled to reduce + * the latency the first time call log is shown. + */ +public final class AnnotatedCallLogMigrator { + + private static final String PREF_MIGRATED = "annotatedCallLogMigratorMigrated"; + + private final Context appContext; + private final SharedPreferences sharedPreferences; + private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; + private final ListeningExecutorService backgorundExecutor; + + @Inject + AnnotatedCallLogMigrator( + @ApplicationContext Context appContext, + @Unencrypted SharedPreferences sharedPreferences, + @BackgroundExecutor ListeningExecutorService backgroundExecutor, + RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker) { + this.appContext = appContext; + this.sharedPreferences = sharedPreferences; + this.backgorundExecutor = backgroundExecutor; + this.refreshAnnotatedCallLogWorker = refreshAnnotatedCallLogWorker; + } + + /** + * Builds the annotated call log on application create once after the feature is enabled to reduce + * the latency the first time call log is shown. + */ + public ListenableFuture migrate() { + + return Futures.transformAsync( + shouldMigrate(), + (shouldMigrate) -> { + if (!shouldMigrate) { + return Futures.immediateFuture(null); + } + return Futures.transform( + refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(), + (unused) -> { + sharedPreferences.edit().putBoolean(PREF_MIGRATED, true).apply(); + return null; + }, + MoreExecutors.directExecutor()); + }, + MoreExecutors.directExecutor()); + } + + private ListenableFuture shouldMigrate() { + return backgorundExecutor.submit( + () -> { + if (!(ConfigProviderBindings.get(appContext) + .getBoolean("is_nui_shortcut_enabled", false))) { + return false; + } + if (sharedPreferences.getBoolean(PREF_MIGRATED, false)) { + return false; + } + return true; + }); + } +} diff --git a/java/com/android/dialer/calllog/CallLogComponent.java b/java/com/android/dialer/calllog/CallLogComponent.java index 4f147f1a6..a2a5084fc 100644 --- a/java/com/android/dialer/calllog/CallLogComponent.java +++ b/java/com/android/dialer/calllog/CallLogComponent.java @@ -30,6 +30,8 @@ public abstract class CallLogComponent { public abstract RefreshAnnotatedCallLogWorker getRefreshAnnotatedCallLogWorker(); + public abstract AnnotatedCallLogMigrator getAnnotatedCallLogMigrator(); + public abstract ClearMissedCalls getClearMissedCalls(); public static CallLogComponent get(Context context) { -- cgit v1.2.3 From d40d4f09e32ecfc6b95f57bc6d3e3d1703592cd4 Mon Sep 17 00:00:00 2001 From: uabdullah Date: Wed, 21 Feb 2018 02:40:20 -0800 Subject: When there is no VM support hide the tab in the Old Main Activity In the case where the user is not able to dial into the voicemail tab, or there exists another voicemail app, or VVM is not supported, we do not want to show the VM tab. This CL does not update the activity when a sim is inserted/removed. Bug: 73123614 Test: Unit tests PiperOrigin-RevId: 186433072 Change-Id: I0396b1e15c9a4740eee721af89dbfdf95696cace --- .../dialer/main/impl/OldMainActivityPeer.java | 79 +++++++++++++++++++++- .../dialer/main/impl/bottomnav/BottomNavBar.java | 4 ++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 69d8032f2..99e49557c 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -32,6 +32,9 @@ import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.v7.widget.Toolbar; import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; @@ -81,6 +84,7 @@ import com.android.dialer.storage.StorageComponent; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.TransactionSafeActivity; +import com.android.voicemail.VoicemailComponent; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.TimeUnit; @@ -190,6 +194,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen MainBottomNavBarBottomNavTabListener bottomNavTabListener = new MainBottomNavBarBottomNavTabListener(mainActivity, mainActivity.getFragmentManager()); bottomNav.addOnTabSelectedListener(bottomNavTabListener); + // TODO(uabdullah): Handle case of when a sim is inserted/removed while the activity is open. + boolean showVoicemailTab = canVoicemailTabBeShown(mainActivity); + bottomNav.showVoicemail(showVoicemailTab); callLogFragmentListener = new MainCallLogFragmentListener( @@ -218,7 +225,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen mainActivity.findViewById(R.id.remove_view), mainActivity.findViewById(R.id.search_view_container)); - lastTabController = new LastTabController(mainActivity, bottomNav); + lastTabController = new LastTabController(mainActivity, bottomNav, showVoicemailTab); // Restore our view state if needed, else initialize as if the app opened for the first time if (savedInstanceState != null) { @@ -230,6 +237,66 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } } + /** + * Check and return whether the voicemail tab should be shown or not. This includes the following + * criteria under which we show the voicemail tab: + *

  • The voicemail number exists (e.g we are able to dial into listen to voicemail or press and + * hold 1) + *
  • Visual voicemail is enabled from the settings tab + *
  • Visual voicemail carrier is supported by dialer + *
  • There is no voicemail carrier app installed. + * + * @param context + * @return return if voicemail tab should be shown or not depending on what the voicemail state is + * for the carrier. + */ + private static boolean canVoicemailTabBeShown(Context context) { + PhoneAccountHandle defaultUserSelectedAccount = + TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_VOICEMAIL); + + if (isVoicemailAvailable(context, defaultUserSelectedAccount)) { + return true; + } + if (VoicemailComponent.get(context) + .getVoicemailClient() + .isVoicemailEnabled(context, defaultUserSelectedAccount)) { + return true; + } + return false; + } + + /** + * Check if voicemail is enabled/accessible. + * + * @return true if voicemail is enabled and accessible. Note that this can be false "temporarily" + * after the app boot e.g if the sim isn't fully recognized. TODO(uabdullah): Possibly add a + * listener of some kind to detect when a sim is recognized. TODO(uabdullah): Move this to a + * utility class or wrap it all in a static inner class. + */ + private static boolean isVoicemailAvailable( + Context context, PhoneAccountHandle defaultUserSelectedAccount) { + + if (!TelecomUtil.hasReadPhoneStatePermission(context)) { + LogUtil.i( + "OldMainActivityPeer.isVoicemailAvailable", + "No read phone permisison or not the default dialer."); + return false; + } + + if (defaultUserSelectedAccount == null) { + // In a single-SIM phone, there is no default outgoing phone account selected by + // the user, so just call TelephonyManager#getVoicemailNumber directly. + return !TextUtils.isEmpty(getTelephonyManager(context).getVoiceMailNumber()); + } else { + return !TextUtils.isEmpty( + TelecomUtil.getVoicemailNumber(context, defaultUserSelectedAccount)); + } + } + + private static TelephonyManager getTelephonyManager(Context context) { + return context.getSystemService(TelephonyManager.class); + } + @Override public void onNewIntent(Intent intent) { LogUtil.enterBlock("OldMainActivityPeer.onNewIntent"); @@ -947,11 +1014,13 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private final Context context; private final BottomNavBar bottomNavBar; private final boolean isEnabled; + private final boolean canShowVoicemailTab; - LastTabController(Context context, BottomNavBar bottomNavBar) { + LastTabController(Context context, BottomNavBar bottomNavBar, boolean canShowVoicemailTab) { this.context = context; this.bottomNavBar = bottomNavBar; isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false); + this.canShowVoicemailTab = canShowVoicemailTab; } /** Sets the last tab if the feature is enabled, otherwise defaults to speed dial. */ @@ -963,6 +1032,12 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen .unencryptedSharedPrefs() .getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL); } + + // If the voicemail tab cannot be shown, default to showing speed dial + if (tabIndex == TabIndex.VOICEMAIL && !canShowVoicemailTab) { + tabIndex = TabIndex.SPEED_DIAL; + } + bottomNavBar.selectTab(tabIndex); } diff --git a/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java b/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java index 2945e39a9..d9a446f84 100644 --- a/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java +++ b/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java @@ -123,6 +123,10 @@ public final class BottomNavBar extends LinearLayout { } } + public void showVoicemail(boolean showTab) { + voicemail.setVisibility(showTab ? View.VISIBLE : View.GONE); + } + public void setNotificationCount(@TabIndex int tab, int count) { if (tab == TabIndex.SPEED_DIAL) { speedDial.setNotificationCount(count); -- cgit v1.2.3 From f94391034e9d591c18d04c0b796d944938201f6a Mon Sep 17 00:00:00 2001 From: Android Dialer Date: Wed, 21 Feb 2018 08:05:14 -0800 Subject: Updating PrimaryInfo value class to use AutoValue with builder pattern. Bug: 34502119 Test: BottomRowTest,CallCardPresenterTest,PrimaryInfoTest,TopRowTest PiperOrigin-RevId: 186460178 Change-Id: Ifb90019b6a5568788d51f4a55a07f7693c803eaf --- java/com/android/incallui/CallCardPresenter.java | 86 ++++----- .../incallui/answer/impl/AnswerFragment.java | 16 +- .../incallui/callpending/CallPendingActivity.java | 38 ++-- .../android/incallui/contactgrid/BottomRow.java | 16 +- .../incallui/contactgrid/ContactGridManager.java | 28 +-- java/com/android/incallui/contactgrid/TopRow.java | 12 +- .../incallui/incall/impl/InCallFragment.java | 4 +- .../incallui/incall/protocol/PrimaryInfo.java | 197 ++++++++++++--------- .../android/incallui/rtt/impl/RttChatFragment.java | 2 +- 9 files changed, 212 insertions(+), 187 deletions(-) diff --git a/java/com/android/incallui/CallCardPresenter.java b/java/com/android/incallui/CallCardPresenter.java index da5d1a8dd..b945b0810 100644 --- a/java/com/android/incallui/CallCardPresenter.java +++ b/java/com/android/incallui/CallCardPresenter.java @@ -690,7 +690,7 @@ public class CallCardPresenter if (primary == null) { // Clear the primary display info. - inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo()); + inCallScreen.setPrimary(PrimaryInfo.empty()); return; } @@ -713,26 +713,22 @@ public class CallCardPresenter "update primary display info for conference call."); inCallScreen.setPrimary( - new PrimaryInfo( - null /* number */, - CallerInfoUtils.getConferenceString( - context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)), - false /* nameIsNumber */, - null /* location */, - null /* label */, - null /* photo */, - ContactPhotoType.DEFAULT_PLACEHOLDER, - false /* isSipCall */, - showContactPhoto, - hasWorkCallProperty, - false /* isSpam */, - false /* isLocalContact */, - false /* answeringDisconnectsOngoingCall */, - shouldShowLocation(), - null /* contactInfoLookupKey */, - null /* enrichedCallMultimediaData */, - true /* showInCallButtonGrid */, - primary.getNumberPresentation())); + PrimaryInfo.builder() + .setName( + CallerInfoUtils.getConferenceString( + context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE))) + .setNameIsNumber(false) + .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER) + .setIsSipCall(false) + .setIsContactPhotoShown(showContactPhoto) + .setIsWorkCall(hasWorkCallProperty) + .setIsSpam(false) + .setIsLocalContact(false) + .setAnsweringDisconnectsOngoingCall(false) + .setShouldShowLocation(shouldShowLocation()) + .setShowInCallButtonGrid(true) + .setNumberPresentation(primary.getNumberPresentation()) + .build()); } else if (primaryContactInfo != null) { LogUtil.v( "CallCardPresenter.updatePrimaryDisplayInfo", @@ -761,30 +757,33 @@ public class CallCardPresenter // DialerCall with caller that is a work contact. boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK); inCallScreen.setPrimary( - new PrimaryInfo( - number, - primary.updateNameIfRestricted(name), - nameIsNumber, - shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation) - ? primaryContactInfo.location - : null, - isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label, - primaryContactInfo.photo, - primaryContactInfo.photoType, - primaryContactInfo.isSipCall, - showContactPhoto, - hasWorkCallProperty || isWorkContact, - primary.isSpam(), - primaryContactInfo.isLocalContact(), - primary.answeringDisconnectsForegroundVideoCall(), - shouldShowLocation(), - primaryContactInfo.lookupKey, - multimediaData, - true /* showInCallButtonGrid */, - primary.getNumberPresentation())); + PrimaryInfo.builder() + .setNumber(number) + .setName(primary.updateNameIfRestricted(name)) + .setNameIsNumber(nameIsNumber) + .setLabel( + shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation) + ? primaryContactInfo.location + : null) + .setLocation( + isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label) + .setPhoto(primaryContactInfo.photo) + .setPhotoType(primaryContactInfo.photoType) + .setIsSipCall(primaryContactInfo.isSipCall) + .setIsContactPhotoShown(showContactPhoto) + .setIsWorkCall(hasWorkCallProperty || isWorkContact) + .setIsSpam(primary.isSpam()) + .setIsLocalContact(primaryContactInfo.isLocalContact()) + .setAnsweringDisconnectsOngoingCall(primary.answeringDisconnectsForegroundVideoCall()) + .setShouldShowLocation(shouldShowLocation()) + .setContactInfoLookupKey(primaryContactInfo.lookupKey) + .setMultimediaData(multimediaData) + .setShowInCallButtonGrid(true) + .setNumberPresentation(primary.getNumberPresentation()) + .build()); } else { // Clear the primary display info. - inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo()); + inCallScreen.setPrimary(PrimaryInfo.empty()); } if (isInCallScreenReady) { @@ -1192,6 +1191,7 @@ public class CallCardPresenter return inCallScreen; } + /** Callback for contact lookup. */ public static class ContactLookupCallback implements ContactInfoCacheCallback { private final WeakReference callCardPresenter; diff --git a/java/com/android/incallui/answer/impl/AnswerFragment.java b/java/com/android/incallui/answer/impl/AnswerFragment.java index 8626e6d0e..3439a3e3c 100644 --- a/java/com/android/incallui/answer/impl/AnswerFragment.java +++ b/java/com/android/incallui/answer/impl/AnswerFragment.java @@ -149,7 +149,7 @@ public class AnswerFragment extends Fragment private boolean buttonAcceptClicked; private boolean buttonRejectClicked; private boolean hasAnimatedEntry; - private PrimaryInfo primaryInfo = PrimaryInfo.createEmptyPrimaryInfo(); + private PrimaryInfo primaryInfo = PrimaryInfo.empty(); private PrimaryCallState primaryCallState; private ArrayList textResponses; private SmsBottomSheetFragment textResponsesFragment; @@ -523,13 +523,13 @@ public class AnswerFragment extends Fragment return; } contactGridManager.setPrimary(primaryInfo); - getAnswerMethod().setShowIncomingWillDisconnect(primaryInfo.answeringDisconnectsOngoingCall); + getAnswerMethod().setShowIncomingWillDisconnect(primaryInfo.answeringDisconnectsOngoingCall()); getAnswerMethod() .setContactPhoto( - primaryInfo.photoType == ContactPhotoType.CONTACT ? primaryInfo.photo : null); + primaryInfo.photoType() == ContactPhotoType.CONTACT ? primaryInfo.photo() : null); updateDataFragment(); - if (primaryInfo.shouldShowLocation) { + if (primaryInfo.shouldShowLocation()) { // Hide the avatar to make room for location contactGridManager.setAvatarHidden(true); } @@ -562,8 +562,8 @@ public class AnswerFragment extends Fragment MultimediaFragment.newInstance( multimediaData, false /* isInteractive */, - !primaryInfo.isSpam /* showAvatar */, - primaryInfo.isSpam); + !primaryInfo.isSpam() /* showAvatar */, + primaryInfo.isSpam()); } } else if (shouldShowAvatar()) { // Needs Avatar @@ -1067,7 +1067,7 @@ public class AnswerFragment extends Fragment return; } - if (!getResources().getBoolean(R.bool.answer_important_call_allowed) || primaryInfo.isSpam) { + if (!getResources().getBoolean(R.bool.answer_important_call_allowed) || primaryInfo.isSpam()) { importanceBadge.setVisibility(View.GONE); return; } @@ -1088,7 +1088,7 @@ public class AnswerFragment extends Fragment if (isVideoUpgradeRequest()) { return null; } - return primaryInfo.multimediaData; + return primaryInfo.multimediaData(); } /** Shows the Avatar image if available. */ diff --git a/java/com/android/incallui/callpending/CallPendingActivity.java b/java/com/android/incallui/callpending/CallPendingActivity.java index 47325d823..306eed8fb 100644 --- a/java/com/android/incallui/callpending/CallPendingActivity.java +++ b/java/com/android/incallui/callpending/CallPendingActivity.java @@ -177,25 +177,25 @@ public class CallPendingActivity extends FragmentActivity String number = getNumber(); // DialerCall with caller that is a work contact. - return new PrimaryInfo( - number, - name, - name != null && name.equals(number), - null /* location */, - getPhoneLabel(), - photo, - ContactPhotoType.CONTACT, - false /* isSipCall */, - true /* isContactPhotoShown */, - false /* isWorkCall */, - false /* isSpam */, - true /* isLocalContact */, - false /* answeringDisconnectsOngoingCall */, - false /* shouldShowLocation */, - getLookupKey(), - multimediaData, - false /*showInCallButtonGrid */, - TelecomManager.PRESENTATION_ALLOWED); + return PrimaryInfo.builder() + .setNumber(number) + .setName(name) + .setNameIsNumber(name != null && name.equals(number)) + .setLabel(getPhoneLabel()) + .setPhoto(photo) + .setPhotoType(ContactPhotoType.CONTACT) + .setIsSipCall(false) + .setIsContactPhotoShown(true) + .setIsWorkCall(false) + .setIsSpam(false) + .setIsLocalContact(true) + .setAnsweringDisconnectsOngoingCall(false) + .setShouldShowLocation(false) + .setContactInfoLookupKey(getLookupKey()) + .setMultimediaData(multimediaData) + .setShowInCallButtonGrid(false) + .setNumberPresentation(TelecomManager.PRESENTATION_ALLOWED) + .build(); } @Override diff --git a/java/com/android/incallui/contactgrid/BottomRow.java b/java/com/android/incallui/contactgrid/BottomRow.java index f9fc870dc..610eeca39 100644 --- a/java/com/android/incallui/contactgrid/BottomRow.java +++ b/java/com/android/incallui/contactgrid/BottomRow.java @@ -82,7 +82,7 @@ public class BottomRow { boolean isSpamIconVisible = false; boolean shouldPopulateAccessibilityEvent = true; - if (isIncoming(state) && primaryInfo.isSpam) { + if (isIncoming(state) && primaryInfo.isSpam()) { label = context.getString(R.string.contact_grid_incoming_suspected_spam); isSpamIconVisible = true; isHdIconVisible = false; @@ -99,7 +99,7 @@ public class BottomRow { } } else { label = getLabelForPhoneNumber(primaryInfo); - shouldPopulateAccessibilityEvent = primaryInfo.nameIsNumber; + shouldPopulateAccessibilityEvent = primaryInfo.nameIsNumber(); } return new Info( @@ -114,15 +114,15 @@ public class BottomRow { } private static CharSequence getLabelForPhoneNumber(PrimaryInfo primaryInfo) { - if (primaryInfo.location != null) { - return primaryInfo.location; + if (primaryInfo.location() != null) { + return primaryInfo.location(); } - if (!primaryInfo.nameIsNumber && !TextUtils.isEmpty(primaryInfo.number)) { - CharSequence spannedNumber = spanDisplayNumber(primaryInfo.number); - if (primaryInfo.label == null) { + if (!primaryInfo.nameIsNumber() && !TextUtils.isEmpty(primaryInfo.number())) { + CharSequence spannedNumber = spanDisplayNumber(primaryInfo.number()); + if (primaryInfo.label() == null) { return spannedNumber; } else { - return TextUtils.concat(primaryInfo.label, " ", spannedNumber); + return TextUtils.concat(primaryInfo.label(), " ", spannedNumber); } } return null; diff --git a/java/com/android/incallui/contactgrid/ContactGridManager.java b/java/com/android/incallui/contactgrid/ContactGridManager.java index 58d318892..6dec7646f 100644 --- a/java/com/android/incallui/contactgrid/ContactGridManager.java +++ b/java/com/android/incallui/contactgrid/ContactGridManager.java @@ -85,7 +85,7 @@ public class ContactGridManager { private final TextView deviceNumberTextView; private final View deviceNumberDivider; - private PrimaryInfo primaryInfo = PrimaryInfo.createEmptyPrimaryInfo(); + private PrimaryInfo primaryInfo = PrimaryInfo.empty(); private PrimaryCallState primaryCallState = PrimaryCallState.createEmptyPrimaryCallState(); private final LetterTileDrawable letterTile; private boolean isInMultiWindowMode; @@ -213,7 +213,7 @@ public class ContactGridManager { } boolean hasPhoto = - primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT; + primaryInfo.photo() != null && primaryInfo.photoType() == ContactPhotoType.CONTACT; if (!hasPhoto && !showAnonymousAvatar) { avatarImageView.setVisibility(View.GONE); return false; @@ -271,17 +271,17 @@ public class ContactGridManager { * */ private void updatePrimaryNameAndPhoto() { - if (TextUtils.isEmpty(primaryInfo.name)) { + if (TextUtils.isEmpty(primaryInfo.name())) { contactNameTextView.setText(null); } else { contactNameTextView.setText( - primaryInfo.nameIsNumber - ? PhoneNumberUtilsCompat.createTtsSpannable(primaryInfo.name) - : primaryInfo.name); + primaryInfo.nameIsNumber() + ? PhoneNumberUtilsCompat.createTtsSpannable(primaryInfo.name()) + : primaryInfo.name()); // Set direction of the name field int nameDirection = View.TEXT_DIRECTION_INHERIT; - if (primaryInfo.nameIsNumber) { + if (primaryInfo.nameIsNumber()) { nameDirection = View.TEXT_DIRECTION_LTR; } contactNameTextView.setTextDirection(nameDirection); @@ -292,23 +292,23 @@ public class ContactGridManager { avatarImageView.setVisibility(View.GONE); } else if (avatarSize > 0 && updateAvatarVisibility()) { boolean hasPhoto = - primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT; + primaryInfo.photo() != null && primaryInfo.photoType() == ContactPhotoType.CONTACT; // Contact has a photo, don't render a letter tile. if (hasPhoto) { avatarImageView.setBackground( DrawableConverter.getRoundedDrawable( - context, primaryInfo.photo, avatarSize, avatarSize)); + context, primaryInfo.photo(), avatarSize, avatarSize)); // Contact has a name, that isn't a number. } else { letterTile.setCanonicalDialerLetterTileDetails( - primaryInfo.name, - primaryInfo.contactInfoLookupKey, + primaryInfo.name(), + primaryInfo.contactInfoLookupKey(), LetterTileDrawable.SHAPE_CIRCLE, LetterTileDrawable.getContactTypeFromPrimitives( primaryCallState.isVoiceMailNumber, - primaryInfo.isSpam, + primaryInfo.isSpam(), primaryCallState.isBusinessNumber, - primaryInfo.numberPresentation, + primaryInfo.numberPresentation(), primaryCallState.isConference)); // By invalidating the avatarImageView we force a redraw of the letter tile. // This is required to properly display the updated letter tile iconography based on the @@ -413,7 +413,7 @@ public class ContactGridManager { deviceNumberTextView.setText( context.getString(R.string.contact_grid_callback_number, primaryCallState.callbackNumber)); deviceNumberTextView.setVisibility(View.VISIBLE); - if (primaryInfo.shouldShowLocation) { + if (primaryInfo.shouldShowLocation()) { deviceNumberDivider.setVisibility(View.VISIBLE); } } diff --git a/java/com/android/incallui/contactgrid/TopRow.java b/java/com/android/incallui/contactgrid/TopRow.java index 556b11ba0..5eb38f98c 100644 --- a/java/com/android/incallui/contactgrid/TopRow.java +++ b/java/com/android/incallui/contactgrid/TopRow.java @@ -81,7 +81,7 @@ public class TopRow { // Show phone number if it's not displayed in name (center row) or location field (bottom // row). if (shouldShowNumber(primaryInfo, true /* isIncoming */)) { - label = TextUtils.concat(label, " ", spanDisplayNumber(primaryInfo.number)); + label = TextUtils.concat(label, " ", spanDisplayNumber(primaryInfo.number())); } } } else if (VideoUtils.hasSentVideoUpgradeRequest(state.sessionModificationState) @@ -97,7 +97,7 @@ public class TopRow { label = context.getString(R.string.incall_remotely_held); } else if (state.state == State.ACTIVE && shouldShowNumber(primaryInfo, false /* isIncoming */)) { - label = spanDisplayNumber(primaryInfo.number); + label = spanDisplayNumber(primaryInfo.number()); } else if (state.state == State.CALL_PENDING && !TextUtils.isEmpty(state.customLabel)) { label = state.customLabel; } else { @@ -115,18 +115,18 @@ public class TopRow { } private static boolean shouldShowNumber(PrimaryInfo primaryInfo, boolean isIncoming) { - if (primaryInfo.nameIsNumber) { + if (primaryInfo.nameIsNumber()) { return false; } // Don't show number since it's already shown in bottom row of incoming screen if there is no // location info. - if (primaryInfo.location == null && isIncoming) { + if (primaryInfo.location() == null && isIncoming) { return false; } - if (primaryInfo.isLocalContact && !isIncoming) { + if (primaryInfo.isLocalContact() && !isIncoming) { return false; } - if (TextUtils.isEmpty(primaryInfo.number)) { + if (TextUtils.isEmpty(primaryInfo.number())) { return false; } return true; diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java index a4dcd72a8..9a6d1c41e 100644 --- a/java/com/android/incallui/incall/impl/InCallFragment.java +++ b/java/com/android/incallui/incall/impl/InCallFragment.java @@ -253,10 +253,10 @@ public class InCallFragment extends Fragment @Override public void setPrimary(@NonNull PrimaryInfo primaryInfo) { LogUtil.i("InCallFragment.setPrimary", primaryInfo.toString()); - setAdapterMedia(primaryInfo.multimediaData, primaryInfo.showInCallButtonGrid); + setAdapterMedia(primaryInfo.multimediaData(), primaryInfo.showInCallButtonGrid()); contactGridManager.setPrimary(primaryInfo); - if (primaryInfo.shouldShowLocation) { + if (primaryInfo.shouldShowLocation()) { // Hide the avatar to make room for location contactGridManager.setAvatarHidden(true); diff --git a/java/com/android/incallui/incall/protocol/PrimaryInfo.java b/java/com/android/incallui/incall/protocol/PrimaryInfo.java index f7457c3ff..63dc39ed3 100644 --- a/java/com/android/incallui/incall/protocol/PrimaryInfo.java +++ b/java/com/android/incallui/incall/protocol/PrimaryInfo.java @@ -20,91 +20,116 @@ import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import com.android.dialer.common.LogUtil; import com.android.dialer.multimedia.MultimediaData; +import com.google.auto.value.AutoValue; import java.util.Locale; /** Information about the primary call. */ -public class PrimaryInfo { - @Nullable public final String number; - @Nullable public final String name; - public final boolean nameIsNumber; +@AutoValue +public abstract class PrimaryInfo { + @Nullable + public abstract String number(); + + @Nullable + public abstract String name(); + + public abstract boolean nameIsNumber(); // This is from contacts and shows the type of number. For example, "Mobile". - @Nullable public final String label; - @Nullable public final String location; - @Nullable public final Drawable photo; - @ContactPhotoType public final int photoType; - public final boolean isSipCall; - public final boolean isContactPhotoShown; - public final boolean isWorkCall; - public final boolean isSpam; - public final boolean isLocalContact; - public final boolean answeringDisconnectsOngoingCall; - public final boolean shouldShowLocation; + @Nullable + public abstract String label(); + + @Nullable + public abstract String location(); + + @Nullable + public abstract Drawable photo(); + + @ContactPhotoType + public abstract int photoType(); + + public abstract boolean isSipCall(); + + public abstract boolean isContactPhotoShown(); + + public abstract boolean isWorkCall(); + + public abstract boolean isSpam(); + + public abstract boolean isLocalContact(); + + public abstract boolean answeringDisconnectsOngoingCall(); + + public abstract boolean shouldShowLocation(); // Used for consistent LetterTile coloring. - @Nullable public final String contactInfoLookupKey; - @Nullable public final MultimediaData multimediaData; - public final boolean showInCallButtonGrid; - public final int numberPresentation; - - // TODO: Convert to autovalue. a bug - public static PrimaryInfo createEmptyPrimaryInfo() { - return new PrimaryInfo( - null, - null, - false, - null, - null, - null, - ContactPhotoType.DEFAULT_PLACEHOLDER, - false, - false, - false, - false, - false, - false, - false, - null, - null, - true, - -1); + @Nullable + public abstract String contactInfoLookupKey(); + + @Nullable + public abstract MultimediaData multimediaData(); + + public abstract boolean showInCallButtonGrid(); + + public abstract int numberPresentation(); + + public static Builder builder() { + return new AutoValue_PrimaryInfo.Builder(); + } + /** Builder class for primary call info. */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setNumber(String number); + + public abstract Builder setName(String name); + + public abstract Builder setNameIsNumber(boolean nameIsNumber); + + public abstract Builder setLabel(String label); + + public abstract Builder setLocation(String location); + + public abstract Builder setPhoto(Drawable photo); + + public abstract Builder setPhotoType(@ContactPhotoType int photoType); + + public abstract Builder setIsSipCall(boolean isSipCall); + + public abstract Builder setIsContactPhotoShown(boolean isContactPhotoShown); + + public abstract Builder setIsWorkCall(boolean isWorkCall); + + public abstract Builder setIsSpam(boolean isSpam); + + public abstract Builder setIsLocalContact(boolean isLocalContact); + + public abstract Builder setAnsweringDisconnectsOngoingCall( + boolean answeringDisconnectsOngoingCall); + + public abstract Builder setShouldShowLocation(boolean shouldShowLocation); + + public abstract Builder setContactInfoLookupKey(String contactInfoLookupKey); + + public abstract Builder setMultimediaData(MultimediaData multimediaData); + + public abstract Builder setShowInCallButtonGrid(boolean showInCallButtonGrid); + + public abstract Builder setNumberPresentation(int numberPresentation); + + public abstract PrimaryInfo build(); } - public PrimaryInfo( - @Nullable String number, - @Nullable String name, - boolean nameIsNumber, - @Nullable String location, - @Nullable String label, - @Nullable Drawable photo, - @ContactPhotoType int phototType, - boolean isSipCall, - boolean isContactPhotoShown, - boolean isWorkCall, - boolean isSpam, - boolean isLocalContact, - boolean answeringDisconnectsOngoingCall, - boolean shouldShowLocation, - @Nullable String contactInfoLookupKey, - @Nullable MultimediaData multimediaData, - boolean showInCallButtonGrid, - int numberPresentation) { - this.number = number; - this.name = name; - this.nameIsNumber = nameIsNumber; - this.location = location; - this.label = label; - this.photo = photo; - this.photoType = phototType; - this.isSipCall = isSipCall; - this.isContactPhotoShown = isContactPhotoShown; - this.isWorkCall = isWorkCall; - this.isSpam = isSpam; - this.isLocalContact = isLocalContact; - this.answeringDisconnectsOngoingCall = answeringDisconnectsOngoingCall; - this.shouldShowLocation = shouldShowLocation; - this.contactInfoLookupKey = contactInfoLookupKey; - this.multimediaData = multimediaData; - this.showInCallButtonGrid = showInCallButtonGrid; - this.numberPresentation = numberPresentation; + public static PrimaryInfo empty() { + return PrimaryInfo.builder() + .setNameIsNumber(false) + .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER) + .setIsSipCall(false) + .setIsContactPhotoShown(false) + .setIsWorkCall(false) + .setIsSpam(false) + .setIsLocalContact(false) + .setAnsweringDisconnectsOngoingCall(false) + .setShouldShowLocation(false) + .setShowInCallButtonGrid(true) + .setNumberPresentation(-1) + .build(); } @Override @@ -113,13 +138,13 @@ public class PrimaryInfo { Locale.US, "PrimaryInfo, number: %s, name: %s, location: %s, label: %s, " + "photo: %s, photoType: %d, isPhotoVisible: %b, MultimediaData: %s", - LogUtil.sanitizePhoneNumber(number), - LogUtil.sanitizePii(name), - LogUtil.sanitizePii(location), - label, - photo, - photoType, - isContactPhotoShown, - multimediaData); + LogUtil.sanitizePhoneNumber(number()), + LogUtil.sanitizePii(name()), + LogUtil.sanitizePii(location()), + label(), + photo(), + photoType(), + isContactPhotoShown(), + multimediaData()); } } diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index ba99b2bf6..a33029501 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -277,7 +277,7 @@ public class RttChatFragment extends Fragment @Override public void setPrimary(@NonNull PrimaryInfo primaryInfo) { LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString()); - nameTextView.setText(primaryInfo.name); + nameTextView.setText(primaryInfo.name()); } @Override -- cgit v1.2.3 From 9e3a39d9427525684d9c106587a5fa077b110812 Mon Sep 17 00:00:00 2001 From: linyuh Date: Wed, 21 Feb 2018 09:53:13 -0800 Subject: Make MarkDirtyObserver available to all call log data sources & PhoneLookups. Bug: 73347270 Test: Existing tests PiperOrigin-RevId: 186475562 Change-Id: I622b52f4d74b26fd084b6588da6321c46458aa9d --- .../systemcalllog/SystemCallLogDataSource.java | 54 ++++------------------ .../dialer/calllog/observer/MarkDirtyObserver.java | 53 +++++++++++++++++++++ .../DialerBlockedNumberPhoneLookup.java | 1 + .../blockednumber/MarkDirtyObserver.java | 50 -------------------- .../SystemBlockedNumberPhoneLookup.java | 1 + 5 files changed, 65 insertions(+), 94 deletions(-) create mode 100644 java/com/android/dialer/calllog/observer/MarkDirtyObserver.java delete mode 100644 java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index e9f7c00bf..6daa5e757 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -20,13 +20,10 @@ import android.Manifest.permission; import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; -import android.database.ContentObserver; import android.database.Cursor; -import android.net.Uri; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.os.Handler; import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.VoicemailContract; @@ -47,12 +44,11 @@ import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.Ann import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.CallLogMutations; import com.android.dialer.calllog.datasources.util.RowCombiner; -import com.android.dialer.calllog.notifier.RefreshAnnotatedCallLogNotifier; +import com.android.dialer.calllog.observer.MarkDirtyObserver; import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; -import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.compat.android.provider.VoicemailCompat; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.android.dialer.storage.StorageComponent; @@ -80,16 +76,16 @@ public class SystemCallLogDataSource implements CallLogDataSource { static final String PREF_LAST_TIMESTAMP_PROCESSED = "systemCallLogLastTimestampProcessed"; private final ListeningExecutorService backgroundExecutorService; - private final RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier; + private final MarkDirtyObserver markDirtyObserver; @Nullable private Long lastTimestampProcessed; @Inject SystemCallLogDataSource( @BackgroundExecutor ListeningExecutorService backgroundExecutorService, - RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier) { + MarkDirtyObserver markDirtyObserver) { this.backgroundExecutorService = backgroundExecutorService; - this.refreshAnnotatedCallLogNotifier = refreshAnnotatedCallLogNotifier; + this.markDirtyObserver = markDirtyObserver; } @MainThread @@ -105,12 +101,13 @@ public class SystemCallLogDataSource implements CallLogDataSource { } // TODO(zachh): Need to somehow register observers if user enables permission after launch? - CallLogObserver callLogObserver = - new CallLogObserver(ThreadUtil.getUiThreadHandler(), refreshAnnotatedCallLogNotifier); - + // The system call log has a last updated timestamp, but deletes are physical (the "deleted" + // column is unused). This means that we can't detect deletes without scanning the entire table, + // which would be too slow. So, we just rely on content observers to trigger rebuilds when any + // change is made to the system call log. appContext .getContentResolver() - .registerContentObserver(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, true, callLogObserver); + .registerContentObserver(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, true, markDirtyObserver); if (!PermissionsUtil.hasAddVoicemailPermissions(appContext)) { LogUtil.i("SystemCallLogDataSource.registerContentObservers", "no add voicemail permissions"); @@ -119,7 +116,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { // TODO(uabdullah): Need to somehow register observers if user enables permission after launch? appContext .getContentResolver() - .registerContentObserver(VoicemailContract.Status.CONTENT_URI, true, callLogObserver); + .registerContentObserver(VoicemailContract.Status.CONTENT_URI, true, markDirtyObserver); } @Override @@ -527,35 +524,4 @@ public class SystemCallLogDataSource implements CallLogDataSource { } return ids; } - - // TODO(a bug): Consider replacing it with MarkDirtyObserver. - private static class CallLogObserver extends ContentObserver { - private final RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier; - - CallLogObserver( - Handler handler, RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier) { - super(handler); - this.refreshAnnotatedCallLogNotifier = refreshAnnotatedCallLogNotifier; - } - - @MainThread - @Override - public void onChange(boolean selfChange, Uri uri) { - Assert.isMainThread(); - LogUtil.i( - "SystemCallLogDataSource.CallLogObserver.onChange", - "Uri:%s, SelfChange:%b", - String.valueOf(uri), - selfChange); - super.onChange(selfChange, uri); - - /* - * The system call log has a last updated timestamp, but deletes are physical (the "deleted" - * column is unused). This means that we can't detect deletes without scanning the entire - * table, which would be too slow. So, we just rely on content observers to trigger rebuilds - * when any change is made to the system call log. - */ - refreshAnnotatedCallLogNotifier.markDirtyAndNotify(); - } - } } diff --git a/java/com/android/dialer/calllog/observer/MarkDirtyObserver.java b/java/com/android/dialer/calllog/observer/MarkDirtyObserver.java new file mode 100644 index 000000000..8ab1974bc --- /dev/null +++ b/java/com/android/dialer/calllog/observer/MarkDirtyObserver.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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.calllog.observer; + +import android.database.ContentObserver; +import android.net.Uri; +import android.support.annotation.MainThread; +import android.support.annotation.VisibleForTesting; +import com.android.dialer.calllog.notifier.RefreshAnnotatedCallLogNotifier; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; +import javax.inject.Inject; + +/** + * Mark the annotated call log as dirty and notify that a refresh is in order when the content + * changes. + */ +public final class MarkDirtyObserver extends ContentObserver { + + private final RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier; + + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + @Inject + public MarkDirtyObserver(RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier) { + super(ThreadUtil.getUiThreadHandler()); + this.refreshAnnotatedCallLogNotifier = refreshAnnotatedCallLogNotifier; + } + + @MainThread + @Override + public void onChange(boolean selfChange, Uri uri) { + Assert.isMainThread(); + LogUtil.i( + "MarkDirtyObserver.onChange", "Uri:%s, SelfChange:%b", String.valueOf(uri), selfChange); + + refreshAnnotatedCallLogNotifier.markDirtyAndNotify(); + } +} diff --git a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java index 2d019c8c2..b6b02e135 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java @@ -22,6 +22,7 @@ import android.support.annotation.WorkerThread; import android.util.ArraySet; import com.android.dialer.DialerPhoneNumber; import com.android.dialer.blocking.FilteredNumberCompat; +import com.android.dialer.calllog.observer.MarkDirtyObserver; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; diff --git a/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java b/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java deleted file mode 100644 index 9f72ba48b..000000000 --- a/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2018 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.phonelookup.blockednumber; - -import android.database.ContentObserver; -import android.net.Uri; -import android.support.annotation.MainThread; -import com.android.dialer.calllog.notifier.RefreshAnnotatedCallLogNotifier; -import com.android.dialer.common.Assert; -import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.ThreadUtil; -import javax.inject.Inject; - -/** - * Mark the annotated call log as dirty and notify that a refresh is in order when the content - * changes. - */ -// TODO(a bug): Consider making this class available to all data sources and PhoneLookups. -class MarkDirtyObserver extends ContentObserver { - - private final RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier; - - @Inject - MarkDirtyObserver(RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier) { - super(ThreadUtil.getUiThreadHandler()); - this.refreshAnnotatedCallLogNotifier = refreshAnnotatedCallLogNotifier; - } - - @MainThread - @Override - public void onChange(boolean selfChange, Uri uri) { - Assert.isMainThread(); - LogUtil.enterBlock("SystemBlockedNumberPhoneLookup.FilteredNumberObserver.onChange"); - refreshAnnotatedCallLogNotifier.markDirtyAndNotify(); - } -} diff --git a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java index f35b3e131..d791e9b9e 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java @@ -27,6 +27,7 @@ import android.support.annotation.WorkerThread; import android.util.ArraySet; import com.android.dialer.DialerPhoneNumber; import com.android.dialer.blocking.FilteredNumberCompat; +import com.android.dialer.calllog.observer.MarkDirtyObserver; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; -- cgit v1.2.3 From 99606aa5368e1eea30730a899aacdfdd7a5fd61a Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Wed, 21 Feb 2018 10:40:41 -0800 Subject: Fixed some Dialer theming bugs in NUI. - URL links are now blue - Raised buttons are now blue w/ white text - android nav bar is white - call log actions/icons are the correct colors - search bar hint text is the correct color Bug: 72525324 Test: screenshot PiperOrigin-RevId: 186484088 Change-Id: I1863a28e1ea9bf16863f42299afca319a6bdcc97 --- .../android/dialer/main/impl/AndroidManifest.xml | 2 +- .../dialer/main/impl/res/values-v27/styles.xml | 26 ++++++++++++++++++++++ .../android/dialer/main/impl/res/values/styles.xml | 23 ++++++++++++++++++- .../com/android/dialer/theme/res/values/styles.xml | 2 +- 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 java/com/android/dialer/main/impl/res/values-v27/styles.xml diff --git a/java/com/android/dialer/main/impl/AndroidManifest.xml b/java/com/android/dialer/main/impl/AndroidManifest.xml index 6b7475f97..972c9d929 100644 --- a/java/com/android/dialer/main/impl/AndroidManifest.xml +++ b/java/com/android/dialer/main/impl/AndroidManifest.xml @@ -29,7 +29,7 @@ android:launchMode="singleTask" android:name="com.android.dialer.main.impl.MainActivity" android:resizeableActivity="true" - android:theme="@style/NuiMainActivityTheme" + android:theme="@style/NuiActivityTheme" android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"> diff --git a/java/com/android/dialer/main/impl/res/values-v27/styles.xml b/java/com/android/dialer/main/impl/res/values-v27/styles.xml new file mode 100644 index 000000000..c91cba245 --- /dev/null +++ b/java/com/android/dialer/main/impl/res/values-v27/styles.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/main/impl/res/values/styles.xml b/java/com/android/dialer/main/impl/res/values/styles.xml index 2865f2587..47fdbac93 100644 --- a/java/com/android/dialer/main/impl/res/values/styles.xml +++ b/java/com/android/dialer/main/impl/res/values/styles.xml @@ -15,14 +15,35 @@ ~ limitations under the License --> -