• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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