diff options
| author | 2014-02-19 14:31:52 -0800 | |
|---|---|---|
| committer | 2014-04-08 10:51:05 -0700 | |
| commit | 4f58263d02f296430a9653126d28501e95c7bb6c (patch) | |
| tree | ee424e547166ddfc5da545654ac9ee2908803b02 | |
| parent | 2d925545b6190153928658ce320d9b681baad882 (diff) | |
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
17 files changed, 926 insertions, 6 deletions
diff --git a/Android.mk b/Android.mk index e1c15470c648..62338fce4f79 100644 --- a/Android.mk +++ b/Android.mk @@ -119,6 +119,8 @@ LOCAL_SRC_FILES += \ 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 c6c8e42101a4..d0536b6c08d5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6476,6 +6476,7 @@ package android.content { 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 @@ package android.content.pm { 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 @@ package android.os { 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 7149ab9d989e..c3c406dc66a1 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -36,7 +36,9 @@ import android.content.ReceiverCallNotAllowedException; 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 @@ class ContextImpl extends Context { } }); + 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 27e526b2a31d..a9a2347d840b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1999,6 +1999,7 @@ public abstract class Context { BLUETOOTH_SERVICE, //@hide: SIP_SERVICE, USB_SERVICE, + LAUNCHER_APPS_SERVICE, //@hide: SERIAL_SERVICE, INPUT_SERVICE, DISPLAY_SERVICE, @@ -2563,6 +2564,16 @@ public abstract class Context { /** * 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 000000000000..796b1139f33e --- /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 000000000000..796b58d41ae4 --- /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 000000000000..92b91468d12d --- /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 000000000000..300955a05939 --- /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 520a08c0a0ab..6392bd442f94 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -20,10 +20,16 @@ import android.content.Context; 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 @@ public class UserManager { /** * 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 @@ public class UserManager { } /** - * 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 31ca3dee8c09..9df8ad5e6821 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -242,7 +242,11 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { 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 Binary files differnew file mode 100644 index 000000000000..f6473757242f --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_corp_badge.png diff --git a/core/res/res/drawable-xhdpi/ic_corp_badge.png b/core/res/res/drawable-xhdpi/ic_corp_badge.png Binary files differnew file mode 100644 index 000000000000..80d848df9912 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_corp_badge.png diff --git a/core/res/res/drawable-xxhdpi/ic_corp_badge.png b/core/res/res/drawable-xxhdpi/ic_corp_badge.png Binary files differnew file mode 100644 index 000000000000..885e2ac76cfb --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_corp_badge.png diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 682293d7e434..b011483a08c1 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 000000000000..27c7b39c6e7d --- /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 a39e958a5b6f..32515b5b73b1 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 @@ public class UserManagerService extends IUserManager.Stub { @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 8f2adc8f47b2..bc7f08e41a7c 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.net.NetworkStatsService; 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 @@ public final class SystemServer { 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 |