1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.nearby.common.servicemonitor; 18 19 import static android.content.pm.PackageManager.GET_META_DATA; 20 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; 21 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; 22 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 23 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; 24 25 import android.app.ActivityManager; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.ResolveInfo; 32 import android.os.UserHandle; 33 34 import com.android.internal.util.Preconditions; 35 import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener; 36 import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceProvider; 37 38 import java.util.Comparator; 39 import java.util.List; 40 41 /** 42 * This is mostly borrowed from frameworks CurrentUserServiceSupplier. 43 * Provides services based on the current active user and version as defined in the service 44 * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to 45 * ensure only system (ie, privileged) services are matched. It also handles services that are not 46 * direct boot aware, and will automatically pick the best service as the user's direct boot state 47 * changes. 48 */ 49 public final class CurrentUserServiceProvider extends BroadcastReceiver implements 50 ServiceProvider<CurrentUserServiceProvider.BoundServiceInfo> { 51 52 private static final String TAG = "CurrentUserServiceProvider"; 53 54 private static final String EXTRA_SERVICE_VERSION = "serviceVersion"; 55 56 // This is equal to the hidden Intent.ACTION_USER_SWITCHED. 57 private static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED"; 58 // This is equal to the hidden Intent.EXTRA_USER_HANDLE. 59 private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle"; 60 // This is equal to the hidden UserHandle.USER_NULL. 61 private static final int USER_NULL = -10000; 62 63 private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> { 64 if (o1 == o2) { 65 return 0; 66 } else if (o1 == null) { 67 return -1; 68 } else if (o2 == null) { 69 return 1; 70 } 71 72 // ServiceInfos with higher version numbers always win. 73 return Integer.compare(o1.getVersion(), o2.getVersion()); 74 }; 75 76 /** Bound service information with version information. */ 77 public static class BoundServiceInfo extends ServiceMonitor.BoundServiceInfo { 78 parseUid(ResolveInfo resolveInfo)79 private static int parseUid(ResolveInfo resolveInfo) { 80 return resolveInfo.serviceInfo.applicationInfo.uid; 81 } 82 parseVersion(ResolveInfo resolveInfo)83 private static int parseVersion(ResolveInfo resolveInfo) { 84 int version = Integer.MIN_VALUE; 85 if (resolveInfo.serviceInfo.metaData != null) { 86 version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version); 87 } 88 return version; 89 } 90 91 private final int mVersion; 92 BoundServiceInfo(String action, ResolveInfo resolveInfo)93 protected BoundServiceInfo(String action, ResolveInfo resolveInfo) { 94 this( 95 action, 96 parseUid(resolveInfo), 97 new ComponentName( 98 resolveInfo.serviceInfo.packageName, 99 resolveInfo.serviceInfo.name), 100 parseVersion(resolveInfo)); 101 } 102 BoundServiceInfo(String action, int uid, ComponentName componentName, int version)103 protected BoundServiceInfo(String action, int uid, ComponentName componentName, 104 int version) { 105 super(action, uid, componentName); 106 mVersion = version; 107 } 108 getVersion()109 public int getVersion() { 110 return mVersion; 111 } 112 113 @Override toString()114 public String toString() { 115 return super.toString() + "@" + mVersion; 116 } 117 } 118 119 /** 120 * Creates an instance with the specific service details. 121 * 122 * @param context the context the provider is to use 123 * @param action the action the service must declare in its intent-filter 124 */ create(Context context, String action)125 public static CurrentUserServiceProvider create(Context context, String action) { 126 return new CurrentUserServiceProvider(context, action); 127 } 128 129 private final Context mContext; 130 private final Intent mIntent; 131 private volatile ServiceChangedListener mListener; 132 CurrentUserServiceProvider(Context context, String action)133 private CurrentUserServiceProvider(Context context, String action) { 134 mContext = context; 135 mIntent = new Intent(action); 136 } 137 138 @Override hasMatchingService()139 public boolean hasMatchingService() { 140 int intentQueryFlags = 141 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY; 142 List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( 143 mIntent, intentQueryFlags, UserHandle.SYSTEM); 144 return !resolveInfos.isEmpty(); 145 } 146 147 @Override register(ServiceChangedListener listener)148 public void register(ServiceChangedListener listener) { 149 Preconditions.checkState(mListener == null); 150 151 mListener = listener; 152 153 IntentFilter intentFilter = new IntentFilter(); 154 intentFilter.addAction(ACTION_USER_SWITCHED); 155 intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); 156 mContext.registerReceiverForAllUsers(this, intentFilter, null, 157 ForegroundThread.getHandler()); 158 } 159 160 @Override unregister()161 public void unregister() { 162 Preconditions.checkArgument(mListener != null); 163 164 mListener = null; 165 mContext.unregisterReceiver(this); 166 } 167 168 @Override getServiceInfo()169 public BoundServiceInfo getServiceInfo() { 170 BoundServiceInfo bestServiceInfo = null; 171 172 // only allow services in the correct direct boot state to match 173 int intentQueryFlags = MATCH_DIRECT_BOOT_AUTO | GET_META_DATA | MATCH_SYSTEM_ONLY; 174 List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( 175 mIntent, intentQueryFlags, UserHandle.of(ActivityManager.getCurrentUser())); 176 for (ResolveInfo resolveInfo : resolveInfos) { 177 BoundServiceInfo serviceInfo = 178 new BoundServiceInfo(mIntent.getAction(), resolveInfo); 179 180 if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) { 181 bestServiceInfo = serviceInfo; 182 } 183 } 184 185 return bestServiceInfo; 186 } 187 188 @Override onReceive(Context context, Intent intent)189 public void onReceive(Context context, Intent intent) { 190 String action = intent.getAction(); 191 if (action == null) { 192 return; 193 } 194 int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL); 195 if (userId == USER_NULL) { 196 return; 197 } 198 ServiceChangedListener listener = mListener; 199 if (listener == null) { 200 return; 201 } 202 203 switch (action) { 204 case ACTION_USER_SWITCHED: 205 listener.onServiceChanged(); 206 break; 207 case Intent.ACTION_USER_UNLOCKED: 208 // user unlocked implies direct boot mode may have changed 209 if (userId == ActivityManager.getCurrentUser()) { 210 listener.onServiceChanged(); 211 } 212 break; 213 default: 214 break; 215 } 216 } 217 } 218