1 /* 2 * Copyright (C) 2018 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 package com.android.launcher3.util; 17 18 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 19 20 import android.content.Context; 21 import android.content.ContextWrapper; 22 import android.os.Looper; 23 import android.util.Log; 24 25 import androidx.annotation.UiThread; 26 import androidx.annotation.VisibleForTesting; 27 28 import com.android.launcher3.util.ResourceBasedOverride.Overrides; 29 30 import java.util.ArrayList; 31 import java.util.HashMap; 32 import java.util.Map; 33 import java.util.concurrent.ExecutionException; 34 import java.util.function.Consumer; 35 36 /** 37 * Utility class for defining singletons which are initiated on main thread. 38 */ 39 public class MainThreadInitializedObject<T extends SafeCloseable> { 40 41 private final ObjectProvider<T> mProvider; 42 private T mValue; 43 MainThreadInitializedObject(ObjectProvider<T> provider)44 public MainThreadInitializedObject(ObjectProvider<T> provider) { 45 mProvider = provider; 46 } 47 get(Context context)48 public T get(Context context) { 49 Context app = context.getApplicationContext(); 50 if (app instanceof SandboxApplication sc) { 51 return sc.getObject(this); 52 } 53 54 if (mValue == null) { 55 if (Looper.myLooper() == Looper.getMainLooper()) { 56 mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app)); 57 } else { 58 try { 59 return MAIN_EXECUTOR.submit(() -> get(context)).get(); 60 } catch (InterruptedException|ExecutionException e) { 61 throw new RuntimeException(e); 62 } 63 } 64 } 65 return mValue; 66 } 67 68 /** 69 * Executes the callback is the value is already created 70 * @return true if the callback was executed, false otherwise 71 */ executeIfCreated(Consumer<T> callback)72 public boolean executeIfCreated(Consumer<T> callback) { 73 T v = mValue; 74 if (v != null) { 75 callback.accept(v); 76 return true; 77 } else { 78 return false; 79 } 80 } 81 82 @VisibleForTesting initializeForTesting(T value)83 public void initializeForTesting(T value) { 84 mValue = value; 85 } 86 87 /** 88 * Initializes a provider based on resource overrides 89 */ 90 public static <T extends ResourceBasedOverride & SafeCloseable> MainThreadInitializedObject<T> forOverride(Class<T> clazz, int resourceId)91 forOverride(Class<T> clazz, int resourceId) { 92 return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId)); 93 } 94 95 public interface ObjectProvider<T> { 96 get(Context context)97 T get(Context context); 98 } 99 100 public interface SandboxApplication { 101 102 /** 103 * Find a cached object from mObjectMap if we have already created one. If not, generate 104 * an object using the provider. 105 */ getObject(MainThreadInitializedObject<T> object)106 <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object); 107 108 @UiThread createObject(MainThreadInitializedObject<T> object)109 default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) { 110 return object.mProvider.get((Context) this); 111 } 112 } 113 114 /** 115 * Abstract Context which allows custom implementations for 116 * {@link MainThreadInitializedObject} providers 117 */ 118 public static class SandboxContext extends ContextWrapper implements SandboxApplication { 119 120 private static final String TAG = "SandboxContext"; 121 122 private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>(); 123 private final ArrayList<SafeCloseable> mOrderedObjects = new ArrayList<>(); 124 125 private final Object mDestroyLock = new Object(); 126 private boolean mDestroyed = false; 127 SandboxContext(Context base)128 public SandboxContext(Context base) { 129 super(base); 130 } 131 132 @Override getApplicationContext()133 public Context getApplicationContext() { 134 return this; 135 } 136 onDestroy()137 public void onDestroy() { 138 synchronized (mDestroyLock) { 139 // Destroy in reverse order 140 for (int i = mOrderedObjects.size() - 1; i >= 0; i--) { 141 mOrderedObjects.get(i).close(); 142 } 143 mDestroyed = true; 144 } 145 } 146 147 @Override getObject(MainThreadInitializedObject<T> object)148 public <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object) { 149 synchronized (mDestroyLock) { 150 if (mDestroyed) { 151 Log.e(TAG, "Static object access with a destroyed context"); 152 } 153 T t = (T) mObjectMap.get(object); 154 if (t != null) { 155 return t; 156 } 157 if (Looper.myLooper() == Looper.getMainLooper()) { 158 t = createObject(object); 159 mObjectMap.put(object, t); 160 mOrderedObjects.add(t); 161 return t; 162 } 163 } 164 165 try { 166 return MAIN_EXECUTOR.submit(() -> getObject(object)).get(); 167 } catch (InterruptedException | ExecutionException e) { 168 throw new RuntimeException(e); 169 } 170 } 171 172 /** 173 * Put a value into mObjectMap, can be used to put mocked MainThreadInitializedObject 174 * instances into SandboxContext. 175 */ putObject( MainThreadInitializedObject<T> object, T value)176 public <T extends SafeCloseable> void putObject( 177 MainThreadInitializedObject<T> object, T value) { 178 mObjectMap.put(object, value); 179 } 180 } 181 } 182