blob: 80248e8bfca9288e7af4a66721c02f9b61966b48 [file] [log] [blame]
/*
* 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.nearby.common.servicemonitor;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import com.android.internal.util.Preconditions;
import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener;
import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceProvider;
import java.util.Comparator;
import java.util.List;
/**
* This is mostly borrowed from frameworks CurrentUserServiceSupplier.
* Provides services based on the current active user and version as defined in the service
* manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
* ensure only system (ie, privileged) services are matched. It also handles services that are not
* direct boot aware, and will automatically pick the best service as the user's direct boot state
* changes.
*/
public final class CurrentUserServiceProvider extends BroadcastReceiver implements
ServiceProvider<CurrentUserServiceProvider.BoundServiceInfo> {
private static final String TAG = "CurrentUserServiceProvider";
private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
// This is equal to the hidden Intent.ACTION_USER_SWITCHED.
private static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED";
// This is equal to the hidden Intent.EXTRA_USER_HANDLE.
private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
// This is equal to the hidden UserHandle.USER_NULL.
private static final int USER_NULL = -10000;
private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
if (o1 == o2) {
return 0;
} else if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
}
// ServiceInfos with higher version numbers always win.
return Integer.compare(o1.getVersion(), o2.getVersion());
};
/** Bound service information with version information. */
public static class BoundServiceInfo extends ServiceMonitor.BoundServiceInfo {
private static int parseUid(ResolveInfo resolveInfo) {
return resolveInfo.serviceInfo.applicationInfo.uid;
}
private static int parseVersion(ResolveInfo resolveInfo) {
int version = Integer.MIN_VALUE;
if (resolveInfo.serviceInfo.metaData != null) {
version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
}
return version;
}
private final int mVersion;
protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
this(
action,
parseUid(resolveInfo),
new ComponentName(
resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name),
parseVersion(resolveInfo));
}
protected BoundServiceInfo(String action, int uid, ComponentName componentName,
int version) {
super(action, uid, componentName);
mVersion = version;
}
public int getVersion() {
return mVersion;
}
@Override
public String toString() {
return super.toString() + "@" + mVersion;
}
}
/**
* Creates an instance with the specific service details.
*
* @param context the context the provider is to use
* @param action the action the service must declare in its intent-filter
*/
public static CurrentUserServiceProvider create(Context context, String action) {
return new CurrentUserServiceProvider(context, action);
}
private final Context mContext;
private final Intent mIntent;
private volatile ServiceChangedListener mListener;
private CurrentUserServiceProvider(Context context, String action) {
mContext = context;
mIntent = new Intent(action);
}
@Override
public boolean hasMatchingService() {
int intentQueryFlags =
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY;
List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
mIntent, intentQueryFlags, UserHandle.SYSTEM);
return !resolveInfos.isEmpty();
}
@Override
public void register(ServiceChangedListener listener) {
Preconditions.checkState(mListener == null);
mListener = listener;
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverForAllUsers(this, intentFilter, null,
ForegroundThread.getHandler());
}
@Override
public void unregister() {
Preconditions.checkArgument(mListener != null);
mListener = null;
mContext.unregisterReceiver(this);
}
@Override
public BoundServiceInfo getServiceInfo() {
BoundServiceInfo bestServiceInfo = null;
// only allow services in the correct direct boot state to match
int intentQueryFlags = MATCH_DIRECT_BOOT_AUTO | GET_META_DATA | MATCH_SYSTEM_ONLY;
List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
mIntent, intentQueryFlags, UserHandle.of(ActivityManager.getCurrentUser()));
for (ResolveInfo resolveInfo : resolveInfos) {
BoundServiceInfo serviceInfo =
new BoundServiceInfo(mIntent.getAction(), resolveInfo);
if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
bestServiceInfo = serviceInfo;
}
}
return bestServiceInfo;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
return;
}
int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL);
if (userId == USER_NULL) {
return;
}
ServiceChangedListener listener = mListener;
if (listener == null) {
return;
}
switch (action) {
case ACTION_USER_SWITCHED:
listener.onServiceChanged();
break;
case Intent.ACTION_USER_UNLOCKED:
// user unlocked implies direct boot mode may have changed
if (userId == ActivityManager.getCurrentUser()) {
listener.onServiceChanged();
}
break;
default:
break;
}
}
}