UnusedAppRestrictionsBackportServiceConnection.java
/*
* Copyright 2021 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 androidx.core.content;
import static androidx.core.content.PackageManagerCompat.getPermissionRevocationVerifierApp;
import static androidx.core.content.UnusedAppRestrictionsBackportService.ACTION_UNUSED_APP_RESTRICTIONS_BACKPORT_CONNECTION;
import static androidx.core.content.UnusedAppRestrictionsConstants.API_30_BACKPORT;
import static androidx.core.content.UnusedAppRestrictionsConstants.DISABLED;
import static androidx.core.content.UnusedAppRestrictionsConstants.ERROR;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.core.app.unusedapprestrictions.IUnusedAppRestrictionsBackportCallback;
import androidx.core.app.unusedapprestrictions.IUnusedAppRestrictionsBackportService;
/**
* {@link ServiceConnection} to use while binding to a
* {@link IUnusedAppRestrictionsBackportService}.
*
* The lifecycle of this object is as follows:
* <ul>
* <li>Call {@link #connectAndFetchResult(ResolvableFuture)}}
* <li>This method binds the service and fetches the data from it
* <li>It is recommended that the caller disconnects the connection after serving the request,
* and creates a new connection if a new request is necessary
* </ul>
*
* The data flow is as follows:
* <ol>
* <li>3P app calls {@link PackageManagerCompat#getUnusedAppRestrictionsStatus(Context)}
* <li>Jetpack calls {@link #connectAndFetchResult(ResolvableFuture)}
* <li>This method triggers binding of the {@link UnusedAppRestrictionsBackportService}.
* Once the service is bound, isPermissionRevocationEnabledForApp is called with a
* {@link UnusedAppRestrictionsBackportCallback}
* <li>This callback is defined in Jetpack, which maps the results of
* isPermissionRevocationEnabledForApp to an
* {@link PackageManagerCompat.UnusedAppRestrictionsStatus} and sets it as the
* {@link ResolvableFuture}'s value
* <li>The 3P app attaches a listener to the returned {@link ResolvableFuture} that utilizes
* the calculated UnusedAppRestrictionsStatus
* </ol>
*/
class UnusedAppRestrictionsBackportServiceConnection implements ServiceConnection {
@VisibleForTesting @Nullable IUnusedAppRestrictionsBackportService
mUnusedAppRestrictionsService = null;
@NonNull ResolvableFuture<Integer> mResultFuture;
private final Context mContext;
private boolean mHasBoundService = false;
UnusedAppRestrictionsBackportServiceConnection(@NonNull Context context) {
mContext = context;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mUnusedAppRestrictionsService =
IUnusedAppRestrictionsBackportService.Stub.asInterface(service);
try {
mUnusedAppRestrictionsService.isPermissionRevocationEnabledForApp(
getBackportCallback());
} catch (RemoteException e) {
// Unable to call bound service's isPermissionRevocationEnabledForApp().
mResultFuture.set(ERROR);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mUnusedAppRestrictionsService = null;
}
public void connectAndFetchResult(@NonNull ResolvableFuture<Integer> resultFuture) {
if (mHasBoundService) {
throw new IllegalStateException("Each UnusedAppRestrictionsBackportServiceConnection "
+ "can only be bound once.");
}
mHasBoundService = true;
mResultFuture = resultFuture;
Intent intent = new Intent(ACTION_UNUSED_APP_RESTRICTIONS_BACKPORT_CONNECTION)
.setPackage(getPermissionRevocationVerifierApp(
mContext.getPackageManager()));
mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
}
public void disconnectFromService() {
if (!mHasBoundService) {
throw new IllegalStateException("bindService must be called before unbind");
}
mHasBoundService = false;
mContext.unbindService(this);
}
private IUnusedAppRestrictionsBackportCallback getBackportCallback() {
return new IUnusedAppRestrictionsBackportCallback.Stub() {
@Override
public void onIsPermissionRevocationEnabledForAppResult(boolean success,
boolean isEnabled) throws RemoteException {
if (success) {
if (isEnabled) {
mResultFuture.set(API_30_BACKPORT);
} else {
mResultFuture.set(DISABLED);
}
} else {
// Unable to retrieve the permission revocation setting
mResultFuture.set(ERROR);
Log.e(PackageManagerCompat.LOG_TAG, "Unable to retrieve the permission "
+ "revocation setting from the backport");
}
}
};
}
}