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