1 /*
2  * Copyright (C) 2023 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.devicelockcontroller.policy;
18 
19 import android.os.OutcomeReceiver;
20 
21 import androidx.concurrent.futures.CallbackToFutureAdapter;
22 
23 import com.android.devicelockcontroller.SystemDeviceLockManager;
24 import com.android.devicelockcontroller.storage.SetupParametersClient;
25 import com.android.devicelockcontroller.util.LogUtil;
26 
27 import com.google.common.util.concurrent.Futures;
28 import com.google.common.util.concurrent.ListenableFuture;
29 import com.google.common.util.concurrent.MoreExecutors;
30 
31 import java.util.concurrent.Executor;
32 
33 final class AppOpsPolicyHandler implements PolicyHandler {
34     private static final String TAG = "AppOpsPolicyHandler";
35     private final SystemDeviceLockManager mSystemDeviceLockManager;
36     private final Executor mBgExecutor;
37 
AppOpsPolicyHandler(SystemDeviceLockManager systemDeviceLockManager, Executor bgExecutor)38     AppOpsPolicyHandler(SystemDeviceLockManager systemDeviceLockManager, Executor bgExecutor) {
39         mSystemDeviceLockManager = systemDeviceLockManager;
40         mBgExecutor = bgExecutor;
41     }
42 
43     /**
44      * Set the exempt from activity background start restriction state for dlc app.
45      */
setDlcExemptFromActivityBgStartRestrictionState( boolean exempt)46     private ListenableFuture<Boolean> setDlcExemptFromActivityBgStartRestrictionState(
47             boolean exempt) {
48         return CallbackToFutureAdapter.getFuture(completer -> {
49             mSystemDeviceLockManager.setDlcExemptFromActivityBgStartRestrictionState(exempt,
50                     mBgExecutor, new OutcomeReceiver<>() {
51                         @Override
52                         public void onResult(Void unused) {
53                             completer.set(true);
54                         }
55 
56                         @Override
57                         public void onError(Exception error) {
58                             LogUtil.e(TAG, "Cannot set background start exemption", error);
59                             completer.set(false);
60                         }
61                     });
62             // Used only for debugging.
63             return "setDlcExemptFromActivityBgStartRestrictionState";
64         });
65     }
66 
67     /**
68      * Set whether the dlc can send undismissible notifications.
69      */
70     private ListenableFuture<Boolean> setDlcAllowedToSendUndismissibleNotifications(
71             boolean allowed) {
72         return CallbackToFutureAdapter.getFuture(completer -> {
73             mSystemDeviceLockManager.setDlcAllowedToSendUndismissibleNotifications(allowed,
74                     mBgExecutor, new OutcomeReceiver<>() {
75                         @Override
76                         public void onResult(Void unused) {
77                             completer.set(true);
78                         }
79 
80                         @Override
81                         public void onError(Exception error) {
82                             LogUtil.e(TAG, "Cannot set undismissible notifs", error);
83                             // Returns true here to make sure state transition
84                             // success.
85                             completer.set(true);
86                         }
87                     });
88             // Used only for debugging.
89             return "setDlcAllowedToSendUndismissibleNotifications";
90         });
91     }
92 
93     /**
94      * Set the exempt from hibernation, battery and data usage restriction state for kiosk app.
95      */
96     private ListenableFuture<Boolean> setKioskAppExemptionsState(boolean exempt) {
97         return Futures.transformAsync(SetupParametersClient.getInstance().getKioskPackage(),
98                 kioskPackageName -> kioskPackageName == null ? Futures.immediateFuture(true)
99                         : CallbackToFutureAdapter.getFuture(completer -> {
100                             mSystemDeviceLockManager.setKioskAppExemptFromRestrictionsState(
101                                     kioskPackageName, exempt, mBgExecutor, new OutcomeReceiver<>() {
102                                         @Override
103                                         public void onResult(Void unused) {
104                                             completer.set(true);
105                                         }
106 
107                                         @Override
108                                         public void onError(Exception error) {
109                                             LogUtil.e(TAG, "Cannot set exempt kiosk app", error);
110                                             // Returns true here to make sure state transition
111                                             // success.
112                                             completer.set(true);
113                                         }
114                                     });
115                             // Used only for debugging.
116                             return "setKioskAppExemptionsState";
117                         }), MoreExecutors.directExecutor());
118     }
119 
120     /**
121      * Set the exempt state for dlc and kiosk app. Note that the exemptions are different between
122      * dlc app and kiosk app.
123      */
124     private ListenableFuture<Boolean> setDlcAndKioskAppExemptionsState(boolean exempt) {
125         final ListenableFuture<Boolean> backgroundFuture =
126                 setDlcExemptFromActivityBgStartRestrictionState(exempt);
127         final ListenableFuture<Boolean> kioskExemptionFuture = setKioskAppExemptionsState(exempt);
128         return Futures.whenAllSucceed(backgroundFuture, kioskExemptionFuture).call(
129                 () -> Futures.getDone(backgroundFuture) && Futures.getDone(kioskExemptionFuture),
130                 MoreExecutors.directExecutor());
131     }
132 
133     @Override
134     public ListenableFuture<Boolean> onProvisioned() {
135         return setDlcAndKioskAppExemptionsState(/* exempt= */ true);
136     }
137 
138     @Override
139     public ListenableFuture<Boolean> onProvisionInProgress() {
140         final ListenableFuture<Boolean> notifFuture =
141                 setDlcAllowedToSendUndismissibleNotifications(/* allowed= */ true);
142         final ListenableFuture<Boolean> backgroundFuture =
143                 setDlcExemptFromActivityBgStartRestrictionState(/* exempt= */ true);
144         return Futures.whenAllSucceed(notifFuture, backgroundFuture).call(
145                 () -> Futures.getDone(notifFuture) && Futures.getDone(backgroundFuture),
146                 MoreExecutors.directExecutor());
147     }
148 
149     @Override
150     public ListenableFuture<Boolean> onProvisionFailed() {
151         return setDlcExemptFromActivityBgStartRestrictionState(/* exempt= */ false);
152     }
153 
154     @Override
155     public ListenableFuture<Boolean> onCleared() {
156         final ListenableFuture<Boolean> notifFuture =
157                 setDlcAllowedToSendUndismissibleNotifications(/* allowed= */ false);
158         final ListenableFuture<Boolean> clearExemptions =
159                 setDlcAndKioskAppExemptionsState(/* exempt= */ false);
160         return Futures.whenAllSucceed(notifFuture, clearExemptions).call(
161                 () -> Futures.getDone(notifFuture) && Futures.getDone(clearExemptions),
162                 MoreExecutors.directExecutor());
163     }
164 
165     // Due to some reason, AppOpsManager does not persist exemption after reboot, therefore we
166     // need to always set them from our end.
167     @Override
168     public ListenableFuture<Boolean> onLocked() {
169         return setDlcAndKioskAppExemptionsState(/* exempt= */ true);
170     }
171 
172     // Due to some reason, AppOpsManager does not persist exemption after reboot, therefore we
173     // need to always set them from our end.
174     @Override
175     public ListenableFuture<Boolean> onUnlocked() {
176         return setKioskAppExemptionsState(/* exempt= */ true);
177     }
178 }
179