Add tool tip view for first run of volume dialog to notify users of odi

captions functionality.

Bug:128970574
Change-Id: I4710fd1d84f4116e003c14720a2ce55c5b3ff899
Test: manual
This commit is contained in:
Anthony Tripaldi
2019-03-18 11:47:52 -04:00
parent 4c005528df
commit ea0236d0ec
20 changed files with 593 additions and 58 deletions

View File

@@ -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);
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?android:attr/textColorPrimary" >
<path
android:fillColor="#FFFFFFFF"
android:pathData="M13.41,12l5.29-5.29c0.39-0.39,0.39-1.02,0-1.41c-0.39-0.39-1.02-0.39-1.41,0L12,10.59L6.71,
5.29c-0.39-0.39-1.02-0.39-1.41,0c-0.39,0.39-0.39,1.02,0,1.41L10.59,12l-5.29,5.29c-0.39,0.39-0.39,1.02,
0,1.41c0.39,0.39,1.02,0.39,1.41,0L12,13.41l5.29,5.29c0.39,0.39,1.02,0.39,1.41,0c0.39-0.39,0.39-1.02,0-1.41L13.41,12z"/>
</vector>

View File

@@ -19,6 +19,23 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20,4C21.1,4 22,4.9 22,6L22,18C22,19.1 21.1,20 20,20L4,20C2.9,20 2,19.1 2,18L2,6C2,4.9 2.9,4 4,4L20,4ZM20,18L20,6L4,6L4,18L20,18ZM6,10L8,10L8,12L6,12L6,10ZM6,14L14,14L14,16L6,16L6,14ZM16,14L18,14L18,16L16,16L16,14ZM10,10L18,10L18,12L10,12L10,10Z"/>
android:pathData="M16,12h2v2h-2z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
<path
android:pathData="M6,12h8v2h-8z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
<path
android:pathData="M20,2C21.1046,2 22,2.8954 22,4L22,16C22,17.1046 21.1046,18 20,18L6,18L2,22L2,4C2,2.8954 2.8954,2 4,2L20,2ZM20,16L20,4L4,4L4,16L20,16Z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
<path
android:pathData="M6,8h2v2h-2z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
<path
android:pathData="M10,8h8v2h-8z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
</vector>

View File

@@ -19,6 +19,19 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.9675,14L18,14L18,15.0275L16.9675,14ZM20,17.0176L20,6L8.9281,6L6.9182,4L20,4C21.1,4 22,4.9 22,6L22,18C22,18.2949 21.9353,18.5755 21.8194,18.8281L20,17.0176ZM12.9478,10L18,10L18,12L14.9576,12L12.9478,10ZM1.2823,0.8824L22.8489,22.4489L21.6337,23.6641L17.9696,20L4,20C2.9,20 2,19.1 2,18L2,6C2,5.4577 2.2188,4.964 2.5724,4.6028L0.0672,2.0975L1.2823,0.8824ZM13.9696,16L6,16L6,14L11.9696,14L8,10.0304L8,12L6,12L6,10L7.9696,10L4,6.0304L4,18L15.9696,18L13.9696,16Z"/>
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"/>
<path
android:pathData="M18,12l-1.74,0l1.74,1.74z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
<path
android:pathData="M18,8l-5.74,0l2,2l3.74,0z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
<path
android:pathData="M20,4L20,15.74L21.53,17.27C21.8296,16.9142 21.9958,16.4651 22,16L22,4C22,2.8954 21.1046,2 20,2L6.26,2L8.26,4L20,4Z"
android:fillColor="#1A73E8"
android:fillType="nonZero"/>
</vector>

View File

@@ -0,0 +1,19 @@
<!--
~ 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
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="?android:attr/colorAccent" />
<corners android:radius="8dp" />
</shape>

View File

@@ -0,0 +1,143 @@
<!--
~ 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
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/volume_dialog_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_gravity="right"
android:background="@android:color/transparent"
android:theme="@style/volume_dialog_theme">
<FrameLayout
android:id="@+id/volume_dialog"
android:minWidth="@dimen/volume_dialog_panel_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_gravity="right"
android:background="@android:color/transparent"
android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right"
android:paddingTop="@dimen/volume_dialog_panel_transparent_padding"
android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding"
android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding"
android:clipToPadding="false">
<FrameLayout
android:id="@+id/ringer"
android:layout_width="@dimen/volume_dialog_ringer_size"
android:layout_height="@dimen/volume_dialog_ringer_size"
android:layout_marginBottom="@dimen/volume_dialog_spacer"
android:gravity="right"
android:layout_gravity="right"
android:translationZ="@dimen/volume_dialog_elevation"
android:clipToPadding="false"
android:background="@drawable/rounded_bg_full">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/ringer_icon"
style="@style/VolumeButtons"
android:background="@drawable/rounded_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tint="@color/accent_tint_color_selector"
android:layout_gravity="center"
android:soundEffectsEnabled="false" />
<include layout="@layout/volume_dnd_icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/volume_dialog_stream_padding"
android:layout_marginTop="6dp"/>
</FrameLayout>
<LinearLayout
android:id="@+id/main"
android:layout_width="wrap_content"
android:minWidth="@dimen/volume_dialog_panel_width"
android:layout_height="wrap_content"
android:layout_marginTop="68dp"
android:gravity="right"
android:layout_gravity="right"
android:orientation="vertical"
android:translationZ="@dimen/volume_dialog_elevation"
android:clipChildren="false"
android:clipToPadding="false"
android:background="@drawable/rounded_bg_full" >
<LinearLayout
android:id="@+id/volume_dialog_rows"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="@dimen/volume_dialog_panel_width"
android:gravity="center"
android:orientation="horizontal"
android:paddingRight="@dimen/volume_dialog_stream_padding"
android:paddingLeft="@dimen/volume_dialog_stream_padding">
<!-- volume rows added and removed here! :-) -->
</LinearLayout>
<FrameLayout
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/rounded_bg_bottom_background">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/settings"
android:src="@drawable/ic_tune_black_16dp"
android:layout_width="@dimen/volume_dialog_tap_target_size"
android:layout_height="@dimen/volume_dialog_tap_target_size"
android:layout_gravity="center"
android:contentDescription="@string/accessibility_volume_settings"
android:background="@drawable/ripple_drawable_20dp"
android:tint="?android:attr/textColorSecondary"
android:soundEffectsEnabled="false" />
</FrameLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/odi_captions"
android:layout_width="@dimen/volume_dialog_caption_size"
android:layout_height="@dimen/volume_dialog_caption_size"
android:layout_marginRight="68dp"
android:gravity="right"
android:layout_gravity="right"
android:clipToPadding="false"
android:translationZ="@dimen/volume_dialog_elevation"
android:background="@drawable/rounded_bg_full">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/odi_captions_icon"
android:src="@drawable/ic_volume_odi_captions_disabled"
style="@style/VolumeButtons"
android:background="@drawable/rounded_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tint="@color/accent_tint_color_selector"
android:layout_gravity="center"
android:soundEffectsEnabled="false" />
</FrameLayout>
<ViewStub
android:id="@+id/odi_captions_tooltip_stub"
android:inflatedId="@+id/odi_captions_tooltip_view"
android:layout="@layout/volume_tool_tip_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/volume_tool_tip_right_margin"
android:layout_marginTop="@dimen/volume_tool_tip_top_margin"
android:layout_gravity="right"/>
</FrameLayout>
</FrameLayout>

View File

@@ -15,32 +15,38 @@
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/volume_dialog_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_gravity="right"
android:background="@android:color/transparent"
android:theme="@style/qs_theme">
android:theme="@style/volume_dialog_theme">
<!-- right-aligned to be physically near volume button -->
<LinearLayout
android:id="@+id/volume_dialog"
android:minWidth="@dimen/volume_dialog_panel_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:gravity="right"
android:layout_gravity="right"
android:background="@android:color/transparent"
android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right"
android:paddingTop="@dimen/volume_dialog_panel_transparent_padding"
android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding"
android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding"
android:orientation="vertical"
android:clipToPadding="false" >
android:clipToPadding="false">
<FrameLayout
android:id="@+id/ringer"
android:layout_width="@dimen/volume_dialog_ringer_size"
android:layout_height="@dimen/volume_dialog_ringer_size"
android:layout_marginBottom="@dimen/volume_dialog_spacer"
android:translationZ="@dimen/volume_dialog_elevation"
android:gravity="right"
android:layout_gravity="right"
android:translationZ="@dimen/volume_dialog_elevation"
android:clipToPadding="false"
android:background="@drawable/rounded_bg_full">
<com.android.keyguard.AlphaOptimizedImageButton
@@ -62,11 +68,14 @@
<LinearLayout
android:id="@+id/main"
android:layout_width="wrap_content"
android:minWidth="@dimen/volume_dialog_panel_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_gravity="right"
android:orientation="vertical"
android:translationZ="@dimen/volume_dialog_elevation"
android:clipChildren="false"
android:clipToPadding="false"
android:background="@drawable/rounded_bg_full" >
<LinearLayout
@@ -103,10 +112,10 @@
android:layout_width="@dimen/volume_dialog_caption_size"
android:layout_height="@dimen/volume_dialog_caption_size"
android:layout_marginTop="@dimen/volume_dialog_spacer"
android:translationZ="@dimen/volume_dialog_elevation"
android:gravity="right"
android:layout_gravity="right"
android:clipToPadding="false"
android:visibility="gone"
android:translationZ="@dimen/volume_dialog_elevation"
android:background="@drawable/rounded_bg_full">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/odi_captions_icon"
@@ -121,4 +130,15 @@
</FrameLayout>
</LinearLayout>
<ViewStub
android:id="@+id/odi_captions_tooltip_stub"
android:inflatedId="@+id/odi_captions_tooltip_view"
android:layout="@layout/volume_tool_tip_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom | right"
android:layout_marginRight="@dimen/volume_tool_tip_right_margin"
android:layout_marginBottom="@dimen/volume_tool_tip_bottom_margin"/>
</FrameLayout>

View File

@@ -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">
<LinearLayout
android:layout_height="wrap_content"

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<com.android.systemui.volume.VolumeToolTipView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tooltip_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="4dp"
android:background="@drawable/volume_tool_tip_rounded_bg"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="@android:color/white"
android:text="@string/volume_odi_captions_tip"
android:textSize="14sp"/>
<ImageView
android:id="@+id/dismiss"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:padding="12dp"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:alpha="0.7"
android:src="@drawable/ic_remove_no_shadow"
android:tint="@android:color/white"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/accessibility_volume_close_odi_captions_tip"/>
</LinearLayout>
<View
android:id="@+id/arrow"
android:layout_width="8dp"
android:layout_height="10dp"
android:layout_marginLeft="-2dp"
android:layout_gravity="center_vertical"/>
</com.android.systemui.volume.VolumeToolTipView>

View File

@@ -34,4 +34,7 @@
<bool name="quick_settings_wide">true</bool>
<dimen name="qs_detail_margin_top">0dp</dimen>
<dimen name="qs_paged_tile_layout_padding_bottom">0dp</dimen>
<dimen name="volume_tool_tip_right_margin">136dp</dimen>
<dimen name="volume_tool_tip_top_margin">12dp</dimen>
</resources>

View File

@@ -351,6 +351,12 @@
<dimen name="volume_dialog_elevation">9dp</dimen>
<dimen name="volume_tool_tip_right_margin">76dp</dimen>
<dimen name="volume_tool_tip_bottom_margin">32dp</dimen>
<dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen>
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->

View File

@@ -1307,6 +1307,12 @@
<!-- Content description for accessibility (not shown on the screen): volume dialog collapse button. [CHAR LIMIT=NONE] -->
<string name="accessibility_volume_collapse">Collapse</string>
<!-- Label for the odi caption initial tool tip. [CHAR LIMIT=28] -->
<string name="volume_odi_captions_tip">Automatically caption media</string>
<!-- Content description for accessibility: Clear the odi caption tool tip. [CHAR LIMIT=NONE] -->
<string name="accessibility_volume_close_odi_captions_tip">Close captions tip</string>
<!-- content description for audio output chooser [CHAR LIMIT=NONE]-->
<string name="accessibility_output_chooser">Switch output device</string>

View File

@@ -319,6 +319,10 @@
<item name="android:windowIsFloating">true</item>
</style>
<style name="volume_dialog_theme" parent="qs_theme">
<item name="android:windowIsFloating">false</item>
</style>
<style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
<item name="android:colorAccent">@color/remote_input_accent</item>
</style>

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
entry.getValue().post(
() -> entry.getKey().onCaptionComponentStateChanged(componentEnabled));
() -> entry.getKey().onCaptionComponentStateChanged(
componentEnabled, fromTooltip));
}
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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() {