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.platform.test.ravenwood; 18 19 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; 20 21 import android.content.ClipboardManager; 22 import android.content.Context; 23 import android.content.res.AssetManager; 24 import android.content.res.Resources; 25 import android.content.res.Resources.Theme; 26 import android.hardware.ISerialManager; 27 import android.hardware.SerialManager; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.Looper; 31 import android.os.PermissionEnforcer; 32 import android.os.ServiceManager; 33 import android.os.UserHandle; 34 import android.ravenwood.example.BlueManager; 35 import android.ravenwood.example.RedManager; 36 import android.util.ArrayMap; 37 import android.util.Singleton; 38 39 import com.android.internal.annotations.GuardedBy; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.util.Objects; 44 import java.util.concurrent.Executor; 45 import java.util.function.Supplier; 46 47 public class RavenwoodContext extends RavenwoodBaseContext { 48 private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG; 49 50 private final Object mLock = new Object(); 51 private final String mPackageName; 52 private final HandlerThread mMainThread; 53 54 private final RavenwoodPermissionEnforcer mEnforcer = new RavenwoodPermissionEnforcer(); 55 56 private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>(); 57 private final ArrayMap<String, Supplier<?>> mNameToFactory = new ArrayMap<>(); 58 59 private final File mFilesDir; 60 private final File mCacheDir; 61 private final Supplier<Resources> mResourcesSupplier; 62 63 private RavenwoodContext mAppContext; 64 65 @GuardedBy("mLock") 66 private Resources mResources; 67 68 @GuardedBy("mLock") 69 private Resources.Theme mTheme; 70 registerService(Class<?> serviceClass, String serviceName, Supplier<?> serviceSupplier)71 private void registerService(Class<?> serviceClass, String serviceName, 72 Supplier<?> serviceSupplier) { 73 mClassToName.put(serviceClass, serviceName); 74 mNameToFactory.put(serviceName, serviceSupplier); 75 } 76 RavenwoodContext(String packageName, HandlerThread mainThread, Supplier<Resources> resourcesSupplier)77 public RavenwoodContext(String packageName, HandlerThread mainThread, 78 Supplier<Resources> resourcesSupplier) throws IOException { 79 mPackageName = packageName; 80 mMainThread = mainThread; 81 mResourcesSupplier = resourcesSupplier; 82 mFilesDir = createTempDir(packageName + "_files-dir"); 83 mCacheDir = createTempDir(packageName + "_cache-dir"); 84 85 // Services provided by a typical shipping device 86 registerService(ClipboardManager.class, 87 Context.CLIPBOARD_SERVICE, memoize(() -> 88 new ClipboardManager(this, getMainThreadHandler()))); 89 registerService(PermissionEnforcer.class, 90 Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer); 91 registerService(SerialManager.class, 92 Context.SERIAL_SERVICE, memoize(() -> 93 new SerialManager(this, ISerialManager.Stub.asInterface( 94 ServiceManager.getService(Context.SERIAL_SERVICE))) 95 )); 96 97 // Additional services we provide for testing purposes 98 registerService(BlueManager.class, 99 BlueManager.SERVICE_NAME, memoize(() -> new BlueManager())); 100 registerService(RedManager.class, 101 RedManager.SERVICE_NAME, memoize(() -> new RedManager())); 102 } 103 104 @Override getSystemService(String serviceName)105 public Object getSystemService(String serviceName) { 106 // TODO: pivot to using SystemServiceRegistry 107 final Supplier<?> serviceSupplier = mNameToFactory.get(serviceName); 108 if (serviceSupplier != null) { 109 return serviceSupplier.get(); 110 } else { 111 throw new UnsupportedOperationException( 112 "Service " + serviceName + " not yet supported under Ravenwood"); 113 } 114 } 115 cleanUp()116 void cleanUp() { 117 deleteDir(mFilesDir); 118 deleteDir(mCacheDir); 119 } 120 121 @Override getSystemServiceName(Class<?> serviceClass)122 public String getSystemServiceName(Class<?> serviceClass) { 123 // TODO: pivot to using SystemServiceRegistry 124 final String serviceName = mClassToName.get(serviceClass); 125 if (serviceName != null) { 126 return serviceName; 127 } else { 128 throw new UnsupportedOperationException( 129 "Service " + serviceClass + " not yet supported under Ravenwood"); 130 } 131 } 132 133 @Override getMainLooper()134 public Looper getMainLooper() { 135 return mMainThread.getLooper(); 136 } 137 138 @Override getMainThreadHandler()139 public Handler getMainThreadHandler() { 140 return mMainThread.getThreadHandler(); 141 } 142 143 @Override getMainExecutor()144 public Executor getMainExecutor() { 145 return mMainThread.getThreadExecutor(); 146 } 147 148 @Override getPackageName()149 public String getPackageName() { 150 return mPackageName; 151 } 152 153 @Override getOpPackageName()154 public String getOpPackageName() { 155 return mPackageName; 156 } 157 158 @Override getAttributionTag()159 public String getAttributionTag() { 160 return null; 161 } 162 163 @Override getUser()164 public UserHandle getUser() { 165 return android.os.UserHandle.of(android.os.UserHandle.myUserId()); 166 } 167 168 @Override getUserId()169 public int getUserId() { 170 return android.os.UserHandle.myUserId(); 171 } 172 173 @Override getDeviceId()174 public int getDeviceId() { 175 return Context.DEVICE_ID_DEFAULT; 176 } 177 178 @Override getFilesDir()179 public File getFilesDir() { 180 return mFilesDir; 181 } 182 183 @Override getCacheDir()184 public File getCacheDir() { 185 return mCacheDir; 186 } 187 188 @Override deleteFile(String name)189 public boolean deleteFile(String name) { 190 File f = new File(name); 191 return f.delete(); 192 } 193 194 @Override getResources()195 public Resources getResources() { 196 synchronized (mLock) { 197 if (mResources == null) { 198 mResources = mResourcesSupplier.get(); 199 } 200 return mResources; 201 } 202 } 203 204 @Override getAssets()205 public AssetManager getAssets() { 206 return getResources().getAssets(); 207 } 208 209 @Override getTheme()210 public Theme getTheme() { 211 synchronized (mLock) { 212 if (mTheme == null) { 213 mTheme = getResources().newTheme(); 214 } 215 return mTheme; 216 } 217 } 218 219 @Override getPackageResourcePath()220 public String getPackageResourcePath() { 221 return new File(RAVENWOOD_RESOURCE_APK).getAbsolutePath(); 222 } 223 setApplicationContext(RavenwoodContext appContext)224 public void setApplicationContext(RavenwoodContext appContext) { 225 mAppContext = appContext; 226 } 227 228 @Override getApplicationContext()229 public Context getApplicationContext() { 230 return mAppContext; 231 } 232 233 @Override isRestricted()234 public boolean isRestricted() { 235 return false; 236 } 237 238 @Override canLoadUnsafeResources()239 public boolean canLoadUnsafeResources() { 240 return true; 241 } 242 243 /** 244 * Wrap the given {@link Supplier} to become memoized. 245 * 246 * The underlying {@link Supplier} will only be invoked once, and that result will be cached 247 * and returned for any future requests. 248 */ memoize(ThrowingSupplier<T> supplier)249 private static <T> Supplier<T> memoize(ThrowingSupplier<T> supplier) { 250 final Singleton<T> singleton = new Singleton<>() { 251 @Override 252 protected T create() { 253 try { 254 return supplier.get(); 255 } catch (Exception e) { 256 throw new RuntimeException(e); 257 } 258 } 259 }; 260 return () -> { 261 return singleton.get(); 262 }; 263 } 264 265 public interface ThrowingSupplier<T> { get()266 T get() throws Exception; 267 } 268 269 createTempDir(String prefix)270 static File createTempDir(String prefix) throws IOException { 271 // Create a temp file, delete it and recreate it as a directory. 272 final File dir = File.createTempFile(prefix + "-", ""); 273 dir.delete(); 274 dir.mkdirs(); 275 return dir; 276 } 277 deleteDir(File dir)278 static void deleteDir(File dir) { 279 File[] children = dir.listFiles(); 280 if (children != null) { 281 for (File child : children) { 282 if (child.isDirectory()) { 283 deleteDir(child); 284 } else { 285 child.delete(); 286 } 287 } 288 } 289 } 290 } 291