/** * Copyright (C) 2016 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.vr; import android.annotation.NonNull; import android.app.ActivityManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import com.android.internal.content.PackageMonitor; import com.android.server.vr.SettingsObserver.SettingChangeListener; import java.util.Collection; import java.util.List; import java.util.Set; /** * Detects changes in packages, settings, and current users that may affect whether components * implementing a given service can be run. * * @hide */ public class EnabledComponentsObserver implements SettingChangeListener { private static final String TAG = EnabledComponentsObserver.class.getSimpleName(); private static final String ENABLED_SERVICES_SEPARATOR = ":"; public static final int NO_ERROR = 0; public static final int DISABLED = -1; public static final int NOT_INSTALLED = -2; private final Object mLock; private final Context mContext; private final String mSettingName; private final String mServiceName; private final String mServicePermission; private final SparseArray> mInstalledSet = new SparseArray<>(); private final SparseArray> mEnabledSet = new SparseArray<>(); private final Set mEnabledComponentListeners = new ArraySet<>(); /** * Implement this to receive callbacks when relevant changes to the allowed components occur. */ public interface EnabledComponentChangeListener { /** * Called when a change in the allowed components occurs. */ void onEnabledComponentChanged(); } private EnabledComponentsObserver(@NonNull Context context, @NonNull String settingName, @NonNull String servicePermission, @NonNull String serviceName, @NonNull Object lock, @NonNull Collection listeners) { mLock = lock; mContext = context; mSettingName = settingName; mServiceName = serviceName; mServicePermission = servicePermission; mEnabledComponentListeners.addAll(listeners); } /** * Create a EnabledComponentObserver instance. * * @param context the context to query for changes. * @param handler a handler to receive lifecycle events from system services on. * @param settingName the name of a setting to monitor for a list of enabled components. * @param looper a {@link Looper} to use for receiving package callbacks. * @param servicePermission the permission required by the components to be bound. * @param serviceName the intent action implemented by the tracked components. * @param lock a lock object used to guard instance state in all callbacks and method calls. * @return an EnableComponentObserver instance. */ public static EnabledComponentsObserver build(@NonNull Context context, @NonNull Handler handler, @NonNull String settingName, @NonNull Looper looper, @NonNull String servicePermission, @NonNull String serviceName, @NonNull final Object lock, @NonNull Collection listeners) { SettingsObserver s = SettingsObserver.build(context, handler, settingName); final EnabledComponentsObserver o = new EnabledComponentsObserver(context, settingName, servicePermission, serviceName, lock, listeners); PackageMonitor packageMonitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { o.onPackagesChanged(); } @Override public void onPackageDisappeared(String packageName, int reason) { o.onPackagesChanged(); } @Override public void onPackageModified(String packageName) { o.onPackagesChanged(); } @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { o.onPackagesChanged(); return super.onHandleForceStop(intent, packages, uid, doit); } }; packageMonitor.register(context, looper, UserHandle.ALL, true); s.addListener(o); return o; } public void onPackagesChanged() { rebuildAll(); } @Override public void onSettingChanged() { rebuildAll(); } @Override public void onSettingRestored(String prevValue, String newValue, int userId) { rebuildAll(); } public void onUsersChanged() { rebuildAll(); } /** * Rebuild the sets of allowed components for each current user profile. */ public void rebuildAll() { synchronized (mLock) { mInstalledSet.clear(); mEnabledSet.clear(); final int[] userIds = getCurrentProfileIds(); for (int i : userIds) { ArraySet implementingPackages = loadComponentNamesForUser(i); ArraySet packagesFromSettings = loadComponentNamesFromSetting(mSettingName, i); packagesFromSettings.retainAll(implementingPackages); mInstalledSet.put(i, implementingPackages); mEnabledSet.put(i, packagesFromSettings); } } sendSettingChanged(); } /** * Check whether a given component is present and enabled for the given user. * * @param component the component to check. * @param userId the user ID for the component to check. * @return {@code true} if present and enabled. */ public int isValid(ComponentName component, int userId) { synchronized (mLock) { ArraySet installedComponents = mInstalledSet.get(userId); if (installedComponents == null || !installedComponents.contains(component)) { return NOT_INSTALLED; } ArraySet validComponents = mEnabledSet.get(userId); if (validComponents == null || !validComponents.contains(component)) { return DISABLED; } return NO_ERROR; } } /** * Return all VrListenerService components installed for this user. * * @param userId ID of the user to check. * @return a set of {@link ComponentName}s. */ public ArraySet getInstalled(int userId) { synchronized (mLock) { ArraySet ret = mInstalledSet.get(userId); if (ret == null) { return new ArraySet(); } return ret; } } /** * Return all VrListenerService components enabled for this user. * * @param userId ID of the user to check. * @return a set of {@link ComponentName}s. */ public ArraySet getEnabled(int userId) { synchronized (mLock) { ArraySet ret = mEnabledSet.get(userId); if (ret == null) { return new ArraySet(); } return ret; } } private int[] getCurrentProfileIds() { UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); if (userManager == null) { return null; } return userManager.getEnabledProfileIds(ActivityManager.getCurrentUser()); } public static ArraySet loadComponentNames(PackageManager pm, int userId, String serviceName, String permissionName) { ArraySet installed = new ArraySet<>(); Intent queryIntent = new Intent(serviceName); List installedServices = pm.queryIntentServicesAsUser( queryIntent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); 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 (!permissionName.equals(info.permission)) { Slog.w(TAG, "Skipping service " + info.packageName + "/" + info.name + ": it does not require the permission " + permissionName); continue; } installed.add(component); } } return installed; } private ArraySet loadComponentNamesForUser(int userId) { return loadComponentNames(mContext.getPackageManager(), userId, mServiceName, mServicePermission); } private ArraySet loadComponentNamesFromSetting(String settingName, int userId) { final ContentResolver cr = mContext.getContentResolver(); String settingValue = Settings.Secure.getStringForUser( cr, settingName, userId); if (TextUtils.isEmpty(settingValue)) return new ArraySet<>(); String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR); ArraySet 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 sendSettingChanged() { for (EnabledComponentChangeListener l : mEnabledComponentListeners) { l.onEnabledComponentChanged(); } } }