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