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