blob: eec32144141a4e8be3a35a6df740c7b7b66fdabb [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.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.ondevicepersonalization.aidl.IPrivacyStatusService;
import android.ondevicepersonalization.aidl.IPrivacyStatusServiceCallback;
import android.os.IBinder;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.util.Slog;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* OnDevicePersonalizationPrivacyStatusManager provides system APIs
* for GMSCore to control privacy statuses of ODP users.
* @hide
*/
public class OnDevicePersonalizationPrivacyStatusManager {
public static final String ON_DEVICE_PERSONALIZATION_PRIVACY_STATUS_SERVICE =
"on_device_personalization_privacy_status_service";
private static final String TAG = "OdpPrivacyStatusManager";
private static final String ODP_PRIVACY_STATUS_SERVICE_INTENT =
"android.OnDevicePersonalizationPrivacyStatusService";
private boolean mBound = false;
private IPrivacyStatusService mService = null;
private final Context mContext;
private final CountDownLatch mConnectionLatch = new CountDownLatch(1);
public OnDevicePersonalizationPrivacyStatusManager(@NonNull Context context) {
mContext = context;
}
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
mService = IPrivacyStatusService.Stub.asInterface(binder);
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;
/**
* Modify the user's kid status in ODP from GMSCore.
*
* @param isKidStatusEnabled user's kid status available at GMSCore.
* @param executor the {@link Executor} on which to invoke the callback
* @param receiver This either returns true on success or {@link Exception} on failure.
* @hide
*/
public void setKidStatus(boolean isKidStatusEnabled,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, Exception> receiver) {
try {
bindService(executor);
mService.setKidStatus(isKidStatusEnabled, new IPrivacyStatusServiceCallback.Stub() {
@Override
public void onSuccess() {
executor.execute(() -> receiver.onResult(true));
}
@Override
public void onFailure(int errorCode) {
executor.execute(() -> receiver.onError(
new OnDevicePersonalizationException(errorCode)));
}
});
} catch (InterruptedException | RemoteException e) {
receiver.onError(e);
}
}
private void bindService(@NonNull Executor executor) throws InterruptedException {
if (!mBound) {
Intent intent = new Intent(ODP_PRIVACY_STATUS_SERVICE_INTENT);
ComponentName serviceComponent = resolveService(intent);
if (serviceComponent == null) {
Slog.e(TAG, "Invalid component for ODP privacy status 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.
*
* @return ComponentName of the service. Null if the service is not found.
*/
@Nullable
private ComponentName resolveService(@NonNull Intent intent) {
List<ResolveInfo> services = mContext.getPackageManager().queryIntentServices(intent, 0);
if (services == null || services.isEmpty()) {
Slog.e(TAG, "Failed to find OdpPrivacyStatus service");
return null;
}
for (int i = 0; i < services.size(); i++) {
ServiceInfo serviceInfo = services.get(i).serviceInfo;
if (serviceInfo == null) {
Slog.e(TAG, "Failed to find serviceInfo for OdpPrivacyStatus service.");
return null;
}
// There should only be one matching service inside the given package.
// If there's more than one, return the first one found.
return new ComponentName(serviceInfo.packageName, serviceInfo.name);
}
Slog.e(TAG, "Didn't find any matching OdpPrivacyStatus service.");
return null;
}
}