• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 android.security.advancedprotection;
18 
19 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
20 import static android.os.UserManager.DISALLOW_CELLULAR_2G;
21 import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
22 
23 import android.Manifest;
24 import android.annotation.CallbackExecutor;
25 import android.annotation.FlaggedApi;
26 import android.annotation.IntDef;
27 import android.annotation.NonNull;
28 import android.annotation.RequiresPermission;
29 import android.annotation.SystemApi;
30 import android.annotation.SystemService;
31 import android.app.admin.DevicePolicyManager;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.net.wifi.WifiManager;
35 import android.os.Binder;
36 import android.os.RemoteException;
37 import android.os.UserManager;
38 import android.security.Flags;
39 import android.util.Log;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.util.List;
44 import java.util.Objects;
45 import java.util.Set;
46 import java.util.concurrent.ConcurrentHashMap;
47 import java.util.concurrent.Executor;
48 
49 /**
50  * <p>Advanced Protection is a mode that users can enroll their device into, that enhances security
51  * by enabling features and restrictions across both the platform and user apps.
52  *
53  * <p>This class provides methods to query and control the advanced protection mode
54  * for the device.
55  */
56 @FlaggedApi(Flags.FLAG_AAPM_API)
57 @SystemService(Context.ADVANCED_PROTECTION_SERVICE)
58 public final class AdvancedProtectionManager {
59     private static final String TAG = "AdvancedProtectionMgr";
60     private static final String PKG_SETTINGS = "com.android.settings";
61 
62     //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
63     //when the appropriate flag is launched.
64     private static final String MEMORY_TAGGING_POLICY = "memoryTagging";
65 
66     /**
67      * Advanced Protection's identifier for setting policies or restrictions in
68      * {@link DevicePolicyManager}.
69      *
70      * @hide */
71     public static final String ADVANCED_PROTECTION_SYSTEM_ENTITY =
72             "android.security.advancedprotection";
73 
74     /**
75      * Feature identifier for disallowing connections to 2G networks.
76      *
77      * @see UserManager#DISALLOW_CELLULAR_2G
78      * @hide */
79     @SystemApi
80     public static final int FEATURE_ID_DISALLOW_CELLULAR_2G = 0;
81 
82     /**
83      * Feature identifier for disallowing installs of apps from unknown sources.
84      *
85      * @see UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
86      * @hide */
87     @SystemApi
88     public static final int FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = 1;
89 
90     /**
91      * Feature identifier for disallowing USB connections.
92      *
93      * @hide */
94     @SystemApi
95     public static final int FEATURE_ID_DISALLOW_USB = 2;
96 
97     /**
98      * Feature identifier for disallowing connections to Wi-Fi Wired Equivalent Privacy (WEP)
99      * networks.
100      *
101      * @see WifiManager#isWepSupported()
102      * @hide */
103     @SystemApi
104     public static final int FEATURE_ID_DISALLOW_WEP = 3;
105 
106     /**
107      * Feature identifier for enabling the Memory Tagging Extension (MTE). MTE is a CPU extension
108      * that allows to protect against certain classes of security problems at a small runtime
109      * performance cost overhead.
110      *
111      * @see DevicePolicyManager#setMtePolicy(int)
112      * @hide */
113     @SystemApi
114     public static final int FEATURE_ID_ENABLE_MTE = 4;
115 
116     /** @hide */
117     @IntDef(prefix = { "FEATURE_ID_" }, value = {
118             FEATURE_ID_DISALLOW_CELLULAR_2G,
119             FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
120             FEATURE_ID_DISALLOW_USB,
121             FEATURE_ID_DISALLOW_WEP,
122             FEATURE_ID_ENABLE_MTE,
123     })
124     @Retention(RetentionPolicy.SOURCE)
125     public @interface FeatureId {}
126 
127     /** @hide */
featureIdToString(@eatureId int featureId)128     public static String featureIdToString(@FeatureId int featureId) {
129         return switch(featureId) {
130             case FEATURE_ID_DISALLOW_CELLULAR_2G -> "DISALLOW_CELLULAR_2G";
131             case FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES -> "DISALLOW_INSTALL_UNKNOWN_SOURCES";
132             case FEATURE_ID_DISALLOW_USB -> "DISALLOW_USB";
133             case FEATURE_ID_DISALLOW_WEP -> "DISALLOW_WEP";
134             case FEATURE_ID_ENABLE_MTE -> "ENABLE_MTE";
135             default -> "UNKNOWN";
136         };
137     }
138 
139     private static final Set<Integer> ALL_FEATURE_IDS = Set.of(
140             FEATURE_ID_DISALLOW_CELLULAR_2G,
141             FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
142             FEATURE_ID_DISALLOW_USB,
143             FEATURE_ID_DISALLOW_WEP,
144             FEATURE_ID_ENABLE_MTE);
145 
146     /**
147      * Activity Action: Show a dialog with disabled by advanced protection message.
148      * <p> If a user action or a setting toggle is disabled by advanced protection, this dialog can
149      * be triggered to let the user know about this.
150      * <p>
151      * Input:
152      * <p>{@link #EXTRA_SUPPORT_DIALOG_FEATURE}: The feature identifier.
153      * <p>{@link #EXTRA_SUPPORT_DIALOG_TYPE}: The type of the action.
154      * <p>
155      * Output: Nothing.
156      *
157      * @hide */
158     public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG =
159             "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG";
160 
161     /**
162      * An int extra used with {@link #createSupportIntent} to identify the feature that needs to
163      * show a support dialog explaining it was disabled by advanced protection.
164      *
165      * @hide */
166     @FeatureId
167     public static final String EXTRA_SUPPORT_DIALOG_FEATURE =
168             "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE";
169 
170     /**
171      * An int extra used with {@link #createSupportIntent} to identify the type of the action that
172      * needs to be explained in the support dialog.
173      *
174      * @hide */
175     @SupportDialogType
176     public static final String EXTRA_SUPPORT_DIALOG_TYPE =
177             "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE";
178 
179     /**
180      * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating an unknown action was blocked by
181      * advanced protection, hence the support dialog should display a default explanation.
182      *
183      * @hide */
184     public static final int SUPPORT_DIALOG_TYPE_UNKNOWN = 0;
185 
186     /**
187      * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user performed an action that was
188      * blocked by advanced protection.
189      *
190      * @hide */
191     public static final int SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = 1;
192 
193     /**
194      * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user pressed on a setting toggle
195      * that was disabled by advanced protection.
196      *
197      * @hide */
198     public static final int SUPPORT_DIALOG_TYPE_DISABLED_SETTING = 2;
199 
200     /** @hide */
201     @IntDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = {
202             SUPPORT_DIALOG_TYPE_UNKNOWN,
203             SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
204             SUPPORT_DIALOG_TYPE_DISABLED_SETTING,
205     })
206     @Retention(RetentionPolicy.SOURCE)
207     public @interface SupportDialogType {}
208 
209     /** @hide */
supportDialogTypeToString(@upportDialogType int type)210     public static String supportDialogTypeToString(@SupportDialogType int type) {
211         return switch(type) {
212             case SUPPORT_DIALOG_TYPE_UNKNOWN -> "UNKNOWN";
213             case SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION -> "BLOCKED_INTERACTION";
214             case SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> "DISABLED_SETTING";
215             default -> "UNKNOWN";
216         };
217     }
218 
219     private static final Set<Integer> ALL_SUPPORT_DIALOG_TYPES = Set.of(
220             SUPPORT_DIALOG_TYPE_UNKNOWN,
221             SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
222             SUPPORT_DIALOG_TYPE_DISABLED_SETTING);
223 
224     private final ConcurrentHashMap<Callback, IAdvancedProtectionCallback>
225             mCallbackMap = new ConcurrentHashMap<>();
226 
227     @NonNull
228     private final IAdvancedProtectionService mService;
229 
230     /** @hide */
231     public AdvancedProtectionManager(@NonNull IAdvancedProtectionService service) {
232         mService = service;
233     }
234 
235     /**
236      * Checks if advanced protection is enabled on the device.
237      *
238      * @return {@code true} if advanced protection is enabled, {@code false} otherwise.
239      */
240     @RequiresPermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE)
241     public boolean isAdvancedProtectionEnabled() {
242         try {
243             return mService.isAdvancedProtectionEnabled();
244         } catch (RemoteException e) {
245             throw e.rethrowFromSystemServer();
246         }
247     }
248 
249     /**
250      * Registers a {@link Callback} to be notified of changes to the Advanced Protection state.
251      *
252      * <p>The provided callback will be called on the specified executor with the updated
253      * state. Methods are called when the state changes, as well as once
254      * on initial registration.
255      *
256      * @param executor The executor of where the callback will execute.
257      * @param callback The {@link Callback} object to register..
258      */
259     @RequiresPermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE)
260     public void registerAdvancedProtectionCallback(@NonNull @CallbackExecutor Executor executor,
261             @NonNull Callback callback) {
262         if (mCallbackMap.get(callback) != null) {
263             Log.d(TAG, "registerAdvancedProtectionCallback callback already present");
264             return;
265         }
266 
267         IAdvancedProtectionCallback delegate = new IAdvancedProtectionCallback.Stub() {
268             @Override
269             public void onAdvancedProtectionChanged(boolean enabled) {
270                 final long identity = Binder.clearCallingIdentity();
271                 try {
272                     executor.execute(() -> callback.onAdvancedProtectionChanged(enabled));
273                 } finally {
274                     Binder.restoreCallingIdentity(identity);
275                 }
276             }
277         };
278 
279         try {
280             mService.registerAdvancedProtectionCallback(delegate);
281         } catch (RemoteException e) {
282             throw e.rethrowFromSystemServer();
283         }
284 
285         mCallbackMap.put(callback, delegate);
286     }
287 
288     /**
289      * Unregister an existing {@link Callback}.
290      *
291      * @param callback The {@link Callback} object to unregister.
292      */
293     @RequiresPermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE)
294     public void unregisterAdvancedProtectionCallback(@NonNull Callback callback) {
295         IAdvancedProtectionCallback delegate = mCallbackMap.get(callback);
296         if (delegate == null) {
297             Log.d(TAG, "unregisterAdvancedProtectionCallback callback not present");
298             return;
299         }
300 
301         try {
302             mService.unregisterAdvancedProtectionCallback(delegate);
303         } catch (RemoteException e) {
304             throw e.rethrowFromSystemServer();
305         }
306 
307         mCallbackMap.remove(callback);
308     }
309 
310     /**
311      * Enables or disables advanced protection on the device. Can only be called by an admin user.
312      *
313      * @param enabled {@code true} to enable advanced protection, {@code false} to disable it.
314      * @hide
315      */
316     @SystemApi
317     @RequiresPermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
318     public void setAdvancedProtectionEnabled(boolean enabled) {
319         try {
320             mService.setAdvancedProtectionEnabled(enabled);
321         } catch (RemoteException e) {
322             throw e.rethrowFromSystemServer();
323         }
324     }
325 
326     /**
327      * Returns the list of advanced protection features which are available on this device.
328      *
329      * @hide
330      */
331     @SystemApi
332     @NonNull
333     @RequiresPermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
334     public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() {
335         try {
336             return mService.getAdvancedProtectionFeatures();
337         } catch (RemoteException e) {
338             throw e.rethrowFromSystemServer();
339         }
340     }
341 
342     /**
343      * Called by a feature to display a support dialog when a feature was disabled by advanced
344      * protection. This returns an intent that can be used with
345      * {@link Context#startActivity(Intent)} to display the dialog.
346      *
347      * <p>Note that this method doesn't check if the feature is actually disabled, i.e. this method
348      * will always return an intent.
349      *
350      * @param featureId The feature identifier.
351      * @param type The type of the feature describing the action that needs to be explained
352      *                 in the dialog or {@link #SUPPORT_DIALOG_TYPE_UNKNOWN} for default
353      *                 explanation.
354      * @return Intent An intent to be used to start the dialog-activity that explains a feature was
355      *                disabled by advanced protection.
356      * @hide
357      */
358     public static @NonNull Intent createSupportIntent(@FeatureId int featureId,
359             @SupportDialogType int type) {
360         if (!ALL_FEATURE_IDS.contains(featureId)) {
361             throw new IllegalArgumentException(featureId + " is not a valid feature ID. See"
362                     + " FEATURE_ID_* APIs.");
363         }
364         if (!ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
365             throw new IllegalArgumentException(type + " is not a valid type. See"
366                     + " SUPPORT_DIALOG_TYPE_* APIs.");
367         }
368 
369         Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG);
370         intent.setPackage(PKG_SETTINGS);
371         intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
372         intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId);
373         intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type);
374         return intent;
375     }
376 
377     /**
378      * Called by a feature to display a support dialog when a feature was disabled by advanced
379      * protection based on a policy identifier or restriction. This returns an intent that can be
380      * used with {@link Context#startActivity(Intent)} to display the dialog.
381      *
382      * <p>At the moment, if the dialog is for {@link #FEATURE_ID_DISALLOW_CELLULAR_2G} or
383      * {@link #FEATURE_ID_ENABLE_MTE} and the provided type is
384      * {@link #SUPPORT_DIALOG_TYPE_UNKNOWN}, the type will be changed to
385      * {@link #SUPPORT_DIALOG_TYPE_DISABLED_SETTING} in the returned intent, as these features only
386      * have a disabled setting UI.
387      *
388      * <p>Note that this method doesn't check if the feature is actually disabled, i.e. this method
389      * will always return an intent.
390      *
391      * @param identifier The policy identifier or restriction.
392      * @param type The type of the feature describing the action that needs to be explained
393      *                 in the dialog or {@link #SUPPORT_DIALOG_TYPE_UNKNOWN} for default
394      *                 explanation.
395      * @return Intent An intent to be used to start the dialog-activity that explains a feature was
396      *                disabled by advanced protection.
397      * @hide */
398     public static @NonNull Intent createSupportIntentForPolicyIdentifierOrRestriction(
399             @NonNull String identifier, @SupportDialogType int type) {
400         Objects.requireNonNull(identifier);
401         if (!ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
402             throw new IllegalArgumentException(type + " is not a valid type. See"
403                     + " SUPPORT_DIALOG_TYPE_* APIs.");
404         }
405         final int featureId;
406         int dialogType = type;
407         if (DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY.equals(identifier)) {
408             featureId = FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
409         } else if (DISALLOW_CELLULAR_2G.equals(identifier)) {
410             featureId = FEATURE_ID_DISALLOW_CELLULAR_2G;
411             dialogType = (dialogType == SUPPORT_DIALOG_TYPE_UNKNOWN)
412                     ? SUPPORT_DIALOG_TYPE_DISABLED_SETTING : dialogType;
413         } else if (MEMORY_TAGGING_POLICY.equals(identifier)) {
414             featureId = FEATURE_ID_ENABLE_MTE;
415             dialogType = (dialogType == SUPPORT_DIALOG_TYPE_UNKNOWN)
416                     ? SUPPORT_DIALOG_TYPE_DISABLED_SETTING : dialogType;
417         } else {
418             throw new UnsupportedOperationException("Unsupported identifier: " + identifier);
419         }
420         return createSupportIntent(featureId, dialogType);
421     }
422 
423     /** @hide */
424     @RequiresPermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
425     public void logDialogShown(@FeatureId int featureId, @SupportDialogType int type,
426             boolean learnMoreClicked) {
427         try {
428             mService.logDialogShown(featureId, type, learnMoreClicked);
429         } catch (RemoteException e) {
430             throw e.rethrowFromSystemServer();
431         }
432     }
433 
434     /**
435      * A callback class for monitoring changes to Advanced Protection state
436      *
437      * <p>To register a callback, implement this interface, and register it with
438      * {@link AdvancedProtectionManager#registerAdvancedProtectionCallback(Executor, Callback)}.
439      * Methods are called when the state changes, as well as once on initial registration.
440      */
441     @FlaggedApi(Flags.FLAG_AAPM_API)
442     public interface Callback {
443         /**
444          * Called when advanced protection state changes
445          * @param enabled the new state
446          */
447         void onAdvancedProtectionChanged(boolean enabled);
448     }
449 }
450