| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.supplementalprocess; |
| |
| import android.annotation.RequiresPermission; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.ServiceConnection; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.supplementalprocess.IRemoteCodeCallback; |
| import android.supplementalprocess.ISupplementalProcessManager; |
| import android.supplementalprocess.SupplementalProcessManager; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.view.SurfaceControlViewHost; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.server.SystemService; |
| import com.android.supplemental.process.ISupplementalProcessManagerToSupplementalProcessCallback; |
| import com.android.supplemental.process.ISupplementalProcessService; |
| import com.android.supplemental.process.ISupplementalProcessToSupplementalProcessManagerCallback; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| |
| import javax.annotation.concurrent.ThreadSafe; |
| |
| /** |
| * Implementation of Supplemental Process Manager service. |
| * |
| * @hide |
| */ |
| public class SupplementalProcessManagerService extends ISupplementalProcessManager.Stub { |
| |
| private static final String TAG = "SupplementalProcessManager"; |
| |
| private final Context mContext; |
| private final CodeTokenManager mCodeTokenManager = new CodeTokenManager(); |
| |
| private final SupplementalProcessServiceProvider mServiceProvider; |
| |
| // For communication between app<-ManagerService->RemoteCode for each codeToken |
| // TODO(b/208824602): Remove from this map when an app dies. |
| @GuardedBy("mAppAndRemoteCodeLinks") |
| private final ArrayMap<IBinder, AppAndRemoteCodeLink> mAppAndRemoteCodeLinks = new ArrayMap(); |
| |
| SupplementalProcessManagerService(Context context, |
| SupplementalProcessServiceProvider provider) { |
| mContext = context; |
| mServiceProvider = provider; |
| } |
| |
| @Override |
| public void loadCode(String name, String version, Bundle params, IRemoteCodeCallback callback) { |
| final int callingUid = Binder.getCallingUid(); |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| loadCodeWithClearIdentity(callingUid, name, params, callback); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| private void loadCodeWithClearIdentity(int callingUid, String name, Bundle params, |
| IRemoteCodeCallback callback) { |
| // Step 1: create unique identity for the {callingUid, name} pair |
| final IBinder codeToken = mCodeTokenManager.createOrGetCodeToken(callingUid, name); |
| |
| // Ensure we are not already loading code for this codeToken. That's determined by |
| // checking if we already have an AppAndRemoteCodeLink for the codeToken. |
| final AppAndRemoteCodeLink link = new AppAndRemoteCodeLink(codeToken, callback); |
| synchronized (mAppAndRemoteCodeLinks) { |
| if (mAppAndRemoteCodeLinks.putIfAbsent(codeToken, link) != null) { |
| link.sendLoadCodeErrorToApp(SupplementalProcessManager.LOAD_CODE_ALREADY_LOADED, |
| name + " is being loaded or has been loaded already"); |
| return; |
| } |
| } |
| // Step 2: fetch the installed code in device |
| final ApplicationInfo info = getCodeInfo(name); |
| if (info == null) { |
| String errorMsg = name + " not found for loading"; |
| Log.w(TAG, errorMsg); |
| link.sendLoadCodeErrorToApp(SupplementalProcessManager.LOAD_CODE_NOT_FOUND, errorMsg); |
| return; |
| } |
| // TODO(b/204991850): ensure requested code is included in the AndroidManifest.xml |
| |
| invokeCodeLoaderServiceToLoadCode(callingUid, codeToken, info, params, link); |
| |
| // Register a death recipient to clean up codeToken and unbind its service after app dies. |
| try { |
| callback.asBinder().linkToDeath(() -> { |
| cleanUp(codeToken); |
| mServiceProvider.unbindService(callingUid); |
| }, 0); |
| } catch (RemoteException re) { |
| // App has already died, cleanup code token and link, and unbind its service |
| cleanUp(codeToken); |
| mServiceProvider.unbindService(callingUid); |
| } |
| } |
| |
| private ApplicationInfo getCodeInfo(String packageName) { |
| // TODO(b/204991850): code info should be version specific too |
| try { |
| // TODO(b/204991850): update this when PM provides better API for getting code info |
| return mContext.getPackageManager().getApplicationInfo(packageName, /*flags=*/0); |
| } catch (PackageManager.NameNotFoundException ignored) { |
| return null; |
| } |
| } |
| |
| @Override |
| public void requestSurfacePackage(IBinder codeToken, IBinder hostToken, |
| int displayId, Bundle params) { |
| //TODO(b/204991850): verify that codeToken belongs to the callingUid |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| requestSurfacePackageWithClearIdentity(codeToken, |
| hostToken, displayId, params); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| private void requestSurfacePackageWithClearIdentity(IBinder codeToken, |
| IBinder hostToken, int displayId, Bundle params) { |
| synchronized (mAppAndRemoteCodeLinks) { |
| if (!mAppAndRemoteCodeLinks.containsKey(codeToken)) { |
| throw new SecurityException("codeToken is invalid"); |
| } |
| final AppAndRemoteCodeLink link = mAppAndRemoteCodeLinks.get(codeToken); |
| link.requestSurfacePackageToCode(hostToken, displayId, params); |
| } |
| } |
| |
| @Override |
| public void sendData(int id, Bundle params) {} |
| |
| @Override |
| @RequiresPermission(android.Manifest.permission.DUMP) |
| protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { |
| mContext.enforceCallingPermission(android.Manifest.permission.DUMP, |
| "Can't dump " + TAG); |
| |
| // TODO(b/211575098): Use IndentingPrintWriter for better formatting |
| synchronized (mAppAndRemoteCodeLinks) { |
| writer.println("mAppAndRemoteCodeLinks size: " + mAppAndRemoteCodeLinks.size()); |
| } |
| |
| writer.println("mCodeTokenManager:"); |
| mCodeTokenManager.dump(writer); |
| writer.println(); |
| |
| writer.println("mServiceProvider:"); |
| mServiceProvider.dump(writer); |
| writer.println(); |
| } |
| |
| private void invokeCodeLoaderServiceToLoadCode( |
| int callingUid, IBinder codeToken, ApplicationInfo info, Bundle params, |
| AppAndRemoteCodeLink link) { |
| |
| // check first if service already bound |
| ISupplementalProcessService service = mServiceProvider.getBoundServiceForApp(callingUid); |
| if (service != null) { |
| loadCodeForService(codeToken, info, params, link, service); |
| return; |
| } |
| |
| mServiceProvider.bindService( |
| callingUid, |
| new ServiceConnection() { |
| private boolean mIsServiceBound = false; |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| final ISupplementalProcessService mService = |
| ISupplementalProcessService.Stub.asInterface(service); |
| Log.i(TAG, "Supplemental process has been bound"); |
| mServiceProvider.registerServiceForApp(callingUid, mService); |
| |
| // Ensuring the code is not loaded again if connection restarted |
| if (!mIsServiceBound) { |
| loadCodeForService(codeToken, info, params, link, mService); |
| mIsServiceBound = true; |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| // Supplemental process crashed or killed, system will start it again. |
| // TODO(b/204991850): Handle restarts differently |
| // (e.g. Exponential backoff retry strategy) |
| mServiceProvider.registerServiceForApp(callingUid, null); |
| } |
| |
| @Override |
| public void onBindingDied(ComponentName name) { |
| mServiceProvider.registerServiceForApp(callingUid, null); |
| mServiceProvider.unbindService(callingUid); |
| mServiceProvider.bindService(callingUid, this); |
| } |
| |
| @Override |
| public void onNullBinding(ComponentName name) { |
| link.sendLoadCodeErrorToApp( |
| SupplementalProcessManager.LOAD_CODE_INTERNAL_ERROR, |
| "Failed to bind the service"); |
| } |
| } |
| ); |
| |
| } |
| |
| private void loadCodeForService( |
| IBinder codeToken, ApplicationInfo info, Bundle params, |
| AppAndRemoteCodeLink link, ISupplementalProcessService service) { |
| try { |
| // TODO(b/208631926): Pass a meaningful value for codeProviderClassName |
| service.loadCode(codeToken, info, "", params, link); |
| } catch (RemoteException e) { |
| String errorMsg = "Failed to load code"; |
| Log.w(TAG, errorMsg, e); |
| link.sendLoadCodeErrorToApp( |
| SupplementalProcessManager.LOAD_CODE_INTERNAL_ERROR, errorMsg); |
| } |
| } |
| /** |
| * Clean up all internal data structures related to {@code codeToken} |
| */ |
| private void cleanUp(IBinder codeToken) { |
| // Destroy the codeToken first, to free up the {callingUid, name} pair |
| mCodeTokenManager.destroy(codeToken); |
| // Now clean up rest of the state which is using an obsolete codeToken |
| synchronized (mAppAndRemoteCodeLinks) { |
| mAppAndRemoteCodeLinks.remove(codeToken); |
| } |
| } |
| |
| @ThreadSafe |
| private static class CodeTokenManager { |
| // Keep track of codeToken for each unique pair of {callingUid, name} |
| @GuardedBy("mCodeTokens") |
| final ArrayMap<Pair<Integer, String>, IBinder> mCodeTokens = new ArrayMap<>(); |
| @GuardedBy("mCodeTokens") |
| final ArrayMap<IBinder, Pair<Integer, String>> mReverseCodeTokens = new ArrayMap<>(); |
| |
| /** |
| * For the given {callingUid, name} pair, create unique codeToken or |
| * return existing one. |
| */ |
| public IBinder createOrGetCodeToken(int callingUid, String name) { |
| final Pair<Integer, String> pair = Pair.create(callingUid, name); |
| synchronized (mCodeTokens) { |
| if (!mCodeTokens.containsKey(pair)) { |
| final IBinder codeToken = new Binder(); |
| mCodeTokens.put(pair, codeToken); |
| mReverseCodeTokens.put(codeToken, pair); |
| } |
| return mCodeTokens.get(pair); |
| } |
| } |
| |
| public void destroy(IBinder codeToken) { |
| synchronized (mCodeTokens) { |
| mCodeTokens.remove(mReverseCodeTokens.get(codeToken)); |
| mReverseCodeTokens.remove(codeToken); |
| } |
| } |
| |
| void dump(PrintWriter writer) { |
| synchronized (mCodeTokens) { |
| if (mCodeTokens.isEmpty()) { |
| writer.println("mCodeTokens is empty"); |
| } else { |
| writer.print("mCodeTokens size: "); |
| writer.println(mCodeTokens.size()); |
| for (Pair<Integer, String> pair : mCodeTokens.keySet()) { |
| writer.printf("callingUid: %s, name: %s", pair.first, pair.second); |
| writer.println(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * A callback object to establish a link between the app calling into manager service |
| * and the remote code being loaded in SupplementalProcess. |
| * |
| * Overview of communication: |
| * 1. App to ManagerService: App calls into this service via app context |
| * 2. ManagerService to App: {@link AppAndRemoteCodeLink} holds reference to |
| * {@link IRemoteCodeCallback} object which provides call back into the app. |
| * 3. RemoteCode to ManagerService: {@link AppAndRemoteCodeLink} extends |
| * {@link ISupplementalProcessToSupplementalProcessManagerCallback} interface. We |
| * pass on this object to {@link ISupplementalProcessService} so that remote code |
| * can call back into ManagerService |
| * 4. ManagerService to RemoteCode: When code is loaded for the first time and remote |
| * code calls back with successful result, it also sends reference to |
| * {@link ISupplementalProcessManagerToSupplementalProcessCallback} callback object. |
| * ManagerService uses this to callback into the remote code. |
| * |
| * We maintain a link for each unique {app, remoteCode} pair, which is identified with |
| * {@code codeToken}. |
| */ |
| private class AppAndRemoteCodeLink extends |
| ISupplementalProcessToSupplementalProcessManagerCallback.Stub { |
| // The codeToken for which this channel has been created |
| private final IBinder mCodeToken; |
| private final IRemoteCodeCallback mManagerToAppCallback; |
| |
| @GuardedBy("this") |
| private ISupplementalProcessManagerToSupplementalProcessCallback mManagerToCodeCallback; |
| |
| AppAndRemoteCodeLink(IBinder codeToken, IRemoteCodeCallback managerToAppCallback) { |
| mCodeToken = codeToken; |
| mManagerToAppCallback = managerToAppCallback; |
| } |
| |
| @Override |
| public void onLoadCodeSuccess(Bundle params, |
| ISupplementalProcessManagerToSupplementalProcessCallback callback) { |
| // Keep reference to callback so that manager service can |
| // callback to remote code loaded. |
| synchronized (this) { |
| mManagerToCodeCallback = callback; |
| } |
| sendLoadCodeSuccessToApp(params); |
| } |
| |
| @Override |
| public void onLoadCodeError(int errorCode, String errorMsg) { |
| sendLoadCodeErrorToApp(errorCode, errorMsg); |
| } |
| |
| @Override |
| public void onSurfacePackageReady(SurfaceControlViewHost.SurfacePackage surfacePackage, |
| int surfacePackageId, Bundle params) { |
| sendSurfacePackageReadyToApp(surfacePackage, surfacePackageId, params); |
| } |
| |
| @Override |
| public void onSurfacePackageError(int errorCode, String errorMsg) { |
| sendSurfacePackageErrorToApp(errorCode, errorMsg); |
| } |
| |
| private void sendLoadCodeSuccessToApp(Bundle params) { |
| try { |
| mManagerToAppCallback.onLoadCodeSuccess(mCodeToken, params); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to send onLoadCodeSuccess", e); |
| } |
| } |
| |
| void sendLoadCodeErrorToApp(int errorCode, String errorMsg) { |
| // Since loadCode failed, manager should no longer concern itself with communication |
| // between the app and a non-existing remote code. |
| cleanUp(mCodeToken); |
| |
| try { |
| mManagerToAppCallback.onLoadCodeFailure(errorCode, errorMsg); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to send onLoadCodeFailure", e); |
| } |
| } |
| |
| void sendSurfacePackageErrorToApp(int errorCode, String errorMsg) { |
| try { |
| mManagerToAppCallback.onSurfacePackageError(errorCode, errorMsg); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to send onSurfacePackageError", e); |
| } |
| } |
| |
| private void sendSurfacePackageReadyToApp( |
| SurfaceControlViewHost.SurfacePackage surfacePackage, |
| int surfacePackageId, Bundle params) { |
| try { |
| mManagerToAppCallback.onSurfacePackageReady(surfacePackage, |
| surfacePackageId, params); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to send onSurfacePackageReady callback", e); |
| } |
| } |
| |
| void requestSurfacePackageToCode(IBinder hostToken, int displayId, Bundle params) { |
| try { |
| synchronized (this) { |
| mManagerToCodeCallback.onSurfacePackageRequested(hostToken, displayId, params); |
| } |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to requestSurfacePackage", e); |
| // TODO(b/204991850): send request surface package error back to app |
| } |
| } |
| } |
| |
| /** @hide */ |
| public static class Lifecycle extends SystemService { |
| public Lifecycle(Context context) { |
| super(context); |
| } |
| |
| @Override |
| public void onStart() { |
| SupplementalProcessServiceProvider provider = |
| new SupplementalProcessServiceProviderImpl(getContext()); |
| SupplementalProcessManagerService service = |
| new SupplementalProcessManagerService(getContext(), provider); |
| publishBinderService(Context.SUPPLEMENTAL_PROCESS_SERVICE, service); |
| } |
| } |
| } |