1 /*
2  * Copyright (C) 2021 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.am;
18 
19 import static com.android.server.am.ActivityManagerService.TAG;
20 import static com.android.server.am.OomAdjuster.CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID;
21 import static com.android.server.am.OomAdjuster.PROCESS_CAPABILITY_CHANGE_ID;
22 import static com.android.server.am.OomAdjuster.USE_SHORT_FGS_USAGE_INTERACTION_TIME;
23 
24 import android.annotation.IntDef;
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.util.ArrayMap;
31 import android.util.LongSparseArray;
32 import android.util.Pair;
33 import android.util.Slog;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.compat.IPlatformCompat;
37 import com.android.server.compat.CompatChange;
38 import com.android.server.compat.PlatformCompat;
39 
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.lang.ref.WeakReference;
43 
44 /**
45  * Local platform compat cache.
46  */
47 final class PlatformCompatCache {
48 
49     static final int CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY = 0;
50     static final int CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY = 1;
51     static final int CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME = 2;
52 
53     @IntDef(prefix = { "CACHED_COMPAT_CHANGE_" }, value = {
54         CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY,
55         CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY,
56         CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME,
57     })
58     @Retention(RetentionPolicy.SOURCE)
59     static @interface CachedCompatChangeId{}
60 
61     /**
62      * Mapping from CACHED_COMPAT_CHANGE_* to the actual compat change id.
63      */
64     static final long[] CACHED_COMPAT_CHANGE_IDS_MAPPING = new long[] {
65         PROCESS_CAPABILITY_CHANGE_ID,
66         CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
67         USE_SHORT_FGS_USAGE_INTERACTION_TIME,
68     };
69 
70     private final PlatformCompat mPlatformCompat;
71     private final IPlatformCompat mIPlatformCompatProxy;
72     private final LongSparseArray<CacheItem> mCaches = new LongSparseArray<>();
73     private final boolean mCacheEnabled;
74 
75     private static PlatformCompatCache sPlatformCompatCache;
76 
PlatformCompatCache(long[] compatChanges)77     private PlatformCompatCache(long[] compatChanges) {
78         IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
79         if (b instanceof PlatformCompat) {
80             mPlatformCompat = (PlatformCompat) ServiceManager.getService(
81                     Context.PLATFORM_COMPAT_SERVICE);
82             for (long changeId: compatChanges) {
83                 mCaches.put(changeId, new CacheItem(mPlatformCompat, changeId));
84             }
85             mIPlatformCompatProxy = null;
86             mCacheEnabled = true;
87         } else {
88             // we are in UT where the platform_compat is not running within the same process
89             mIPlatformCompatProxy = IPlatformCompat.Stub.asInterface(b);
90             mPlatformCompat = null;
91             mCacheEnabled = false;
92         }
93     }
94 
getInstance()95     static PlatformCompatCache getInstance() {
96         if (sPlatformCompatCache == null) {
97             sPlatformCompatCache = new PlatformCompatCache(new long[] {
98                 PROCESS_CAPABILITY_CHANGE_ID,
99                 CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
100                 USE_SHORT_FGS_USAGE_INTERACTION_TIME,
101             });
102         }
103         return sPlatformCompatCache;
104     }
105 
isChangeEnabled(long changeId, ApplicationInfo app, boolean defaultValue)106     private boolean isChangeEnabled(long changeId, ApplicationInfo app, boolean defaultValue) {
107         try {
108             return mCacheEnabled ? mCaches.get(changeId).isChangeEnabled(app)
109                     : mIPlatformCompatProxy.isChangeEnabled(changeId, app);
110         } catch (RemoteException e) {
111             Slog.w(TAG, "Error reading platform compat change " + changeId, e);
112             return defaultValue;
113         }
114     }
115 
116     /**
117      * @return If the given cached compat change id is enabled.
118      */
isChangeEnabled(@achedCompatChangeId int cachedCompatChangeId, ApplicationInfo app, boolean defaultValue)119     static boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
120             ApplicationInfo app, boolean defaultValue) {
121         return getInstance().isChangeEnabled(
122                 CACHED_COMPAT_CHANGE_IDS_MAPPING[cachedCompatChangeId], app, defaultValue);
123     }
124 
125     /**
126      * Invalidate the cache for the given app.
127      */
invalidate(ApplicationInfo app)128     void invalidate(ApplicationInfo app) {
129         for (int i = mCaches.size() - 1; i >= 0; i--) {
130             mCaches.valueAt(i).invalidate(app);
131         }
132     }
133 
134     /**
135      * Update the cache due to application info changes.
136      */
onApplicationInfoChanged(ApplicationInfo app)137     void onApplicationInfoChanged(ApplicationInfo app) {
138         for (int i = mCaches.size() - 1; i >= 0; i--) {
139             mCaches.valueAt(i).onApplicationInfoChanged(app);
140         }
141     }
142 
143     static class CacheItem implements CompatChange.ChangeListener {
144         private final PlatformCompat mPlatformCompat;
145         private final long mChangeId;
146         private final Object mLock = new Object();
147 
148         private final ArrayMap<String, Pair<Boolean, WeakReference<ApplicationInfo>>> mCache =
149                 new ArrayMap<>();
150 
CacheItem(PlatformCompat platformCompat, long changeId)151         CacheItem(PlatformCompat platformCompat, long changeId) {
152             mPlatformCompat = platformCompat;
153             mChangeId = changeId;
154             mPlatformCompat.registerListener(changeId, this);
155         }
156 
isChangeEnabled(ApplicationInfo app)157         boolean isChangeEnabled(ApplicationInfo app) {
158             synchronized (mLock) {
159                 final int index = mCache.indexOfKey(app.packageName);
160                 Pair<Boolean, WeakReference<ApplicationInfo>> p;
161                 if (index < 0) {
162                     return fetchLocked(app, index);
163                 }
164                 p = mCache.valueAt(index);
165                 if (p.second.get() == app) {
166                     return p.first;
167                 }
168                 // Cache is invalid, regenerate it
169                 return fetchLocked(app, index);
170             }
171         }
172 
invalidate(ApplicationInfo app)173         void invalidate(ApplicationInfo app) {
174             synchronized (mLock) {
175                 mCache.remove(app.packageName);
176             }
177         }
178 
179         @GuardedBy("mLock")
fetchLocked(ApplicationInfo app, int index)180         boolean fetchLocked(ApplicationInfo app, int index) {
181             final Pair<Boolean, WeakReference<ApplicationInfo>> p = new Pair<>(
182                     mPlatformCompat.isChangeEnabledInternalNoLogging(mChangeId, app),
183                     new WeakReference<>(app));
184             if (index >= 0) {
185                 mCache.setValueAt(index, p);
186             } else {
187                 mCache.put(app.packageName, p);
188             }
189             return p.first;
190         }
191 
onApplicationInfoChanged(ApplicationInfo app)192         void onApplicationInfoChanged(ApplicationInfo app) {
193             synchronized (mLock) {
194                 final int index = mCache.indexOfKey(app.packageName);
195                 if (index >= 0) {
196                     fetchLocked(app, index);
197                 }
198             }
199         }
200 
201         @Override
onCompatChange(String packageName)202         public void onCompatChange(String packageName) {
203             synchronized (mLock) {
204                 final int index = mCache.indexOfKey(packageName);
205                 if (index >= 0) {
206                     final ApplicationInfo app = mCache.valueAt(index).second.get();
207                     if (app != null) {
208                         fetchLocked(app, index);
209                     } else {
210                         mCache.removeAt(index);
211                     }
212                 }
213             }
214         }
215     }
216 }
217