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;
18 
19 import android.annotation.CallbackExecutor;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.content.pm.PackageManager.PackageInfoFlags;
23 import android.devicelock.DeviceLockManager;
24 import android.devicelock.IDeviceLockService;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.OutcomeReceiver;
29 import android.os.RemoteCallback;
30 import android.os.RemoteException;
31 
32 import androidx.annotation.NonNull;
33 
34 import java.util.Objects;
35 import java.util.concurrent.Executor;
36 
37 /**
38  * Implementation for SystemDeviceLockManager.
39  */
40 public final class SystemDeviceLockManagerImpl implements SystemDeviceLockManager {
41     private static final String TAG = "SystemDeviceLockManagerImpl";
42 
43     private final IDeviceLockService mIDeviceLockService;
44     private final Context mContext;
45 
SystemDeviceLockManagerImpl(Context context)46     private SystemDeviceLockManagerImpl(Context context) {
47         mContext = context;
48         final DeviceLockManager deviceLockManager = context.getSystemService(
49                 DeviceLockManager.class);
50 
51         mIDeviceLockService = deviceLockManager.getService();
52     }
53 
SystemDeviceLockManagerImpl()54     private SystemDeviceLockManagerImpl() {
55         this(DeviceLockControllerApplication.getAppContext());
56     }
57 
58     // Initialization-on-demand holder.
59     private static final class SystemDeviceLockManagerHolder {
60         static final SystemDeviceLockManagerImpl sSystemDeviceLockManager =
61                 new SystemDeviceLockManagerImpl();
62     }
63 
64     /**
65      * Returns the lazily initialized singleton instance of the SystemDeviceLockManager.
66      */
getInstance()67     public static SystemDeviceLockManager getInstance() {
68         return SystemDeviceLockManagerHolder.sSystemDeviceLockManager;
69     }
70 
71     @Override
addFinancedDeviceKioskRole(@onNull String packageName, @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)72     public void addFinancedDeviceKioskRole(@NonNull String packageName,
73             @CallbackExecutor Executor executor,
74             @NonNull OutcomeReceiver<Void, Exception> callback) {
75         Objects.requireNonNull(executor);
76         Objects.requireNonNull(callback);
77 
78         try {
79             mIDeviceLockService.addFinancedDeviceKioskRole(packageName,
80                     new RemoteCallback(result -> executor.execute(() -> {
81                         processResult(result, callback,
82                                 "Failed to add financed role to: " + packageName);
83                     }), new Handler(Looper.getMainLooper())));
84         } catch (RemoteException e) {
85             executor.execute(() -> callback.onError(new RuntimeException(e)));
86         }
87     }
88 
89     @Override
removeFinancedDeviceKioskRole(@onNull String packageName, @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)90     public void removeFinancedDeviceKioskRole(@NonNull String packageName,
91             @CallbackExecutor Executor executor,
92             @NonNull OutcomeReceiver<Void, Exception> callback) {
93         Objects.requireNonNull(executor);
94         Objects.requireNonNull(callback);
95 
96         try {
97             mIDeviceLockService.removeFinancedDeviceKioskRole(packageName,
98                     new RemoteCallback(result -> executor.execute(() -> {
99                         processResult(result, callback,
100                                 "Failed to remove financed role from: " + packageName);
101                     }), new Handler(Looper.getMainLooper())));
102         } catch (RemoteException e) {
103             executor.execute(() -> callback.onError(new RuntimeException(e)));
104         }
105     }
106 
107     @Override
setDlcExemptFromActivityBgStartRestrictionState(boolean exempt, @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)108     public void setDlcExemptFromActivityBgStartRestrictionState(boolean exempt,
109             @CallbackExecutor Executor executor,
110             @NonNull OutcomeReceiver<Void, Exception> callback) {
111         Objects.requireNonNull(executor);
112         Objects.requireNonNull(callback);
113 
114         try {
115             mIDeviceLockService.setCallerExemptFromActivityBgStartRestrictionState(exempt,
116                     new RemoteCallback(result -> executor.execute(() -> {
117                         processResult(result, callback,
118                                 "Failed to change exempt from activity background start to: "
119                                         + (exempt ? "exempt" : "non exempt"));
120                     }), new Handler(Looper.getMainLooper())));
121         } catch (RemoteException e) {
122             executor.execute(() -> callback.onError(new RuntimeException(e)));
123         }
124     }
125 
126     @Override
setDlcAllowedToSendUndismissibleNotifications(boolean allowed, @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)127     public void setDlcAllowedToSendUndismissibleNotifications(boolean allowed,
128             @CallbackExecutor Executor executor,
129             @NonNull OutcomeReceiver<Void, Exception> callback) {
130         Objects.requireNonNull(executor);
131         Objects.requireNonNull(callback);
132 
133         try {
134             mIDeviceLockService.setCallerAllowedToSendUndismissibleNotifications(allowed,
135                     new RemoteCallback(result -> executor.execute(() -> {
136                         processResult(result, callback,
137                                 "Failed to change undismissible notifs allowed to: "
138                                         + (allowed ? "allowed" : "not allowed"));
139                     }), new Handler(Looper.getMainLooper())));
140         } catch (RemoteException e) {
141             executor.execute(() -> callback.onError(new RuntimeException(e)));
142         }
143     }
144 
145     @Override
setKioskAppExemptFromRestrictionsState(String packageName, boolean exempt, Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)146     public void setKioskAppExemptFromRestrictionsState(String packageName, boolean exempt,
147             Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback) {
148         Objects.requireNonNull(executor);
149         Objects.requireNonNull(callback);
150 
151         try {
152             int kioskUid = mContext.getPackageManager().getPackageUid(packageName,
153                     PackageInfoFlags.of(0));
154             mIDeviceLockService.setUidExemptFromRestrictionsState(kioskUid, exempt,
155                     new RemoteCallback(result -> executor.execute(() -> {
156                         processResult(result, callback, "Failed to exempt for UID: " + kioskUid);
157                     }), new Handler(Looper.getMainLooper())));
158         } catch (RemoteException | PackageManager.NameNotFoundException e) {
159             executor.execute(() -> callback.onError(new RuntimeException(e)));
160         }
161     }
162 
163     @Override
enableKioskKeepalive(String packageName, Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)164     public void enableKioskKeepalive(String packageName, Executor executor,
165             @NonNull OutcomeReceiver<Void, Exception> callback) {
166         Objects.requireNonNull(packageName);
167         Objects.requireNonNull(executor);
168         Objects.requireNonNull(callback);
169 
170         try {
171             mIDeviceLockService.enableKioskKeepalive(packageName,
172                     new RemoteCallback(result -> executor.execute(() -> {
173                         processResult(result, callback,
174                                 "Failed to enable kiosk keep-alive for package: "
175                                         + packageName);
176                     }), new Handler(Looper.getMainLooper())));
177         } catch (RemoteException e) {
178             executor.execute(() -> callback.onError(new RuntimeException(e)));
179         }
180     }
181 
182     @Override
disableKioskKeepalive(Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)183     public void disableKioskKeepalive(Executor executor,
184             @NonNull OutcomeReceiver<Void, Exception> callback) {
185         Objects.requireNonNull(executor);
186         Objects.requireNonNull(callback);
187 
188         try {
189             mIDeviceLockService.disableKioskKeepalive(
190                     new RemoteCallback(result -> executor.execute(() -> {
191                         processResult(result, callback, "Failed to disable kiosk keep-alive");
192                     }), new Handler(Looper.getMainLooper())));
193         } catch (RemoteException e) {
194             executor.execute(() -> callback.onError(new RuntimeException(e)));
195         }
196     }
197 
198     @Override
enableControllerKeepalive(Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)199     public void enableControllerKeepalive(Executor executor,
200             @NonNull OutcomeReceiver<Void, Exception> callback) {
201         Objects.requireNonNull(executor);
202         Objects.requireNonNull(callback);
203 
204         try {
205             mIDeviceLockService.enableControllerKeepalive(
206                     new RemoteCallback(result -> executor.execute(() -> {
207                         processResult(result, callback, "Failed to enable controller keep-alive");
208                     }), new Handler(Looper.getMainLooper())));
209         } catch (RemoteException e) {
210             executor.execute(() -> callback.onError(new RuntimeException(e)));
211         }
212     }
213 
214     @Override
disableControllerKeepalive(Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)215     public void disableControllerKeepalive(Executor executor,
216             @NonNull OutcomeReceiver<Void, Exception> callback) {
217         Objects.requireNonNull(executor);
218         Objects.requireNonNull(callback);
219 
220         try {
221             mIDeviceLockService.disableControllerKeepalive(
222                     new RemoteCallback(result -> executor.execute(() -> {
223                         processResult(result, callback, "Failed to disable controller keep-alive");
224                     }), new Handler(Looper.getMainLooper())));
225         } catch (RemoteException e) {
226             executor.execute(() -> callback.onError(new RuntimeException(e)));
227         }
228     }
229 
230     @Override
setDeviceFinalized(boolean finalized, Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)231     public void setDeviceFinalized(boolean finalized, Executor executor,
232             @NonNull OutcomeReceiver<Void, Exception> callback) {
233         Objects.requireNonNull(executor);
234         Objects.requireNonNull(callback);
235 
236         try {
237             mIDeviceLockService.setDeviceFinalized(finalized,
238                     new RemoteCallback(result -> executor.execute(() -> {
239                         processResult(result, callback, "Failed to set device finalized");
240                     }), new Handler(Looper.getMainLooper())));
241         } catch (RemoteException e) {
242             executor.execute(() -> callback.onError(new RuntimeException(e)));
243         }
244     }
245 
246     @Override
setPostNotificationsSystemFixed(boolean systemFixed, Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)247     public void setPostNotificationsSystemFixed(boolean systemFixed, Executor executor,
248             @NonNull OutcomeReceiver<Void, Exception> callback) {
249         Objects.requireNonNull(executor);
250         Objects.requireNonNull(callback);
251 
252         try {
253             mIDeviceLockService.setPostNotificationsSystemFixed(systemFixed,
254                     new RemoteCallback(result -> executor.execute(() -> {
255                         processResult(result, callback, "Failed to change POST_NOTIFICATIONS "
256                                 + "SYSTEM_FIXED flag to: " + systemFixed);
257                     }), new Handler(Looper.getMainLooper())));
258         } catch (RemoteException e) {
259             executor.execute(() -> callback.onError(new RuntimeException(e)));
260         }
261     }
262 
processResult(Bundle result, @NonNull OutcomeReceiver<Void, Exception> callback, String message)263     private static void processResult(Bundle result,
264             @NonNull OutcomeReceiver<Void, Exception> callback, String message) {
265         final boolean remoteCallbackResult = result.getBoolean(
266                 IDeviceLockService.KEY_REMOTE_CALLBACK_RESULT);
267         if (remoteCallbackResult) {
268             callback.onResult(null /* result */);
269         } else {
270             callback.onError(new Exception(message));
271         }
272     }
273 }
274