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