1 /* 2 * Copyright (C) 2020 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 package com.google.android.car.kitchensink.admin; 17 18 import static android.app.admin.DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED; 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.AlertDialog; 23 import android.app.admin.DevicePolicyManager; 24 import android.car.Car; 25 import android.car.admin.CarDevicePolicyManager; 26 import android.car.admin.CreateUserResult; 27 import android.car.admin.RemoveUserResult; 28 import android.car.admin.StartUserInBackgroundResult; 29 import android.car.admin.StopUserResult; 30 import android.content.ComponentName; 31 import android.content.Intent; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.content.pm.UserInfo; 35 import android.os.Bundle; 36 import android.os.UserHandle; 37 import android.os.UserManager; 38 import android.text.TextUtils; 39 import android.util.DebugUtils; 40 import android.util.Log; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.widget.ArrayAdapter; 45 import android.widget.Button; 46 import android.widget.CheckBox; 47 import android.widget.EditText; 48 import android.widget.Spinner; 49 50 import androidx.fragment.app.Fragment; 51 52 import com.google.android.car.kitchensink.KitchenSinkActivity; 53 import com.google.android.car.kitchensink.R; 54 import com.google.android.car.kitchensink.users.ExistingUsersView; 55 import com.google.android.car.kitchensink.users.UserInfoView; 56 57 import java.util.ArrayList; 58 import java.util.List; 59 60 /** 61 * Test UI for {@link CarDevicePolicyManager}. 62 */ 63 public final class DevicePolicyFragment extends Fragment { 64 65 private static final String TAG = DevicePolicyFragment.class.getSimpleName(); 66 67 private UserManager mUserManager; 68 private DevicePolicyManager mDevicePolicyManager; 69 private CarDevicePolicyManager mCarDevicePolicyManager; 70 71 // Current user 72 private UserInfoView mCurrentUser; 73 74 // Existing users 75 private ExistingUsersView mCurrentUsers; 76 77 // New user 78 private EditText mNewUserNameText; 79 private CheckBox mNewUserIsAdminCheckBox; 80 private CheckBox mNewUserIsGuestCheckBox; 81 private EditText mNewUserExtraFlagsText; 82 private Button mCreateUserButton; 83 84 // Reset password 85 private EditText mPasswordText; 86 private Button mResetPasswordButton; 87 88 // Other actions 89 private Button mRemoveUserButton; 90 private Button mStartUserInBackgroundButton; 91 private Button mStopUserButton; 92 private Button mLockNowButton; 93 private EditText mWipeDataFlagsText; 94 private Button mWipeDataButton; 95 96 // Lock tasks 97 private Button mCheckLockTasksButton; 98 private Button mStartLockTasksButton; 99 private Button mStopLockTasksButton; 100 101 // Set device admin 102 private final List<DeviceAdminApp> mDeviceAdminApps = new ArrayList<>(); 103 private Spinner mDeviceAdminAppsSpinner; 104 private Button mSetDeviceAdminAppButton; 105 106 @Nullable 107 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)108 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 109 @Nullable Bundle savedInstanceState) { 110 return inflater.inflate(R.layout.device_policy, container, false); 111 } 112 113 @Override onViewCreated(View view, Bundle savedInstanceState)114 public void onViewCreated(View view, Bundle savedInstanceState) { 115 mUserManager = UserManager.get(getContext()); 116 mDevicePolicyManager = getContext().getSystemService(DevicePolicyManager.class); 117 Car car = ((KitchenSinkActivity) getHost()).getCar(); 118 mCarDevicePolicyManager = (CarDevicePolicyManager) car 119 .getCarManager(Car.CAR_DEVICE_POLICY_SERVICE); 120 121 mCurrentUser = view.findViewById(R.id.current_user); 122 mCurrentUsers = view.findViewById(R.id.current_users); 123 mRemoveUserButton = view.findViewById(R.id.remove_user); 124 mStartUserInBackgroundButton = view.findViewById(R.id.start_user_in_background); 125 mStopUserButton = view.findViewById(R.id.stop_user); 126 127 mNewUserNameText = view.findViewById(R.id.new_user_name); 128 mNewUserIsAdminCheckBox = view.findViewById(R.id.new_user_is_admin); 129 mNewUserIsGuestCheckBox = view.findViewById(R.id.new_user_is_guest); 130 mCreateUserButton = view.findViewById(R.id.create_user); 131 132 mRemoveUserButton.setOnClickListener((v) -> removeUser()); 133 mCreateUserButton.setOnClickListener((v) -> createUser()); 134 mStartUserInBackgroundButton.setOnClickListener((v) -> startUserInBackground()); 135 mStopUserButton.setOnClickListener((v) -> stopUser()); 136 137 mPasswordText = view.findViewById(R.id.password); 138 mResetPasswordButton = view.findViewById(R.id.reset_password); 139 mResetPasswordButton.setOnClickListener((v) -> resetPassword()); 140 141 mLockNowButton = view.findViewById(R.id.lock_now); 142 mLockNowButton.setOnClickListener((v) -> lockNow()); 143 144 mWipeDataFlagsText = view.findViewById(R.id.wipe_data_flags); 145 mWipeDataButton = view.findViewById(R.id.wipe_data); 146 mWipeDataButton.setOnClickListener((v) -> wipeData()); 147 148 mCheckLockTasksButton = view.findViewById(R.id.check_lock_tasks); 149 mCheckLockTasksButton.setOnClickListener((v) -> checkLockTasks()); 150 151 mStartLockTasksButton = view.findViewById(R.id.start_lock_tasks); 152 mStartLockTasksButton.setOnClickListener((v) -> startLockTask()); 153 154 mStopLockTasksButton = view.findViewById(R.id.stop_lock_tasks); 155 mStopLockTasksButton.setOnClickListener((v) -> stopLockTasks()); 156 157 mDeviceAdminAppsSpinner = view.findViewById(R.id.device_admin_apps); 158 mSetDeviceAdminAppButton = view.findViewById(R.id.set_device_admin_app); 159 mSetDeviceAdminAppButton.setOnClickListener((v) -> launchSetDeviceAdminIntent()); 160 161 updateState(); 162 } 163 updateState()164 private void updateState() { 165 // Current user 166 int userId = UserHandle.myUserId(); 167 UserInfo user = mUserManager.getUserInfo(userId); 168 Log.v(TAG, "updateState(): currentUser= " + user); 169 mCurrentUser.update(user); 170 171 // Existing users 172 mCurrentUsers.updateState(); 173 174 setAdminApps(); 175 } 176 setAdminApps()177 private void setAdminApps() { 178 mDeviceAdminApps.clear(); 179 180 PackageManager pm = getContext().getPackageManager(); 181 182 List<ResolveInfo> receivers = pm.queryBroadcastReceivers( 183 new Intent(ACTION_DEVICE_ADMIN_ENABLED), /* flags= */ 0); 184 if (receivers.isEmpty()) { 185 Log.i(TAG, "setDeviceAdminApps(): no receivers for " + ACTION_DEVICE_ADMIN_ENABLED); 186 return; 187 } 188 Log.i(TAG, receivers.size() + " receivers for " + ACTION_DEVICE_ADMIN_ENABLED); 189 190 String[] entries = new String[receivers.size()]; 191 int i = 0; 192 for (ResolveInfo receiver : receivers) { 193 DeviceAdminApp adminApp = new DeviceAdminApp(receiver, pm); 194 Log.d(TAG, "Adding " + adminApp); 195 mDeviceAdminApps.add(adminApp); 196 entries[i++] = adminApp.name; 197 } 198 mDeviceAdminAppsSpinner.setAdapter( 199 new ArrayAdapter<String>(getContext(), android.R.layout.simple_spinner_item, 200 entries)); 201 } 202 removeUser()203 private void removeUser() { 204 int userId = mCurrentUsers.getSelectedUserId(); 205 Log.i(TAG, "Remove user: " + userId); 206 RemoveUserResult result = mCarDevicePolicyManager.removeUser(UserHandle.of(userId)); 207 if (result.isSuccess()) { 208 updateState(); 209 showMessage("User %d removed", userId); 210 } else { 211 showMessage("Failed to remove user %d: %s", userId, result); 212 } 213 } 214 createUser()215 private void createUser() { 216 String name = mNewUserNameText.getText().toString(); 217 if (TextUtils.isEmpty(name)) { 218 name = null; 219 } 220 // Type is treated as a flag here so we can emulate an invalid value by selecting both. 221 int type = CarDevicePolicyManager.USER_TYPE_REGULAR; 222 boolean isAdmin = mNewUserIsAdminCheckBox.isChecked(); 223 if (isAdmin) { 224 type |= CarDevicePolicyManager.USER_TYPE_ADMIN; 225 } 226 boolean isGuest = mNewUserIsGuestCheckBox.isChecked(); 227 if (isGuest) { 228 type |= CarDevicePolicyManager.USER_TYPE_GUEST; 229 } 230 CreateUserResult result = mCarDevicePolicyManager.createUser(name, type); 231 if (result.isSuccess()) { 232 showMessage("User created: %s", result.getUserHandle().getIdentifier()); 233 updateState(); 234 } else { 235 showMessage("Failed to create user with type %d: %s", type, result); 236 } 237 } 238 startUserInBackground()239 private void startUserInBackground() { 240 int userId = mCurrentUsers.getSelectedUserId(); 241 Log.i(TAG, "Start user in background: " + userId); 242 StartUserInBackgroundResult result = 243 mCarDevicePolicyManager.startUserInBackground(UserHandle.of(userId)); 244 if (result.isSuccess()) { 245 updateState(); 246 showMessage("User %d started", userId); 247 } else { 248 showMessage("Failed to start user %d in background: %s", userId, result); 249 } 250 } 251 stopUser()252 private void stopUser() { 253 int userId = mCurrentUsers.getSelectedUserId(); 254 Log.i(TAG, "Stop user: " + userId); 255 StopUserResult result = mCarDevicePolicyManager.stopUser(UserHandle.of(userId)); 256 if (result.isSuccess()) { 257 updateState(); 258 showMessage("User %d stopped", userId); 259 } else { 260 showMessage("Failed to stop user %d: %s", userId, result); 261 } 262 } 263 resetPassword()264 private void resetPassword() { 265 String password = mPasswordText.getText().toString(); 266 // NOTE: on "real" code the password should NEVER be logged in plain text, but it's fine 267 // here (as it's used for testing / development purposes) 268 Log.i(TAG, "Calling resetPassword('" + password + "')..."); 269 run(() -> mDevicePolicyManager.resetPassword(password, /* flags= */ 0), "Password reset!"); 270 } 271 lockNow()272 private void lockNow() { 273 Log.i(TAG, "Calling lockNow()..."); 274 run(() -> mDevicePolicyManager.lockNow(), "Locked!"); 275 } 276 wipeData()277 private void wipeData() { 278 new AlertDialog.Builder(getContext()) 279 .setMessage("Wiping data is irreversible, are you sure you want to self-destruct?") 280 .setPositiveButton("Yes", (d, w) -> selfDestruct()) 281 .show(); 282 } 283 isAllowedToCheckLockTasks()284 private boolean isAllowedToCheckLockTasks() { 285 return mDevicePolicyManager.isLockTaskPermitted(getContext().getPackageName()); 286 } 287 checkLockTasks()288 private void checkLockTasks() { 289 boolean isAllowed = isAllowedToCheckLockTasks(); 290 showMessage("KitchenSink %s allowed to lock tasks", isAllowed ? "IS" : "is NOT"); 291 } 292 startLockTask()293 private void startLockTask() { 294 Log.v(TAG, "startLockTask()"); 295 if (!isAllowedToCheckLockTasks()) { 296 showMessage("KitchenSink is not allowed to lock tasks, " 297 + "you must use the DPC app to allow it"); 298 return; 299 } 300 301 try { 302 getActivity().startLockTask(); 303 } catch (IllegalStateException e) { 304 showError(e, "No lock task present"); 305 } 306 } 307 stopLockTasks()308 private void stopLockTasks() { 309 Log.v(TAG, "stopLockTasks()"); 310 try { 311 getActivity().stopLockTask(); 312 } catch (IllegalStateException e) { 313 showError(e, "No lock task present"); 314 } 315 } 316 launchSetDeviceAdminIntent()317 private void launchSetDeviceAdminIntent() { 318 if (mDeviceAdminApps.isEmpty()) { 319 // Should be disabled 320 Log.e(TAG, "setAdminApp(): no admin"); 321 return; 322 } 323 int index = mDeviceAdminAppsSpinner.getSelectedItemPosition(); 324 DeviceAdminApp app; 325 try { 326 app = mDeviceAdminApps.get(index); 327 } catch (Exception e) { 328 Log.e(TAG, "Could not get app at index " + index, e); 329 return; 330 } 331 Log.v(TAG, "setAdminApp(): index=" + index + ",size=" + mDeviceAdminApps.size() + ",app=" 332 + app); 333 Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) 334 .putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, app.admin); 335 Log.i(TAG, "launching intent " + intent + " for " + app); 336 getActivity().startActivity(intent); 337 } 338 selfDestruct()339 private void selfDestruct() { 340 int flags = 0; 341 String flagsText = mWipeDataFlagsText.getText().toString(); 342 if (!TextUtils.isEmpty(flagsText)) { 343 try { 344 flags = Integer.parseInt(flagsText); 345 } catch (Exception e) { 346 Log.e(TAG, "Invalid wipeData flags: " + flagsText); 347 } 348 } 349 350 String flagsDesc = flags == 0 ? "0" : flags + "(" 351 + DebugUtils.flagsToString(DevicePolicyManager.class, "WIPE_", flags) + ")"; 352 353 Log.i(TAG, "Calling wipeData(" + flagsDesc + ")..."); 354 try { 355 mDevicePolicyManager.wipeData(flags, "SelfDestruct"); 356 } catch (Exception e) { 357 Log.e(TAG, "wipeData(" + flagsDesc + ") failed", e); 358 showMessage("wipeData(%s) failed: %s", flagsDesc, e); 359 } 360 } 361 run(@onNull Runnable runnable, @NonNull String successMessage)362 private void run(@NonNull Runnable runnable, @NonNull String successMessage) { 363 try { 364 runnable.run(); 365 showMessage(successMessage); 366 } catch (RuntimeException e) { 367 Log.e(TAG, "Failed", e); 368 showMessage("failed: " + e); 369 } 370 } 371 showMessage(@onNull String pattern, @Nullable Object... args)372 private void showMessage(@NonNull String pattern, @Nullable Object... args) { 373 String message = String.format(pattern, args); 374 Log.v(TAG, "showMessage(): " + message); 375 new AlertDialog.Builder(getContext()).setMessage(message).show(); 376 } 377 showError(@onNull Exception e, @NonNull String pattern, @Nullable Object... args)378 private void showError(@NonNull Exception e, @NonNull String pattern, 379 @Nullable Object... args) { 380 String message = String.format(pattern, args); 381 Log.e(TAG, "showError(): " + message, e); 382 new AlertDialog.Builder(getContext()).setMessage(message).show(); 383 } 384 385 private static final class DeviceAdminApp { 386 public final ComponentName admin; 387 public final String name; 388 DeviceAdminApp(ResolveInfo resolveInfo, PackageManager pm)389 DeviceAdminApp(ResolveInfo resolveInfo, PackageManager pm) { 390 admin = resolveInfo.getComponentInfo().getComponentName(); 391 CharSequence label = resolveInfo.loadLabel(pm); 392 if (TextUtils.isEmpty(label)) { 393 name = resolveInfo.getComponentInfo().name; 394 Log.v(TAG, "no label for " + admin.flattenToShortString() + "; using " + name); 395 } else { 396 name = label.toString(); 397 } 398 } 399 400 @Override toString()401 public String toString() { 402 return "AdminApp[name=" + name + ", admin=" + admin.flattenToShortString() + ']'; 403 } 404 } 405 } 406