1 /*
2  * Copyright (C) 2023 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.app.sdksandbox.sdkprovider;
18 
19 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SANDBOXED_ACTIVITY_HANDLER;
20 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.Activity;
25 import android.app.sdksandbox.SandboxedSdkContext;
26 import android.app.sdksandbox.SdkSandboxLocalSingleton;
27 import android.app.sdksandbox.SdkSandboxManager;
28 import android.app.sdksandbox.StatsdUtil;
29 import android.app.sdksandbox.sandboxactivity.ActivityContextInfo;
30 import android.content.Intent;
31 import android.os.Binder;
32 import android.os.Build;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.os.SystemClock;
36 import android.util.ArrayMap;
37 
38 import androidx.annotation.RequiresApi;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.util.Iterator;
44 import java.util.Map;
45 
46 /**
47  * It is a Singleton class to store the registered {@link SdkSandboxActivityHandler} instances and
48  * their associated {@link Activity} instances.
49  *
50  * @hide
51  */
52 public class SdkSandboxActivityRegistry {
53     private static final String TAG = "SdkSandboxActivityRegistry";
54 
55     private static final Object sLock = new Object();
56 
57     @GuardedBy("sLock")
58     private static SdkSandboxActivityRegistry sInstance;
59 
60     // A lock to keep all map synchronized
61     private final Object mMapsLock = new Object();
62     private Injector mInjector;
63 
64     @GuardedBy("mMapsLock")
65     private final Map<SdkSandboxActivityHandler, HandlerInfo> mHandlerToHandlerInfoMap =
66             new ArrayMap<>();
67 
68     @GuardedBy("mMapsLock")
69     private final Map<IBinder, HandlerInfo> mTokenToHandlerInfoMap = new ArrayMap<>();
70 
SdkSandboxActivityRegistry(Injector injector)71     private SdkSandboxActivityRegistry(Injector injector) {
72         setInjector(injector);
73     }
74 
75     /** Returns a singleton instance of this class. */
getInstance()76     public static SdkSandboxActivityRegistry getInstance() {
77         return getInstance(new Injector());
78     }
79 
80     /**
81      * Returns a singleton instance of this class with a custom {@link Injector}. If the instance
82      * already exists, overrides old injector with the new one.
83      *
84      * @hide
85      */
86     @VisibleForTesting
getInstance(@onNull Injector injector)87     public static SdkSandboxActivityRegistry getInstance(@NonNull Injector injector) {
88         synchronized (sLock) {
89             if (sInstance == null) {
90                 sInstance = new SdkSandboxActivityRegistry(injector);
91             } else {
92                 sInstance.setInjector(injector);
93             }
94             return sInstance;
95         }
96     }
97 
setInjector(@onNull Injector injector)98     private void setInjector(@NonNull Injector injector) {
99         mInjector = injector;
100     }
101 
102     /**
103      * Registers the passed {@link SdkSandboxActivityHandler} and returns a {@link IBinder} token
104      * that identifies it.
105      *
106      * <p>If {@link SdkSandboxActivityHandler} is already registered, its {@link IBinder} identifier
107      * will be returned.
108      *
109      * @param sdkContext is the {@link SandboxedSdkContext} which is registering the {@link
110      *     SdkSandboxActivityHandler}
111      * @param handler is the {@link SdkSandboxActivityHandler} to register.
112      */
113     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
114     @NonNull
register( @onNull SandboxedSdkContext sdkContext, @NonNull SdkSandboxActivityHandler handler)115     public IBinder register(
116             @NonNull SandboxedSdkContext sdkContext, @NonNull SdkSandboxActivityHandler handler) {
117         synchronized (mMapsLock) {
118             long timeEventStarted = mInjector.elapsedRealtime();
119             if (mHandlerToHandlerInfoMap.containsKey(handler)) {
120                 HandlerInfo handlerInfo = mHandlerToHandlerInfoMap.get(handler);
121                 return handlerInfo.getToken();
122             }
123 
124             IBinder token = new Binder();
125             HandlerInfo handlerInfo = new HandlerInfo(sdkContext, handler, token);
126             mHandlerToHandlerInfoMap.put(handlerInfo.getHandler(), handlerInfo);
127             mTokenToHandlerInfoMap.put(handlerInfo.getToken(), handlerInfo);
128             logSandboxActivityApiLatency(
129                     StatsdUtil
130                             .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__PUT_SDK_SANDBOX_ACTIVITY_HANDLER,
131                     StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS,
132                     timeEventStarted);
133             return token;
134         }
135     }
136 
137     /**
138      * Unregisters the passed {@link SdkSandboxActivityHandler}.
139      *
140      * @param handler is the {@link SdkSandboxActivityHandler} to unregister.
141      */
142     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
unregister(@onNull SdkSandboxActivityHandler handler)143     public void unregister(@NonNull SdkSandboxActivityHandler handler) {
144         synchronized (mMapsLock) {
145             long timeEventStarted = mInjector.elapsedRealtime();
146             HandlerInfo handlerInfo = mHandlerToHandlerInfoMap.get(handler);
147             if (handlerInfo == null) {
148                 return;
149             }
150             mHandlerToHandlerInfoMap.remove(handlerInfo.getHandler());
151             mTokenToHandlerInfoMap.remove(handlerInfo.getToken());
152             logSandboxActivityApiLatency(
153                     StatsdUtil
154                             .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__REMOVE_SDK_SANDBOX_ACTIVITY_HANDLER,
155                     StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS,
156                     timeEventStarted);
157         }
158     }
159 
160     /**
161      * Notifies the SDK about {@link Activity} creation.
162      *
163      * <p>This should be called by the sandbox {@link Activity} while being created to notify the
164      * SDK that registered the {@link SdkSandboxActivityHandler} that identified by an {@link
165      * IBinder} token which be part of the passed {@link Intent} extras.
166      *
167      * @param intent the {@link Intent} that contains an {@link IBinder} identifier for the {@link
168      *     SdkSandboxActivityHandler} in its extras.
169      * @param activity the {@link Activity} is being created.
170      * @throws IllegalArgumentException if the passed {@link Intent} does not have the handler token
171      *     in its extras or there is no registered handler for the passed handler token (that mostly
172      *     would mean that the handler is de-registered before the passed {@link Activity} is
173      *     created), on both cases the passed {@link Activity} will not start.
174      */
175     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
notifyOnActivityCreation(@onNull Intent intent, @NonNull Activity activity)176     public void notifyOnActivityCreation(@NonNull Intent intent, @NonNull Activity activity) {
177         long sandboxActivityInitiationTime = extractActivityInitiationTime(intent);
178         if (sandboxActivityInitiationTime != 0L) {
179             // Remove time sandbox activity was initiated at, to avoid logging the wrong activity
180             // creation time in case of recreation.
181             intent.removeExtra(EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME);
182         }
183         synchronized (mMapsLock) {
184             long timeEventStarted = mInjector.elapsedRealtime();
185             IBinder handlerToken = extractHandlerToken(intent);
186             if (handlerToken == null) {
187                 logSandboxActivityApiLatency(
188                         StatsdUtil
189                                 .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__NOTIFY_SDK_ON_ACTIVITY_CREATION,
190                         StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__FAILURE,
191                         timeEventStarted);
192                 throw new IllegalArgumentException(
193                         "Extra params of the intent are missing the IBinder value for the key ("
194                                 + SdkSandboxManager.EXTRA_SANDBOXED_ACTIVITY_HANDLER
195                                 + ")");
196             }
197             HandlerInfo handlerInfo = mTokenToHandlerInfoMap.get(handlerToken);
198             if (handlerInfo == null) {
199                 logSandboxActivityApiLatency(
200                         StatsdUtil
201                                 .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__NOTIFY_SDK_ON_ACTIVITY_CREATION,
202                         StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__FAILURE,
203                         timeEventStarted);
204                 throw new IllegalArgumentException(
205                         "There is no registered SdkSandboxActivityHandler to notify");
206             }
207             // TODO(b/326974007): log SandboxActivity creation latency in SandboxedActivity class.
208             // Don't log time taken by SDK to perform 'onActivityCreated' as it can be roughly
209             // calculated using total activity creation latency.
210             logSandboxActivityApiLatency(
211                     StatsdUtil
212                             .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__NOTIFY_SDK_ON_ACTIVITY_CREATION,
213                     StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS,
214                     timeEventStarted);
215             handlerInfo.getHandler().onActivityCreated(activity);
216         }
217         if (sandboxActivityInitiationTime != 0L) {
218             logSandboxActivityApiLatency(
219                     StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__TOTAL,
220                     StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS,
221                     sandboxActivityInitiationTime);
222         }
223     }
224 
225     /**
226      * Returns {@link ActivityContextInfo} instance containing the information which is needed to
227      * build the sandbox activity {@link android.content.Context} for the passed {@link Intent}.
228      *
229      * @param intent an {@link Intent} for a sandbox {@link Activity} containing information to
230      *     identify the SDK which requested the activity.
231      * @return {@link ActivityContextInfo} instance if the intent refers to a registered {@link
232      *     SdkSandboxActivityHandler}, otherwise {@code null}.
233      * @throws IllegalStateException if Customized SDK Context flag is not enabled
234      */
235     @Nullable
236     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
getContextInfo(@onNull Intent intent)237     public ActivityContextInfo getContextInfo(@NonNull Intent intent) {
238         synchronized (mMapsLock) {
239             final IBinder handlerToken = extractHandlerToken(intent);
240             if (handlerToken == null) {
241                 return null;
242             }
243             final HandlerInfo handlerInfo = mTokenToHandlerInfoMap.get(handlerToken);
244             if (handlerInfo == null) {
245                 return null;
246             }
247             return handlerInfo.getContextInfo();
248         }
249     }
250 
251     /**
252      * Returns the SDK {@link SandboxedSdkContext} which requested the activity for the passed
253      * {@link Intent}.
254      *
255      * @param intent the {@link Intent} that contains an {@link IBinder} identifier for the {@link
256      *     SdkSandboxActivityHandler} in its extras.
257      * @return SDK {@link SandboxedSdkContext} if its handler is registered, otherwise {@code null}`
258      *     .
259      */
260     @Nullable
261     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
getSdkContext(@onNull Intent intent)262     public SandboxedSdkContext getSdkContext(@NonNull Intent intent) {
263         synchronized (mMapsLock) {
264             final IBinder handlerToken = extractHandlerToken(intent);
265             if (handlerToken == null) {
266                 return null;
267             }
268             final HandlerInfo handlerInfo = mTokenToHandlerInfoMap.get(handlerToken);
269             if (handlerInfo == null) {
270                 return null;
271             }
272             return handlerInfo.getSdkContext();
273         }
274     }
275 
276     @Nullable
extractHandlerToken(Intent intent)277     private IBinder extractHandlerToken(Intent intent) {
278         if (intent == null || intent.getExtras() == null) {
279             return null;
280         }
281         return intent.getExtras().getBinder(EXTRA_SANDBOXED_ACTIVITY_HANDLER);
282     }
283 
extractActivityInitiationTime(Intent intent)284     private long extractActivityInitiationTime(Intent intent) {
285         if (intent == null || intent.getExtras() == null) {
286             return 0L;
287         }
288         return intent.getExtras().getLong(EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME);
289     }
290 
logSandboxActivityApiLatency(int method, int callResult, long timeEventStarted)291     private void logSandboxActivityApiLatency(int method, int callResult, long timeEventStarted) {
292         try {
293             mInjector
294                     .getSdkSandboxLocalSingleton()
295                     .getSdkToServiceCallback()
296                     .logSandboxActivityApiLatencyFromSandbox(
297                             method,
298                             callResult,
299                             (int) (mInjector.elapsedRealtime() - timeEventStarted));
300         } catch (RemoteException e) {
301             throw e.rethrowFromSystemServer();
302         }
303     }
304 
305     /**
306      * Unregisters all {@link SdkSandboxActivityHandler} instances that are registered by the passed
307      * SDK.
308      *
309      * <p>This is expected to be called by the system when an SDK is unloaded to free memory.
310      *
311      * @param sdkName the name of the SDK to unregister its registered handlers
312      */
313     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
unregisterAllActivityHandlersForSdk(@onNull String sdkName)314     public void unregisterAllActivityHandlersForSdk(@NonNull String sdkName) {
315         synchronized (mMapsLock) {
316             Iterator<Map.Entry<SdkSandboxActivityHandler, HandlerInfo>> iter =
317                     mHandlerToHandlerInfoMap.entrySet().iterator();
318             while (iter.hasNext()) {
319                 Map.Entry<SdkSandboxActivityHandler, HandlerInfo> handlerEntry = iter.next();
320                 HandlerInfo handlerInfo = handlerEntry.getValue();
321                 if (handlerInfo.getSdkContext().getSdkName().equals(sdkName)) {
322                     IBinder handlerToken = handlerInfo.getToken();
323                     iter.remove();
324                     mTokenToHandlerInfoMap.remove(handlerToken);
325                 }
326             }
327         }
328     }
329 
330     /**
331      * Injects dependencies into {@link SdkSandboxActivityRegistry}.
332      *
333      * @hide
334      */
335     @VisibleForTesting
336     public static class Injector {
getSdkSandboxLocalSingleton()337         public SdkSandboxLocalSingleton getSdkSandboxLocalSingleton() {
338             return SdkSandboxLocalSingleton.getExistingInstance();
339         }
340 
elapsedRealtime()341         public long elapsedRealtime() {
342             return SystemClock.elapsedRealtime();
343         }
344     }
345 
346     /**
347      * Holds the information about {@link SdkSandboxActivityHandler}.
348      *
349      * @hide
350      */
351     private static class HandlerInfo {
352         private final SandboxedSdkContext mSdkContext;
353         private final SdkSandboxActivityHandler mHandler;
354         private final IBinder mToken;
355         private final ActivityContextInfo mContextInfo;
356 
HandlerInfo( SandboxedSdkContext sdkContext, SdkSandboxActivityHandler handler, IBinder token)357         HandlerInfo(
358                 SandboxedSdkContext sdkContext, SdkSandboxActivityHandler handler, IBinder token) {
359             this.mSdkContext = sdkContext;
360             this.mHandler = handler;
361             this.mToken = token;
362             mContextInfo = mSdkContext::getApplicationInfo;
363         }
364 
365         @NonNull
getSdkContext()366         public SandboxedSdkContext getSdkContext() {
367             return mSdkContext;
368         }
369 
370         @NonNull
getHandler()371         public SdkSandboxActivityHandler getHandler() {
372             return mHandler;
373         }
374 
375         @NonNull
getToken()376         public IBinder getToken() {
377             return mToken;
378         }
379 
380         @NonNull
getContextInfo()381         public ActivityContextInfo getContextInfo() {
382             return mContextInfo;
383         }
384     }
385 }
386