Launcher APIs and broadcasts for managed profiles
UserManager
- Corp badging
- Querying list of managed profiles
Launcher API
- LauncherApps and Service to proxy changes in managed profile
to the launcher in the primary profile
- Querying and launching launchable apps across profiles
Change-Id: Id8f7b4201afdfb5f414d04156d7b81300119289e
diff --git a/Android.mk b/Android.mk
index e1c15470..62338fc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -119,6 +119,8 @@
core/java/android/content/ISyncContext.aidl \
core/java/android/content/ISyncServiceAdapter.aidl \
core/java/android/content/ISyncStatusObserver.aidl \
+ core/java/android/content/pm/ILauncherApps.aidl \
+ core/java/android/content/pm/IOnAppsChangedListener.aidl \
core/java/android/content/pm/IPackageDataObserver.aidl \
core/java/android/content/pm/IPackageDeleteObserver.aidl \
core/java/android/content/pm/IPackageInstallObserver.aidl \
diff --git a/api/current.txt b/api/current.txt
index c6c8e421..d0536b6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6476,6 +6476,7 @@
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
+ field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
field public static final java.lang.String LOCATION_SERVICE = "location";
field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
@@ -7650,6 +7651,32 @@
field public static final android.os.Parcelable.Creator CREATOR;
}
+ public class LauncherActivityInfo {
+ method public int getApplicationFlags();
+ method public android.graphics.drawable.Drawable getBadgedIcon(int);
+ method public android.content.ComponentName getComponentName();
+ method public long getFirstInstallTime();
+ method public android.graphics.drawable.Drawable getIcon(int);
+ method public java.lang.CharSequence getLabel();
+ method public android.os.UserHandle getUser();
+ }
+
+ public class LauncherApps {
+ method public synchronized void addOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener);
+ method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+ method public synchronized void removeOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener);
+ method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
+ method public void startActivityForProfile(android.content.ComponentName, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
+ }
+
+ public static abstract interface LauncherApps.OnAppsChangedListener {
+ method public abstract void onPackageAdded(android.os.UserHandle, java.lang.String);
+ method public abstract void onPackageChanged(android.os.UserHandle, java.lang.String);
+ method public abstract void onPackageRemoved(android.os.UserHandle, java.lang.String);
+ method public abstract void onPackagesAvailable(android.os.UserHandle, java.lang.String[], boolean);
+ method public abstract void onPackagesUnavailable(android.os.UserHandle, java.lang.String[], boolean);
+ }
+
public class PackageInfo implements android.os.Parcelable {
ctor public PackageInfo();
method public int describeContents();
@@ -19898,6 +19925,7 @@
method public int getUserCount();
method public android.os.UserHandle getUserForSerialNumber(long);
method public java.lang.String getUserName();
+ method public java.util.List<android.os.UserHandle> getUserProfiles();
method public android.os.Bundle getUserRestrictions();
method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean isUserAGoat();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7149ab9..c3c406d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -36,7 +36,9 @@
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ILauncherApps;
import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
@@ -605,6 +607,14 @@
}
});
+ registerService(LAUNCHER_APPS_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(LAUNCHER_APPS_SERVICE);
+ ILauncherApps service = ILauncherApps.Stub.asInterface(b);
+ return new LauncherApps(ctx, service);
+ }
+ });
+
registerService(PRINT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 27e526b..a9a2347 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1999,6 +1999,7 @@
BLUETOOTH_SERVICE,
//@hide: SIP_SERVICE,
USB_SERVICE,
+ LAUNCHER_APPS_SERVICE,
//@hide: SERIAL_SERVICE,
INPUT_SERVICE,
DISPLAY_SERVICE,
@@ -2563,6 +2564,16 @@
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.pm.LauncherApps} for querying and monitoring launchable apps across
+ * profiles of a user.
+ *
+ * @see #getSystemService
+ * @see android.content.pm.LauncherApps
+ */
+ public static final String LAUNCHER_APPS_SERVICE = "launcherapps";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.AppOpsManager} for tracking application operations
* on the device.
*
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
new file mode 100644
index 0000000..796b113
--- /dev/null
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -0,0 +1,38 @@
+/**
+ * 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 android.content.pm;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+interface ILauncherApps {
+ void addOnAppsChangedListener(in IOnAppsChangedListener listener);
+ void removeOnAppsChangedListener(in IOnAppsChangedListener listener);
+ List<ResolveInfo> getLauncherActivities(String packageName, in UserHandle user);
+ ResolveInfo resolveActivity(in Intent intent, in UserHandle user);
+ void startActivityAsUser(in ComponentName component, in Rect sourceBounds,
+ in Bundle opts, in UserHandle user);
+}
diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl
new file mode 100644
index 0000000..796b58d
--- /dev/null
+++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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 android.content.pm;
+
+import android.os.UserHandle;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnAppsChangedListener {
+ void onPackageRemoved(in UserHandle user, String packageName);
+ void onPackageAdded(in UserHandle user, String packageName);
+ void onPackageChanged(in UserHandle user, String packageName);
+ void onPackagesAvailable(in UserHandle user, in String[] packageNames, boolean replacing);
+ void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing);
+}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
new file mode 100644
index 0000000..92b9146
--- /dev/null
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -0,0 +1,146 @@
+/*
+ * 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 android.content.pm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+/**
+ * A representation of an activity that can belong to this user or a managed
+ * profile associated with this user. It can be used to query the label, icon
+ * and badged icon for the activity.
+ */
+public class LauncherActivityInfo {
+ private static final boolean DEBUG = false;
+ private final PackageManager mPm;
+ private final UserManager mUm;
+
+ private ActivityInfo mActivityInfo;
+ private ComponentName mComponentName;
+ private UserHandle mUser;
+ // TODO: Fetch this value from PM
+ private long mFirstInstallTime;
+
+ /**
+ * Create a launchable activity object for a given ResolveInfo and user.
+ *
+ * @param context The context for fetching resources.
+ * @param info ResolveInfo from which to create the LauncherActivityInfo.
+ * @param user The UserHandle of the profile to which this activity belongs.
+ */
+ LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user) {
+ this(context);
+ this.mActivityInfo = info.activityInfo;
+ this.mComponentName = LauncherApps.getComponentName(info);
+ this.mUser = user;
+ }
+
+ LauncherActivityInfo(Context context) {
+ mPm = context.getPackageManager();
+ mUm = UserManager.get(context);
+ }
+
+ /**
+ * Returns the component name of this activity.
+ *
+ * @return ComponentName of the activity
+ */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * Returns the user handle of the user profile that this activity belongs to.
+ *
+ * @return The UserHandle of the profile.
+ */
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Retrieves the label for the activity.
+ *
+ * @return The label for the activity.
+ */
+ public CharSequence getLabel() {
+ return mActivityInfo.loadLabel(mPm);
+ }
+
+ /**
+ * Returns the icon for this activity, without any badging for the profile.
+ * @param density The preferred density of the icon, zero for default density.
+ * @see #getBadgedIcon(int)
+ * @return The drawable associated with the activity
+ */
+ public Drawable getIcon(int density) {
+ // TODO: Use density
+ return mActivityInfo.loadIcon(mPm);
+ }
+
+ /**
+ * Returns the application flags from the ApplicationInfo of the activity.
+ *
+ * @return Application flags
+ */
+ public int getApplicationFlags() {
+ return mActivityInfo.applicationInfo.flags;
+ }
+
+ /**
+ * Returns the time at which the package was first installed.
+ * @return The time of installation of the package, in milliseconds.
+ */
+ public long getFirstInstallTime() {
+ return mFirstInstallTime;
+ }
+
+ /**
+ * Returns the activity icon with badging appropriate for the profile.
+ * @param density Optional density for the icon, or 0 to use the default density.
+ * @return A badged icon for the activity.
+ */
+ public Drawable getBadgedIcon(int density) {
+ // TODO: Handle density
+ if (mUser.equals(android.os.Process.myUserHandle())) {
+ return mActivityInfo.loadIcon(mPm);
+ }
+ Drawable originalIcon = mActivityInfo.loadIcon(mPm);
+ if (originalIcon == null) {
+ if (DEBUG) {
+ Log.w("LauncherActivityInfo", "Couldn't find icon for activity");
+ }
+ originalIcon = mPm.getDefaultActivityIcon();
+ }
+ if (originalIcon instanceof BitmapDrawable) {
+ return mUm.getBadgedDrawableForUser(
+ originalIcon, mUser);
+ }
+ return originalIcon;
+ }
+}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
new file mode 100644
index 0000000..300955a
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -0,0 +1,283 @@
+/*
+ * 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 android.content.pm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ILauncherApps;
+import android.content.pm.IOnAppsChangedListener;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class for retrieving a list of launchable activities for the current user and any associated
+ * managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile.
+ * Since the PackageManager will not deliver package broadcasts for other profiles, you can register
+ * for package changes here.
+ */
+public class LauncherApps {
+
+ static final String TAG = "LauncherApps";
+ static final boolean DEBUG = false;
+
+ private Context mContext;
+ private ILauncherApps mService;
+
+ private List<OnAppsChangedListener> mListeners
+ = new ArrayList<OnAppsChangedListener>();
+
+ /**
+ * Callbacks for changes to this and related managed profiles.
+ */
+ public interface OnAppsChangedListener {
+ /**
+ * Indicates that a package was removed from the specified profile.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageName The name of the package that was removed.
+ */
+ void onPackageRemoved(UserHandle user, String packageName);
+
+ /**
+ * Indicates that a package was added to the specified profile.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageName The name of the package that was added.
+ */
+ void onPackageAdded(UserHandle user, String packageName);
+
+ /**
+ * Indicates that a package was modified in the specified profile.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageName The name of the package that has changed.
+ */
+ void onPackageChanged(UserHandle user, String packageName);
+
+ /**
+ * Indicates that one or more packages have become available. For
+ * example, this can happen when a removable storage card has
+ * reappeared.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageNames The names of the packages that have become
+ * available.
+ * @param replacing Indicates whether these packages are replacing
+ * existing ones.
+ */
+ void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing);
+
+ /**
+ * Indicates that one or more packages have become unavailable. For
+ * example, this can happen when a removable storage card has been
+ * removed.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageNames The names of the packages that have become
+ * unavailable.
+ * @param replacing Indicates whether the packages are about to be
+ * replaced with new versions.
+ */
+ void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing);
+ }
+
+ /** @hide */
+ public LauncherApps(Context context, ILauncherApps service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and
+ * {@link Intent#CATEGORY_LAUNCHER}, for a specified user.
+ *
+ * @param packageName The specific package to query. If null, it checks all installed packages
+ * in the profile.
+ * @param user The UserHandle of the profile.
+ * @return List of launchable activities. Can be an empty list but will not be null.
+ */
+ public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
+ List<ResolveInfo> activities = null;
+ try {
+ activities = mService.getLauncherActivities(packageName, user);
+ } catch (RemoteException re) {
+ }
+ if (activities == null) {
+ return Collections.EMPTY_LIST;
+ }
+ ArrayList<LauncherActivityInfo> lais = new ArrayList<LauncherActivityInfo>();
+ final int count = activities.size();
+ for (int i = 0; i < count; i++) {
+ ResolveInfo ri = activities.get(i);
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user);
+ if (DEBUG) {
+ Log.v(TAG, "Returning activity for profile " + user + " : "
+ + lai.getComponentName());
+ }
+ lais.add(lai);
+ }
+ return lais;
+ }
+
+ static ComponentName getComponentName(ResolveInfo ri) {
+ return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
+ }
+
+ /**
+ * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
+ * returns null.
+ *
+ * @param intent The intent to find a match for.
+ * @param user The profile to look in for a match.
+ * @return An activity info object if there is a match.
+ */
+ public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+ try {
+ ResolveInfo ri = mService.resolveActivity(intent, user);
+ if (ri != null) {
+ LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user);
+ return info;
+ }
+ } catch (RemoteException re) {
+ return null;
+ }
+ return null;
+ }
+
+ /**
+ * Starts an activity in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon
+ * @param opts Options to pass to startActivity
+ * @param user The UserHandle of the profile
+ */
+ public void startActivityForProfile(ComponentName component, Rect sourceBounds,
+ Bundle opts, UserHandle user) {
+ try {
+ mService.startActivityAsUser(component, sourceBounds, opts, user);
+ } catch (RemoteException re) {
+ // Oops!
+ }
+ }
+
+ /**
+ * Adds a listener for changes to packages in current and managed profiles.
+ *
+ * @param listener The listener to add.
+ */
+ public synchronized void addOnAppsChangedListener(OnAppsChangedListener listener) {
+ if (listener != null && !mListeners.contains(listener)) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ try {
+ mService.addOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a listener that was previously added.
+ *
+ * @param listener The listener to remove.
+ * @see #addOnAppsChangedListener(OnAppsChangedListener)
+ */
+ public synchronized void removeOnAppsChangedListener(OnAppsChangedListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ try {
+ mService.removeOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
+ }
+ }
+
+ private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
+
+ @Override
+ public void onPackageRemoved(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackageRemoved(user, packageName);
+ }
+ }
+ }
+
+ @Override
+ public void onPackageChanged(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackageChanged(user, packageName);
+ }
+ }
+ }
+
+ @Override
+ public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackageAdded(user, packageName);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackagesAvailable(user, packageNames, replacing);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackagesUnavailable(user, packageNames, replacing);
+ }
+ }
+ }
+ };
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 520a08c..6392bd4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -20,10 +20,16 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Bitmap.Config;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.util.Log;
import com.android.internal.R;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -474,7 +480,7 @@
/**
* Returns list of the profiles of userHandle including
* userHandle itself.
- *
+ *
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
* @param userHandle profiles of this user will be returned.
* @return the list of profiles.
@@ -490,9 +496,67 @@
}
/**
- * Returns information for all users on this device.
- * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
- * @param excludeDying specify if the list should exclude users being removed.
+ * Returns a list of UserHandles for profiles associated with this user, including this user.
+ *
+ * @return A non-empty list of UserHandles associated with the calling user.
+ */
+ public List<UserHandle> getUserProfiles() {
+ ArrayList<UserHandle> profiles = new ArrayList<UserHandle>();
+ List<UserInfo> users = getProfiles(UserHandle.myUserId());
+ for (UserInfo info : users) {
+ UserHandle userHandle = new UserHandle(info.id);
+ profiles.add(userHandle);
+ }
+ return profiles;
+ }
+
+ /** @hide */
+ public Drawable getBadgedDrawableForUser(Drawable icon, UserHandle user) {
+ int badgeResId = getBadgeResIdForUser(user.getIdentifier());
+ if (badgeResId == 0) {
+ return icon;
+ } else {
+ Drawable badgeIcon = mContext.getPackageManager()
+ .getDrawable("system", badgeResId, null);
+ return getMergedDrawable(icon, badgeIcon);
+ }
+ }
+
+ private int getBadgeResIdForUser(int userHandle) {
+ // Return the framework-provided badge.
+ if (userHandle == UserHandle.myUserId()) {
+ UserInfo user = getUserInfo(userHandle);
+ /* TODO: Allow managed profiles for other users in the future */
+ if (!user.isManagedProfile()
+ || user.profileGroupId != getUserInfo(UserHandle.USER_OWNER).profileGroupId) {
+ return 0;
+ }
+ }
+ return com.android.internal.R.drawable.ic_corp_badge;
+ }
+
+ private Drawable getMergedDrawable(Drawable icon, Drawable badge) {
+ final int width = icon.getIntrinsicWidth();
+ final int height = icon.getIntrinsicHeight();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, width, height);
+ icon.draw(canvas);
+ badge.setBounds(0, 0, width, height);
+ badge.draw(canvas);
+ BitmapDrawable merged = new BitmapDrawable(bitmap);
+ if (icon instanceof BitmapDrawable) {
+ merged.setTargetDensity(((BitmapDrawable) icon).getBitmap().getDensity());
+ }
+ return merged;
+ }
+
+ /**
+ * Returns information for all users on this device. Requires
+ * {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @param excludeDying specify if the list should exclude users being
+ * removed.
* @return the list of users that were created.
* @hide
*/
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 31ca3de..9df8ad5 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -242,7 +242,11 @@
public boolean anyPackagesDisappearing() {
return mDisappearingPackages != null;
}
-
+
+ public boolean isReplacing() {
+ return mChangeType == PACKAGE_UPDATING;
+ }
+
public boolean isPackageModified(String packageName) {
if (mModifiedPackages != null) {
for (int i=mModifiedPackages.length-1; i>=0; i--) {
diff --git a/core/res/res/drawable-hdpi/ic_corp_badge.png b/core/res/res/drawable-hdpi/ic_corp_badge.png
new file mode 100644
index 0000000..f647375
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_corp_badge.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_corp_badge.png b/core/res/res/drawable-xhdpi/ic_corp_badge.png
new file mode 100644
index 0000000..80d848d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_corp_badge.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_corp_badge.png b/core/res/res/drawable-xxhdpi/ic_corp_badge.png
new file mode 100644
index 0000000..885e2ac
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_corp_badge.png
Binary files differ
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 682293d..b011483 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1104,6 +1104,7 @@
<java-symbol type="drawable" name="cling_button" />
<java-symbol type="drawable" name="cling_arrow_up" />
<java-symbol type="drawable" name="cling_bg" />
+ <java-symbol type="drawable" name="ic_corp_badge" />
<java-symbol type="layout" name="action_bar_home" />
<java-symbol type="layout" name="action_bar_title_item" />
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
new file mode 100644
index 0000000..27c7b39
--- /dev/null
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -0,0 +1,292 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ILauncherApps;
+import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+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.util.Slog;
+
+import com.android.internal.content.PackageMonitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service that manages requests and callbacks for launchers that support
+ * managed profiles.
+ */
+public class LauncherAppsService extends ILauncherApps.Stub {
+
+ 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 LauncherAppsService(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 (mListeners.getRegisteredCallbackCount() == 0) {
+ startWatchingPackageBroadcasts();
+ }
+ mListeners.unregister(listener);
+ mListeners.register(listener);
+ }
+ }
+
+ /*
+ * @see android.content.pm.ILauncherApps#removeOnAppsChangedListener(
+ * android.content.pm.IOnAppsChangedListener)
+ */
+ @Override
+ public void removeOnAppsChangedListener(IOnAppsChangedListener listener)
+ throws RemoteException {
+ synchronized (mListeners) {
+ 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() {
+ mPackageMonitor.unregister();
+ }
+
+ void checkCallbackCount() {
+ synchronized (LauncherAppsService.this) {
+ 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);
+ }
+ }
+
+ @Override
+ public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
+ throws RemoteException {
+ ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
+
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0,
+ 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);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ ResolveInfo app = mPm.resolveActivityAsUser(intent, 0, user.getIdentifier());
+ return app;
+ } 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);
+
+ Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+ launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ launchIntent.setComponent(component);
+ launchIntent.setSourceBounds(sourceBounds);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final int callingUserId = UserHandle.getCallingUserId();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.startActivityAsUser(launchIntent, opts, user);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private class MyPackageMonitor extends PackageMonitor {
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ // TODO: if (!isProfile(user)) return;
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ 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());
+ // TODO: if (!isCurrentProfile(user)) return;
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ 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());
+ // TODO: if (!isProfile(user)) return;
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ 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());
+ // TODO: if (!isProfile(user)) return;
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ 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());
+ // TODO: if (!isProfile(user)) return;
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ 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();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a39e958..32515b5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -258,7 +258,9 @@
@Override
public List<UserInfo> getProfiles(int userId) {
- checkManageUsersPermission("query users");
+ if (userId != UserHandle.getCallingUserId()) {
+ checkManageUsersPermission("getting profiles related to user " + userId);
+ }
synchronized (mPackagesLock) {
UserInfo user = getUserInfoLocked(userId);
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8f2adc8..bc7f08e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -70,6 +70,7 @@
import com.android.server.notification.NotificationManagerService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.Installer;
+import com.android.server.pm.LauncherAppsService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
import com.android.server.power.PowerManagerService;
@@ -922,6 +923,14 @@
Slog.e(TAG, "Failure starting TrustManagerService", e);
}
}
+
+ try {
+ Slog.i(TAG, "LauncherAppsService");
+ LauncherAppsService las = new LauncherAppsService(context);
+ ServiceManager.addService(Context.LAUNCHER_APPS_SERVICE, las);
+ } catch (Throwable t) {
+ reportWtf("starting LauncherAppsService", t);
+ }
}
// Before things start rolling, be sure we have decided whether