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.wear; 18 19 import static android.Manifest.permission.ACCESS_COARSE_LOCATION; 20 import static android.Manifest.permission.ACCESS_FINE_LOCATION; 21 22 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON; 23 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON; 24 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON; 25 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON; 26 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON; 27 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON; 28 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS; 29 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY; 30 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY; 31 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON; 32 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON; 33 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_LOCATION_DIALOG; 34 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON; 35 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON; 36 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON; 37 import static com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON; 38 39 import android.app.Activity; 40 import android.graphics.drawable.Icon; 41 import android.os.Bundle; 42 import android.util.SparseIntArray; 43 import android.view.View; 44 import android.view.WindowManager.LayoutParams; 45 46 import androidx.annotation.Nullable; 47 import androidx.compose.ui.platform.ComposeView; 48 49 import com.android.permissioncontroller.R; 50 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler; 51 import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModel; 52 import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModelFactory; 53 54 import kotlin.Unit; 55 56 import java.util.Arrays; 57 import java.util.Collections; 58 import java.util.List; 59 60 /** 61 * Wear-specific view handler for the grant permissions activity. 62 */ 63 public class GrantPermissionsWearViewHandler implements GrantPermissionsViewHandler { 64 65 public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME"; 66 public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT"; 67 public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX"; 68 public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON"; 69 public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"; 70 private static final String ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE"; 71 private static final String ARG_DIALOG_BUTTON_VISIBILITIES = "ARG_DIALOG_BUTTON_VISIBILITIES"; 72 private static final String ARG_DIALOG_LOCATION_VISIBILITIES = 73 "ARG_DIALOG_LOCATION_VISIBILITIES"; 74 75 public static final SparseIntArray BUTTON_RES_ID_TO_NUM = new SparseIntArray(); 76 static { BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_button, ALLOW_BUTTON)77 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)78 BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_always_button, 79 ALLOW_ALWAYS_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_foreground_only_button, ALLOW_FOREGROUND_BUTTON)80 BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_foreground_only_button, 81 ALLOW_FOREGROUND_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_button, DENY_BUTTON)82 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)83 BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_and_dont_ask_again_button, 84 DENY_AND_DONT_ASK_AGAIN_BUTTON); BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_one_time_button, ALLOW_ONE_TIME_BUTTON)85 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)86 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)87 BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_and_dont_ask_again_button, 88 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)89 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)90 BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_and_dont_ask_again_button, 91 NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON); 92 } 93 94 private final Activity mActivity; 95 96 private ResultListener mResultListener; 97 98 // Configuration of the current dialog 99 private String mGroupName; 100 private int mGroupCount; 101 private int mGroupIndex; 102 private Icon mGroupIcon; 103 private CharSequence mGroupMessage; 104 private CharSequence mDetailMessage; 105 private final boolean[] mButtonVisibilities = new boolean[NEXT_BUTTON]; 106 private final boolean[] mLocationVisibilities = new boolean[NEXT_LOCATION_DIALOG]; 107 // View model to update WearGrantPermissionsScreen elements 108 private WearGrantPermissionsViewModel mViewModel; 109 GrantPermissionsWearViewHandler(Activity activity)110 public GrantPermissionsWearViewHandler(Activity activity) { 111 mActivity = activity; 112 } 113 114 @Override setResultListener(ResultListener listener)115 public GrantPermissionsWearViewHandler setResultListener(ResultListener listener) { 116 mResultListener = listener; 117 return this; 118 } 119 120 @Override saveInstanceState(Bundle arguments)121 public void saveInstanceState(Bundle arguments) { 122 arguments.putString(ARG_GROUP_NAME, mGroupName); 123 arguments.putInt(ARG_GROUP_COUNT, mGroupCount); 124 arguments.putInt(ARG_GROUP_INDEX, mGroupIndex); 125 arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon); 126 arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage); 127 arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, mDetailMessage); 128 arguments.putBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES, mButtonVisibilities); 129 arguments.putBooleanArray(ARG_DIALOG_LOCATION_VISIBILITIES, mLocationVisibilities); 130 } 131 132 @Override loadInstanceState(Bundle savedInstanceState)133 public void loadInstanceState(Bundle savedInstanceState) { 134 mGroupName = savedInstanceState.getString(ARG_GROUP_NAME); 135 mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE); 136 mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON); 137 mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT); 138 mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX); 139 mDetailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE); 140 setButtonVisibilities(savedInstanceState.getBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES)); 141 setLocationVisibilities( 142 savedInstanceState.getBooleanArray(ARG_DIALOG_LOCATION_VISIBILITIES)); 143 144 updateScreen(); 145 } 146 147 @Override updateUi(String groupName, int groupCount, int groupIndex, Icon icon, CharSequence message, CharSequence detailMessage, CharSequence permissionRationaleMessage, boolean[] buttonVisibilities, boolean[] locationVisibilities)148 public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, 149 CharSequence message, CharSequence detailMessage, 150 CharSequence permissionRationaleMessage, boolean[] buttonVisibilities, 151 boolean[] locationVisibilities) { 152 // permissionRationaleMessage ignored by wear 153 if (mActivity.isFinishing()) { 154 return; 155 } 156 mGroupName = groupName; 157 mGroupCount = groupCount; 158 mGroupIndex = groupIndex; 159 mGroupIcon = icon; 160 mGroupMessage = message; 161 mDetailMessage = detailMessage; 162 setButtonVisibilities(buttonVisibilities); 163 setLocationVisibilities(locationVisibilities); 164 165 updateScreen(); 166 } 167 updateScreen()168 private void updateScreen() { 169 mViewModel.getIconLiveData().setValue( 170 mGroupIcon == null ? null : mGroupIcon.loadDrawable(mActivity)); 171 mViewModel.getGroupMessageLiveData().setValue( 172 mGroupMessage == null ? "" : mGroupMessage.toString()); 173 mViewModel.getDetailMessageLiveData().setValue(mDetailMessage); 174 int numButtons = BUTTON_RES_ID_TO_NUM.size(); 175 List<Boolean> buttonVisibilityList = Arrays.asList(new Boolean[NEXT_BUTTON]); 176 for (int i = 0; i < numButtons; i++) { 177 int pos = BUTTON_RES_ID_TO_NUM.valueAt(i); 178 buttonVisibilityList.set(pos, mButtonVisibilities[pos] ? Boolean.TRUE : Boolean.FALSE); 179 } 180 mViewModel.getButtonVisibilitiesLiveData().setValue(buttonVisibilityList); 181 List<Boolean> locationVisibilityList = Arrays.asList(new Boolean[NEXT_LOCATION_DIALOG]); 182 for (int i = 0; i < locationVisibilityList.size(); i++) { 183 locationVisibilityList.set(i, mLocationVisibilities[i] ? Boolean.TRUE : Boolean.FALSE); 184 } 185 mViewModel.getLocationVisibilitiesLiveData().setValue(locationVisibilityList); 186 if (mLocationVisibilities[DIALOG_WITH_BOTH_LOCATIONS]) { 187 mViewModel.getPreciseLocationCheckedLiveData() 188 .setValue(mLocationVisibilities[FINE_RADIO_BUTTON]); 189 } else if (mLocationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY] 190 || mLocationVisibilities[DIALOG_WITH_COARSE_LOCATION_ONLY]) { 191 mViewModel.getPreciseLocationCheckedLiveData().setValue(false); 192 } 193 mViewModel.getShowDialog().postValue(true); 194 } 195 onLocationSwitchChanged(boolean checked)196 private void onLocationSwitchChanged(boolean checked) { 197 mViewModel.getPreciseLocationCheckedLiveData().setValue(checked); 198 } 199 onButtonClicked(int id)200 private void onButtonClicked(int id) { 201 List<String> affectedForegroundPermissions = null; 202 if (mLocationVisibilities[DIALOG_WITH_BOTH_LOCATIONS]) { 203 if (Boolean.TRUE.equals(mViewModel.getPreciseLocationCheckedLiveData().getValue())) { 204 affectedForegroundPermissions = Arrays.asList( 205 ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION); 206 } else { 207 affectedForegroundPermissions = Collections.singletonList(ACCESS_COARSE_LOCATION); 208 } 209 } else if (mLocationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY]) { 210 affectedForegroundPermissions = Collections.singletonList(ACCESS_FINE_LOCATION); 211 } else if (mLocationVisibilities[DIALOG_WITH_COARSE_LOCATION_ONLY]) { 212 affectedForegroundPermissions = Collections.singletonList(ACCESS_COARSE_LOCATION); 213 } 214 215 int button = -1; 216 try { 217 button = BUTTON_RES_ID_TO_NUM.get(id); 218 } catch (NullPointerException e) { 219 // Clicked a view which is not one of the defined buttons 220 return; 221 } 222 // TODO (b/297534305): Research on performAccessibilityAction(). 223 switch (button) { 224 case ALLOW_BUTTON: 225 if (mResultListener != null) { 226 mResultListener.onPermissionGrantResult(mGroupName, 227 affectedForegroundPermissions, GRANTED_ALWAYS); 228 } 229 break; 230 case ALLOW_FOREGROUND_BUTTON: 231 if (mResultListener != null) { 232 mResultListener.onPermissionGrantResult(mGroupName, 233 affectedForegroundPermissions, GRANTED_FOREGROUND_ONLY); 234 } 235 break; 236 case ALLOW_ALWAYS_BUTTON: 237 if (mResultListener != null) { 238 mResultListener.onPermissionGrantResult(mGroupName, 239 affectedForegroundPermissions, GRANTED_ALWAYS); 240 } 241 break; 242 case ALLOW_ONE_TIME_BUTTON: 243 if (mResultListener != null) { 244 mResultListener.onPermissionGrantResult(mGroupName, 245 affectedForegroundPermissions, GRANTED_ONE_TIME); 246 } 247 break; 248 case DENY_BUTTON: 249 case NO_UPGRADE_BUTTON: 250 case NO_UPGRADE_OT_BUTTON: 251 if (mResultListener != null) { 252 mResultListener.onPermissionGrantResult(mGroupName, 253 affectedForegroundPermissions, DENIED); 254 } 255 break; 256 case DENY_AND_DONT_ASK_AGAIN_BUTTON: 257 case NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON: 258 case NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON: 259 if (mResultListener != null) { 260 mResultListener.onPermissionGrantResult(mGroupName, 261 affectedForegroundPermissions, DENIED_DO_NOT_ASK_AGAIN); 262 } 263 break; 264 } 265 } 266 267 @Override createView()268 public View createView() { 269 WearGrantPermissionsViewModelFactory factory = new WearGrantPermissionsViewModelFactory(); 270 mViewModel = factory.create(WearGrantPermissionsViewModel.class); 271 ComposeView root = new ComposeView(mActivity); 272 273 WearGrantPermissionsScreenKt.setContent(root, 274 mViewModel, 275 () -> { 276 if (mResultListener != null) { 277 mResultListener.onPermissionGrantResult(null, null, CANCELED); 278 } 279 return Unit.INSTANCE; 280 }, 281 id -> { 282 onButtonClicked(id); 283 return Unit.INSTANCE; 284 }, 285 checked -> { 286 onLocationSwitchChanged(checked); 287 return Unit.INSTANCE; 288 } 289 ); 290 if (mGroupName != null) { 291 updateScreen(); 292 } 293 294 return root; 295 } 296 297 @Override updateWindowAttributes(LayoutParams outLayoutParams)298 public void updateWindowAttributes(LayoutParams outLayoutParams) { 299 // No-op 300 } 301 setButtonVisibilities(@ullable boolean[] visibilities)302 private void setButtonVisibilities(@Nullable boolean[] visibilities) { 303 for (int i = 0; i < mButtonVisibilities.length; i++) { 304 mButtonVisibilities[i] = 305 visibilities != null && i < visibilities.length && visibilities[i]; 306 } 307 } 308 309 private void setLocationVisibilities(@Nullable boolean[] visibilities) { 310 for (int i = 0; i < mLocationVisibilities.length; i++) { 311 mLocationVisibilities[i] = 312 visibilities != null && i < visibilities.length && visibilities[i]; 313 } 314 } 315 316 @Override 317 public void onBackPressed() { 318 if (mResultListener != null) { 319 mResultListener.onPermissionGrantResult(mGroupName, CANCELED); 320 } else { 321 mActivity.finish(); 322 } 323 } 324 } 325