• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.server.sdksandbox;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.pm.ServiceInfo;
28 import android.os.RemoteException;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.modules.utils.build.SdkLevel;
34 import com.android.sdksandbox.ISdkSandboxService;
35 import com.android.server.LocalManagerRegistry;
36 import com.android.server.am.ActivityManagerLocal;
37 
38 import java.io.PrintWriter;
39 import java.util.Objects;
40 
41 import javax.annotation.concurrent.ThreadSafe;
42 
43 /**
44  * Implementation of {@link SdkSandboxServiceProvider}.
45  *
46  * @hide
47  */
48 @ThreadSafe
49 class SdkSandboxServiceProviderImpl implements SdkSandboxServiceProvider {
50 
51     private static final String TAG = "SdkSandboxManager";
52 
53     private final Object mLock = new Object();
54 
55     private final Context mContext;
56     private final ActivityManagerLocal mActivityManagerLocal;
57 
58     @GuardedBy("mLock")
59     private final ArrayMap<CallingInfo, SdkSandboxConnection> mAppSdkSandboxConnections =
60             new ArrayMap<>();
61 
SdkSandboxServiceProviderImpl(Context context)62     SdkSandboxServiceProviderImpl(Context context) {
63         mContext = context;
64         mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
65     }
66 
67     @Override
68     @Nullable
bindService(CallingInfo callingInfo, ServiceConnection serviceConnection)69     public void bindService(CallingInfo callingInfo, ServiceConnection serviceConnection) {
70         synchronized (mLock) {
71             SdkSandboxConnection sdkSandboxConnection = getSdkSandboxConnectionLocked(callingInfo);
72             if (sdkSandboxConnection != null && sdkSandboxConnection.getStatus() != NON_EXISTENT) {
73                 // The sandbox is either already created or is in the process of being
74                 // created/restarted. Do not bind again. Note that later restarts can take a while,
75                 // since retries are done exponentially.
76                 Log.i(TAG, "SDK sandbox for " + callingInfo + " is already created");
77                 return;
78             }
79 
80             Log.i(TAG, "Binding sdk sandbox for " + callingInfo);
81 
82             ComponentName componentName = getServiceComponentName();
83             if (componentName == null) {
84                 Log.e(TAG, "Failed to find sdk sandbox service");
85                 notifyFailedBinding(serviceConnection);
86                 return;
87             }
88             final Intent intent = new Intent().setComponent(componentName);
89 
90             sdkSandboxConnection = new SdkSandboxConnection(serviceConnection);
91 
92             final String callingPackageName = callingInfo.getPackageName();
93             String sandboxProcessName = toSandboxProcessName(callingPackageName);
94             try {
95                 boolean bound;
96                 // For U+, we start the sandbox and then bind to it to prevent restarts. For T,
97                 // the sandbox service is directly bound to using BIND_AUTO_CREATE flag which brings
98                 // up the sandbox but also restarts it if the sandbox dies when bound.
99                 if (SdkLevel.isAtLeastU()) {
100                     ComponentName name =
101                             mActivityManagerLocal.startSdkSandboxService(
102                                     intent,
103                                     callingInfo.getUid(),
104                                     callingPackageName,
105                                     sandboxProcessName);
106                     if (name == null) {
107                         notifyFailedBinding(serviceConnection);
108                         return;
109                     }
110                     bound =
111                             mActivityManagerLocal.bindSdkSandboxService(
112                                     intent,
113                                     serviceConnection,
114                                     callingInfo.getUid(),
115                                     callingInfo.getAppProcessToken(),
116                                     callingPackageName,
117                                     sandboxProcessName,
118                                     0);
119                 } else {
120                     // Using BIND_AUTO_CREATE will create the sandbox process.
121                     bound =
122                             mActivityManagerLocal.bindSdkSandboxService(
123                                     intent,
124                                     serviceConnection,
125                                     callingInfo.getUid(),
126                                     callingPackageName,
127                                     sandboxProcessName,
128                                     Context.BIND_AUTO_CREATE);
129                 }
130                 if (!bound) {
131                     mContext.unbindService(serviceConnection);
132                     notifyFailedBinding(serviceConnection);
133                     return;
134                 }
135             } catch (RemoteException e) {
136                 notifyFailedBinding(serviceConnection);
137                 return;
138             }
139             mAppSdkSandboxConnections.put(callingInfo, sdkSandboxConnection);
140             Log.i(TAG, "Sdk sandbox has been bound");
141         }
142     }
143 
144     // a way to notify manager that binding never happened
notifyFailedBinding(ServiceConnection serviceConnection)145     private void notifyFailedBinding(ServiceConnection serviceConnection) {
146         serviceConnection.onNullBinding(null);
147     }
148 
149     @Override
dump(PrintWriter writer)150     public void dump(PrintWriter writer) {
151         synchronized (mLock) {
152             if (mAppSdkSandboxConnections.size() == 0) {
153                 writer.println("mAppSdkSandboxConnections is empty");
154             } else {
155                 writer.print("mAppSdkSandboxConnections size: ");
156                 writer.println(mAppSdkSandboxConnections.size());
157                 for (int i = 0; i < mAppSdkSandboxConnections.size(); i++) {
158                     CallingInfo callingInfo = mAppSdkSandboxConnections.keyAt(i);
159                     SdkSandboxConnection sdkSandboxConnection =
160                             mAppSdkSandboxConnections.get(callingInfo);
161                     writer.printf(
162                             "Sdk sandbox for UID: %s, app package: %s, isConnected: %s Status: %d",
163                             callingInfo.getUid(),
164                             callingInfo.getPackageName(),
165                             Objects.requireNonNull(sdkSandboxConnection).isConnected(),
166                             sdkSandboxConnection.getStatus());
167                     writer.println();
168                 }
169             }
170         }
171     }
172 
173     @Override
unbindService(CallingInfo callingInfo)174     public void unbindService(CallingInfo callingInfo) {
175         synchronized (mLock) {
176             SdkSandboxConnection sandbox = getSdkSandboxConnectionLocked(callingInfo);
177 
178             if (sandbox == null) {
179                 return;
180             }
181 
182             if (sandbox.isBound) {
183                 try {
184                     mContext.unbindService(sandbox.getServiceConnection());
185                 } catch (Exception e) {
186                     // Sandbox has already unbound previously.
187                 }
188                 sandbox.onUnbind();
189                 Log.i(TAG, "Sdk sandbox for " + callingInfo + " has been unbound");
190             }
191         }
192     }
193 
194     @Override
stopSandboxService(CallingInfo callingInfo)195     public void stopSandboxService(CallingInfo callingInfo) {
196         synchronized (mLock) {
197             SdkSandboxConnection sandbox = getSdkSandboxConnectionLocked(callingInfo);
198 
199             if (!SdkLevel.isAtLeastU() || sandbox == null || sandbox.getStatus() == NON_EXISTENT) {
200                 return;
201             }
202 
203             ComponentName componentName = getServiceComponentName();
204             if (componentName == null) {
205                 Log.e(TAG, "Failed to find sdk sandbox service");
206                 return;
207             }
208             final Intent intent = new Intent().setComponent(componentName);
209             final String callingPackageName = callingInfo.getPackageName();
210             String sandboxProcessName = toSandboxProcessName(callingPackageName);
211 
212             mActivityManagerLocal.stopSdkSandboxService(
213                     intent, callingInfo.getUid(), callingPackageName, sandboxProcessName);
214         }
215     }
216 
217     @Override
218     @Nullable
getSdkSandboxServiceForApp(CallingInfo callingInfo)219     public ISdkSandboxService getSdkSandboxServiceForApp(CallingInfo callingInfo) {
220         synchronized (mLock) {
221             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
222             if (connection != null && connection.getStatus() == CREATED) {
223                 return connection.getSdkSandboxService();
224             }
225         }
226         return null;
227     }
228 
229     @Override
onServiceConnected(CallingInfo callingInfo, @NonNull ISdkSandboxService service)230     public void onServiceConnected(CallingInfo callingInfo, @NonNull ISdkSandboxService service) {
231         synchronized (mLock) {
232             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
233             if (connection != null) {
234                 connection.onServiceConnected(service);
235             }
236         }
237     }
238 
239     @Override
onServiceDisconnected(CallingInfo callingInfo)240     public void onServiceDisconnected(CallingInfo callingInfo) {
241         synchronized (mLock) {
242             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
243             if (connection != null) {
244                 connection.onServiceDisconnected();
245             }
246         }
247     }
248 
249     @Override
onAppDeath(CallingInfo callingInfo)250     public void onAppDeath(CallingInfo callingInfo) {
251         synchronized (mLock) {
252             mAppSdkSandboxConnections.remove(callingInfo);
253         }
254     }
255 
256     @Override
onSandboxDeath(CallingInfo callingInfo)257     public void onSandboxDeath(CallingInfo callingInfo) {
258         synchronized (mLock) {
259             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
260             if (connection != null) {
261                 connection.onSdkSandboxDeath();
262             }
263         }
264     }
265 
266     @Override
getSandboxStatusForApp(CallingInfo callingInfo)267     public int getSandboxStatusForApp(CallingInfo callingInfo) {
268         synchronized (mLock) {
269             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
270             if (connection == null) {
271                 return NON_EXISTENT;
272             } else {
273                 return connection.getStatus();
274             }
275         }
276     }
277 
278     @Override
279     @NonNull
toSandboxProcessName(@onNull String packageName)280     public String toSandboxProcessName(@NonNull String packageName) {
281         return getProcessName(packageName) + SANDBOX_PROCESS_NAME_SUFFIX;
282     }
283 
284     @Nullable
getServiceComponentName()285     private ComponentName getServiceComponentName() {
286         final Intent intent = new Intent(SdkSandboxManagerLocal.SERVICE_INTERFACE);
287         intent.setPackage(mContext.getPackageManager().getSdkSandboxPackageName());
288 
289         final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
290                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
291         if (resolveInfo == null) {
292             Log.e(TAG, "Failed to find resolveInfo for sdk sandbox service");
293             return null;
294         }
295 
296         final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
297         if (serviceInfo == null) {
298             Log.e(TAG, "Failed to find serviceInfo for sdk sandbox service");
299             return null;
300         }
301 
302         return new ComponentName(serviceInfo.packageName, serviceInfo.name);
303     }
304 
305     @GuardedBy("mLock")
306     @Nullable
getSdkSandboxConnectionLocked(CallingInfo callingInfo)307     private SdkSandboxConnection getSdkSandboxConnectionLocked(CallingInfo callingInfo) {
308         return mAppSdkSandboxConnections.get(callingInfo);
309     }
310 
getProcessName(String packageName)311     private String getProcessName(String packageName) {
312         try {
313             return mContext.getPackageManager().getApplicationInfo(packageName,
314                     /*flags=*/ 0).processName;
315         } catch (PackageManager.NameNotFoundException e) {
316             Log.e(TAG, packageName + " package not found");
317         }
318         return packageName;
319     }
320 
321     // Represents the connection to an SDK sandbox service.
322     static class SdkSandboxConnection {
323 
324         private final Object mLock = new Object();
325 
326         @GuardedBy("mLock")
327         @SandboxStatus
328         private int mStatus = CREATE_PENDING;
329 
330         // The connection used to bind and unbind from the SDK sandbox service.
331         private final ServiceConnection mServiceConnection;
332 
333         // The binder returned by the SDK sandbox service on connection.
334         @GuardedBy("mLock")
335         @Nullable
336         private ISdkSandboxService mSdkSandboxService = null;
337 
338         // Set to true when requested to bind to the SDK sandbox service. It is reset back to false
339         // when unbinding the sandbox service.
340         @GuardedBy("mLock")
341         public boolean isBound = true;
342 
SdkSandboxConnection(ServiceConnection serviceConnection)343         SdkSandboxConnection(ServiceConnection serviceConnection) {
344             mServiceConnection = serviceConnection;
345         }
346 
347         @SandboxStatus
getStatus()348         public int getStatus() {
349             synchronized (mLock) {
350                 return mStatus;
351             }
352         }
353 
onUnbind()354         public void onUnbind() {
355             synchronized (mLock) {
356                 isBound = false;
357             }
358         }
359 
onServiceConnected(ISdkSandboxService service)360         public void onServiceConnected(ISdkSandboxService service) {
361             synchronized (mLock) {
362                 mStatus = CREATED;
363                 mSdkSandboxService = service;
364             }
365         }
366 
onServiceDisconnected()367         public void onServiceDisconnected() {
368             synchronized (mLock) {
369                 mSdkSandboxService = null;
370             }
371         }
372 
onSdkSandboxDeath()373         public void onSdkSandboxDeath() {
374             synchronized (mLock) {
375                 // For U+, the sandbox does not restart after dying.
376                 if (SdkLevel.isAtLeastU()) {
377                     mStatus = NON_EXISTENT;
378                     return;
379                 }
380 
381                 if (isBound) {
382                     // If the sandbox was bound at the time of death, the system will automatically
383                     // restart it.
384                     mStatus = CREATE_PENDING;
385                 } else {
386                     // If the sandbox was not bound at the time of death, the sandbox is dead for
387                     // good.
388                     mStatus = NON_EXISTENT;
389                 }
390             }
391         }
392 
393         @Nullable
getSdkSandboxService()394         public ISdkSandboxService getSdkSandboxService() {
395             synchronized (mLock) {
396                 return mSdkSandboxService;
397             }
398         }
399 
getServiceConnection()400         public ServiceConnection getServiceConnection() {
401             return mServiceConnection;
402         }
403 
isConnected()404         boolean isConnected() {
405             synchronized (mLock) {
406                 return mSdkSandboxService != null;
407             }
408         }
409     }
410 }
411