From ea0236d0ec97ccc226401fa0982578b93905f6ce Mon Sep 17 00:00:00 2001 From: Anthony Tripaldi Date: Mon, 18 Mar 2019 11:47:52 -0400 Subject: [PATCH] Add tool tip view for first run of volume dialog to notify users of odi captions functionality. Bug:128970574 Change-Id: I4710fd1d84f4116e003c14720a2ce55c5b3ff899 Test: manual --- .../plugins/VolumeDialogController.java | 5 +- .../res/drawable/ic_remove_no_shadow.xml | 28 ++++ .../res/drawable/ic_volume_odi_captions.xml | 21 ++- .../ic_volume_odi_captions_disabled.xml | 17 +- .../drawable/volume_tool_tip_rounded_bg.xml | 19 +++ .../res/layout-land/volume_dialog.xml | 143 ++++++++++++++++ .../SystemUI/res/layout/volume_dialog.xml | 34 +++- .../SystemUI/res/layout/volume_dialog_row.xml | 2 +- .../res/layout/volume_tool_tip_view.xml | 63 +++++++ packages/SystemUI/res/values-land/dimens.xml | 3 + packages/SystemUI/res/values/dimens.xml | 6 + packages/SystemUI/res/values/strings.xml | 6 + packages/SystemUI/res/values/styles.xml | 4 + .../src/com/android/systemui/Prefs.java | 4 +- .../systemui/recents/TriangleShape.java | 18 ++ .../com/android/systemui/volume/Events.java | 4 +- .../volume/VolumeDialogControllerImpl.java | 23 +-- .../systemui/volume/VolumeDialogImpl.java | 154 ++++++++++++++---- .../systemui/volume/VolumeToolTipView.java | 76 +++++++++ .../systemui/volume/VolumeDialogImplTest.java | 21 +++ 20 files changed, 593 insertions(+), 58 deletions(-) create mode 100644 packages/SystemUI/res/drawable/ic_remove_no_shadow.xml create mode 100644 packages/SystemUI/res/drawable/volume_tool_tip_rounded_bg.xml create mode 100644 packages/SystemUI/res/layout-land/volume_dialog.xml create mode 100644 packages/SystemUI/res/layout/volume_tool_tip_view.xml create mode 100644 packages/SystemUI/src/com/android/systemui/volume/VolumeToolTipView.java diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java index 2b10ccb4ac1a2..3d2f570bde873 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java @@ -60,7 +60,8 @@ public interface VolumeDialogController { boolean areCaptionsEnabled(); void setCaptionsEnabled(boolean isEnabled); - void getCaptionsComponentState(); + + void getCaptionsComponentState(boolean fromTooltip); @ProvidesInterface(version = StreamState.VERSION) public static final class StreamState { @@ -190,6 +191,6 @@ public interface VolumeDialogController { void onScreenOff(); void onShowSafetyWarning(int flags); void onAccessibilityModeChanged(Boolean showA11yStream); - void onCaptionComponentStateChanged(Boolean isComponentEnabled); + void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip); } } diff --git a/packages/SystemUI/res/drawable/ic_remove_no_shadow.xml b/packages/SystemUI/res/drawable/ic_remove_no_shadow.xml new file mode 100644 index 0000000000000..bed06b58e823d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_remove_no_shadow.xml @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_volume_odi_captions.xml b/packages/SystemUI/res/drawable/ic_volume_odi_captions.xml index 9b907290d0e95..675aee9cd1dd6 100644 --- a/packages/SystemUI/res/drawable/ic_volume_odi_captions.xml +++ b/packages/SystemUI/res/drawable/ic_volume_odi_captions.xml @@ -19,6 +19,23 @@ android:viewportWidth="24" android:viewportHeight="24"> + android:pathData="M16,12h2v2h-2z" + android:fillColor="#1A73E8" + android:fillType="nonZero"/> + + + + diff --git a/packages/SystemUI/res/drawable/ic_volume_odi_captions_disabled.xml b/packages/SystemUI/res/drawable/ic_volume_odi_captions_disabled.xml index f3d8d3b9c2e24..e818455950de8 100644 --- a/packages/SystemUI/res/drawable/ic_volume_odi_captions_disabled.xml +++ b/packages/SystemUI/res/drawable/ic_volume_odi_captions_disabled.xml @@ -19,6 +19,19 @@ android:viewportWidth="24" android:viewportHeight="24"> + android:pathData="M2.07,0.64L22,20.59L20.6,22L16.6,18L6,18L2,22L2,4C2.0006,3.8236 2.0276,3.6484 2.08,3.48L0.66,2.05L2.07,0.64ZM5.17,16L14.6,16L12.6,14L6,14L6,12L10.6,12L8,9.4L8,10L6,10L6,8L6.6,8L4,5.4L4,16L5.17,16Z" + android:fillColor="#1A73E8" + android:fillType="nonZero"/> + + + diff --git a/packages/SystemUI/res/drawable/volume_tool_tip_rounded_bg.xml b/packages/SystemUI/res/drawable/volume_tool_tip_rounded_bg.xml new file mode 100644 index 0000000000000..22e8c48b6e6e5 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_tool_tip_rounded_bg.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml new file mode 100644 index 0000000000000..c1e74ef026237 --- /dev/null +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 130be89914a80..1d0a24274b125 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -15,32 +15,38 @@ --> + android:theme="@style/volume_dialog_theme"> + + android:clipToPadding="false"> + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index 6128da8627a95..b9efc5be70c1d 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -20,7 +20,7 @@ android:layout_width="@dimen/volume_dialog_panel_width" android:clipChildren="false" android:clipToPadding="false" - android:theme="@style/qs_theme"> + android:theme="@style/volume_dialog_theme"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index bb0c6f6acb064..77e79c96b6a7d 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -34,4 +34,7 @@ true 0dp 0dp + + 136dp + 12dp diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 30cbaaad33df9..27cebcc112ac3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -351,6 +351,12 @@ 9dp + 76dp + + 32dp + + 2dp + 0x31 diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index da433913bfa33..57c65fe795e8d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1307,6 +1307,12 @@ Collapse + + Automatically caption media + + + Close captions tip + Switch output device diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 0f5df45a0effd..1c13750a13400 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -319,6 +319,10 @@ true + + diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index d437555c62a47..27975707bd8dc 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -57,7 +57,8 @@ public final class Prefs { Key.SEEN_RINGER_GUIDANCE_COUNT, Key.QS_HAS_TURNED_OFF_MOBILE_DATA, Key.TOUCHED_RINGER_TOGGLE, - Key.QUICK_STEP_INTERACTION_FLAGS + Key.QUICK_STEP_INTERACTION_FLAGS, + Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP }) public @interface Key { @Deprecated @@ -103,6 +104,7 @@ public final class Prefs { String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData"; String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle"; String QUICK_STEP_INTERACTION_FLAGS = "QuickStepInteractionFlags"; + String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java index ef4e19559f1f1..de8e6ea4a0cbd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java +++ b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java @@ -50,6 +50,24 @@ public class TriangleShape extends PathShape { return new TriangleShape(triangularPath, width, height); } + /** Create an arrow TriangleShape that points to the left or the right */ + public static TriangleShape createHorizontal( + float width, float height, boolean isPointingLeft) { + Path triangularPath = new Path(); + if (isPointingLeft) { + triangularPath.moveTo(0, height / 2); + triangularPath.lineTo(width, height); + triangularPath.lineTo(width, 0); + triangularPath.close(); + } else { + triangularPath.moveTo(0, height); + triangularPath.lineTo(width, height / 2); + triangularPath.lineTo(0, 0); + triangularPath.close(); + } + return new TriangleShape(triangularPath, width, height); + } + @Override public void getOutline(@NonNull Outline outline) { outline.setConvexPath(mTriangularPath); diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index 6e740b8536b3e..2a84c5d4d44d8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -56,6 +56,7 @@ public class Events { public static final int EVENT_SHOW_USB_OVERHEAT_ALARM = 19; // (reason|int) (keyguard|bool) public static final int EVENT_DISMISS_USB_OVERHEAT_ALARM = 20; // (reason|int) (keyguard|bool) public static final int EVENT_ODI_CAPTIONS_CLICK = 21; + public static final int EVENT_ODI_CAPTIONS_TOOLTIP_CLICK = 22; private static final String[] EVENT_TAGS = { "show_dialog", @@ -79,7 +80,8 @@ public class Events { "ringer_toggle", "show_usb_overheat_alarm", "dismiss_usb_overheat_alarm", - "odi_captions_click" + "odi_captions_click", + "odi_captions_tooltip_click" }; public static final int DISMISS_REASON_UNKNOWN = 0; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 9192a25dcd3b9..2fa8889a02f91 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -282,9 +282,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa Settings.Secure.ODI_CAPTIONS_ENABLED, isEnabled ? 1 : 0); } - public void getCaptionsComponentState() { + public void getCaptionsComponentState(boolean fromTooltip) { if (mDestroyed) return; - mWorker.sendEmptyMessage(W.GET_CAPTIONS_COMPONENT_STATE); + mWorker.obtainMessage(W.GET_CAPTIONS_COMPONENT_STATE, fromTooltip).sendToTarget(); } public void notifyVisible(boolean visible) { @@ -382,13 +382,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } } - private void onGetCaptionsComponentStateW() { + private void onGetCaptionsComponentStateW(boolean fromTooltip) { try { String componentNameString = mContext.getString( com.android.internal.R.string.config_defaultSystemCaptionsService); if (TextUtils.isEmpty(componentNameString)) { // component doesn't exist - mCallbacks.onCaptionComponentStateChanged(false); + mCallbacks.onCaptionComponentStateChanged(false, fromTooltip); return; } @@ -399,18 +399,18 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa ComponentName componentName = ComponentName.unflattenFromString(componentNameString); if (componentName == null) { - mCallbacks.onCaptionComponentStateChanged(false); + mCallbacks.onCaptionComponentStateChanged(false, fromTooltip); return; } PackageManager packageManager = mContext.getPackageManager(); mCallbacks.onCaptionComponentStateChanged( packageManager.getComponentEnabledSetting(componentName) - == PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + == PackageManager.COMPONENT_ENABLED_STATE_ENABLED, fromTooltip); } catch (Exception ex) { Log.e(TAG, "isCaptionsServiceEnabled failed to check for captions component", ex); - mCallbacks.onCaptionComponentStateChanged(false); + mCallbacks.onCaptionComponentStateChanged(false, fromTooltip); } } @@ -790,7 +790,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break; case USER_ACTIVITY: onUserActivityW(); break; case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break; - case GET_CAPTIONS_COMPONENT_STATE: onGetCaptionsComponentStateW(); break; + case GET_CAPTIONS_COMPONENT_STATE: + onGetCaptionsComponentStateW((Boolean) msg.obj); break; case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj); } } @@ -933,11 +934,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } @Override - public void onCaptionComponentStateChanged(Boolean isComponentEnabled) { + public void onCaptionComponentStateChanged( + Boolean isComponentEnabled, Boolean fromTooltip) { boolean componentEnabled = isComponentEnabled == null ? false : isComponentEnabled; for (final Map.Entry entry : mCallbackMap.entrySet()) { entry.getValue().post( - () -> entry.getKey().onCaptionComponentStateChanged(componentEnabled)); + () -> entry.getKey().onCaptionComponentStateChanged( + componentEnabled, fromTooltip)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 398b30963da75..cdda216c5220a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -28,6 +28,7 @@ import static android.media.AudioManager.STREAM_VOICE_CALL; import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.volume.Events.DISMISS_REASON_ODI_CAPTIONS_CLICKED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -64,11 +65,13 @@ import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; import android.view.ContextThemeWrapper; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.ViewGroup; import android.view.ViewPropertyAnimator; +import android.view.ViewStub; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; @@ -113,7 +116,10 @@ public class VolumeDialogImpl implements VolumeDialog { static final int DIALOG_TIMEOUT_MILLIS = 3000; static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000; + static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; + static final int DIALOG_SHOW_ANIMATION_DURATION = 300; + static final int DIALOG_HIDE_ANIMATION_DURATION = 250; private final Context mContext; private final H mHandler = new H(); @@ -152,15 +158,21 @@ public class VolumeDialogImpl implements VolumeDialog { private boolean mHovering = false; private boolean mShowActiveStreamOnly; private boolean mConfigChanged = false; + private boolean mHasSeenODICaptionsTooltip; + private ViewStub mODICaptionsTooltipViewStub; + private View mODICaptionsTooltipView = null; public VolumeDialogImpl(Context context) { - mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); + mContext = + new ContextThemeWrapper(context, com.android.systemui.R.style.volume_dialog_theme); mController = Dependency.get(VolumeDialogController.class); mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mShowActiveStreamOnly = showActiveStreamOnly(); + mHasSeenODICaptionsTooltip = + Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); } public void init(int windowType, Callback callback) { @@ -201,8 +213,9 @@ public class VolumeDialogImpl implements VolumeDialog { lp.format = PixelFormat.TRANSLUCENT; lp.setTitle(VolumeDialogImpl.class.getSimpleName()); lp.windowAnimations = -1; + lp.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; mWindow.setAttributes(lp); - mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT); mDialog.setContentView(R.layout.volume_dialog); mDialogView = mDialog.findViewById(R.id.volume_dialog); @@ -213,12 +226,13 @@ public class VolumeDialogImpl implements VolumeDialog { mDialogView.animate() .alpha(1) .translationX(0) - .setDuration(300) + .setDuration(DIALOG_SHOW_ANIMATION_DURATION) .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) .withEndAction(() -> { if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) { if (mRingerIcon != null) { - mRingerIcon.postOnAnimationDelayed(mSinglePress, 1500); + mRingerIcon.postOnAnimationDelayed( + getSinglePressFor(mRingerIcon), 1500); } } }) @@ -233,20 +247,23 @@ public class VolumeDialogImpl implements VolumeDialog { return true; }); - lp = mWindow.getAttributes(); - lp.gravity = ((FrameLayout.LayoutParams) mDialogView.getLayoutParams()).gravity; - mWindow.setAttributes(lp); - mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows); mRinger = mDialog.findViewById(R.id.ringer); if (mRinger != null) { mRingerIcon = mRinger.findViewById(R.id.ringer_icon); mZenIcon = mRinger.findViewById(R.id.dnd_icon); } + mODICaptionsView = mDialog.findViewById(R.id.odi_captions); if (mODICaptionsView != null) { mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); } + mODICaptionsTooltipViewStub = mDialog.findViewById(R.id.odi_captions_tooltip_stub); + if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) { + mDialogView.removeView(mODICaptionsTooltipViewStub); + mODICaptionsTooltipViewStub = null; + } + mSettingsView = mDialog.findViewById(R.id.settings_container); mSettingsIcon = mDialog.findViewById(R.id.settings); @@ -495,10 +512,70 @@ public class VolumeDialogImpl implements VolumeDialog { }); } - mController.getCaptionsComponentState(); + mController.getCaptionsComponentState(false); } - private void updateODICaptionsH(boolean isServiceComponentEnabled) { + private void checkODICaptionsTooltip(boolean fromDismiss) { + if (!mHasSeenODICaptionsTooltip && !fromDismiss && mODICaptionsTooltipViewStub != null) { + mController.getCaptionsComponentState(true); + } else { + if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { + hideCaptionsTooltip(); + } + } + } + + protected void showCaptionsTooltip() { + if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipViewStub != null) { + mODICaptionsTooltipView = mODICaptionsTooltipViewStub.inflate(); + mODICaptionsTooltipView.findViewById(R.id.dismiss).setOnClickListener(v -> { + hideCaptionsTooltip(); + Events.writeEvent(mContext, Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK); + }); + mODICaptionsTooltipViewStub = null; + rescheduleTimeoutH(); + } + + if (mODICaptionsTooltipView != null) { + mODICaptionsTooltipView.setAlpha(0.f); + mODICaptionsTooltipView.animate() + .alpha(1.f) + .setStartDelay(DIALOG_SHOW_ANIMATION_DURATION) + .withEndAction(() -> { + if (D.BUG) Log.d(TAG, "tool:checkODICaptionsTooltip() putBoolean true"); + Prefs.putBoolean(mContext, + Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, true); + mHasSeenODICaptionsTooltip = true; + if (mODICaptionsIcon != null) { + mODICaptionsIcon + .postOnAnimation(getSinglePressFor(mODICaptionsIcon)); + } + }) + .start(); + } + } + + private void hideCaptionsTooltip() { + if (mODICaptionsTooltipView != null) { + mODICaptionsTooltipView.animate().cancel(); + mODICaptionsTooltipView.setAlpha(1.f); + mODICaptionsTooltipView.animate() + .alpha(0.f) + .setStartDelay(0) + .setDuration(DIALOG_HIDE_ANIMATION_DURATION) + .start(); + } + } + + protected void tryToRemoveCaptionsTooltip() { + if (mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { + ViewGroup container = mDialog.findViewById(R.id.volume_dialog_container); + container.removeView(mODICaptionsTooltipView); + mODICaptionsTooltipView = null; + } + } + + private void updateODICaptionsH(boolean isServiceComponentEnabled, boolean fromTooltip) { if (mODICaptionsView != null) { mODICaptionsView.setVisibility(isServiceComponentEnabled ? VISIBLE : GONE); } @@ -506,6 +583,7 @@ public class VolumeDialogImpl implements VolumeDialog { if (!isServiceComponentEnabled) return; updateCaptionsIcon(); + if (fromTooltip) showCaptionsTooltip(); } private void updateCaptionsIcon() { @@ -602,7 +680,8 @@ public class VolumeDialogImpl implements VolumeDialog { mDialog.show(); Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); mController.notifyVisible(true); - mController.getCaptionsComponentState(); + mController.getCaptionsComponentState(false); + checkODICaptionsTooltip(false); } protected void rescheduleTimeoutH() { @@ -625,11 +704,21 @@ public class VolumeDialogImpl implements VolumeDialog { AccessibilityManager.FLAG_CONTENT_TEXT | AccessibilityManager.FLAG_CONTENT_CONTROLS); } + if (!mHasSeenODICaptionsTooltip && mODICaptionsTooltipView != null) { + return mAccessibilityMgr.getRecommendedTimeoutMillis( + DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS, + AccessibilityManager.FLAG_CONTENT_TEXT + | AccessibilityManager.FLAG_CONTENT_CONTROLS); + } return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS, AccessibilityManager.FLAG_CONTENT_CONTROLS); } protected void dismissH(int reason) { + if (D.BUG) { + Log.d(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason] + + " from: " + Debug.getCaller()); + } mHandler.removeMessages(H.DISMISS); mHandler.removeMessages(H.SHOW); mDialogView.animate().cancel(); @@ -642,14 +731,15 @@ public class VolumeDialogImpl implements VolumeDialog { mDialogView.setAlpha(1); ViewPropertyAnimator animator = mDialogView.animate() .alpha(0) - .setDuration(250) + .setDuration(DIALOG_HIDE_ANIMATION_DURATION) .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) .withEndAction(() -> mHandler.postDelayed(() -> { - if (D.BUG) Log.d(TAG, "mDialog.dismiss()"); mDialog.dismiss(); + tryToRemoveCaptionsTooltip(); }, 50)); if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2); animator.start(); + checkODICaptionsTooltip(true); mController.notifyVisible(false); synchronized (mSafetyWarningLock) { if (mSafetyWarning != null) { @@ -822,6 +912,7 @@ public class VolumeDialogImpl implements VolumeDialog { } protected void onStateChangedH(State state) { + if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString()); if (mState != null && state != null && mState.ringerModeInternal != state.ringerModeInternal && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { @@ -847,7 +938,7 @@ public class VolumeDialogImpl implements VolumeDialog { mActiveStream = state.activeStream; VolumeRow activeRow = getActiveRow(); updateRowsH(activeRow); - rescheduleTimeoutH(); + if (mShowing) rescheduleTimeoutH(); } for (VolumeRow row : mRows) { updateVolumeRowH(row); @@ -1114,24 +1205,22 @@ public class VolumeDialogImpl implements VolumeDialog { } } - private Runnable mSinglePress = new Runnable() { - @Override - public void run() { - if (mRingerIcon != null) { - mRingerIcon.setPressed(true); - mRingerIcon.postOnAnimationDelayed(mSingleUnpress, 200); + private Runnable getSinglePressFor(ImageButton button) { + return () -> { + if (button != null) { + button.setPressed(true); + button.postOnAnimationDelayed(getSingleUnpressFor(button), 200); } - } - }; + }; + } - private Runnable mSingleUnpress = new Runnable() { - @Override - public void run() { - if (mRingerIcon != null) { - mRingerIcon.setPressed(false); + private Runnable getSingleUnpressFor(ImageButton button) { + return () -> { + if (button != null) { + button.setPressed(false); } - } - }; + }; + } private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @@ -1198,8 +1287,9 @@ public class VolumeDialogImpl implements VolumeDialog { } @Override - public void onCaptionComponentStateChanged(Boolean isComponentEnabled) { - updateODICaptionsH(isComponentEnabled); + public void onCaptionComponentStateChanged( + Boolean isComponentEnabled, Boolean fromTooltip) { + updateODICaptionsH(isComponentEnabled, fromTooltip); } }; @@ -1232,7 +1322,7 @@ public class VolumeDialogImpl implements VolumeDialog { private final class CustomDialog extends Dialog implements DialogInterface { public CustomDialog(Context context) { - super(context, com.android.systemui.R.style.qs_theme); + super(context, com.android.systemui.R.style.volume_dialog_theme); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeToolTipView.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeToolTipView.java new file mode 100644 index 0000000000000..1de55856f9428 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeToolTipView.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019 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.systemui.volume; + +import android.content.Context; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.drawable.ShapeDrawable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import androidx.core.content.ContextCompat; + +import com.android.systemui.R; +import com.android.systemui.recents.TriangleShape; + +/** + * Tool tip view that draws an arrow that points to the volume dialog. + */ +public class VolumeToolTipView extends LinearLayout { + public VolumeToolTipView(Context context) { + super(context); + } + + public VolumeToolTipView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public VolumeToolTipView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public VolumeToolTipView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + drawArrow(); + } + + private void drawArrow() { + View arrowView = findViewById(R.id.arrow); + ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams(); + ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.createHorizontal( + arrowLp.width, arrowLp.height, false)); + Paint arrowPaint = arrowDrawable.getPaint(); + TypedValue typedValue = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); + arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId)); + // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. + arrowPaint.setPathEffect(new CornerPathEffect( + getResources().getDimension(R.dimen.volume_tool_tip_arrow_corner_radius))); + arrowView.setBackground(arrowDrawable); + + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 617c17e484524..f3cdbf7409c1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -117,6 +117,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { AccessibilityManager.FLAG_CONTENT_CONTROLS); } + @Test + public void testComputeTimeout_tooltip() { + Mockito.reset(mAccessibilityMgr); + mDialog.showCaptionsTooltip(); + verify(mAccessibilityMgr).getRecommendedTimeoutMillis( + VolumeDialogImpl.DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS, + AccessibilityManager.FLAG_CONTENT_CONTROLS + | AccessibilityManager.FLAG_CONTENT_TEXT); + } + + @Test public void testComputeTimeout_withHovering() { Mockito.reset(mAccessibilityMgr); @@ -146,6 +157,16 @@ public class VolumeDialogImplTest extends SysuiTestCase { | AccessibilityManager.FLAG_CONTENT_CONTROLS); } + @Test + public void testComputeTimeout_standard() { + Mockito.reset(mAccessibilityMgr); + mDialog.tryToRemoveCaptionsTooltip(); + mDialog.rescheduleTimeoutH(); + verify(mAccessibilityMgr).getRecommendedTimeoutMillis( + VolumeDialogImpl.DIALOG_TIMEOUT_MILLIS, + AccessibilityManager.FLAG_CONTENT_CONTROLS); + } + /* @Test public void testContentDescriptions() {