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.android.server.devicepolicy;
17 
18 import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
19 import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
20 import static android.app.admin.DevicePolicyManager.operationToString;
21 
22 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
23 import android.app.admin.DevicePolicyManager.OperationSafetyReason;
24 import android.app.admin.DevicePolicyManagerLiteInternal;
25 import android.app.admin.DevicePolicySafetyChecker;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.util.Slog;
29 
30 import com.android.internal.os.IResultReceiver;
31 import com.android.server.LocalServices;
32 
33 import java.util.Objects;
34 
35 //TODO(b/172376923): add unit tests
36 
37 /**
38  * {@code DevicePolicySafetyChecker} implementation that overrides the real checker for just
39  * one command.
40  *
41  * <p>Used only for debugging and CTS tests.
42  */
43 final class OneTimeSafetyChecker implements DevicePolicySafetyChecker {
44 
45     private static final String TAG = OneTimeSafetyChecker.class.getSimpleName();
46 
47     private static final long SELF_DESTRUCT_TIMEOUT_MS = 10_000;
48 
49     private final DevicePolicyManagerService mService;
50     private final DevicePolicySafetyChecker mRealSafetyChecker;
51     private final @DevicePolicyOperation int mOperation;
52     private final @OperationSafetyReason int mReason;
53     private final Handler mHandler = new Handler(Looper.getMainLooper());
54 
55     private boolean mDone;
56 
OneTimeSafetyChecker(DevicePolicyManagerService service, @DevicePolicyOperation int operation, @OperationSafetyReason int reason)57     OneTimeSafetyChecker(DevicePolicyManagerService service,
58             @DevicePolicyOperation int operation, @OperationSafetyReason int reason) {
59         mService = Objects.requireNonNull(service);
60         mOperation = operation;
61         mReason = reason;
62         mRealSafetyChecker = service.getDevicePolicySafetyChecker();
63         Slog.i(TAG, "OneTimeSafetyChecker constructor: operation=" + operationToString(operation)
64                 + ", reason=" + operationSafetyReasonToString(reason)
65                 + ", realChecker=" + mRealSafetyChecker
66                 + ", maxDuration=" + SELF_DESTRUCT_TIMEOUT_MS + "ms");
67         mHandler.postDelayed(() -> selfDestruct(), SELF_DESTRUCT_TIMEOUT_MS);
68     }
69 
70     @Override
71     @OperationSafetyReason
getUnsafeOperationReason(@evicePolicyOperation int operation)72     public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
73         String name = operationToString(operation);
74         Slog.i(TAG, "getUnsafeOperationReason(" + name + ")");
75         int reason = OPERATION_SAFETY_REASON_NONE;
76         if (operation == mOperation) {
77             reason = mReason;
78         } else {
79             Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name
80                     + ", should be " + operationToString(mOperation));
81         }
82         String reasonName = operationSafetyReasonToString(reason);
83         DevicePolicyManagerLiteInternal dpmi = LocalServices
84                 .getService(DevicePolicyManagerLiteInternal.class);
85 
86         Slog.i(TAG, "notifying " + reasonName + " is UNSAFE");
87         dpmi.notifyUnsafeOperationStateChanged(this, reason, /* isSafe= */ false);
88 
89         Slog.i(TAG, "notifying " + reasonName + " is SAFE");
90         dpmi.notifyUnsafeOperationStateChanged(this, reason, /* isSafe= */ true);
91 
92         Slog.i(TAG, "returning " + reasonName);
93 
94         disableSelf();
95         return reason;
96     }
97 
98     @Override
isSafeOperation(@perationSafetyReason int reason)99     public boolean isSafeOperation(@OperationSafetyReason int reason) {
100         boolean safe = mReason != reason;
101         Slog.i(TAG, "isSafeOperation(" + operationSafetyReasonToString(reason) + "): " + safe);
102 
103         disableSelf();
104         return safe;
105     }
106 
107     @Override
onFactoryReset(IResultReceiver callback)108     public void onFactoryReset(IResultReceiver callback) {
109         throw new UnsupportedOperationException();
110     }
111 
disableSelf()112     private void disableSelf() {
113         if (mDone) {
114             Slog.w(TAG, "disableSelf(): already disabled");
115             return;
116         }
117         Slog.i(TAG, "restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
118         mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
119         mDone = true;
120     }
121 
selfDestruct()122     private void selfDestruct() {
123         if (mDone) return;
124 
125         // Usually happens when a CTS failed before calling the DPM method that would clear it
126         Slog.e(TAG, "Self destructing " + this + ", as it was not automatically disabled");
127         disableSelf();
128     }
129 
130     @Override
toString()131     public String toString() {
132         return "OneTimeSafetyChecker[id=" + System.identityHashCode(this)
133                 + ", reason=" + operationSafetyReasonToString(mReason)
134                 + ", operation=" + operationToString(mOperation) + ']';
135     }
136 }
137