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