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