Merge "Add tutorial dialog for gesture navigation" into qt-dev

This commit is contained in:
Kevin Chang
2019-06-10 00:57:14 +00:00
committed by Android (Google) Code Review
14 changed files with 785 additions and 25 deletions

View File

@@ -1500,6 +1500,11 @@
android:value="true" />
</activity>
<activity
android:name=".SettingsTutorialDialogWrapperActivity"
android:theme="@style/Theme.AlertDialog"
android:exported="false"/>
<activity
android:name="Settings$TextToSpeechSettingsActivity"
android:label="@string/tts_settings">

View File

@@ -0,0 +1,25 @@
<!--
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"
android:viewportHeight="24">
<path
android:fillColor="#757575"
android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1s-5.89,-0.3 -8.5,-1L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1l-0.5,-2zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
</vector>

View File

@@ -0,0 +1,30 @@
<!--
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 android:height="260dp" android:viewportHeight="260"
android:viewportWidth="260" android:width="260dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M55,23.81V197c0,3.31 2.69,6 6,6h138c3.31,0 6,-2.69 6,-6V23.81C183.81,8.81 157.93,0 130,0S76.19,8.81 55,23.81z"/>
<path android:fillColor="#F1F3F4" android:pathData="M61,209h138c6.62,0 12,-5.38 12,-12V28.32c-1.96,-1.56 -3.95,-3.07 -6,-4.52V197c0,3.31 -2.69,6 -6,6H61c-3.31,0 -6,-2.69 -6,-6V23.81c-2.05,1.45 -4.04,2.96 -6,4.52V197C49,203.62 54.38,209 61,209z"/>
<path android:fillColor="#DADCE0" android:pathData="M61,212h138c8.27,0 15,-6.73 15,-15V30.79c-0.5,-0.42 -1,-0.85 -1.5,-1.26V197c0,7.44 -6.06,13.5 -13.5,13.5H61c-7.44,0 -13.5,-6.06 -13.5,-13.5V29.53c-0.5,0.41 -1,0.84 -1.5,1.26V197C46,205.27 52.73,212 61,212z"/>
<path android:fillColor="#DADCE0" android:pathData="M211,197c0,6.62 -5.38,12 -12,12H61c-6.62,0 -12,-5.38 -12,-12V28.32c-0.5,0.4 -1,0.8 -1.5,1.21V197c0,7.44 6.06,13.5 13.5,13.5h138c7.44,0 13.5,-6.06 13.5,-13.5V29.53c-0.5,-0.41 -1,-0.81 -1.5,-1.21V197z"/>
<path android:fillColor="#202124" android:pathData="M199,203H61c-3.31,0 -6,-2.69 -6,-6v-21.24h150V197C205,200.31 202.31,203 199,203z"/>
<path android:fillAlpha="0.8" android:fillColor="#FFFFFF"
android:pathData="M81.89,190.03l6.5,3.75c0.5,0.29 1.12,-0.07 1.12,-0.64v-7.51c0,-0.57 -0.62,-0.93 -1.12,-0.64l-6.5,3.75C81.4,189.02 81.4,189.74 81.89,190.03z" android:strokeAlpha="0.8"/>
<path android:fillAlpha="0.8" android:fillColor="#FFFFFF"
android:pathData="M130,189.38m-4.2,0a4.2,4.2 0,1 1,8.4 0a4.2,4.2 0,1 1,-8.4 0" android:strokeAlpha="0.8"/>
<path android:fillColor="#FFFFFF" android:pathData="M179.78,184.38c-2.18,0.58 -4.73,0.83 -7.08,0.83c-2.36,0 -4.91,-0.25 -7.08,-0.83l-0.42,1.67c1.55,0.42 3.33,0.69 5,0.83v10.83h1.67v-5h1.67v5h1.67v-10.83c1.67,-0.14 3.45,-0.42 5,-0.83L179.78,184.38zM172.69,184.38c0.92,0 1.67,-0.75 1.67,-1.67c0,-0.92 -0.75,-1.67 -1.67,-1.67c-0.92,0 -1.67,0.75 -1.67,1.67C171.03,183.63 171.78,184.38 172.69,184.38z"/>
<path android:fillColor="#E8EAED" android:pathData="M130,2c34.19,0 66.33,13.31 90.51,37.49S258,95.81 258,130s-13.31,66.33 -37.49,90.51S164.19,258 130,258s-66.33,-13.31 -90.51,-37.49S2,164.19 2,130s13.31,-66.33 37.49,-90.51S95.81,2 130,2M130,0C58.2,0 0,58.2 0,130s58.2,130 130,130s130,-58.2 130,-130S201.8,0 130,0L130,0z"/>
</vector>

View File

@@ -0,0 +1,60 @@
<?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
-->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textDirection="locale"
android:scrollbarStyle="outsideOverlay">
<LinearLayout
android:theme="@style/Theme.AlertDialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="24dp">
<TextureView
android:id="@+id/gesture_tutorial_video"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingTop="32dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<TextView
android:id="@+id/gesture_tutorial_title"
android:text="@string/accessibility_tutorial_dialog_title_gesture_settings"
style="@style/AccessibilityDialogTitle" />
<TextView
android:id="@+id/gesture_tutorial_message"
style="@style/AccessibilityDialogDescription" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,61 @@
<?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
-->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textDirection="locale"
android:scrollbarStyle="outsideOverlay">
<LinearLayout
android:theme="@style/Theme.AlertDialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="24dp">
<ImageView
android:id="@+id/button_tutorial_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/illustration_accessibility_button"
android:scaleType="fitCenter"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingTop="32dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<TextView
android:id="@+id/button_tutorial_title"
android:text="@string/accessibility_tutorial_dialog_title_button"
style="@style/AccessibilityDialogTitle" />
<TextView
android:id="@+id/button_tutorial_message"
style="@style/AccessibilityDialogDescription" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,60 @@
<?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
-->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textDirection="locale"
android:scrollbarStyle="outsideOverlay">
<LinearLayout
android:theme="@style/Theme.AlertDialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="24dp">
<TextureView
android:id="@+id/gesture_tutorial_video"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingTop="32dp"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<TextView
android:id="@+id/gesture_tutorial_title"
android:text="@string/accessibility_tutorial_dialog_title_gesture"
style="@style/AccessibilityDialogTitle" />
<TextView
android:id="@+id/gesture_tutorial_message"
style="@style/AccessibilityDialogDescription" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,42 @@
/*
* 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.settings;
import android.app.Activity;
import android.os.Bundle;
import com.android.settings.accessibility.AccessibilityGestureNavigationTutorial;
import com.android.settings.R;
/**
* This activity is to create the tutorial dialog in gesture navigation settings since we couldn't
* use the dialog utils because SystemNavigationGestureSettings extends RadioButtonPickerFragment,
* not SettingsPreferenceFragment.
*/
public class SettingsTutorialDialogWrapperActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showDialog();
}
private void showDialog() {
AccessibilityGestureNavigationTutorial
.showGestureNavigationSettingsTutorialDialog(this, dialog -> finish());
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.view.LayoutInflater;
import android.view.TextureView;
import android.view.View;
import android.view.Window;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Utility class for creating the dialog that guides users for gesture navigation for
* accessibility services.
*/
public class AccessibilityGestureNavigationTutorial {
/** IntDef enum for dialog type. */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON,
DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION,
DialogType.GESTURE_NAVIGATION_SETTINGS,
})
private @interface DialogType {
int LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON = 0;
int LAUNCH_SERVICE_BY_GESTURE_NAVIGATION = 1;
int GESTURE_NAVIGATION_SETTINGS = 2;
}
private static final DialogInterface.OnClickListener mOnClickListener =
(DialogInterface dialog, int which) -> dialog.dismiss();
public static void showGestureNavigationSettingsTutorialDialog(Context context,
DialogInterface.OnDismissListener dismissListener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createTutorialDialogContentView(context,
DialogType.GESTURE_NAVIGATION_SETTINGS))
.setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
.setOnDismissListener(dismissListener)
.create();
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
}
static AlertDialog showAccessibilityButtonTutorialDialog(Context context) {
final AlertDialog alertDialog = createDialog(context,
DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON);
if (!isGestureNavigateEnabled(context)) {
updateMessageWithIcon(context, alertDialog);
}
return alertDialog;
}
static AlertDialog showGestureNavigationTutorialDialog(Context context) {
return createDialog(context, DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION);
}
/**
* Get a content View for a dialog to confirm that they want to enable a service.
*
* @param context A valid context
* @param dialogType The type of tutorial dialog
* @return A content view suitable for viewing
*/
private static View createTutorialDialogContentView(Context context, int dialogType) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View content = null;
switch (dialogType) {
case DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON:
content = inflater.inflate(
R.layout.tutorial_dialog_launch_service_by_accessibility_button, null);
break;
case DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION:
content = inflater.inflate(
R.layout.tutorial_dialog_launch_service_by_gesture_navigation, null);
final TextureView gestureTutorialVideo = content.findViewById(
R.id.gesture_tutorial_video);
final TextView gestureTutorialMessage = content.findViewById(
R.id.gesture_tutorial_message);
VideoPlayer.create(context, isTouchExploreOn(context)
? R.raw.illustration_accessibility_gesture_three_finger
: R.raw.illustration_accessibility_gesture_two_finger,
gestureTutorialVideo);
gestureTutorialMessage.setText(isTouchExploreOn(context)
? R.string.accessibility_tutorial_dialog_message_gesture_with_talkback
: R.string.accessibility_tutorial_dialog_message_gesture_without_talkback);
break;
case DialogType.GESTURE_NAVIGATION_SETTINGS:
content = inflater.inflate(
R.layout.tutorial_dialog_launch_by_gesture_navigation_settings, null);
final TextureView gestureSettingsTutorialVideo = content.findViewById(
R.id.gesture_tutorial_video);
final TextView gestureSettingsTutorialMessage = content.findViewById(
R.id.gesture_tutorial_message);
VideoPlayer.create(context, isTouchExploreOn(context)
? R.raw.illustration_accessibility_gesture_three_finger
: R.raw.illustration_accessibility_gesture_two_finger,
gestureSettingsTutorialVideo);
gestureSettingsTutorialMessage.setText(isTouchExploreOn(context)
?
R.string.accessibility_tutorial_dialog_message_gesture_settings_with_talkback
: R.string.accessibility_tutorial_dialog_message_gesture_settings_without_talkback);
break;
}
return content;
}
private static AlertDialog createDialog(Context context, int dialogType) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createTutorialDialogContentView(context, dialogType))
.setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
.create();
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
return alertDialog;
}
private static void updateMessageWithIcon(Context context, AlertDialog alertDialog) {
final TextView gestureTutorialMessage = alertDialog.findViewById(
R.id.button_tutorial_message);
// Get the textView line height to update [icon] size. Must be called after show()
final int lineHeight = gestureTutorialMessage.getLineHeight();
gestureTutorialMessage.setText(getMessageStringWithIcon(context, lineHeight));
}
private static SpannableString getMessageStringWithIcon(Context context, int lineHeight) {
final String messageString = context
.getString(R.string.accessibility_tutorial_dialog_message_button);
final SpannableString spannableMessage = SpannableString.valueOf(messageString);
// Icon
final int indexIconStart = messageString.indexOf("%s");
final int indexIconEnd = indexIconStart + 2;
final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
icon.setTint(getThemeAttrColor(context, android.R.attr.textColorPrimary));
icon.setBounds(0, 0, lineHeight, lineHeight);
spannableMessage.setSpan(
new ImageSpan(icon), indexIconStart, indexIconEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableMessage;
}
/** Returns the color associated with the specified attribute in the context's theme. */
@ColorInt
private static int getThemeAttrColor(final Context context, final int attributeColor) {
final int colorResId = getAttrResourceId(context, attributeColor);
return ContextCompat.getColor(context, colorResId);
}
/** Returns the identifier of the resolved resource assigned to the given attribute. */
private static int getAttrResourceId(final Context context, final int attributeColor) {
final int[] attrs = {attributeColor};
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
final int colorResId = typedArray.getResourceId(0, 0);
typedArray.recycle();
return colorResId;
}
private static boolean isGestureNavigateEnabled(Context context) {
return context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
}
private static boolean isTouchExploreOn(Context context) {
return ((AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE))
.isTouchExplorationEnabled();
}
}

View File

@@ -16,14 +16,18 @@
package com.android.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -50,6 +54,7 @@ public class ToggleAccessibilityServicePreferenceFragment
private static final int DIALOG_ID_ENABLE_WARNING = 1;
private static final int DIALOG_ID_DISABLE_WARNING = 2;
private static final int DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL = 3;
public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
@@ -57,7 +62,7 @@ public class ToggleAccessibilityServicePreferenceFragment
private final SettingsContentObserver mSettingsContentObserver =
new SettingsContentObserver(new Handler()) {
@Override
@Override
public void onChange(boolean selfChange, Uri uri) {
updateSwitchBarToggleSwitch();
}
@@ -144,6 +149,16 @@ public class ToggleAccessibilityServicePreferenceFragment
.createDisableDialog(getActivity(), info, this);
break;
}
case DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL: {
if (isGestureNavigateEnabled()) {
mDialog = AccessibilityGestureNavigationTutorial
.showGestureNavigationTutorialDialog(getActivity());
} else {
mDialog = AccessibilityGestureNavigationTutorial
.showAccessibilityButtonTutorialDialog(getActivity());
}
break;
}
default: {
throw new IllegalArgumentException();
}
@@ -197,32 +212,54 @@ public class ToggleAccessibilityServicePreferenceFragment
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.permission_enable_allow_button:
if (isFullDiskEncrypted()) {
String title = createConfirmCredentialReasonMessage();
Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
startActivityForResult(intent,
ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
} else {
handleConfirmServiceEnabled(true);
}
break;
case R.id.permission_enable_deny_button:
handleConfirmServiceEnabled(false);
break;
case R.id.permission_disable_stop_button:
handleConfirmServiceEnabled(false);
break;
case R.id.permission_disable_cancel_button:
if (view.getId() == R.id.permission_enable_allow_button) {
if (isFullDiskEncrypted()) {
String title = createConfirmCredentialReasonMessage();
Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
startActivityForResult(intent,
ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
} else {
handleConfirmServiceEnabled(true);
break;
default:
throw new IllegalArgumentException();
if (isServiceSupportAccessibilityButton()) {
showDialog(DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL);
}
}
} else if (view.getId() == R.id.permission_enable_deny_button) {
handleConfirmServiceEnabled(false);
} else if (view.getId() == R.id.permission_disable_stop_button) {
handleConfirmServiceEnabled(false);
} else if (view.getId() == R.id.permission_disable_cancel_button) {
handleConfirmServiceEnabled(true);
} else {
throw new IllegalArgumentException();
}
mDialog.dismiss();
}
private boolean isGestureNavigateEnabled() {
return getContext().getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
}
private boolean isServiceSupportAccessibilityButton() {
final AccessibilityManager ams = (AccessibilityManager) getContext().getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> services = ams.getInstalledAccessibilityServiceList();
for (AccessibilityServiceInfo info : services) {
if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
if (serviceInfo != null && TextUtils.equals(serviceInfo.name,
getAccessibilityServiceInfo().getResolveInfo().serviceInfo.name)) {
return true;
}
}
}
return false;
}
private void handleConfirmServiceEnabled(boolean confirmed) {
mSwitchBar.setCheckedInternal(confirmed);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
@@ -234,11 +271,13 @@ public class ToggleAccessibilityServicePreferenceFragment
switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId())) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: {
resId = R.string.enable_service_pattern_reason;
} break;
}
break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: {
resId = R.string.enable_service_pin_reason;
} break;
}
break;
}
return getString(resId, getAccessibilityServiceInfo().getResolveInfo()
.loadLabel(getPackageManager()));
@@ -248,7 +287,7 @@ public class ToggleAccessibilityServicePreferenceFragment
protected void onInstallSwitchBarToggleSwitch() {
super.onInstallSwitchBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
if (checked) {
mSwitchBar.setCheckedInternal(false);

View File

@@ -16,6 +16,9 @@
package com.android.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +29,8 @@ import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.Display;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
@@ -44,6 +49,10 @@ import com.android.settings.widget.SwitchBar;
public class ToggleScreenMagnificationPreferenceFragment extends
ToggleFeaturePreferenceFragment implements SwitchBar.OnSwitchChangeListener {
private static final int DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL = 1;
private Dialog mDialog;
protected class VideoPreference extends Preference {
private ImageView mVideoBackgroundView;
private OnGlobalLayoutListener mLayoutListener;
@@ -161,12 +170,32 @@ public class ToggleScreenMagnificationPreferenceFragment extends
updateConfigurationWarningIfNeeded();
}
@Override
public Dialog onCreateDialog(int dialogId) {
if (dialogId == DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL) {
if (isGestureNavigateEnabled()) {
mDialog = AccessibilityGestureNavigationTutorial
.showGestureNavigationTutorialDialog(getActivity());
} else {
mDialog = AccessibilityGestureNavigationTutorial
.showAccessibilityButtonTutorialDialog(getActivity());
}
}
return mDialog;
}
@Override
public int getMetricsCategory() {
// TODO: Distinguish between magnification modes
return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION;
}
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
onPreferenceToggled(mPreferenceKey, isChecked);
@@ -174,6 +203,11 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
if (enabled && TextUtils.equals(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
preferenceKey)) {
showDialog(DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL);
}
MagnificationPreferenceFragment.setChecked(getContentResolver(), preferenceKey, enabled);
updateConfigurationWarningIfNeeded();
}
@@ -224,6 +258,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
}
}
private boolean isGestureNavigateEnabled() {
return getContext().getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
}
private void updateConfigurationWarningIfNeeded() {
final CharSequence warningMessage =
MagnificationPreferenceFragment.getConfigurationWarningStringForSecureSettingsKey(

View File

@@ -0,0 +1,149 @@
/*
* 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.settings.accessibility;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.view.Surface;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import androidx.annotation.GuardedBy;
import androidx.annotation.RawRes;
/**
* Plays the video by {@link MediaPlayer} on {@link TextureView}, calls {@link #create(Context, int,
* TextureView)} to setup the listener for TextureView and start to play the video. Once this player
* is no longer used, call {@link #release()} so that MediaPlayer object can be released.
*/
public class VideoPlayer implements SurfaceTextureListener {
private final Context context;
private final Object mediaPlayerLock = new Object();
// Media player object can't be used after it has been released, so it will be set to null. But
// VideoPlayer is asynchronized, media player object might be paused or resumed again before
// released media player is set to null. Therefore, lock mediaPlayer and mediaPlayerState by
// mediaPlayerLock keep their states consistent.
@GuardedBy("mediaPlayerLock")
private MediaPlayer mediaPlayer;
@GuardedBy("mediaPlayerLock")
private State mediaPlayerState = State.NONE;
@RawRes
private final int videoRes;
private Surface animationSurface;
/**
* Creates a {@link MediaPlayer} for a given resource id and starts playback when the surface
* for
* a given {@link TextureView} is ready.
*/
public static VideoPlayer create(Context context, @RawRes int videoRes,
TextureView textureView) {
return new VideoPlayer(context, videoRes, textureView);
}
private VideoPlayer(Context context, @RawRes int videoRes, TextureView textureView) {
this.context = context;
this.videoRes = videoRes;
textureView.setSurfaceTextureListener(this);
}
public void pause() {
synchronized (mediaPlayerLock) {
if (mediaPlayerState == State.STARTED) {
mediaPlayerState = State.PAUSED;
mediaPlayer.pause();
}
}
}
public void resume() {
synchronized (mediaPlayerLock) {
if (mediaPlayerState == State.PAUSED) {
mediaPlayer.start();
mediaPlayerState = State.STARTED;
}
}
}
/** Release media player when it's no longer needed. */
public void release() {
synchronized (mediaPlayerLock) {
if (mediaPlayerState != State.NONE && mediaPlayerState != State.END) {
mediaPlayerState = State.END;
mediaPlayer.release();
mediaPlayer = null;
}
}
if (animationSurface != null) {
animationSurface.release();
animationSurface = null;
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
animationSurface = new Surface(surface);
synchronized (mediaPlayerLock) {
mediaPlayer = MediaPlayer.create(context, videoRes);
mediaPlayerState = State.PREPARED;
mediaPlayer.setSurface(animationSurface);
mediaPlayer.setLooping(true);
mediaPlayer.start();
mediaPlayerState = State.STARTED;
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
release();
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
/**
* The state of MediaPlayer object. Refer to
* https://developer.android.com/reference/android/media/MediaPlayer#StateDiagram.
*/
public enum State {
/** MediaPlayer objects has not be created. */
NONE,
/** MediaPlayer objects is created by create() method. */
PREPARED,
/** MediaPlayer is started. It can be paused by pause() method. */
STARTED,
/** MediaPlayer object is paused. Calling start() to resume it. */
PAUSED,
/**
* MediaPlayer object is stopped and cannot be started until calling prepare() or
* prepareAsync()
* methods.
*/
STOPPED,
/** MediaPlayer object is released. It cannot be used again. */
END
}
}

View File

@@ -25,18 +25,24 @@ import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.E
import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_INFO;
import android.app.AlertDialog;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.om.IOverlayManager;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.SettingsTutorialDialogWrapperActivity;
import com.android.settings.R;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
@@ -165,6 +171,12 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
setCurrentSystemNavigationMode(mOverlayManager, key);
setIllustrationVideo(mVideoPreference, key);
if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, key) && (
isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
Intent intent = new Intent(getActivity(), SettingsTutorialDialogWrapperActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
return true;
}
@@ -256,6 +268,26 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
.show();
}
private boolean isAnyServiceSupportAccessibilityButton() {
final AccessibilityManager ams = (AccessibilityManager) getContext().getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> services = ams.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
for (AccessibilityServiceInfo info : services) {
if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
return true;
}
}
return false;
}
private boolean isNavBarMagnificationEnabled() {
return Settings.Secure.getInt(getContext().getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1;
}
static class NavModeCandidateInfo extends CandidateInfo {
private final CharSequence mLabel;
private final CharSequence mSummary;