1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.accessibility; 18 19 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 20 21 import android.accessibilityservice.AccessibilityServiceInfo; 22 import android.app.Activity; 23 import android.app.Dialog; 24 import android.content.Context; 25 import android.graphics.drawable.Drawable; 26 import android.os.storage.StorageManager; 27 import android.text.BidiFormatter; 28 import android.view.LayoutInflater; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.Window; 32 import android.view.WindowManager; 33 import android.widget.Button; 34 import android.widget.ImageView; 35 import android.widget.TextView; 36 import android.widget.Toast; 37 38 import androidx.appcompat.app.AlertDialog; 39 import androidx.core.content.ContextCompat; 40 41 import com.android.settings.R; 42 43 import java.util.Locale; 44 45 /** 46 * Utility class for creating the dialog that asks users for explicit permission for an 47 * accessibility service to access user data before the service is enabled 48 */ 49 public class AccessibilityServiceWarning { 50 private static final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> { 51 // Filter obscured touches by consuming them. 52 if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) 53 || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) { 54 if (event.getAction() == MotionEvent.ACTION_UP) { 55 Toast.makeText(v.getContext(), R.string.touch_filtered_warning, 56 Toast.LENGTH_SHORT).show(); 57 } 58 return true; 59 } 60 return false; 61 }; 62 createCapabilitiesDialog(Activity parentActivity, AccessibilityServiceInfo info, View.OnClickListener listener)63 public static Dialog createCapabilitiesDialog(Activity parentActivity, 64 AccessibilityServiceInfo info, View.OnClickListener listener) { 65 final AlertDialog ad = new AlertDialog.Builder(parentActivity) 66 .setView(createEnableDialogContentView(parentActivity, info, listener)) 67 .create(); 68 69 Window window = ad.getWindow(); 70 WindowManager.LayoutParams params = window.getAttributes(); 71 params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 72 window.setAttributes(params); 73 ad.create(); 74 ad.setCanceledOnTouchOutside(true); 75 76 return ad; 77 } 78 createDisableDialog(Activity parentActivity, AccessibilityServiceInfo info, View.OnClickListener listener)79 public static Dialog createDisableDialog(Activity parentActivity, 80 AccessibilityServiceInfo info, View.OnClickListener listener) { 81 final AlertDialog ad = new AlertDialog.Builder(parentActivity) 82 .setView(createDisableDialogContentView(parentActivity, info, listener)) 83 .setCancelable(true) 84 .create(); 85 86 return ad; 87 } 88 89 /** 90 * Return whether the device is encrypted with legacy full disk encryption. Newer devices 91 * should be using File Based Encryption. 92 * 93 * @return true if device is encrypted 94 */ isFullDiskEncrypted()95 private static boolean isFullDiskEncrypted() { 96 return StorageManager.isNonDefaultBlockEncrypted(); 97 } 98 99 /** 100 * Get a content View for a dialog to confirm that they want to enable a service. 101 * 102 * @param context A valid context 103 * @param info The info about a service 104 * @return A content view suitable for viewing 105 */ createEnableDialogContentView(Context context, AccessibilityServiceInfo info, View.OnClickListener listener)106 private static View createEnableDialogContentView(Context context, 107 AccessibilityServiceInfo info, View.OnClickListener listener) { 108 LayoutInflater inflater = (LayoutInflater) context.getSystemService( 109 Context.LAYOUT_INFLATER_SERVICE); 110 111 View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content, 112 null); 113 114 TextView encryptionWarningView = (TextView) content.findViewById( 115 R.id.encryption_warning); 116 if (isFullDiskEncrypted()) { 117 String text = context.getString(R.string.enable_service_encryption_warning, 118 getServiceName(context, info)); 119 encryptionWarningView.setText(text); 120 encryptionWarningView.setVisibility(View.VISIBLE); 121 } else { 122 encryptionWarningView.setVisibility(View.GONE); 123 } 124 125 final Drawable icon; 126 if (info.getResolveInfo().getIconResource() == 0) { 127 icon = ContextCompat.getDrawable(context, R.drawable.ic_accessibility_generic); 128 } else { 129 icon = info.getResolveInfo().loadIcon(context.getPackageManager()); 130 } 131 132 ImageView permissionDialogIcon = content.findViewById( 133 R.id.permissionDialog_icon); 134 permissionDialogIcon.setImageDrawable(icon); 135 136 TextView permissionDialogTitle = content.findViewById(R.id.permissionDialog_title); 137 permissionDialogTitle.setText(context.getString(R.string.enable_service_title, 138 getServiceName(context, info))); 139 140 Button permissionAllowButton = content.findViewById( 141 R.id.permission_enable_allow_button); 142 Button permissionDenyButton = content.findViewById( 143 R.id.permission_enable_deny_button); 144 permissionAllowButton.setOnClickListener(listener); 145 permissionAllowButton.setOnTouchListener(filterTouchListener); 146 permissionDenyButton.setOnClickListener(listener); 147 148 return content; 149 } 150 createDisableDialogContentView(Context context, AccessibilityServiceInfo info, View.OnClickListener listener)151 private static View createDisableDialogContentView(Context context, 152 AccessibilityServiceInfo info, View.OnClickListener listener) { 153 LayoutInflater inflater = (LayoutInflater) context.getSystemService( 154 Context.LAYOUT_INFLATER_SERVICE); 155 156 View content = inflater.inflate(R.layout.disable_accessibility_service_dialog_content, 157 null); 158 159 TextView permissionDialogTitle = content.findViewById(R.id.permissionDialog_disable_title); 160 permissionDialogTitle.setText(context.getString(R.string.disable_service_title, 161 getServiceName(context, info))); 162 TextView permissionDialogMessage = content 163 .findViewById(R.id.permissionDialog_disable_message); 164 permissionDialogMessage.setText(context.getString(R.string.disable_service_message, 165 context.getString(R.string.accessibility_dialog_button_stop), 166 getServiceName(context, info))); 167 168 Button permissionAllowButton = content.findViewById( 169 R.id.permission_disable_stop_button); 170 Button permissionDenyButton = content.findViewById( 171 R.id.permission_disable_cancel_button); 172 permissionAllowButton.setOnClickListener(listener); 173 permissionDenyButton.setOnClickListener(listener); 174 175 return content; 176 } 177 178 // Get the service name and bidi wrap it to protect from bidi side effects. getServiceName(Context context, AccessibilityServiceInfo info)179 private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) { 180 final Locale locale = context.getResources().getConfiguration().getLocales().get(0); 181 final CharSequence label = 182 info.getResolveInfo().loadLabel(context.getPackageManager()); 183 return BidiFormatter.getInstance(locale).unicodeWrap(label); 184 } 185 } 186