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