• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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