1 /*
2  * Copyright (C) 2017 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.settingslib.applications;
18 
19 import android.app.ActivityManager;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.pm.ServiceInfo;
29 import android.database.ContentObserver;
30 import android.net.Uri;
31 import android.os.Handler;
32 import android.provider.Settings;
33 import android.util.Slog;
34 
35 import java.util.ArrayList;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.function.Predicate;
39 
40 /**
41  * Class for managing services matching a given intent and requesting a given permission.
42  */
43 public class ServiceListing {
44     private final ContentResolver mContentResolver;
45     private final Context mContext;
46     private final String mTag;
47     private final String mSetting;
48     private final String mIntentAction;
49     private final String mPermission;
50     private final String mNoun;
51     private final boolean mAddDeviceLockedFlags;
52     private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
53     private final List<ServiceInfo> mServices = new ArrayList<>();
54     private final List<Callback> mCallbacks = new ArrayList<>();
55     private final Predicate mValidator;
56 
57     private boolean mListening;
58 
ServiceListing(Context context, String tag, String setting, String intentAction, String permission, String noun, boolean addDeviceLockedFlags, Predicate validator)59     private ServiceListing(Context context, String tag,
60             String setting, String intentAction, String permission, String noun,
61             boolean addDeviceLockedFlags, Predicate validator) {
62         mContentResolver = context.getContentResolver();
63         mContext = context;
64         mTag = tag;
65         mSetting = setting;
66         mIntentAction = intentAction;
67         mPermission = permission;
68         mNoun = noun;
69         mAddDeviceLockedFlags = addDeviceLockedFlags;
70         mValidator = validator;
71     }
72 
addCallback(Callback callback)73     public void addCallback(Callback callback) {
74         mCallbacks.add(callback);
75     }
76 
removeCallback(Callback callback)77     public void removeCallback(Callback callback) {
78         mCallbacks.remove(callback);
79     }
80 
setListening(boolean listening)81     public void setListening(boolean listening) {
82         if (mListening == listening) return;
83         mListening = listening;
84         if (mListening) {
85             // listen for package changes
86             IntentFilter filter = new IntentFilter();
87             filter.addAction(Intent.ACTION_PACKAGE_ADDED);
88             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
89             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
90             filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
91             filter.addDataScheme("package");
92             mContext.registerReceiver(mPackageReceiver, filter);
93             mContentResolver.registerContentObserver(Settings.Secure.getUriFor(mSetting),
94                     false, mSettingsObserver);
95         } else {
96             mContext.unregisterReceiver(mPackageReceiver);
97             mContentResolver.unregisterContentObserver(mSettingsObserver);
98         }
99     }
100 
saveEnabledServices()101     private void saveEnabledServices() {
102         StringBuilder sb = null;
103         for (ComponentName cn : mEnabledServices) {
104             if (sb == null) {
105                 sb = new StringBuilder();
106             } else {
107                 sb.append(':');
108             }
109             sb.append(cn.flattenToString());
110         }
111         Settings.Secure.putString(mContentResolver, mSetting,
112                 sb != null ? sb.toString() : "");
113     }
114 
loadEnabledServices()115     private void loadEnabledServices() {
116         mEnabledServices.clear();
117         final String flat = Settings.Secure.getString(mContentResolver, mSetting);
118         if (flat != null && !"".equals(flat)) {
119             final String[] names = flat.split(":");
120             for (String name : names) {
121                 final ComponentName cn = ComponentName.unflattenFromString(name);
122                 if (cn != null) {
123                     mEnabledServices.add(cn);
124                 }
125             }
126         }
127     }
128 
reload()129     public void reload() {
130         loadEnabledServices();
131         mServices.clear();
132         final int user = ActivityManager.getCurrentUser();
133 
134         int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
135         if (mAddDeviceLockedFlags) {
136             flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
137                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
138         }
139 
140         final PackageManager pmWrapper = mContext.getPackageManager();
141         List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
142                 new Intent(mIntentAction), flags, user);
143         for (ResolveInfo resolveInfo : installedServices) {
144             ServiceInfo info = resolveInfo.serviceInfo;
145 
146             if (!mPermission.equals(info.permission)) {
147                 Slog.w(mTag, "Skipping " + mNoun + " service "
148                         + info.packageName + "/" + info.name
149                         + ": it does not require the permission "
150                         + mPermission);
151                 continue;
152             }
153             if (mValidator != null && !mValidator.test(info)) {
154                 continue;
155             }
156             mServices.add(info);
157         }
158         for (Callback callback : mCallbacks) {
159             callback.onServicesReloaded(mServices);
160         }
161     }
162 
isEnabled(ComponentName cn)163     public boolean isEnabled(ComponentName cn) {
164         return mEnabledServices.contains(cn);
165     }
166 
setEnabled(ComponentName cn, boolean enabled)167     public void setEnabled(ComponentName cn, boolean enabled) {
168         if (enabled) {
169             mEnabledServices.add(cn);
170         } else {
171             mEnabledServices.remove(cn);
172         }
173         saveEnabledServices();
174     }
175 
176     private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
177         @Override
178         public void onChange(boolean selfChange, Uri uri) {
179             reload();
180         }
181     };
182 
183     private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
184         @Override
185         public void onReceive(Context context, Intent intent) {
186             reload();
187         }
188     };
189 
190     public interface Callback {
onServicesReloaded(List<ServiceInfo> services)191         void onServicesReloaded(List<ServiceInfo> services);
192     }
193 
194     public static class Builder {
195         private final Context mContext;
196         private String mTag;
197         private String mSetting;
198         private String mIntentAction;
199         private String mPermission;
200         private String mNoun;
201         private boolean mAddDeviceLockedFlags = false;
202         private Predicate mValidator;
203 
Builder(Context context)204         public Builder(Context context) {
205             mContext = context;
206         }
207 
setTag(String tag)208         public Builder setTag(String tag) {
209             mTag = tag;
210             return this;
211         }
212 
setSetting(String setting)213         public Builder setSetting(String setting) {
214             mSetting = setting;
215             return this;
216         }
217 
setIntentAction(String intentAction)218         public Builder setIntentAction(String intentAction) {
219             mIntentAction = intentAction;
220             return this;
221         }
222 
setPermission(String permission)223         public Builder setPermission(String permission) {
224             mPermission = permission;
225             return this;
226         }
227 
setNoun(String noun)228         public Builder setNoun(String noun) {
229             mNoun = noun;
230             return this;
231         }
232 
setValidator(Predicate<ServiceInfo> validator)233         public Builder setValidator(Predicate<ServiceInfo> validator) {
234             mValidator = validator;
235             return this;
236         }
237 
238         /**
239          * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and
240          * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results
241          * prior to the user unlocking the device for the first time.
242          */
setAddDeviceLockedFlags(boolean addDeviceLockedFlags)243         public Builder setAddDeviceLockedFlags(boolean addDeviceLockedFlags) {
244             mAddDeviceLockedFlags = addDeviceLockedFlags;
245             return this;
246         }
247 
build()248         public ServiceListing build() {
249             return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun,
250                     mAddDeviceLockedFlags, mValidator);
251         }
252     }
253 }
254