Merge "[8/8] Use PresenceController and AppController in CdmService"

This commit is contained in:
TreeHugger Robot
2022-02-10 09:58:21 +00:00
committed by Android (Google) Code Review
7 changed files with 346 additions and 857 deletions

View File

@@ -207,6 +207,18 @@ public final class AssociationInfo implements Parcelable {
return macAddress.equals(mDeviceMacAddress);
}
/**
* Utility method to be used by CdmService only.
*
* @return whether CdmService should bind the companion application that "owns" this association
* when the device is present.
*
* @hide
*/
public boolean shouldBindWhenPresent() {
return mNotifyOnDeviceNearby || mSelfManaged;
}
/** @hide */
public @NonNull String toShortString() {
final StringBuilder sb = new StringBuilder();

View File

@@ -16,7 +16,9 @@
package com.android.server.companion;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;
import static java.util.concurrent.TimeUnit.DAYS;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -37,17 +39,16 @@ import com.android.server.LocalServices;
*/
public class AssociationCleanUpService extends JobService {
private static final int JOB_ID = AssociationCleanUpService.class.hashCode();
private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day
private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService(
CompanionDeviceManagerServiceInternal.class);
private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
@Override
public boolean onStartJob(final JobParameters params) {
Slog.i(LOG_TAG, "Execute the Association CleanUp job");
Slog.i(TAG, "Execute the Association CleanUp job");
// Special policy for APP_STREAMING role that need to revoke associations if the device
// does not connect for 3 months.
AsyncTask.execute(() -> {
mCdmServiceInternal.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
jobFinished(params, false);
});
return true;
@@ -55,7 +56,7 @@ public class AssociationCleanUpService extends JobService {
@Override
public boolean onStopJob(final JobParameters params) {
Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId()
Slog.i(TAG, "Association cleanup job stopped; id=" + params.getJobId()
+ ", reason="
+ JobParameters.getInternalReasonCodeDescription(
params.getInternalStopReasonCode()));
@@ -63,7 +64,7 @@ public class AssociationCleanUpService extends JobService {
}
static void schedule(Context context) {
Slog.i(LOG_TAG, "Scheduling the Association Cleanup job");
Slog.i(TAG, "Scheduling the Association Cleanup job");
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
final JobInfo job = new JobInfo.Builder(JOB_ID,
new ComponentName(context, AssociationCleanUpService.class))

View File

@@ -1,238 +0,0 @@
/*
* Copyright (C) 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 com.android.server.companion;
import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE;
import static android.content.Context.BIND_IMPORTANT;
import static com.android.internal.util.CollectionUtils.filter;
import android.annotation.NonNull;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceService;
import android.companion.ICompanionDeviceService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.infra.PerUser;
import com.android.internal.infra.ServiceConnector;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This class creates/removes {@link ServiceConnector}s between {@link CompanionDeviceService} and
* the companion apps. The controller also will notify the companion apps with device status.
*/
public class CompanionDevicePresenceController {
private static final String LOG_TAG = "CompanionDevicePresenceController";
PerUser<ArrayMap<String, List<BoundService>>> mBoundServices;
private static final String META_DATA_KEY_PRIMARY = "android.companion.primary";
private final CompanionDeviceManagerService mService;
public CompanionDevicePresenceController(CompanionDeviceManagerService service) {
mService = service;
mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() {
@NonNull
@Override
protected ArrayMap<String, List<BoundService>> create(int userId) {
return new ArrayMap<>();
}
};
}
void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) {
for (BoundService boundService : getDeviceListenerServiceConnector(
association, context, handler)) {
if (boundService.mIsPrimary) {
Slog.i(LOG_TAG,
"Sending onDeviceAppeared to " + association.getPackageName() + ")");
boundService.mServiceConnector.run(
service -> service.onDeviceAppeared(association));
} else {
Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName);
boundService.mServiceConnector.connect();
}
}
}
void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) {
for (BoundService boundService : getDeviceListenerServiceConnector(
association, context, handler)) {
if (boundService.mIsPrimary) {
Slog.i(LOG_TAG,
"Sending onDeviceDisappeared to " + association.getPackageName() + ")");
boundService.mServiceConnector.run(service ->
service.onDeviceDisappeared(association));
}
}
}
void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association,
Context context, Handler handler) {
for (BoundService boundService : getDeviceListenerServiceConnector(
association, context, handler)) {
if (boundService.mIsPrimary) {
Slog.i(LOG_TAG,
"Sending onDeviceDisappeared to " + association.getPackageName() + ")");
boundService.mServiceConnector.post(
service -> {
service.onDeviceDisappeared(association);
}).thenRun(() -> unbindDevicePresenceListener(
association.getPackageName(), association.getUserId()));
}
}
}
void unbindDevicePresenceListener(String packageName, int userId) {
List<BoundService> boundServices = mBoundServices.forUser(userId)
.remove(packageName);
if (boundServices != null) {
for (BoundService boundService: boundServices) {
Slog.d(LOG_TAG, "Unbinding the serviceConnector: " + boundService.mComponentName);
boundService.mServiceConnector.unbind();
}
}
}
private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context,
Handler handler) {
return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
a.getPackageName(),
pkg -> createDeviceListenerServiceConnector(a, context, handler));
}
private List<BoundService> createDeviceListenerServiceConnector(AssociationInfo a,
Context context, Handler handler) {
List<ResolveInfo> resolveInfos = context
.getPackageManager()
.queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE),
PackageManager.GET_META_DATA, a.getUserId());
List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
List<BoundService> serviceConnectors = new ArrayList<>();
if (!validatePackageInfo(packageResolveInfos, a)) {
return serviceConnectors;
}
for (ResolveInfo packageResolveInfo : packageResolveInfos) {
boolean isPrimary = (packageResolveInfo.serviceInfo.metaData != null
&& packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY))
|| packageResolveInfos.size() == 1;
ComponentName componentName = packageResolveInfo.serviceInfo.getComponentName();
Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
ServiceConnector<ICompanionDeviceService> serviceConnector =
new ServiceConnector.Impl<ICompanionDeviceService>(context,
new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(
componentName), BIND_IMPORTANT, a.getUserId(),
ICompanionDeviceService.Stub::asInterface) {
@Override
protected long getAutoDisconnectTimeoutMs() {
// Service binding is managed manually based on corresponding device
// being nearby
return -1;
}
@Override
public void binderDied() {
super.binderDied();
if (a.isSelfManaged()) {
mBoundServices.forUser(a.getUserId()).remove(a.getPackageName());
mService.mPresentSelfManagedDevices.remove(a.getId());
} else {
// Re-connect to the service if process gets killed
handler.postDelayed(
this::connect,
CompanionDeviceManagerService
.DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
}
}
};
serviceConnectors.add(new BoundService(componentName, isPrimary, serviceConnector));
}
return serviceConnectors;
}
private boolean validatePackageInfo(List<ResolveInfo> packageResolveInfos,
AssociationInfo association) {
if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) {
Slog.e(LOG_TAG, "Device presence listener package must have at least one and not "
+ "more than five CompanionDeviceService(s) declared. But "
+ association.getPackageName()
+ " has " + packageResolveInfos.size());
return false;
}
int primaryCount = 0;
for (ResolveInfo packageResolveInfo : packageResolveInfos) {
String servicePermission = packageResolveInfo.serviceInfo.permission;
if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) {
Slog.e(LOG_TAG, "Binding CompanionDeviceService must have "
+ BIND_COMPANION_DEVICE_SERVICE + " permission.");
return false;
}
if (packageResolveInfo.serviceInfo.metaData != null
&& packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) {
primaryCount++;
if (primaryCount > 1) {
Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
+ "to be bound but "
+ association.getPackageName() + "has " + primaryCount);
return false;
}
}
}
if (packageResolveInfos.size() > 1 && primaryCount == 0) {
Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
+ "to be bound when declare more than one CompanionDeviceService but "
+ association.getPackageName() + " has " + primaryCount);
return false;
}
if (packageResolveInfos.size() == 1 && primaryCount != 0) {
Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one"
+ " CompanionDeviceService " + "but " + association.getPackageName()
+ " has " + primaryCount);
}
return true;
}
private static class BoundService {
private final ComponentName mComponentName;
private final boolean mIsPrimary;
private final ServiceConnector<ICompanionDeviceService> mServiceConnector;
BoundService(ComponentName componentName,
boolean isPrimary, ServiceConnector<ICompanionDeviceService> serviceConnector) {
this.mComponentName = componentName;
this.mIsPrimary = isPrimary;
this.mServiceConnector = serviceConnector;
}
}
}

View File

@@ -21,7 +21,7 @@ import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;
import android.Manifest;
import android.annotation.NonNull;
@@ -96,7 +96,7 @@ final class PackageUtils {
final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE
.equals(resolveInfo.serviceInfo.permission);
if (!requiresPermission) {
Slog.w(LOG_TAG, "CompanionDeviceService "
Slog.w(TAG, "CompanionDeviceService "
+ service.getComponentName().flattenToShortString() + " must require "
+ "android.permission.BIND_COMPANION_DEVICE_SERVICE");
continue;

View File

@@ -36,6 +36,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Context;
@@ -190,6 +191,19 @@ final class PermissionsUtils {
return checkCallerCanManageCompanionDevice(context);
}
static @Nullable AssociationInfo sanitizeWithCallerChecks(@NonNull Context context,
@Nullable AssociationInfo association) {
if (association == null) return null;
final int userId = association.getUserId();
final String packageName = association.getPackageName();
if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) {
return null;
}
return association;
}
private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
try {
return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;

View File

@@ -19,6 +19,7 @@ package com.android.server.companion;
import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -35,7 +36,6 @@ import java.util.List;
/** Utility methods for accessing {@link RoleManager} APIs. */
@SuppressLint("LongLogTag")
final class RolesUtils {
private static final String TAG = CompanionDeviceManagerService.LOG_TAG;
static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
@NonNull String packageName, @NonNull String role) {