blob: 7a41238d13ef216f1396fbaf71a3294be8d1bb95 [file] [log] [blame]
/*
* Copyright (C) 2022 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 android.ondevicepersonalization;
import android.annotation.CallbackExecutor;
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.ondevicepersonalization.aidl.IExecuteCallback;
import android.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
import android.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
import android.os.Bundle;
import android.os.IBinder;
import android.os.OutcomeReceiver;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Slog;
import android.view.SurfaceControlViewHost;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* OnDevicePersonalizationManager.
*
* @hide
*/
public class OnDevicePersonalizationManager {
public static final String ON_DEVICE_PERSONALIZATION_SERVICE =
"on_device_personalization_service";
/**
* The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
* its value should define the integer width of the {@link SurfacePackage} in pixels.
*/
public static final String EXTRA_WIDTH_IN_PIXELS =
"android.ondevicepersonalization.extra.WIDTH_IN_PIXELS";
/**
* The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
* its value should define the integer height of the {@link SurfacePackage} in pixels.
*/
public static final String EXTRA_HEIGHT_IN_PIXELS =
"android.ondevicepersonalization.extra.HEIGHT_IN_PIXELS";
/**
* The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
* its value should define the integer ID of the logical
* display to display the {@link SurfacePackage}.
*/
public static final String EXTRA_DISPLAY_ID =
"android.ondevicepersonalization.extra.DISPLAY_ID";
/**
* The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
* its value should present the token returned by {@link
* android.view.SurfaceView#getHostToken()} once the {@link android.view.SurfaceView}
* has been added to the view hierarchy. Only a non-null value is accepted to enable
* ANR reporting.
*/
public static final String EXTRA_HOST_TOKEN =
"android.ondevicepersonalization.extra.HOST_TOKEN";
/**
* The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
* its value should define a {@link PersistableBundle} that is passed to the
* {@link IsolatedComputationService}.
*/
public static final String EXTRA_APP_PARAMS =
"android.ondevicepersonalization.extra.APP_PARAMS";
/**
* The name of key in the Bundle which is passed to the {@code onResult} function of the {@link
* OutcomeReceiver} which is field of {@link #requestSurfacePackage()},
* its value presents the requested {@link SurfacePackage}.
*/
public static final String EXTRA_SURFACE_PACKAGE =
"android.ondevicepersonalization.extra.SURFACE_PACKAGE";
private boolean mBound = false;
private static final String TAG = "OdpManager";
private IOnDevicePersonalizationManagingService mService;
private final Context mContext;
public OnDevicePersonalizationManager(Context context) {
mContext = context;
}
private final CountDownLatch mConnectionLatch = new CountDownLatch(1);
private final ServiceConnection mConnection =
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IOnDevicePersonalizationManagingService.Stub.asInterface(service);
mBound = true;
mConnectionLatch.countDown();
}
@Override
public void onNullBinding(ComponentName name) {
mBound = false;
mConnectionLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
mBound = false;
}
};
private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
private static final String VERSION = "1.0";
/**
* Gets OnDevicePersonalization version.
* This function is a temporary place holder. It will be removed when new APIs are added.
*
* @hide
*/
public String getVersion() {
return VERSION;
}
/**
* Executes a {@link IsolatedComputationHandler} in the OnDevicePersonalization sandbox.
*
* @param servicePackageName The name of the package containing the handler.
* @param params a {@link PersistableBundle} passed from the calling app to the handler.
* @param executor the {@link Executor} on which to invoke the callback
* @param receiver This returns a list of {@link SlotResultHandle} objects, each of which is an
* opaque reference to a {@link SlotResult} returned by a
* {@link IsolatedComputationHandler}, or an {@link Exception} on failure. The returned
* {@link SlotResultHandle} objects can be used in a subsequent
* {@link requestSurfacePackage} call to display the result in a view.
*/
public void execute(
@NonNull String servicePackageName,
@NonNull PersistableBundle params,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<List<SlotResultHandle>, Exception> receiver
) {
try {
bindService(executor);
IExecuteCallback callbackWrapper = new IExecuteCallback.Stub() {
@Override
public void onSuccess(
@NonNull List<String> slotResultTokens) {
executor.execute(() -> {
try {
ArrayList<SlotResultHandle> slotResults =
new ArrayList<>(slotResultTokens.size());
for (String token : slotResultTokens) {
if (token == null) {
slotResults.add(null);
} else {
slotResults.add(new SlotResultHandle(token));
}
}
receiver.onResult(slotResults);
} catch (Exception e) {
receiver.onError(e);
}
});
}
@Override
public void onError(int errorCode) {
executor.execute(() -> receiver.onError(
new OnDevicePersonalizationException(errorCode)));
}
};
mService.execute(
mContext.getPackageName(), servicePackageName, params, callbackWrapper);
} catch (Exception e) {
receiver.onError(e);
}
}
/**
* Requests a surface package. The surface package will contain a {@link WebView} with html from
* a {@link IsolatedComputationHandler} running in the OnDevicePersonalization sandbox.
*
* @param slotResultHandle a reference to a {@link SlotResultHandle} returned by a prior call to
* {@link execute}.
* @param params the parameters from the client application, it must
* contain the following params: (EXTRA_WIDTH_IN_PIXELS, EXTRA_HEIGHT_IN_PIXELS,
* EXTRA_DISPLAY_ID, EXTRA_HOST_TOKEN). If any of these params is missing, an
* IllegalArgumentException will be thrown.
* @param executor the {@link Executor} on which to invoke the callback
* @param receiver This either returns a {@link Bundle} on success which should contain the key
* EXTRA_SURFACE_PACKAGE with value of {@link SurfacePackage} response, or {@link
* Exception} on failure.
* @throws IllegalArgumentException if any of the following params (EXTRA_WIDTH_IN_PIXELS,
* EXTRA_HEIGHT_IN_PIXELS, EXTRA_DISPLAY_ID, EXTRA_HOST_TOKEN) are missing from the Bundle
* or passed with the wrong value or type.
*
* @hide
*/
public void requestSurfacePackage(
@NonNull SlotResultHandle slotResultHandle,
IBinder hostToken,
int displayId,
int width,
int height,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver
) {
try {
bindService(executor);
IRequestSurfacePackageCallback callbackWrapper =
new IRequestSurfacePackageCallback.Stub() {
@Override
public void onSuccess(
@NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
executor.execute(() -> {
receiver.onResult(surfacePackage);
});
}
@Override
public void onError(int errorCode) {
executor.execute(() -> receiver.onError(
new OnDevicePersonalizationException(errorCode)));
}
};
mService.requestSurfacePackage(
slotResultHandle.getSlotResultToken(), hostToken, displayId,
width, height, callbackWrapper);
} catch (InterruptedException
| NullPointerException
| RemoteException e) {
receiver.onError(e);
}
}
/** Bind to the service, if not already bound. */
private void bindService(@NonNull Executor executor) throws InterruptedException {
if (!mBound) {
Intent intent = new Intent("android.OnDevicePersonalizationService");
ComponentName serviceComponent =
resolveService(intent, mContext.getPackageManager());
if (serviceComponent == null) {
Slog.e(TAG, "Invalid component for ondevicepersonalization service");
return;
}
intent.setComponent(serviceComponent);
boolean r = mContext.bindService(
intent, Context.BIND_AUTO_CREATE, executor, mConnection);
if (!r) {
return;
}
mConnectionLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
}
}
/**
* Find the ComponentName of the service, given its intent and package manager.
*
* @return ComponentName of the service. Null if the service is not found.
*/
private @Nullable ComponentName resolveService(
@NonNull Intent intent, @NonNull PackageManager pm) {
List<ResolveInfo> services =
pm.queryIntentServices(intent, PackageManager.ResolveInfoFlags.of(0));
if (services == null || services.isEmpty()) {
Slog.e(TAG, "Failed to find ondevicepersonalization service");
return null;
}
for (int i = 0; i < services.size(); i++) {
ResolveInfo ri = services.get(i);
ComponentName resolved =
new ComponentName(ri.serviceInfo.packageName, ri.serviceInfo.name);
// There should only be one matching service inside the given package.
// If there's more than one, return the first one found.
return resolved;
}
Slog.e(TAG, "Didn't find any matching ondevicepersonalization service.");
return null;
}
}