| /** |
| * Copyright (c) 2014, 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.notification; |
| |
| import android.app.ActivityManager; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.ServiceConnection; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| import android.content.pm.UserInfo; |
| import android.database.ContentObserver; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.IInterface; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.provider.Settings; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| |
| import com.android.server.notification.NotificationManagerService.DumpFilter; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * Manages the lifecycle of application-provided services bound by system server. |
| * |
| * Services managed by this helper must have: |
| * - An associated system settings value with a list of enabled component names. |
| * - A well-known action for services to use in their intent-filter. |
| * - A system permission for services to require in order to ensure system has exclusive binding. |
| * - A settings page for user configuration of enabled services, and associated intent action. |
| * - A remote interface definition (aidl) provided by the service used for communication. |
| */ |
| abstract public class ManagedServices { |
| protected final String TAG = getClass().getSimpleName(); |
| protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
| |
| protected static final String ENABLED_SERVICES_SEPARATOR = ":"; |
| |
| protected final Context mContext; |
| protected final Object mMutex; |
| private final UserProfiles mUserProfiles; |
| private final SettingsObserver mSettingsObserver; |
| private final Config mConfig; |
| private ArraySet<String> mRestored; |
| |
| // contains connections to all connected services, including app services |
| // and system services |
| protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>(); |
| // things that will be put into mServices as soon as they're ready |
| private final ArrayList<String> mServicesBinding = new ArrayList<String>(); |
| // lists the component names of all enabled (and therefore potentially connected) |
| // app services for current profiles. |
| private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles |
| = new ArraySet<ComponentName>(); |
| // Just the packages from mEnabledServicesForCurrentProfiles |
| private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>(); |
| // List of packages in restored setting across all mUserProfiles, for quick |
| // filtering upon package updates. |
| private ArraySet<String> mRestoredPackages = new ArraySet<>(); |
| // State of current service categories |
| private ArrayMap<String, Boolean> mCategoryEnabled = new ArrayMap<>(); |
| // List of enabled packages that have nevertheless asked not to be run |
| private ArraySet<ComponentName> mSnoozingForCurrentProfiles = new ArraySet<>(); |
| |
| |
| // Kept to de-dupe user change events (experienced after boot, when we receive a settings and a |
| // user change). |
| private int[] mLastSeenProfileIds; |
| |
| private final BroadcastReceiver mRestoreReceiver; |
| |
| public ManagedServices(Context context, Handler handler, Object mutex, |
| UserProfiles userProfiles) { |
| mContext = context; |
| mMutex = mutex; |
| mUserProfiles = userProfiles; |
| mConfig = getConfig(); |
| mSettingsObserver = new SettingsObserver(handler); |
| |
| mRestoreReceiver = new SettingRestoredReceiver(); |
| IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED); |
| context.registerReceiver(mRestoreReceiver, filter); |
| rebuildRestoredPackages(); |
| } |
| |
| class SettingRestoredReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) { |
| String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); |
| if (Objects.equals(element, mConfig.secureSettingName)) { |
| String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE); |
| String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE); |
| settingRestored(element, prevValue, newValue, getSendingUserId()); |
| } |
| } |
| } |
| } |
| |
| abstract protected Config getConfig(); |
| |
| private String getCaption() { |
| return mConfig.caption; |
| } |
| |
| abstract protected IInterface asInterface(IBinder binder); |
| |
| abstract protected boolean checkType(IInterface service); |
| |
| abstract protected void onServiceAdded(ManagedServiceInfo info); |
| |
| protected void onServiceRemovedLocked(ManagedServiceInfo removed) { } |
| |
| private ManagedServiceInfo newServiceInfo(IInterface service, |
| ComponentName component, int userid, boolean isSystem, ServiceConnection connection, |
| int targetSdkVersion) { |
| return new ManagedServiceInfo(service, component, userid, isSystem, connection, |
| targetSdkVersion); |
| } |
| |
| public void onBootPhaseAppsCanStart() { |
| mSettingsObserver.observe(); |
| } |
| |
| public void dump(PrintWriter pw, DumpFilter filter) { |
| pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size() |
| + ") enabled for current profiles:"); |
| for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) { |
| if (filter != null && !filter.matches(cmpt)) continue; |
| pw.println(" " + cmpt); |
| } |
| |
| pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):"); |
| for (ManagedServiceInfo info : mServices) { |
| if (filter != null && !filter.matches(info.component)) continue; |
| pw.println(" " + info.component |
| + " (user " + info.userid + "): " + info.service |
| + (info.isSystem?" SYSTEM":"") |
| + (info.isGuest(this)?" GUEST":"")); |
| } |
| |
| pw.println(" Snoozed " + getCaption() + "s (" + |
| mSnoozingForCurrentProfiles.size() + "):"); |
| for (ComponentName name : mSnoozingForCurrentProfiles) { |
| pw.println(" " + name.flattenToShortString()); |
| } |
| } |
| |
| // By convention, restored settings are replicated to another settings |
| // entry, named similarly but with a disambiguation suffix. |
| public static String restoredSettingName(Config config) { |
| return config.secureSettingName + ":restored"; |
| } |
| |
| // The OS has done a restore of this service's saved state. We clone it to the |
| // 'restored' reserve, and then once we return and the actual write to settings is |
| // performed, our observer will do the work of maintaining the restored vs live |
| // settings data. |
| public void settingRestored(String element, String oldValue, String newValue, int userid) { |
| if (DEBUG) Slog.d(TAG, "Restored managed service setting: " + element |
| + " ovalue=" + oldValue + " nvalue=" + newValue); |
| if (mConfig.secureSettingName.equals(element)) { |
| if (element != null) { |
| mRestored = null; |
| Settings.Secure.putStringForUser(mContext.getContentResolver(), |
| restoredSettingName(mConfig), |
| newValue, |
| userid); |
| updateSettingsAccordingToInstalledServices(userid); |
| rebuildRestoredPackages(); |
| } |
| } |
| } |
| |
| public boolean isComponentEnabledForPackage(String pkg) { |
| return mEnabledServicesPackageNames.contains(pkg); |
| } |
| |
| public void onPackagesChanged(boolean queryReplace, String[] pkgList) { |
| if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace |
| + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) |
| + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames); |
| boolean anyServicesInvolved = false; |
| |
| if (pkgList != null && (pkgList.length > 0)) { |
| for (String pkgName : pkgList) { |
| if (mEnabledServicesPackageNames.contains(pkgName) || |
| mRestoredPackages.contains(pkgName)) { |
| anyServicesInvolved = true; |
| } |
| } |
| } |
| |
| if (anyServicesInvolved) { |
| // if we're not replacing a package, clean up orphaned bits |
| if (!queryReplace) { |
| updateSettingsAccordingToInstalledServices(); |
| rebuildRestoredPackages(); |
| } |
| // make sure we're still bound to any of our services who may have just upgraded |
| rebindServices(); |
| } |
| } |
| |
| public void onUserSwitched(int user) { |
| if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user); |
| rebuildRestoredPackages(); |
| if (Arrays.equals(mLastSeenProfileIds, mUserProfiles.getCurrentProfileIds())) { |
| if (DEBUG) Slog.d(TAG, "Current profile IDs didn't change, skipping rebindServices()."); |
| return; |
| } |
| rebindServices(); |
| } |
| |
| public ManagedServiceInfo getServiceFromTokenLocked(IInterface service) { |
| if (service == null) { |
| return null; |
| } |
| final IBinder token = service.asBinder(); |
| final int N = mServices.size(); |
| for (int i = 0; i < N; i++) { |
| final ManagedServiceInfo info = mServices.get(i); |
| if (info.service.asBinder() == token) return info; |
| } |
| return null; |
| } |
| |
| public ManagedServiceInfo checkServiceTokenLocked(IInterface service) { |
| checkNotNull(service); |
| ManagedServiceInfo info = getServiceFromTokenLocked(service); |
| if (info != null) { |
| return info; |
| } |
| throw new SecurityException("Disallowed call from unknown " + getCaption() + ": " |
| + service); |
| } |
| |
| public void unregisterService(IInterface service, int userid) { |
| checkNotNull(service); |
| // no need to check permissions; if your service binder is in the list, |
| // that's proof that you had permission to add it in the first place |
| unregisterServiceImpl(service, userid); |
| } |
| |
| public void registerService(IInterface service, ComponentName component, int userid) { |
| checkNotNull(service); |
| ManagedServiceInfo info = registerServiceImpl(service, component, userid); |
| if (info != null) { |
| onServiceAdded(info); |
| } |
| } |
| |
| /** |
| * Add a service to our callbacks. The lifecycle of this service is managed externally, |
| * but unlike a system service, it should not be considered privledged. |
| * */ |
| public void registerGuestService(ManagedServiceInfo guest) { |
| checkNotNull(guest.service); |
| checkType(guest.service); |
| if (registerServiceImpl(guest) != null) { |
| onServiceAdded(guest); |
| onServiceAdded(guest); |
| } |
| } |
| |
| public void setComponentState(ComponentName component, boolean enabled) { |
| boolean previous = !mSnoozingForCurrentProfiles.contains(component); |
| if (previous == enabled) { |
| return; |
| } |
| |
| if (enabled) { |
| mSnoozingForCurrentProfiles.remove(component); |
| } else { |
| mSnoozingForCurrentProfiles.add(component); |
| } |
| |
| // State changed |
| if (DEBUG) { |
| Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " + |
| component.flattenToShortString()); |
| } |
| |
| final int[] userIds = mUserProfiles.getCurrentProfileIds(); |
| for (int userId : userIds) { |
| if (enabled) { |
| registerServiceLocked(component, userId); |
| } else { |
| unregisterServiceLocked(component, userId); |
| } |
| } |
| } |
| |
| public void setCategoryState(String category, boolean enabled) { |
| synchronized (mMutex) { |
| final Boolean previous = mCategoryEnabled.put(category, enabled); |
| if (!(previous == null || previous != enabled)) { |
| return; |
| } |
| |
| // State changed |
| if (DEBUG) { |
| Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "category " + category); |
| } |
| |
| final int[] userIds = mUserProfiles.getCurrentProfileIds(); |
| for (int userId : userIds) { |
| final Set<ComponentName> componentNames = queryPackageForServices(null, |
| userId, category); |
| |
| // Disallow services not enabled in Settings |
| final ArraySet<ComponentName> userComponents = |
| loadComponentNamesFromSetting(mConfig.secureSettingName, userId); |
| if (userComponents == null) { |
| componentNames.clear(); |
| } else { |
| componentNames.retainAll(userComponents); |
| } |
| |
| if (DEBUG) { |
| Slog.d(TAG, "Components for category " + category + ": " + componentNames); |
| } |
| for (ComponentName c : componentNames) { |
| if (enabled) { |
| registerServiceLocked(c, userId); |
| } else { |
| unregisterServiceLocked(c, userId); |
| } |
| } |
| } |
| |
| } |
| } |
| |
| private void rebuildRestoredPackages() { |
| mRestoredPackages.clear(); |
| mSnoozingForCurrentProfiles.clear(); |
| String settingName = restoredSettingName(mConfig); |
| int[] userIds = mUserProfiles.getCurrentProfileIds(); |
| final int N = userIds.length; |
| for (int i = 0; i < N; ++i) { |
| ArraySet<ComponentName> names = loadComponentNamesFromSetting(settingName, userIds[i]); |
| if (names == null) |
| continue; |
| for (ComponentName name: names) { |
| mRestoredPackages.add(name.getPackageName()); |
| } |
| } |
| } |
| |
| |
| protected ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName, |
| int userId) { |
| final ContentResolver cr = mContext.getContentResolver(); |
| String settingValue = Settings.Secure.getStringForUser( |
| cr, |
| settingName, |
| userId); |
| if (TextUtils.isEmpty(settingValue)) |
| return null; |
| String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR); |
| ArraySet<ComponentName> result = new ArraySet<>(restored.length); |
| for (int i = 0; i < restored.length; i++) { |
| ComponentName value = ComponentName.unflattenFromString(restored[i]); |
| if (null != value) { |
| result.add(value); |
| } |
| } |
| return result; |
| } |
| |
| private void storeComponentsToSetting(Set<ComponentName> components, |
| String settingName, |
| int userId) { |
| String[] componentNames = null; |
| if (null != components) { |
| componentNames = new String[components.size()]; |
| int index = 0; |
| for (ComponentName c: components) { |
| componentNames[index++] = c.flattenToString(); |
| } |
| } |
| final String value = (componentNames == null) ? "" : |
| TextUtils.join(ENABLED_SERVICES_SEPARATOR, componentNames); |
| final ContentResolver cr = mContext.getContentResolver(); |
| Settings.Secure.putStringForUser( |
| cr, |
| settingName, |
| value, |
| userId); |
| } |
| |
| /** |
| * Remove access for any services that no longer exist. |
| */ |
| private void updateSettingsAccordingToInstalledServices() { |
| int[] userIds = mUserProfiles.getCurrentProfileIds(); |
| final int N = userIds.length; |
| for (int i = 0; i < N; ++i) { |
| updateSettingsAccordingToInstalledServices(userIds[i]); |
| } |
| rebuildRestoredPackages(); |
| } |
| |
| protected Set<ComponentName> queryPackageForServices(String packageName, int userId) { |
| return queryPackageForServices(packageName, userId, null); |
| } |
| |
| protected Set<ComponentName> queryPackageForServices(String packageName, int userId, |
| String category) { |
| Set<ComponentName> installed = new ArraySet<>(); |
| final PackageManager pm = mContext.getPackageManager(); |
| Intent queryIntent = new Intent(mConfig.serviceInterface); |
| if (!TextUtils.isEmpty(packageName)) { |
| queryIntent.setPackage(packageName); |
| } |
| if (category != null) { |
| queryIntent.addCategory(category); |
| } |
| List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( |
| queryIntent, |
| PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, |
| userId); |
| if (DEBUG) |
| Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices); |
| if (installedServices != null) { |
| for (int i = 0, count = installedServices.size(); i < count; i++) { |
| ResolveInfo resolveInfo = installedServices.get(i); |
| ServiceInfo info = resolveInfo.serviceInfo; |
| |
| ComponentName component = new ComponentName(info.packageName, info.name); |
| if (!mConfig.bindPermission.equals(info.permission)) { |
| Slog.w(TAG, "Skipping " + getCaption() + " service " |
| + info.packageName + "/" + info.name |
| + ": it does not require the permission " |
| + mConfig.bindPermission); |
| continue; |
| } |
| installed.add(component); |
| } |
| } |
| return installed; |
| } |
| |
| private void updateSettingsAccordingToInstalledServices(int userId) { |
| boolean restoredChanged = false; |
| boolean currentChanged = false; |
| Set<ComponentName> restored = |
| loadComponentNamesFromSetting(restoredSettingName(mConfig), userId); |
| Set<ComponentName> current = |
| loadComponentNamesFromSetting(mConfig.secureSettingName, userId); |
| // Load all services for all packages. |
| Set<ComponentName> installed = queryPackageForServices(null, userId); |
| |
| ArraySet<ComponentName> retained = new ArraySet<>(); |
| |
| for (ComponentName component : installed) { |
| if (null != restored) { |
| boolean wasRestored = restored.remove(component); |
| if (wasRestored) { |
| // Freshly installed package has service that was mentioned in restored setting. |
| if (DEBUG) |
| Slog.v(TAG, "Restoring " + component + " for user " + userId); |
| restoredChanged = true; |
| currentChanged = true; |
| retained.add(component); |
| continue; |
| } |
| } |
| |
| if (null != current) { |
| if (current.contains(component)) |
| retained.add(component); |
| } |
| } |
| |
| currentChanged |= ((current == null ? 0 : current.size()) != retained.size()); |
| |
| if (currentChanged) { |
| if (DEBUG) Slog.v(TAG, "List of " + getCaption() + " services was updated " + current); |
| storeComponentsToSetting(retained, mConfig.secureSettingName, userId); |
| } |
| |
| if (restoredChanged) { |
| if (DEBUG) Slog.v(TAG, |
| "List of " + getCaption() + " restored services was updated " + restored); |
| storeComponentsToSetting(restored, restoredSettingName(mConfig), userId); |
| } |
| } |
| |
| /** |
| * Called whenever packages change, the user switches, or the secure setting |
| * is altered. (For example in response to USER_SWITCHED in our broadcast receiver) |
| */ |
| private void rebindServices() { |
| if (DEBUG) Slog.d(TAG, "rebindServices"); |
| final int[] userIds = mUserProfiles.getCurrentProfileIds(); |
| final int nUserIds = userIds.length; |
| |
| final SparseArray<ArraySet<ComponentName>> componentsByUser = new SparseArray<>(); |
| |
| for (int i = 0; i < nUserIds; ++i) { |
| componentsByUser.put(userIds[i], |
| loadComponentNamesFromSetting(mConfig.secureSettingName, userIds[i])); |
| } |
| |
| final ArrayList<ManagedServiceInfo> toRemove = new ArrayList<>(); |
| final SparseArray<ArrayList<ComponentName>> toAdd = new SparseArray<>(); |
| |
| synchronized (mMutex) { |
| // Unbind automatically bound services, retain system services. |
| for (ManagedServiceInfo service : mServices) { |
| if (!service.isSystem && !service.isGuest(this)) { |
| toRemove.add(service); |
| } |
| } |
| |
| final ArraySet<ComponentName> newEnabled = new ArraySet<>(); |
| final ArraySet<String> newPackages = new ArraySet<>(); |
| |
| for (int i = 0; i < nUserIds; ++i) { |
| // decode the list of components |
| final ArraySet<ComponentName> userComponents = componentsByUser.get(userIds[i]); |
| if (null == userComponents) { |
| toAdd.put(userIds[i], new ArrayList<ComponentName>()); |
| continue; |
| } |
| |
| final ArrayList<ComponentName> add = new ArrayList<>(userComponents); |
| |
| // Remove components from disabled categories so that those services aren't run. |
| for (Entry<String, Boolean> e : mCategoryEnabled.entrySet()) { |
| if (!e.getValue()) { |
| Set<ComponentName> c = queryPackageForServices(null, userIds[i], |
| e.getKey()); |
| add.removeAll(c); |
| } |
| } |
| add.removeAll(mSnoozingForCurrentProfiles); |
| |
| toAdd.put(userIds[i], add); |
| |
| newEnabled.addAll(userComponents); |
| |
| for (int j = 0; j < userComponents.size(); j++) { |
| final ComponentName component = userComponents.valueAt(j); |
| newPackages.add(component.getPackageName()); |
| } |
| } |
| mEnabledServicesForCurrentProfiles = newEnabled; |
| mEnabledServicesPackageNames = newPackages; |
| } |
| |
| for (ManagedServiceInfo info : toRemove) { |
| final ComponentName component = info.component; |
| final int oldUser = info.userid; |
| Slog.v(TAG, "disabling " + getCaption() + " for user " |
| + oldUser + ": " + component); |
| unregisterService(component, info.userid); |
| } |
| |
| for (int i = 0; i < nUserIds; ++i) { |
| final ArrayList<ComponentName> add = toAdd.get(userIds[i]); |
| final int N = add.size(); |
| for (int j = 0; j < N; j++) { |
| final ComponentName component = add.get(j); |
| Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": " |
| + component); |
| registerService(component, userIds[i]); |
| } |
| } |
| |
| mLastSeenProfileIds = userIds; |
| } |
| |
| /** |
| * Version of registerService that takes the name of a service component to bind to. |
| */ |
| private void registerService(final ComponentName name, final int userid) { |
| synchronized (mMutex) { |
| registerServiceLocked(name, userid); |
| } |
| } |
| |
| private void registerServiceLocked(final ComponentName name, final int userid) { |
| if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid); |
| |
| final String servicesBindingTag = name.toString() + "/" + userid; |
| if (mServicesBinding.contains(servicesBindingTag)) { |
| // stop registering this thing already! we're working on it |
| return; |
| } |
| mServicesBinding.add(servicesBindingTag); |
| |
| final int N = mServices.size(); |
| for (int i = N - 1; i >= 0; i--) { |
| final ManagedServiceInfo info = mServices.get(i); |
| if (name.equals(info.component) |
| && info.userid == userid) { |
| // cut old connections |
| if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": " |
| + info.service); |
| removeServiceLocked(i); |
| if (info.connection != null) { |
| mContext.unbindService(info.connection); |
| } |
| } |
| } |
| |
| Intent intent = new Intent(mConfig.serviceInterface); |
| intent.setComponent(name); |
| |
| intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel); |
| |
| final PendingIntent pendingIntent = PendingIntent.getActivity( |
| mContext, 0, new Intent(mConfig.settingsAction), 0); |
| intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent); |
| |
| ApplicationInfo appInfo = null; |
| try { |
| appInfo = mContext.getPackageManager().getApplicationInfo( |
| name.getPackageName(), 0); |
| } catch (NameNotFoundException e) { |
| // Ignore if the package doesn't exist we won't be able to bind to the service. |
| } |
| final int targetSdkVersion = |
| appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE; |
| |
| try { |
| if (DEBUG) Slog.v(TAG, "binding: " + intent); |
| ServiceConnection serviceConnection = new ServiceConnection() { |
| IInterface mService; |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder binder) { |
| boolean added = false; |
| ManagedServiceInfo info = null; |
| synchronized (mMutex) { |
| mServicesBinding.remove(servicesBindingTag); |
| try { |
| mService = asInterface(binder); |
| info = newServiceInfo(mService, name, |
| userid, false /*isSystem*/, this, targetSdkVersion); |
| binder.linkToDeath(info, 0); |
| added = mServices.add(info); |
| } catch (RemoteException e) { |
| // already dead |
| } |
| } |
| if (added) { |
| onServiceAdded(info); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| Slog.v(TAG, getCaption() + " connection lost: " + name); |
| } |
| }; |
| if (!mContext.bindServiceAsUser(intent, |
| serviceConnection, |
| Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, |
| new UserHandle(userid))) { |
| mServicesBinding.remove(servicesBindingTag); |
| Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent); |
| return; |
| } |
| } catch (SecurityException ex) { |
| Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex); |
| return; |
| } |
| } |
| |
| /** |
| * Remove a service for the given user by ComponentName |
| */ |
| private void unregisterService(ComponentName name, int userid) { |
| synchronized (mMutex) { |
| unregisterServiceLocked(name, userid); |
| } |
| } |
| |
| private void unregisterServiceLocked(ComponentName name, int userid) { |
| final int N = mServices.size(); |
| for (int i = N - 1; i >= 0; i--) { |
| final ManagedServiceInfo info = mServices.get(i); |
| if (name.equals(info.component) |
| && info.userid == userid) { |
| removeServiceLocked(i); |
| if (info.connection != null) { |
| try { |
| mContext.unbindService(info.connection); |
| } catch (IllegalArgumentException ex) { |
| // something happened to the service: we think we have a connection |
| // but it's bogus. |
| Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Removes a service from the list but does not unbind |
| * |
| * @return the removed service. |
| */ |
| private ManagedServiceInfo removeServiceImpl(IInterface service, final int userid) { |
| if (DEBUG) Slog.d(TAG, "removeServiceImpl service=" + service + " u=" + userid); |
| ManagedServiceInfo serviceInfo = null; |
| synchronized (mMutex) { |
| final int N = mServices.size(); |
| for (int i = N - 1; i >= 0; i--) { |
| final ManagedServiceInfo info = mServices.get(i); |
| if (info.service.asBinder() == service.asBinder() |
| && info.userid == userid) { |
| if (DEBUG) Slog.d(TAG, "Removing active service " + info.component); |
| serviceInfo = removeServiceLocked(i); |
| } |
| } |
| } |
| return serviceInfo; |
| } |
| |
| private ManagedServiceInfo removeServiceLocked(int i) { |
| final ManagedServiceInfo info = mServices.remove(i); |
| onServiceRemovedLocked(info); |
| return info; |
| } |
| |
| private void checkNotNull(IInterface service) { |
| if (service == null) { |
| throw new IllegalArgumentException(getCaption() + " must not be null"); |
| } |
| } |
| |
| private ManagedServiceInfo registerServiceImpl(final IInterface service, |
| final ComponentName component, final int userid) { |
| ManagedServiceInfo info = newServiceInfo(service, component, userid, |
| true /*isSystem*/, null /*connection*/, Build.VERSION_CODES.LOLLIPOP); |
| return registerServiceImpl(info); |
| } |
| |
| private ManagedServiceInfo registerServiceImpl(ManagedServiceInfo info) { |
| synchronized (mMutex) { |
| try { |
| info.service.asBinder().linkToDeath(info, 0); |
| mServices.add(info); |
| return info; |
| } catch (RemoteException e) { |
| // already dead |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Removes a service from the list and unbinds. |
| */ |
| private void unregisterServiceImpl(IInterface service, int userid) { |
| ManagedServiceInfo info = removeServiceImpl(service, userid); |
| if (info != null && info.connection != null && !info.isGuest(this)) { |
| mContext.unbindService(info.connection); |
| } |
| } |
| |
| private class SettingsObserver extends ContentObserver { |
| private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName); |
| |
| private SettingsObserver(Handler handler) { |
| super(handler); |
| } |
| |
| private void observe() { |
| ContentResolver resolver = mContext.getContentResolver(); |
| resolver.registerContentObserver(mSecureSettingsUri, |
| false, this, UserHandle.USER_ALL); |
| update(null); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange, Uri uri) { |
| update(uri); |
| } |
| |
| private void update(Uri uri) { |
| if (uri == null || mSecureSettingsUri.equals(uri)) { |
| if (DEBUG) Slog.d(TAG, "Setting changed: mSecureSettingsUri=" + mSecureSettingsUri + |
| " / uri=" + uri); |
| rebindServices(); |
| rebuildRestoredPackages(); |
| } |
| } |
| } |
| |
| public class ManagedServiceInfo implements IBinder.DeathRecipient { |
| public IInterface service; |
| public ComponentName component; |
| public int userid; |
| public boolean isSystem; |
| public ServiceConnection connection; |
| public int targetSdkVersion; |
| |
| public ManagedServiceInfo(IInterface service, ComponentName component, |
| int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) { |
| this.service = service; |
| this.component = component; |
| this.userid = userid; |
| this.isSystem = isSystem; |
| this.connection = connection; |
| this.targetSdkVersion = targetSdkVersion; |
| } |
| |
| public boolean isGuest(ManagedServices host) { |
| return ManagedServices.this != host; |
| } |
| |
| public ManagedServices getOwner() { |
| return ManagedServices.this; |
| } |
| |
| @Override |
| public String toString() { |
| return new StringBuilder("ManagedServiceInfo[") |
| .append("component=").append(component) |
| .append(",userid=").append(userid) |
| .append(",isSystem=").append(isSystem) |
| .append(",targetSdkVersion=").append(targetSdkVersion) |
| .append(",connection=").append(connection == null ? null : "<connection>") |
| .append(",service=").append(service) |
| .append(']').toString(); |
| } |
| |
| public boolean enabledAndUserMatches(int nid) { |
| if (!isEnabledForCurrentProfiles()) { |
| return false; |
| } |
| if (this.userid == UserHandle.USER_ALL) return true; |
| if (nid == UserHandle.USER_ALL || nid == this.userid) return true; |
| return supportsProfiles() && mUserProfiles.isCurrentProfile(nid); |
| } |
| |
| public boolean supportsProfiles() { |
| return targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP; |
| } |
| |
| @Override |
| public void binderDied() { |
| if (DEBUG) Slog.d(TAG, "binderDied"); |
| // Remove the service, but don't unbind from the service. The system will bring the |
| // service back up, and the onServiceConnected handler will readd the service with the |
| // new binding. If this isn't a bound service, and is just a registered |
| // service, just removing it from the list is all we need to do anyway. |
| removeServiceImpl(this.service, this.userid); |
| } |
| |
| /** convenience method for looking in mEnabledServicesForCurrentProfiles */ |
| public boolean isEnabledForCurrentProfiles() { |
| if (this.isSystem) return true; |
| if (this.connection == null) return false; |
| return mEnabledServicesForCurrentProfiles.contains(this.component); |
| } |
| } |
| |
| /** convenience method for looking in mEnabledServicesForCurrentProfiles */ |
| public boolean isComponentEnabledForCurrentProfiles(ComponentName component) { |
| return mEnabledServicesForCurrentProfiles.contains(component); |
| } |
| |
| public static class UserProfiles { |
| // Profiles of the current user. |
| private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); |
| |
| public void updateCache(Context context) { |
| UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); |
| if (userManager != null) { |
| int currentUserId = ActivityManager.getCurrentUser(); |
| List<UserInfo> profiles = userManager.getProfiles(currentUserId); |
| synchronized (mCurrentProfiles) { |
| mCurrentProfiles.clear(); |
| for (UserInfo user : profiles) { |
| mCurrentProfiles.put(user.id, user); |
| } |
| } |
| } |
| } |
| |
| public int[] getCurrentProfileIds() { |
| synchronized (mCurrentProfiles) { |
| int[] users = new int[mCurrentProfiles.size()]; |
| final int N = mCurrentProfiles.size(); |
| for (int i = 0; i < N; ++i) { |
| users[i] = mCurrentProfiles.keyAt(i); |
| } |
| return users; |
| } |
| } |
| |
| public boolean isCurrentProfile(int userId) { |
| synchronized (mCurrentProfiles) { |
| return mCurrentProfiles.get(userId) != null; |
| } |
| } |
| } |
| |
| protected static class Config { |
| String caption; |
| String serviceInterface; |
| String secureSettingName; |
| String bindPermission; |
| String settingsAction; |
| int clientLabel; |
| } |
| } |