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.server.contentprotection;
18 
19 import static android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.UserIdInt;
24 import android.app.admin.DevicePolicyCache;
25 import android.app.admin.DevicePolicyManager;
26 import android.app.admin.DevicePolicyManagerInternal;
27 import android.content.ContentResolver;
28 import android.database.ContentObserver;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.UserHandle;
32 import android.provider.Settings;
33 import android.util.Slog;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.server.LocalServices;
37 
38 /**
39  * Manages consent for content protection.
40  *
41  * @hide
42  */
43 public class ContentProtectionConsentManager {
44 
45     private static final String TAG = "ContentProtectionConsentManager";
46 
47     private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
48 
49     private static final String KEY_CONTENT_PROTECTION_USER_CONSENT =
50             "content_protection_user_consent";
51 
52     @NonNull private final ContentResolver mContentResolver;
53 
54     @NonNull private final DevicePolicyCache mDevicePolicyCache;
55 
56     @NonNull private final DevicePolicyManagerInternal mDevicePolicyManagerInternal;
57 
58     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
59     @NonNull
60     public final ContentObserver mContentObserver;
61 
62     private volatile boolean mCachedPackageVerifierConsent;
63 
64     private volatile boolean mCachedContentProtectionUserConsent;
65 
ContentProtectionConsentManager( @onNull Handler handler, @NonNull ContentResolver contentResolver, @NonNull DevicePolicyCache devicePolicyCache)66     public ContentProtectionConsentManager(
67             @NonNull Handler handler,
68             @NonNull ContentResolver contentResolver,
69             @NonNull DevicePolicyCache devicePolicyCache) {
70         mContentResolver = contentResolver;
71         mDevicePolicyCache = devicePolicyCache;
72         mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
73         mContentObserver = new SettingsObserver(handler);
74 
75         registerSettingsGlobalObserver(KEY_PACKAGE_VERIFIER_USER_CONSENT);
76         registerSettingsGlobalObserver(KEY_CONTENT_PROTECTION_USER_CONSENT);
77         readPackageVerifierConsentGranted();
78         readContentProtectionUserConsentGranted();
79     }
80 
81     /** Returns true if the consent is ultimately granted. */
isConsentGranted(@serIdInt int userId)82     public boolean isConsentGranted(@UserIdInt int userId) {
83         return mCachedPackageVerifierConsent && isContentProtectionConsentGranted(userId);
84     }
85 
86     /**
87      * Not always cached internally and can be expensive, when possible prefer to use {@link
88      * #mCachedPackageVerifierConsent} instead.
89      */
isPackageVerifierConsentGranted()90     private boolean isPackageVerifierConsentGranted() {
91         return Settings.Global.getInt(
92                         mContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, /* def= */ 0)
93                 >= 1;
94     }
95 
96     /**
97      * Not always cached internally and can be expensive, when possible prefer to use {@link
98      * #mCachedContentProtectionUserConsent} instead.
99      */
isContentProtectionUserConsentGranted()100     private boolean isContentProtectionUserConsentGranted() {
101         return Settings.Global.getInt(
102                         mContentResolver, KEY_CONTENT_PROTECTION_USER_CONSENT, /* def= */ 0)
103                 >= 0;
104     }
105 
readPackageVerifierConsentGranted()106     private void readPackageVerifierConsentGranted() {
107         mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
108     }
109 
readContentProtectionUserConsentGranted()110     private void readContentProtectionUserConsentGranted() {
111         mCachedContentProtectionUserConsent = isContentProtectionUserConsentGranted();
112     }
113 
114     /** Always cached internally, cheap and safe to use. */
isUserOrganizationManaged(@serIdInt int userId)115     private boolean isUserOrganizationManaged(@UserIdInt int userId) {
116         return mDevicePolicyManagerInternal.isUserOrganizationManaged(userId);
117     }
118 
119     /** Always cached internally, cheap and safe to use. */
isContentProtectionPolicyGranted(@serIdInt int userId)120     private boolean isContentProtectionPolicyGranted(@UserIdInt int userId) {
121         if (!manageDevicePolicyEnabled()) {
122             return false;
123         }
124 
125         @DevicePolicyManager.ContentProtectionPolicy
126         int policy = mDevicePolicyCache.getContentProtectionPolicy(userId);
127 
128         return switch (policy) {
129             case DevicePolicyManager.CONTENT_PROTECTION_ENABLED -> true;
130             case DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY ->
131                     mCachedContentProtectionUserConsent;
132             default -> false;
133         };
134     }
135 
136     /** Always cached internally, cheap and safe to use. */
isContentProtectionConsentGranted(@serIdInt int userId)137     private boolean isContentProtectionConsentGranted(@UserIdInt int userId) {
138         if (!manageDevicePolicyEnabled()) {
139             return mCachedContentProtectionUserConsent && !isUserOrganizationManaged(userId);
140         }
141 
142         return isUserOrganizationManaged(userId)
143                 ? isContentProtectionPolicyGranted(userId)
144                 : mCachedContentProtectionUserConsent;
145     }
146 
registerSettingsGlobalObserver(@onNull String key)147     private void registerSettingsGlobalObserver(@NonNull String key) {
148         registerSettingsObserver(Settings.Global.getUriFor(key));
149     }
150 
registerSettingsObserver(@onNull Uri uri)151     private void registerSettingsObserver(@NonNull Uri uri) {
152         mContentResolver.registerContentObserver(
153                 uri, /* notifyForDescendants= */ false, mContentObserver, UserHandle.USER_ALL);
154     }
155 
156     private final class SettingsObserver extends ContentObserver {
157 
SettingsObserver(Handler handler)158         SettingsObserver(Handler handler) {
159             super(handler);
160         }
161 
162         @Override
onChange(boolean selfChange, @Nullable Uri uri, @UserIdInt int userId)163         public void onChange(boolean selfChange, @Nullable Uri uri, @UserIdInt int userId) {
164             if (uri == null) {
165                 return;
166             }
167             final String property = uri.getLastPathSegment();
168             if (property == null) {
169                 return;
170             }
171             switch (property) {
172                 case KEY_PACKAGE_VERIFIER_USER_CONSENT:
173                     readPackageVerifierConsentGranted();
174                     return;
175                 case KEY_CONTENT_PROTECTION_USER_CONSENT:
176                     readContentProtectionUserConsentGranted();
177                     return;
178                 default:
179                     Slog.w(TAG, "Ignoring unexpected property: " + property);
180             }
181         }
182     }
183 }
184