1 /* 2 * Copyright (C) 2015 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.permissioncontroller.permission.ui.handheld; 18 19 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON; 20 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON; 21 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON; 22 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON; 23 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON; 24 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON; 25 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON; 26 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON; 27 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON; 28 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON; 29 30 import android.app.Activity; 31 import android.graphics.drawable.Icon; 32 import android.os.Bundle; 33 import android.os.UserHandle; 34 import android.text.method.LinkMovementMethod; 35 import android.transition.ChangeBounds; 36 import android.transition.TransitionManager; 37 import android.util.SparseArray; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.view.View.OnClickListener; 41 import android.view.ViewGroup; 42 import android.view.WindowManager.LayoutParams; 43 import android.view.accessibility.AccessibilityNodeInfo; 44 import android.view.animation.AnimationUtils; 45 import android.widget.Button; 46 import android.widget.ImageView; 47 import android.widget.TextView; 48 49 import androidx.annotation.NonNull; 50 51 import com.android.permissioncontroller.R; 52 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity; 53 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler; 54 55 public class GrantPermissionsViewHandlerImpl implements GrantPermissionsViewHandler, 56 OnClickListener { 57 58 public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME"; 59 public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT"; 60 public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX"; 61 public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON"; 62 public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"; 63 private static final String ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE"; 64 private static final String ARG_DIALOG_BUTTON_VISIBILITIES = "ARG_DIALOG_BUTTON_VISIBILITIES"; 65 66 // Animation parameters. 67 private static final long SWITCH_TIME_MILLIS = 75; 68 private static final long ANIMATION_DURATION_MILLIS = 200; 69 70 private static final SparseArray<Integer> BUTTON_RES_ID_TO_NUM = new SparseArray<>(); 71 static { BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_button, ALLOW_BUTTON)72 BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_button, ALLOW_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_always_button, ALLOW_ALWAYS_BUTTON)73 BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_always_button, 74 ALLOW_ALWAYS_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_foreground_only_button, ALLOW_FOREGROUND_BUTTON)75 BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_foreground_only_button, 76 ALLOW_FOREGROUND_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_button, DENY_BUTTON)77 BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_button, DENY_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_and_dont_ask_again_button, DENY_AND_DONT_ASK_AGAIN_BUTTON)78 BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_and_dont_ask_again_button, 79 DENY_AND_DONT_ASK_AGAIN_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_one_time_button, ALLOW_ONE_TIME_BUTTON)80 BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_one_time_button, ALLOW_ONE_TIME_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_button, NO_UPGRADE_BUTTON)81 BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_button, NO_UPGRADE_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_and_dont_ask_again_button, NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON)82 BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_and_dont_ask_again_button, 83 NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_button, NO_UPGRADE_OT_BUTTON)84 BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_button, NO_UPGRADE_OT_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_and_dont_ask_again_button, NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON)85 BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_and_dont_ask_again_button, 86 NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON); 87 } 88 89 private final Activity mActivity; 90 private final String mAppPackageName; 91 private final UserHandle mUserHandle; 92 93 private ResultListener mResultListener; 94 95 // Configuration of the current dialog 96 private String mGroupName; 97 private int mGroupCount; 98 private int mGroupIndex; 99 private Icon mGroupIcon; 100 private CharSequence mGroupMessage; 101 private CharSequence mDetailMessage; 102 private boolean[] mButtonVisibilities; 103 104 // Views 105 private ImageView mIconView; 106 private TextView mMessageView; 107 private TextView mDetailMessageView; 108 private Button[] mButtons; 109 private ViewGroup mRootView; 110 GrantPermissionsViewHandlerImpl(Activity activity, String appPackageName, @NonNull UserHandle userHandle)111 public GrantPermissionsViewHandlerImpl(Activity activity, String appPackageName, 112 @NonNull UserHandle userHandle) { 113 mActivity = activity; 114 mAppPackageName = appPackageName; 115 mUserHandle = userHandle; 116 } 117 118 @Override setResultListener(ResultListener listener)119 public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) { 120 mResultListener = listener; 121 return this; 122 } 123 124 @Override saveInstanceState(Bundle arguments)125 public void saveInstanceState(Bundle arguments) { 126 arguments.putString(ARG_GROUP_NAME, mGroupName); 127 arguments.putInt(ARG_GROUP_COUNT, mGroupCount); 128 arguments.putInt(ARG_GROUP_INDEX, mGroupIndex); 129 arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon); 130 arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage); 131 arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, mDetailMessage); 132 arguments.putBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES, mButtonVisibilities); 133 } 134 135 @Override loadInstanceState(Bundle savedInstanceState)136 public void loadInstanceState(Bundle savedInstanceState) { 137 mGroupName = savedInstanceState.getString(ARG_GROUP_NAME); 138 mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE); 139 mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON); 140 mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT); 141 mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX); 142 mDetailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE); 143 mButtonVisibilities = savedInstanceState.getBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES); 144 145 updateAll(); 146 } 147 148 @Override updateUi(String groupName, int groupCount, int groupIndex, Icon icon, CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities)149 public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, 150 CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities) { 151 boolean isNewGroup = mGroupIndex != groupIndex; 152 153 mGroupName = groupName; 154 mGroupCount = groupCount; 155 mGroupIndex = groupIndex; 156 mGroupIcon = icon; 157 mGroupMessage = message; 158 mDetailMessage = detailMessage; 159 mButtonVisibilities = buttonVisibilities; 160 161 // If this is a second (or later) permission and the views exist, then animate. 162 if (mIconView != null) { 163 updateAll(); 164 } 165 } 166 updateAll()167 private void updateAll() { 168 updateDescription(); 169 updateDetailDescription(); 170 updateButtons(); 171 172 // Animate change in size 173 // Grow or shrink the content container to size of new content 174 ChangeBounds growShrinkToNewContentSize = new ChangeBounds(); 175 growShrinkToNewContentSize.setDuration(ANIMATION_DURATION_MILLIS); 176 growShrinkToNewContentSize.setInterpolator(AnimationUtils.loadInterpolator(mActivity, 177 android.R.interpolator.fast_out_slow_in)); 178 TransitionManager.beginDelayedTransition(mRootView, growShrinkToNewContentSize); 179 } 180 181 @Override createView()182 public View createView() { 183 mRootView = (ViewGroup) LayoutInflater.from(mActivity) 184 .inflate(R.layout.grant_permissions, null); 185 186 int h = mActivity.getResources().getDisplayMetrics().heightPixels; 187 mRootView.setMinimumHeight(h); 188 mRootView.findViewById(R.id.grant_singleton).setOnClickListener(this); // Cancel dialog 189 mRootView.findViewById(R.id.grant_dialog).setOnClickListener(this); // Swallow click event 190 191 mMessageView = mRootView.findViewById(R.id.permission_message); 192 mDetailMessageView = mRootView.findViewById(R.id.detail_message); 193 mDetailMessageView.setMovementMethod(LinkMovementMethod.getInstance()); 194 mIconView = mRootView.findViewById(R.id.permission_icon); 195 196 mButtons = new Button[GrantPermissionsActivity.NEXT_BUTTON]; 197 198 int numButtons = BUTTON_RES_ID_TO_NUM.size(); 199 for (int i = 0; i < numButtons; i++) { 200 Button button = mRootView.findViewById(BUTTON_RES_ID_TO_NUM.keyAt(i)); 201 button.setOnClickListener(this); 202 mButtons[BUTTON_RES_ID_TO_NUM.valueAt(i)] = button; 203 } 204 205 if (mGroupName != null) { 206 updateAll(); 207 } 208 209 return mRootView; 210 } 211 212 @Override updateWindowAttributes(LayoutParams outLayoutParams)213 public void updateWindowAttributes(LayoutParams outLayoutParams) { 214 // No-op 215 } 216 updateDescription()217 private void updateDescription() { 218 if (mGroupIcon != null) { 219 mIconView.setImageDrawable(mGroupIcon.loadDrawable(mActivity)); 220 } 221 mMessageView.setText(mGroupMessage); 222 } 223 updateDetailDescription()224 private void updateDetailDescription() { 225 if (mDetailMessage == null) { 226 mDetailMessageView.setVisibility(View.GONE); 227 } else { 228 mDetailMessageView.setText(mDetailMessage); 229 mDetailMessageView.setVisibility(View.VISIBLE); 230 } 231 } 232 updateButtons()233 private void updateButtons() { 234 int numButtons = BUTTON_RES_ID_TO_NUM.size(); 235 for (int i = 0; i < numButtons; i++) { 236 int pos = BUTTON_RES_ID_TO_NUM.valueAt(i); 237 mButtons[pos].setVisibility(mButtonVisibilities[pos] ? View.VISIBLE : View.GONE); 238 } 239 } 240 241 @Override onClick(View view)242 public void onClick(View view) { 243 int id = view.getId(); 244 if (id == R.id.grant_singleton) { 245 if (mResultListener != null) { 246 mResultListener.onPermissionGrantResult(mGroupName, CANCELED); 247 } else { 248 mActivity.finish(); 249 } 250 return; 251 } 252 int button = -1; 253 try { 254 button = BUTTON_RES_ID_TO_NUM.get(id); 255 } catch (NullPointerException e) { 256 // Clicked a view which is not one of the defined buttons 257 return; 258 } 259 switch (button) { 260 case ALLOW_BUTTON: 261 if (mResultListener != null) { 262 view.performAccessibilityAction( 263 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); 264 mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ALWAYS); 265 } 266 break; 267 case ALLOW_FOREGROUND_BUTTON: 268 if (mResultListener != null) { 269 view.performAccessibilityAction( 270 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); 271 mResultListener.onPermissionGrantResult(mGroupName, 272 GRANTED_FOREGROUND_ONLY); 273 } 274 break; 275 case ALLOW_ALWAYS_BUTTON: 276 if (mResultListener != null) { 277 view.performAccessibilityAction( 278 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); 279 mResultListener.onPermissionGrantResult(mGroupName, 280 GRANTED_ALWAYS); 281 } 282 break; 283 case ALLOW_ONE_TIME_BUTTON: 284 if (mResultListener != null) { 285 view.performAccessibilityAction( 286 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); 287 mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ONE_TIME); 288 } 289 break; 290 case DENY_BUTTON: 291 case NO_UPGRADE_BUTTON: 292 case NO_UPGRADE_OT_BUTTON: 293 if (mResultListener != null) { 294 view.performAccessibilityAction( 295 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); 296 mResultListener.onPermissionGrantResult(mGroupName, DENIED); 297 } 298 break; 299 case DENY_AND_DONT_ASK_AGAIN_BUTTON: 300 case NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON: 301 case NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON: 302 if (mResultListener != null) { 303 view.performAccessibilityAction( 304 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null); 305 mResultListener.onPermissionGrantResult(mGroupName, 306 DENIED_DO_NOT_ASK_AGAIN); 307 } 308 break; 309 } 310 311 } 312 313 @Override onBackPressed()314 public void onBackPressed() { 315 if (mResultListener != null) { 316 mResultListener.onPermissionGrantResult(mGroupName, CANCELED); 317 } else { 318 mActivity.finish(); 319 } 320 } 321 322 } 323