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.Arrays;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.concurrent.ExecutionException;
37 
38 /**
39  * Utility class for defining singletons which are initiated on main thread.
40  */
41 public class MainThreadInitializedObject<T> {
42 
43     private final ObjectProvider<T> mProvider;
44     private T mValue;
45 
MainThreadInitializedObject(ObjectProvider<T> provider)46     public MainThreadInitializedObject(ObjectProvider<T> provider) {
47         mProvider = provider;
48     }
49 
get(Context context)50     public T get(Context context) {
51         if (context instanceof SandboxContext) {
52             return ((SandboxContext) context).getObject(this, mProvider);
53         }
54 
55         if (mValue == null) {
56             if (Looper.myLooper() == Looper.getMainLooper()) {
57                 mValue = TraceHelper.allowIpcs("main.thread.object",
58                         () -> mProvider.get(context.getApplicationContext()));
59             } else {
60                 try {
61                     return MAIN_EXECUTOR.submit(() -> get(context)).get();
62                 } catch (InterruptedException|ExecutionException e) {
63                     throw new RuntimeException(e);
64                 }
65             }
66         }
67         return mValue;
68     }
69 
getNoCreate()70     public T getNoCreate() {
71         return mValue;
72     }
73 
74     @VisibleForTesting
initializeForTesting(T value)75     public void initializeForTesting(T value) {
76         mValue = value;
77     }
78 
79     /**
80      * Initializes a provider based on resource overrides
81      */
forOverride( Class<T> clazz, int resourceId)82     public static <T extends ResourceBasedOverride> MainThreadInitializedObject<T> forOverride(
83             Class<T> clazz, int resourceId) {
84         return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
85     }
86 
87     public interface ObjectProvider<T> {
88 
get(Context context)89         T get(Context context);
90     }
91 
92     /**
93      * Abstract Context which allows custom implementations for
94      * {@link MainThreadInitializedObject} providers
95      */
96     public static abstract class SandboxContext extends ContextWrapper {
97 
98         private static final String TAG = "SandboxContext";
99 
100         protected final Set<MainThreadInitializedObject> mAllowedObjects;
101         protected final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
102         protected final ArrayList<Object> mOrderedObjects = new ArrayList<>();
103 
104         private final Object mDestroyLock = new Object();
105         private boolean mDestroyed = false;
106 
SandboxContext(Context base, MainThreadInitializedObject... allowedObjects)107         public SandboxContext(Context base, MainThreadInitializedObject... allowedObjects) {
108             super(base);
109             mAllowedObjects = new HashSet<>(Arrays.asList(allowedObjects));
110         }
111 
112         @Override
getApplicationContext()113         public Context getApplicationContext() {
114             return this;
115         }
116 
onDestroy()117         public void onDestroy() {
118             synchronized (mDestroyLock) {
119                 // Destroy in reverse order
120                 for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
121                     Object o = mOrderedObjects.get(i);
122                     if (o instanceof SafeCloseable) {
123                         ((SafeCloseable) o).close();
124                     }
125                 }
126                 mDestroyed = true;
127             }
128         }
129 
130         /**
131          * Find a cached object from mObjectMap if we have already created one. If not, generate
132          * an object using the provider.
133          */
getObject(MainThreadInitializedObject<T> object, ObjectProvider<T> provider)134         private <T> T getObject(MainThreadInitializedObject<T> object, ObjectProvider<T> provider) {
135             synchronized (mDestroyLock) {
136                 if (mDestroyed) {
137                     Log.e(TAG, "Static object access with a destroyed context");
138                 }
139                 if (!mAllowedObjects.contains(object)) {
140                     throw new IllegalStateException(
141                             "Leaking unknown objects " + object + "  " + provider);
142                 }
143                 T t = (T) mObjectMap.get(object);
144                 if (t != null) {
145                     return t;
146                 }
147                 if (Looper.myLooper() == Looper.getMainLooper()) {
148                     t = createObject(provider);
149                     mObjectMap.put(object, t);
150                     mOrderedObjects.add(t);
151                     return t;
152                 }
153             }
154 
155             try {
156                 return MAIN_EXECUTOR.submit(() -> getObject(object, provider)).get();
157             } catch (InterruptedException | ExecutionException e) {
158                 throw new RuntimeException(e);
159             }
160         }
161 
162         @UiThread
createObject(ObjectProvider<T> provider)163         protected <T> T createObject(ObjectProvider<T> provider) {
164             return provider.get(this);
165         }
166     }
167 }
168