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