/* * 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.pm; import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IInterface; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.Log; import android.util.Slog; import com.android.internal.content.PackageMonitor; import com.android.server.SystemService; import java.util.ArrayList; import java.util.List; /** * Service that manages requests and callbacks for launchers that support * managed profiles. */ public class LauncherAppsService extends SystemService { private final LauncherAppsImpl mLauncherAppsImpl; public LauncherAppsService(Context context) { super(context); mLauncherAppsImpl = new LauncherAppsImpl(context); } @Override public void onStart() { publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl); } class LauncherAppsImpl extends ILauncherApps.Stub { private static final boolean DEBUG = false; private static final String TAG = "LauncherAppsService"; private final Context mContext; private final PackageManager mPm; private final UserManager mUm; private final PackageCallbackList<IOnAppsChangedListener> mListeners = new PackageCallbackList<IOnAppsChangedListener>(); private MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); public LauncherAppsImpl(Context context) { mContext = context; mPm = mContext.getPackageManager(); mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); } /* * @see android.content.pm.ILauncherApps#addOnAppsChangedListener( * android.content.pm.IOnAppsChangedListener) */ @Override public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException { synchronized (mListeners) { if (DEBUG) { Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle()); } if (mListeners.getRegisteredCallbackCount() == 0) { if (DEBUG) { Log.d(TAG, "Starting package monitoring"); } startWatchingPackageBroadcasts(); } mListeners.unregister(listener); mListeners.register(listener, Binder.getCallingUserHandle()); } } /* * @see android.content.pm.ILauncherApps#removeOnAppsChangedListener( * android.content.pm.IOnAppsChangedListener) */ @Override public void removeOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException { synchronized (mListeners) { if (DEBUG) { Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle()); } mListeners.unregister(listener); if (mListeners.getRegisteredCallbackCount() == 0) { stopWatchingPackageBroadcasts(); } } } /** * Register a receiver to watch for package broadcasts */ private void startWatchingPackageBroadcasts() { mPackageMonitor.register(mContext, null, UserHandle.ALL, true); } /** * Unregister package broadcast receiver */ private void stopWatchingPackageBroadcasts() { if (DEBUG) { Log.d(TAG, "Stopped watching for packages"); } mPackageMonitor.unregister(); } void checkCallbackCount() { synchronized (mListeners) { if (DEBUG) { Log.d(TAG, "Callback count = " + mListeners.getRegisteredCallbackCount()); } if (mListeners.getRegisteredCallbackCount() == 0) { stopWatchingPackageBroadcasts(); } } } /** * Checks if the caller is in the same group as the userToCheck. */ private void ensureInUserProfiles(UserHandle userToCheck, String message) { final int callingUserId = UserHandle.getCallingUserId(); final int targetUserId = userToCheck.getIdentifier(); if (targetUserId == callingUserId) return; long ident = Binder.clearCallingIdentity(); try { UserInfo callingUserInfo = mUm.getUserInfo(callingUserId); UserInfo targetUserInfo = mUm.getUserInfo(targetUserId); if (targetUserInfo == null || targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID || targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) { throw new SecurityException(message); } } finally { Binder.restoreCallingIdentity(ident); } } /** * Checks if the user is enabled. */ private boolean isUserEnabled(UserHandle user) { long ident = Binder.clearCallingIdentity(); try { UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier()); return targetUserInfo != null && targetUserInfo.isEnabled(); } finally { Binder.restoreCallingIdentity(ident); } } @Override public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user) throws RemoteException { ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user); if (!isUserEnabled(user)) { return new ArrayList<ResolveInfo>(); } final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); mainIntent.setPackage(packageName); long ident = Binder.clearCallingIdentity(); try { List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0 /* flags */, user.getIdentifier()); return apps; } finally { Binder.restoreCallingIdentity(ident); } } @Override public ResolveInfo resolveActivity(Intent intent, UserHandle user) throws RemoteException { ensureInUserProfiles(user, "Cannot resolve activity for unrelated profile " + user); if (!isUserEnabled(user)) { return null; } long ident = Binder.clearCallingIdentity(); try { ResolveInfo app = mPm.resolveActivityAsUser(intent, 0, user.getIdentifier()); return app; } finally { Binder.restoreCallingIdentity(ident); } } @Override public boolean isPackageEnabled(String packageName, UserHandle user) throws RemoteException { ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user); if (!isUserEnabled(user)) { return false; } long ident = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); PackageInfo info = pm.getPackageInfo(packageName, 0, user.getIdentifier()); return info != null && info.applicationInfo.enabled; } finally { Binder.restoreCallingIdentity(ident); } } @Override public boolean isActivityEnabled(ComponentName component, UserHandle user) throws RemoteException { ensureInUserProfiles(user, "Cannot check component for unrelated profile " + user); if (!isUserEnabled(user)) { return false; } long ident = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); ActivityInfo info = pm.getActivityInfo(component, 0, user.getIdentifier()); return info != null; } finally { Binder.restoreCallingIdentity(ident); } } @Override public void startActivityAsUser(ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user); if (!isUserEnabled(user)) { throw new IllegalStateException("Cannot start activity for disabled profile " + user); } Intent launchIntent = new Intent(Intent.ACTION_MAIN); launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); launchIntent.setSourceBounds(sourceBounds); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); launchIntent.setPackage(component.getPackageName()); long ident = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); ActivityInfo info = pm.getActivityInfo(component, 0, user.getIdentifier()); if (!info.exported) { throw new SecurityException("Cannot launch non-exported components " + component); } // Check that the component actually has Intent.CATEGORY_LAUCNCHER // as calling startActivityAsUser ignores the category and just // resolves based on the component if present. List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(launchIntent, 0 /* flags */, user.getIdentifier()); final int size = apps.size(); for (int i = 0; i < size; ++i) { ActivityInfo activityInfo = apps.get(i).activityInfo; if (activityInfo.packageName.equals(component.getPackageName()) && activityInfo.name.equals(component.getClassName())) { // Found an activity with category launcher that matches // this component so ok to launch. launchIntent.setComponent(component); mContext.startActivityAsUser(launchIntent, opts, user); return; } } throw new SecurityException("Attempt to launch activity without " + " category Intent.CATEGORY_LAUNCHER " + component); } finally { Binder.restoreCallingIdentity(ident); } } @Override public void showAppDetailsAsUser(ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { ensureInUserProfiles(user, "Cannot show app details for unrelated profile " + user); if (!isUserEnabled(user)) { throw new IllegalStateException("Cannot show app details for disabled profile " + user); } long ident = Binder.clearCallingIdentity(); try { String packageName = component.getPackageName(); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", packageName, null)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.setSourceBounds(sourceBounds); mContext.startActivityAsUser(intent, opts, user); } finally { Binder.restoreCallingIdentity(ident); } } private class MyPackageMonitor extends PackageMonitor { /** Checks if user is a profile of or same as listeningUser. * and the user is enabled. */ private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, String debugMsg) { if (user.getIdentifier() == listeningUser.getIdentifier()) { if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg); return true; } long ident = Binder.clearCallingIdentity(); try { UserInfo userInfo = mUm.getUserInfo(user.getIdentifier()); UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier()); if (userInfo == null || listeningUserInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID || userInfo.profileGroupId != listeningUserInfo.profileGroupId || !userInfo.isEnabled()) { if (DEBUG) { Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":" + debugMsg); } return false; } else { if (DEBUG) { Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":" + debugMsg); } return true; } } finally { Binder.restoreCallingIdentity(ident); } } @Override public void onPackageAdded(String packageName, int uid) { UserHandle user = new UserHandle(getChangingUserId()); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue; try { listener.onPackageAdded(user, packageName); } catch (RemoteException re) { Slog.d(TAG, "Callback failed ", re); } } mListeners.finishBroadcast(); super.onPackageAdded(packageName, uid); } @Override public void onPackageRemoved(String packageName, int uid) { UserHandle user = new UserHandle(getChangingUserId()); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue; try { listener.onPackageRemoved(user, packageName); } catch (RemoteException re) { Slog.d(TAG, "Callback failed ", re); } } mListeners.finishBroadcast(); super.onPackageRemoved(packageName, uid); } @Override public void onPackageModified(String packageName) { UserHandle user = new UserHandle(getChangingUserId()); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue; try { listener.onPackageChanged(user, packageName); } catch (RemoteException re) { Slog.d(TAG, "Callback failed ", re); } } mListeners.finishBroadcast(); super.onPackageModified(packageName); } @Override public void onPackagesAvailable(String[] packages) { UserHandle user = new UserHandle(getChangingUserId()); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue; try { listener.onPackagesAvailable(user, packages, isReplacing()); } catch (RemoteException re) { Slog.d(TAG, "Callback failed ", re); } } mListeners.finishBroadcast(); super.onPackagesAvailable(packages); } @Override public void onPackagesUnavailable(String[] packages) { UserHandle user = new UserHandle(getChangingUserId()); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue; try { listener.onPackagesUnavailable(user, packages, isReplacing()); } catch (RemoteException re) { Slog.d(TAG, "Callback failed ", re); } } mListeners.finishBroadcast(); super.onPackagesUnavailable(packages); } } class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> { @Override public void onCallbackDied(T callback, Object cookie) { checkCallbackCount(); } } } }