blob: c88b3da2ecd288c34b45b1df1b646e4921afa2f3 [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.NonNull;
import android.annotation.Nullable;
import android.app.Service;
import android.content.Intent;
import android.ondevicepersonalization.aidl.IDataAccessService;
import android.ondevicepersonalization.aidl.IPersonalizationService;
import android.ondevicepersonalization.aidl.IPersonalizationServiceCallback;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Log;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Base class for services that produce personalized content based on User data. These platform
* runs the service in an isolated process and manages its access to user data.
*
* @hide
*/
public abstract class PersonalizationService extends Service {
private static final String TAG = "PersonalizationService";
private IBinder mBinder;
/**
* Callback to return results of incoming requests.
*
* @hide
*/
public interface AppRequestCallback {
/** Return the result of a successful request. */
void onSuccess(AppRequestResult result);
/** Error */
void onError();
}
/**
* Callback to signal completion of download post-processing.
*
* @hide
*/
public interface DownloadCallback {
/** Retains the provided keys */
void onSuccess(DownloadResult downloadResult);
/** Error in download processing. The platform will retry the download. */
void onError();
}
/**
* Callback to signal the completion of a render request.
*/
public interface RenderContentCallback {
/** Provides the result of a successful render request. */
void onSuccess(RenderContentResult result);
/** Error in rendering. */
void onError();
}
@Override public void onCreate() {
mBinder = new ServiceBinder();
}
@Override public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Handle a request from an app. A {@link PersonalizationService} that
* processes requests from apps must override this method.
*
* @param appPackageName Package name of the calling app.
* @param appParams Parameters provided by the calling app.
* @param odpContext The per-request state for this request.
* @param callback Callback to be invoked on completion.
*/
public void onAppRequest(
@NonNull String appPackageName,
@Nullable PersistableBundle appParams,
@NonNull OnDevicePersonalizationContext odpContext,
@NonNull AppRequestCallback callback
) {
callback.onError();
}
/**
* Handle a completed download. The platform downloads content using the
* parameters defined in the package manifest of the {@link PersonalizationService}
* and calls this function after the download is complete.
*
* @param fd A file descriptor to read the downloaded content.
* @param odpContext The per-request state for this request.
* @param callback Callback to be invoked on completion.
*/
public void onDownload(
@NonNull ParcelFileDescriptor fd,
@NonNull OnDevicePersonalizationContext odpContext,
@NonNull DownloadCallback callback
) {
callback.onError();
}
/**
* Generate HTML for the winning bids that returned as a result of {@link onAppRequest}.
* The platform will render this HTML in a WebView inside a fenced frame.
*
* @param slotInfo Properties of the slot to be rendered in.
* @param bidIds A List of Bid Ids to be rendered
* @param odpContext The per-request state for this request.
* @param callback Callback to be invoked on completion.
*/
public void renderContent(
@NonNull SlotInfo slotInfo,
@NonNull List<String> bidIds,
@NonNull OnDevicePersonalizationContext odpContext,
@NonNull RenderContentCallback callback
) {
callback.onError();
}
// TODO(b/228200518): Add onBidRequest()/onBidResponse() methods.
class ServiceBinder extends IPersonalizationService.Stub {
@Override public void onRequest(
int operationCode,
@NonNull Bundle params,
@NonNull IPersonalizationServiceCallback callback) {
Objects.requireNonNull(params);
Objects.requireNonNull(callback);
// TODO(b/228200518): Ensure that caller is ODP Service.
if (operationCode == Constants.OP_APP_REQUEST) {
String appPackageName = Objects.requireNonNull(
params.getString(Constants.EXTRA_APP_NAME));
PersistableBundle appParams =
(PersistableBundle) params.getParcelable(Constants.EXTRA_APP_PARAMS);
IDataAccessService binder =
(IDataAccessService) Objects.requireNonNull(
params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER));
OnDevicePersonalizationContext odpContext =
new OnDevicePersonalizationContextImpl(binder);
var wrappedCallback = new AppRequestCallback() {
@Override public void onSuccess(AppRequestResult result) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.EXTRA_RESULT, result);
try {
callback.onSuccess(bundle);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed.", e);
}
}
@Override public void onError() {
try {
callback.onError(Constants.STATUS_INTERNAL_ERROR);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed.", e);
}
}
};
PersonalizationService.this.onAppRequest(
appPackageName, appParams, odpContext, wrappedCallback);
} else if (operationCode == Constants.OP_DOWNLOAD_FINISHED) {
ParcelFileDescriptor fd = Objects.requireNonNull(
params.getParcelable(Constants.EXTRA_PARCEL_FD));
IDataAccessService binder = (IDataAccessService) Objects.requireNonNull(
params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER));
OnDevicePersonalizationContext odpContext =
new OnDevicePersonalizationContextImpl(binder);
var wrappedCallback = new DownloadCallback() {
@Override public void onSuccess(DownloadResult result) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.EXTRA_RESULT, result);
try {
callback.onSuccess(bundle);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed.", e);
}
}
@Override public void onError() {
try {
callback.onError(Constants.STATUS_INTERNAL_ERROR);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed.", e);
}
}
};
PersonalizationService.this.onDownload(
fd, odpContext, wrappedCallback);
} else if (operationCode == Constants.OP_RENDER_CONTENT) {
SlotInfo slotInfo = Objects.requireNonNull(
params.getParcelable(Constants.EXTRA_SLOT_INFO));
List<String> bidIds = Arrays.asList(Objects.requireNonNull(
params.getStringArray(Constants.EXTRA_BID_IDS)));
IDataAccessService binder = (IDataAccessService) Objects.requireNonNull(
params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER));
OnDevicePersonalizationContext odpContext =
new OnDevicePersonalizationContextImpl(binder);
var wrappedCallback = new RenderContentCallback() {
@Override public void onSuccess(RenderContentResult result) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.EXTRA_RESULT, result);
try {
callback.onSuccess(bundle);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed.", e);
}
}
@Override public void onError() {
try {
callback.onError(Constants.STATUS_INTERNAL_ERROR);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed.", e);
}
}
};
PersonalizationService.this.renderContent(
slotInfo, bidIds, odpContext, wrappedCallback);
} else {
throw new IllegalArgumentException("Invalid op code: " + operationCode);
}
}
}
}