/* * Copyright (C) 2016 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.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.drawable.Drawable; import android.os.storage.StorageManager; import android.text.BidiFormatter; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import com.android.settings.R; import java.util.Locale; /** * Utility class for creating the dialog that asks users for explicit permission for an * accessibility service to access user data before the service is enabled */ public class AccessibilityServiceWarning { private static final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> { // Filter obscured touches by consuming them. if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) { if (event.getAction() == MotionEvent.ACTION_UP) { Toast.makeText(v.getContext(), R.string.touch_filtered_warning, Toast.LENGTH_SHORT).show(); } return true; } return false; }; /** Returns a {@link Dialog} to be shown to confirm that they want to enable a service. */ public static Dialog createCapabilitiesDialog(Context context, AccessibilityServiceInfo info, View.OnClickListener listener) { final AlertDialog ad = new AlertDialog.Builder(context) .setView(createEnableDialogContentView(context, info, listener)) .create(); Window window = ad.getWindow(); WindowManager.LayoutParams params = window.getAttributes(); params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; window.setAttributes(params); ad.create(); ad.setCanceledOnTouchOutside(true); return ad; } /** * Returns whether the device is encrypted with legacy full disk encryption. Newer devices * should be using File Based Encryption. * * @return true if device is encrypted */ private static boolean isFullDiskEncrypted() { return StorageManager.isNonDefaultBlockEncrypted(); } private static View createEnableDialogContentView(Context context, AccessibilityServiceInfo info, View.OnClickListener listener) { LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE); View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content, null); TextView encryptionWarningView = (TextView) content.findViewById( R.id.encryption_warning); if (isFullDiskEncrypted()) { String text = context.getString(R.string.enable_service_encryption_warning, getServiceName(context, info)); encryptionWarningView.setText(text); encryptionWarningView.setVisibility(View.VISIBLE); } else { encryptionWarningView.setVisibility(View.GONE); } final Drawable icon; if (info.getResolveInfo().getIconResource() == 0) { icon = ContextCompat.getDrawable(context, R.drawable.ic_accessibility_generic); } else { icon = info.getResolveInfo().loadIcon(context.getPackageManager()); } ImageView permissionDialogIcon = content.findViewById( R.id.permissionDialog_icon); permissionDialogIcon.setImageDrawable(icon); TextView permissionDialogTitle = content.findViewById(R.id.permissionDialog_title); permissionDialogTitle.setText(context.getString(R.string.enable_service_title, getServiceName(context, info))); Button permissionAllowButton = content.findViewById( R.id.permission_enable_allow_button); Button permissionDenyButton = content.findViewById( R.id.permission_enable_deny_button); permissionAllowButton.setOnClickListener(listener); permissionAllowButton.setOnTouchListener(filterTouchListener); permissionDenyButton.setOnClickListener(listener); return content; } /** Returns a {@link Dialog} to be shown to confirm that they want to disable a service. */ public static Dialog createDisableDialog(Context context, AccessibilityServiceInfo info, DialogInterface.OnClickListener listener) { final AlertDialog dialog = new AlertDialog.Builder(context) .setTitle(context.getString(R.string.disable_service_title, info.getResolveInfo().loadLabel(context.getPackageManager()))) .setMessage(context.getString(R.string.disable_service_message, context.getString(R.string.accessibility_dialog_button_stop), getServiceName(context, info))) .setCancelable(true) .setPositiveButton(R.string.accessibility_dialog_button_stop, listener) .setNegativeButton(R.string.accessibility_dialog_button_cancel, listener) .create(); return dialog; } // Get the service name and bidi wrap it to protect from bidi side effects. private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) { final Locale locale = context.getResources().getConfiguration().getLocales().get(0); final CharSequence label = info.getResolveInfo().loadLabel(context.getPackageManager()); return BidiFormatter.getInstance(locale).unicodeWrap(label); } }