1 /*
2  * Copyright (C) 2021 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 
17 package com.android.car.settings.enterprise;
18 
19 import android.annotation.Nullable;
20 import android.app.AppOpsManager;
21 import android.car.drivingstate.CarUxRestrictions;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.graphics.drawable.Drawable;
25 import android.os.Binder;
26 import android.os.IBinder;
27 import android.text.TextUtils;
28 
29 import androidx.preference.TwoStatePreference;
30 
31 import com.android.car.settings.common.FragmentController;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 /**
35  * Controller for the header preference the device admin details screen.
36  */
37 public final class DeviceAdminAddHeaderPreferenceController
38         extends BaseDeviceAdminAddPreferenceController<TwoStatePreference> {
39 
40     private AppOpsManager mAppOps;
41     private final IBinder mToken = new Binder();
42     private @Nullable ActivationListener mActivationListener;
43 
DeviceAdminAddHeaderPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)44     public DeviceAdminAddHeaderPreferenceController(Context context, String preferenceKey,
45             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
46         super(context, preferenceKey, fragmentController, uxRestrictions);
47 
48         mAppOps = context.getSystemService(AppOpsManager.class);
49     }
50 
setActivationListener(ActivationListener listener)51     DeviceAdminAddHeaderPreferenceController setActivationListener(ActivationListener listener) {
52         mActivationListener = listener;
53         return this;
54     }
55 
56     @Override
getPreferenceType()57     protected Class<TwoStatePreference> getPreferenceType() {
58         return TwoStatePreference.class;
59     }
60 
61     @Override
onResumeInternal()62     protected void onResumeInternal() {
63         super.onResumeInternal();
64 
65         // Split as a separate method for easier testing.
66         onResumeInternal((TwoStatePreference) getPreference());
67     }
68 
69     @VisibleForTesting
onResumeInternal(TwoStatePreference preference)70     void onResumeInternal(TwoStatePreference preference) {
71         setCurrentStatus(preference);
72 
73         // As long as we are running, don't let anyone overlay stuff on top of the screen.
74         mAppOps.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, true, mToken);
75         mAppOps.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW, true, mToken);
76     }
77 
78     @Override
onPauseInternal()79     protected void onPauseInternal() {
80         super.onPauseInternal();
81 
82         // Split as a separate method for easier testing.
83         onPauseInternal((TwoStatePreference) getPreference());
84     }
85 
86     @VisibleForTesting
onPauseInternal(TwoStatePreference preference)87     void onPauseInternal(TwoStatePreference preference) {
88         // Disable the toggle button when paused, to prevent tapjacking.
89         preference.setEnabled(false);
90 
91         mAppOps.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, false, mToken);
92         mAppOps.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW, false, mToken);
93     }
94 
95     @Override
updateState(TwoStatePreference preference)96     protected void updateState(TwoStatePreference preference) {
97         CharSequence name = mDeviceAdminInfo.loadLabel(mPm);
98         Drawable icon = mDeviceAdminInfo.loadIcon(mPm);
99         CharSequence description = getDescription(mDeviceAdminInfo);
100 
101         mLogger.d("updateState: name=" + name  + ", description=" + description);
102         preference.setTitle(name);
103         preference.setIcon(icon);
104         if (!TextUtils.isEmpty(description)) {
105             preference.setSummary(description);
106         }
107 
108         setCurrentStatus(preference);
109     }
110 
111     @Override
handlePreferenceChanged(TwoStatePreference preference, Object newValue)112     protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
113         boolean activated = (boolean) newValue;
114         ComponentName admin = mDeviceAdminInfo.getComponent();
115         if (activated) {
116             mLogger.i("Activating " + ComponentName.flattenToShortString(admin));
117             // TODO(b/192372143): support refreshing
118             mDpm.setActiveAdmin(admin, /* refreshing= */ false);
119         } else {
120             mLogger.i("Deactivating " + ComponentName.flattenToShortString(admin));
121             mDpm.removeActiveAdmin(admin);
122         }
123         if (mActivationListener != null) {
124             mActivationListener.onChanged(activated);
125         }
126         return true;
127     }
128 
129     /** Sets the checked status and enabled status according to the device admin */
setCurrentStatus(TwoStatePreference preference)130     private void setCurrentStatus(TwoStatePreference preference) {
131         ComponentName componentName = mDeviceAdminInfo.getComponent();
132         preference.setChecked(isActive(componentName));
133         if (isProfileOrDeviceOwner(componentName)) {
134             // TODO(b/170332519): once work profiles are supported, they could be removed
135             mLogger.d("updateState(): " + ComponentName.flattenToShortString(componentName)
136                     + " is PO or DO");
137             preference.setEnabled(false);
138         } else {
139             preference.setEnabled(true);
140         }
141     }
142 
isActive(ComponentName componentName)143     private boolean isActive(ComponentName componentName) {
144         return mDpm.isAdminActive(componentName);
145     }
146 
147     interface ActivationListener {
onChanged(boolean active)148         void onChanged(boolean active);
149     }
150 }
151