blob: 46aa33fc86cbf6f8b6333958a0de7cdf42b02a64 [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.devicelock;
import static com.android.devicelock.flags.Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS;
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresFeature;
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.text.TextUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* Manager used to interact with the system device lock service.
* The device lock feature is used by special applications ('kiosk apps', downloaded and installed
* by the device lock solution) to lock and unlock a device.
* A typical use case is a financed device, where the financing entity has the capability to lock
* the device in case of a missed payment.
* When a device is locked, only a limited set of interactions with the device is allowed (for
* example, placing emergency calls).
* <p>
* Use {@link android.content.Context#getSystemService(java.lang.String)}
* with {@link Context#DEVICE_LOCK_SERVICE} to create a {@link DeviceLockManager}.
* </p>
*
*/
@SystemService(Context.DEVICE_LOCK_SERVICE)
@RequiresFeature(PackageManager.FEATURE_DEVICE_LOCK)
public final class DeviceLockManager {
private static final String TAG = "DeviceLockManager";
private final IDeviceLockService mService;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "DEVICE_LOCK_ROLE_", value = {
DEVICE_LOCK_ROLE_FINANCING,
})
public @interface DeviceLockRole {}
/**
* Constant representing a financed device role, returned by {@link #getKioskApps}.
*/
public static final int DEVICE_LOCK_ROLE_FINANCING = 0;
/**
* @hide
*/
public DeviceLockManager(Context context, IDeviceLockService service) {
mService = service;
}
/**
* Return the underlying service interface.
* This is used to implement private APIs between the Device Lock Controller and the
* Device Lock System Service.
*
* @hide
*/
@NonNull
public IDeviceLockService getService() {
return mService;
}
/**
* Lock the device.
*
* @param executor the {@link Executor} on which to invoke the callback.
* @param callback this returns either success or an exception.
*/
@RequiresPermission(permission.MANAGE_DEVICE_LOCK_STATE)
public void lockDevice(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, Exception> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
mService.lockDevice(
new IVoidResultCallback.Stub() {
@Override
public void onSuccess() {
executor.execute(() -> callback.onResult(/* result= */ null));
}
@Override
public void onError(ParcelableException parcelableException) {
callback.onError(parcelableException.getException());
}
});
} catch (RemoteException e) {
executor.execute(() -> callback.onError(new RuntimeException(e)));
}
}
/**
* Unlock the device.
*
* @param executor the {@link Executor} on which to invoke the callback.
* @param callback this returns either success or an exception.
*/
@RequiresPermission(permission.MANAGE_DEVICE_LOCK_STATE)
public void unlockDevice(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, Exception> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
mService.unlockDevice(
new IVoidResultCallback.Stub() {
@Override
public void onSuccess() {
executor.execute(() -> callback.onResult(/* result= */ null));
}
@Override
public void onError(ParcelableException parcelableException) {
callback.onError(parcelableException.getException());
}
});
} catch (RemoteException e) {
executor.execute(() -> callback.onError(new RuntimeException(e)));
}
}
/**
* Check if the device is locked or not.
*
* @param executor the {@link Executor} on which to invoke the callback.
* @param callback this returns either the lock status or an exception.
*/
@RequiresPermission(permission.MANAGE_DEVICE_LOCK_STATE)
public void isDeviceLocked(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, Exception> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
mService.isDeviceLocked(
new IIsDeviceLockedCallback.Stub() {
@Override
public void onIsDeviceLocked(boolean locked) {
executor.execute(() -> callback.onResult(locked));
}
@Override
public void onError(ParcelableException parcelableException) {
executor.execute(() ->
callback.onError(parcelableException.getException()));
}
});
} catch (RemoteException e) {
executor.execute(() -> callback.onError(new RuntimeException(e)));
}
}
/**
* Clear device restrictions.
*
* <p>After a device determines that it's part of a program (e.g. financing) by checking in with
* the device lock backend, it will go though a provisioning flow and install a kiosk app.
*
* <p>At this point, the device is "restricted" and the creditor kiosk app is able to lock
* the device. For example, a creditor kiosk app in a financing use case may lock the device
* (using {@link #lockDevice}) if payments are missed and unlock (using {@link #unlockDevice})
* once they are resumed.
*
* <p>The Device Lock solution will also put in place some additional restrictions when a device
* is enrolled in the program, namely:
*
* <ul>
* <li>Disable debugging features
* ({@link android.os.UserManager#DISALLOW_DEBUGGING_FEATURES})
* <li>Disable installing from unknown sources
* ({@link android.os.UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES},
* when configured in the backend)
* <li>Disable outgoing calls
* ({@link android.os.UserManager#DISALLOW_OUTGOING_CALLS},
* when configured in the backend and the device is locked)
* </ul>
*
* <p>Once the program is completed (e.g. the device has been fully paid off), the kiosk app
* can use the {@link #clearDeviceRestrictions} API to lift the above restrictions.
*
* <p>At this point, the kiosk app has relinquished its ability to lock the device.
*
* @param executor the {@link Executor} on which to invoke the callback.
* @param callback this returns either success or an exception.
*/
@RequiresPermission(permission.MANAGE_DEVICE_LOCK_STATE)
@FlaggedApi(FLAG_CLEAR_DEVICE_RESTRICTIONS)
public void clearDeviceRestrictions(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, Exception> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
mService.clearDeviceRestrictions(
new IVoidResultCallback.Stub() {
@Override
public void onSuccess() {
executor.execute(() -> callback.onResult(/* result= */ null));
}
@Override
public void onError(ParcelableException parcelableException) {
callback.onError(parcelableException.getException());
}
}
);
} catch (RemoteException e) {
executor.execute(() -> callback.onError(new RuntimeException(e)));
}
}
/**
* Get the device id.
*
* @param executor the {@link Executor} on which to invoke the callback.
* @param callback this returns either the {@link DeviceId} or an exception.
*/
@RequiresPermission(permission.MANAGE_DEVICE_LOCK_STATE)
public void getDeviceId(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<DeviceId, Exception> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
mService.getDeviceId(
new IGetDeviceIdCallback.Stub() {
@Override
public void onDeviceIdReceived(int type, String id) {
if (TextUtils.isEmpty(id)) {
executor.execute(() -> {
callback.onError(new Exception("Cannot get device id (empty)"));
});
} else {
executor.execute(() -> {
callback.onResult(new DeviceId(type, id));
});
}
}
@Override
public void onError(ParcelableException parcelableException) {
callback.onError(parcelableException.getException());
}
}
);
} catch (RemoteException e) {
executor.execute(() -> callback.onError(new RuntimeException(e)));
}
}
/**
* Get the kiosk app roles and packages.
*
* @param executor the {@link Executor} on which to invoke the callback.
* @param callback this returns either a {@link Map} of device roles/package names,
* or an exception. The Integer in the map represent the device lock role
* (at this moment, the only supported role is
* {@value #DEVICE_LOCK_ROLE_FINANCING}. The String represents tha package
* name of the kiosk app for that role.
*/
@RequiresNoPermission
public void getKioskApps(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Map<Integer, String>, Exception> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
try {
mService.getKioskApps(
new IGetKioskAppsCallback.Stub() {
@Override
public void onKioskAppsReceived(Map kioskApps) {
executor.execute(() -> callback.onResult(kioskApps));
}
@Override
public void onError(ParcelableException parcelableException) {
callback.onError(parcelableException.getException());
}
}
);
} catch (RemoteException e) {
executor.execute(() -> callback.onError(new RuntimeException(e)));
}
}
}