Loading core/java/android/service/attestation/ImpressionAttestationService.java +8 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,14 @@ public abstract class ImpressionAttestationService extends Service { public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.attestation.available_algorithms"; /** * The {@link Intent} action that must be declared as handled by a service in its manifest * for the system to recognize it as an impression attestation providing service. * @hide */ public static final String SERVICE_INTERFACE = "android.service.attestation.ImpressionAttestationService"; private ImpressionAttestationServiceWrapper mWrapper; private Handler mHandler; Loading services/core/java/com/android/server/wm/ImpressionAttestationController.java 0 → 100644 +356 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.wm; import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.service.attestation.IImpressionAttestationService; import android.service.attestation.ImpressionAttestationService; import android.service.attestation.ImpressionToken; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; /** * Handles requests into {@link ImpressionAttestationService} * * Do not hold the {@link WindowManagerService#mGlobalLock} when calling methods since they are * blocking calls into another service. */ public class ImpressionAttestationController { private static final String TAG = "ImpressionAttestationController"; private static final boolean DEBUG = false; private final Object mServiceConnectionLock = new Object(); @GuardedBy("mServiceConnectionLock") private ImpressionAttestationServiceConnection mServiceConnection; private final Context mContext; /** * Lock used for the cached {@link #mImpressionAlgorithms} array */ private final Object mImpressionAlgorithmsLock = new Object(); @GuardedBy("mImpressionAlgorithmsLock") private String[] mImpressionAlgorithms; private final Handler mHandler; private interface Command { void run(IImpressionAttestationService service) throws RemoteException; } ImpressionAttestationController(Context context) { mContext = context; mHandler = new Handler(Looper.getMainLooper()); } String[] getSupportedImpressionAlgorithms() { // We have a separate lock for the impression algorithm array since it doesn't need to make // the request through the service connection. Instead, we have a lock to ensure we can // properly cache the impression algorithms array so we don't need to call into the // ExtServices process for each request. synchronized (mImpressionAlgorithmsLock) { // Already have cached values if (mImpressionAlgorithms != null) { return mImpressionAlgorithms; } final ServiceInfo serviceInfo = getServiceInfo(); if (serviceInfo == null) return null; final PackageManager pm = mContext.getPackageManager(); final Resources res; try { res = pm.getResourcesForApplication(serviceInfo.applicationInfo); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Error getting application resources for " + serviceInfo, e); return null; } final int resourceId = serviceInfo.metaData.getInt( SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS); mImpressionAlgorithms = res.getStringArray(resourceId); return mImpressionAlgorithms; } } int verifyImpressionToken(ImpressionToken impressionToken) { final SyncCommand syncCommand = new SyncCommand(); Bundle results = syncCommand.run((service, remoteCallback) -> { try { service.verifyImpressionToken(impressionToken, remoteCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to invoke verifyImpressionToken command"); } }); return results.getInt(ImpressionAttestationService.EXTRA_VERIFICATION_STATUS); } ImpressionToken generateImpressionToken(HardwareBuffer screenshot, Rect bounds, String hashAlgorithm) { final SyncCommand syncCommand = new SyncCommand(); Bundle results = syncCommand.run((service, remoteCallback) -> { try { service.generateImpressionToken(screenshot, bounds, hashAlgorithm, remoteCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to invoke generateImpressionToken command", e); } }); return results.getParcelable(ImpressionAttestationService.EXTRA_IMPRESSION_TOKEN); } /** * Run a command, starting the service connection if necessary. */ private void connectAndRun(@NonNull Command command) { synchronized (mServiceConnectionLock) { mHandler.resetTimeoutMessage(); if (mServiceConnection == null) { if (DEBUG) Slog.v(TAG, "creating connection"); // Create the connection mServiceConnection = new ImpressionAttestationServiceConnection(); final ComponentName component = getServiceComponentName(); if (DEBUG) Slog.v(TAG, "binding to: " + component); if (component != null) { final Intent intent = new Intent(); intent.setComponent(component); final long token = Binder.clearCallingIdentity(); try { mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT); if (DEBUG) Slog.v(TAG, "bound"); } finally { Binder.restoreCallingIdentity(token); } } } mServiceConnection.runCommandLocked(command); } } @Nullable private ServiceInfo getServiceInfo() { final String packageName = mContext.getPackageManager().getServicesSystemSharedLibraryPackageName(); if (packageName == null) { Slog.w(TAG, "no external services package!"); return null; } final Intent intent = new Intent(ImpressionAttestationService.SERVICE_INTERFACE); intent.setPackage(packageName); final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); if (resolveInfo == null || resolveInfo.serviceInfo == null) { Slog.w(TAG, "No valid components found."); return null; } return resolveInfo.serviceInfo; } @Nullable private ComponentName getServiceComponentName() { final ServiceInfo serviceInfo = getServiceInfo(); if (serviceInfo == null) return null; final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name); if (!Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE .equals(serviceInfo.permission)) { Slog.w(TAG, name.flattenToShortString() + " requires permission " + Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE); return null; } if (DEBUG) Slog.v(TAG, "getServiceComponentName(): " + name); return name; } private class SyncCommand { private static final int WAIT_TIME_S = 5; private Bundle mResult; private final CountDownLatch mCountDownLatch = new CountDownLatch(1); public Bundle run(BiConsumer<IImpressionAttestationService, RemoteCallback> func) { connectAndRun(service -> { RemoteCallback callback = new RemoteCallback(result -> { mResult = result; mCountDownLatch.countDown(); }); func.accept(service, callback); }); try { mCountDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS); } catch (Exception e) { Slog.e(TAG, "Failed to wait for command", e); } return mResult; } } private class ImpressionAttestationServiceConnection implements ServiceConnection { @GuardedBy("mServiceConnectionLock") private IImpressionAttestationService mRemoteService; @GuardedBy("mServiceConnectionLock") private ArrayList<Command> mQueuedCommands; @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Slog.v(TAG, "onServiceConnected(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = IImpressionAttestationService.Stub.asInterface(service); if (mQueuedCommands != null) { final int size = mQueuedCommands.size(); if (DEBUG) Slog.d(TAG, "running " + size + " queued commands"); for (int i = 0; i < size; i++) { final Command queuedCommand = mQueuedCommands.get(i); try { if (DEBUG) Slog.v(TAG, "running queued command #" + i); queuedCommand.run(mRemoteService); } catch (RemoteException e) { Slog.w(TAG, "exception calling " + name + ": " + e); } } mQueuedCommands = null; } else if (DEBUG) { Slog.d(TAG, "no queued commands"); } } } @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) Slog.v(TAG, "onServiceDisconnected(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = null; } } @Override public void onBindingDied(ComponentName name) { if (DEBUG) Slog.v(TAG, "onBindingDied(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = null; } } @Override public void onNullBinding(ComponentName name) { if (DEBUG) Slog.v(TAG, "onNullBinding(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = null; } } /** * Only call while holding {@link #mServiceConnectionLock} */ private void runCommandLocked(Command command) { if (mRemoteService == null) { if (DEBUG) Slog.d(TAG, "service is null; queuing command"); if (mQueuedCommands == null) { mQueuedCommands = new ArrayList<>(1); } mQueuedCommands.add(command); } else { try { if (DEBUG) Slog.v(TAG, "running command right away"); command.run(mRemoteService); } catch (RemoteException e) { Slog.w(TAG, "exception calling service: " + e); } } } } private class Handler extends android.os.Handler { static final long SERVICE_SHUTDOWN_TIMEOUT_MILLIS = 10000; // 10s static final int MSG_SERVICE_SHUTDOWN_TIMEOUT = 1; Handler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (msg.what == MSG_SERVICE_SHUTDOWN_TIMEOUT) { if (DEBUG) { Slog.v(TAG, "Shutting down service"); } synchronized (mServiceConnectionLock) { if (mServiceConnection != null) { mContext.unbindService(mServiceConnection); mServiceConnection = null; } } } } /** * Set a timer for {@link #SERVICE_SHUTDOWN_TIMEOUT_MILLIS} so we can tear down the service * if it's inactive. The requests will be coming from apps so it's hard to tell how often * the requests can come in. Therefore, we leave the service running if requests continue * to come in. Once there's been no activity for 10s, we can shut down the service and * restart when we get a new request. */ void resetTimeoutMessage() { if (DEBUG) { Slog.v(TAG, "Reset shutdown message"); } removeMessages(MSG_SERVICE_SHUTDOWN_TIMEOUT); sendEmptyMessageDelayed(MSG_SERVICE_SHUTDOWN_TIMEOUT, SERVICE_SHUTDOWN_TIMEOUT_MILLIS); } } } Loading
core/java/android/service/attestation/ImpressionAttestationService.java +8 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,14 @@ public abstract class ImpressionAttestationService extends Service { public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.attestation.available_algorithms"; /** * The {@link Intent} action that must be declared as handled by a service in its manifest * for the system to recognize it as an impression attestation providing service. * @hide */ public static final String SERVICE_INTERFACE = "android.service.attestation.ImpressionAttestationService"; private ImpressionAttestationServiceWrapper mWrapper; private Handler mHandler; Loading
services/core/java/com/android/server/wm/ImpressionAttestationController.java 0 → 100644 +356 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.wm; import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.service.attestation.IImpressionAttestationService; import android.service.attestation.ImpressionAttestationService; import android.service.attestation.ImpressionToken; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; /** * Handles requests into {@link ImpressionAttestationService} * * Do not hold the {@link WindowManagerService#mGlobalLock} when calling methods since they are * blocking calls into another service. */ public class ImpressionAttestationController { private static final String TAG = "ImpressionAttestationController"; private static final boolean DEBUG = false; private final Object mServiceConnectionLock = new Object(); @GuardedBy("mServiceConnectionLock") private ImpressionAttestationServiceConnection mServiceConnection; private final Context mContext; /** * Lock used for the cached {@link #mImpressionAlgorithms} array */ private final Object mImpressionAlgorithmsLock = new Object(); @GuardedBy("mImpressionAlgorithmsLock") private String[] mImpressionAlgorithms; private final Handler mHandler; private interface Command { void run(IImpressionAttestationService service) throws RemoteException; } ImpressionAttestationController(Context context) { mContext = context; mHandler = new Handler(Looper.getMainLooper()); } String[] getSupportedImpressionAlgorithms() { // We have a separate lock for the impression algorithm array since it doesn't need to make // the request through the service connection. Instead, we have a lock to ensure we can // properly cache the impression algorithms array so we don't need to call into the // ExtServices process for each request. synchronized (mImpressionAlgorithmsLock) { // Already have cached values if (mImpressionAlgorithms != null) { return mImpressionAlgorithms; } final ServiceInfo serviceInfo = getServiceInfo(); if (serviceInfo == null) return null; final PackageManager pm = mContext.getPackageManager(); final Resources res; try { res = pm.getResourcesForApplication(serviceInfo.applicationInfo); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Error getting application resources for " + serviceInfo, e); return null; } final int resourceId = serviceInfo.metaData.getInt( SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS); mImpressionAlgorithms = res.getStringArray(resourceId); return mImpressionAlgorithms; } } int verifyImpressionToken(ImpressionToken impressionToken) { final SyncCommand syncCommand = new SyncCommand(); Bundle results = syncCommand.run((service, remoteCallback) -> { try { service.verifyImpressionToken(impressionToken, remoteCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to invoke verifyImpressionToken command"); } }); return results.getInt(ImpressionAttestationService.EXTRA_VERIFICATION_STATUS); } ImpressionToken generateImpressionToken(HardwareBuffer screenshot, Rect bounds, String hashAlgorithm) { final SyncCommand syncCommand = new SyncCommand(); Bundle results = syncCommand.run((service, remoteCallback) -> { try { service.generateImpressionToken(screenshot, bounds, hashAlgorithm, remoteCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to invoke generateImpressionToken command", e); } }); return results.getParcelable(ImpressionAttestationService.EXTRA_IMPRESSION_TOKEN); } /** * Run a command, starting the service connection if necessary. */ private void connectAndRun(@NonNull Command command) { synchronized (mServiceConnectionLock) { mHandler.resetTimeoutMessage(); if (mServiceConnection == null) { if (DEBUG) Slog.v(TAG, "creating connection"); // Create the connection mServiceConnection = new ImpressionAttestationServiceConnection(); final ComponentName component = getServiceComponentName(); if (DEBUG) Slog.v(TAG, "binding to: " + component); if (component != null) { final Intent intent = new Intent(); intent.setComponent(component); final long token = Binder.clearCallingIdentity(); try { mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT); if (DEBUG) Slog.v(TAG, "bound"); } finally { Binder.restoreCallingIdentity(token); } } } mServiceConnection.runCommandLocked(command); } } @Nullable private ServiceInfo getServiceInfo() { final String packageName = mContext.getPackageManager().getServicesSystemSharedLibraryPackageName(); if (packageName == null) { Slog.w(TAG, "no external services package!"); return null; } final Intent intent = new Intent(ImpressionAttestationService.SERVICE_INTERFACE); intent.setPackage(packageName); final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); if (resolveInfo == null || resolveInfo.serviceInfo == null) { Slog.w(TAG, "No valid components found."); return null; } return resolveInfo.serviceInfo; } @Nullable private ComponentName getServiceComponentName() { final ServiceInfo serviceInfo = getServiceInfo(); if (serviceInfo == null) return null; final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name); if (!Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE .equals(serviceInfo.permission)) { Slog.w(TAG, name.flattenToShortString() + " requires permission " + Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE); return null; } if (DEBUG) Slog.v(TAG, "getServiceComponentName(): " + name); return name; } private class SyncCommand { private static final int WAIT_TIME_S = 5; private Bundle mResult; private final CountDownLatch mCountDownLatch = new CountDownLatch(1); public Bundle run(BiConsumer<IImpressionAttestationService, RemoteCallback> func) { connectAndRun(service -> { RemoteCallback callback = new RemoteCallback(result -> { mResult = result; mCountDownLatch.countDown(); }); func.accept(service, callback); }); try { mCountDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS); } catch (Exception e) { Slog.e(TAG, "Failed to wait for command", e); } return mResult; } } private class ImpressionAttestationServiceConnection implements ServiceConnection { @GuardedBy("mServiceConnectionLock") private IImpressionAttestationService mRemoteService; @GuardedBy("mServiceConnectionLock") private ArrayList<Command> mQueuedCommands; @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Slog.v(TAG, "onServiceConnected(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = IImpressionAttestationService.Stub.asInterface(service); if (mQueuedCommands != null) { final int size = mQueuedCommands.size(); if (DEBUG) Slog.d(TAG, "running " + size + " queued commands"); for (int i = 0; i < size; i++) { final Command queuedCommand = mQueuedCommands.get(i); try { if (DEBUG) Slog.v(TAG, "running queued command #" + i); queuedCommand.run(mRemoteService); } catch (RemoteException e) { Slog.w(TAG, "exception calling " + name + ": " + e); } } mQueuedCommands = null; } else if (DEBUG) { Slog.d(TAG, "no queued commands"); } } } @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) Slog.v(TAG, "onServiceDisconnected(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = null; } } @Override public void onBindingDied(ComponentName name) { if (DEBUG) Slog.v(TAG, "onBindingDied(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = null; } } @Override public void onNullBinding(ComponentName name) { if (DEBUG) Slog.v(TAG, "onNullBinding(): " + name); synchronized (mServiceConnectionLock) { mRemoteService = null; } } /** * Only call while holding {@link #mServiceConnectionLock} */ private void runCommandLocked(Command command) { if (mRemoteService == null) { if (DEBUG) Slog.d(TAG, "service is null; queuing command"); if (mQueuedCommands == null) { mQueuedCommands = new ArrayList<>(1); } mQueuedCommands.add(command); } else { try { if (DEBUG) Slog.v(TAG, "running command right away"); command.run(mRemoteService); } catch (RemoteException e) { Slog.w(TAG, "exception calling service: " + e); } } } } private class Handler extends android.os.Handler { static final long SERVICE_SHUTDOWN_TIMEOUT_MILLIS = 10000; // 10s static final int MSG_SERVICE_SHUTDOWN_TIMEOUT = 1; Handler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (msg.what == MSG_SERVICE_SHUTDOWN_TIMEOUT) { if (DEBUG) { Slog.v(TAG, "Shutting down service"); } synchronized (mServiceConnectionLock) { if (mServiceConnection != null) { mContext.unbindService(mServiceConnection); mServiceConnection = null; } } } } /** * Set a timer for {@link #SERVICE_SHUTDOWN_TIMEOUT_MILLIS} so we can tear down the service * if it's inactive. The requests will be coming from apps so it's hard to tell how often * the requests can come in. Therefore, we leave the service running if requests continue * to come in. Once there's been no activity for 10s, we can shut down the service and * restart when we get a new request. */ void resetTimeoutMessage() { if (DEBUG) { Slog.v(TAG, "Reset shutdown message"); } removeMessages(MSG_SERVICE_SHUTDOWN_TIMEOUT); sendEmptyMessageDelayed(MSG_SERVICE_SHUTDOWN_TIMEOUT, SERVICE_SHUTDOWN_TIMEOUT_MILLIS); } } }