From c5f860cea05bb3294b0a68ea954097eb819535b4 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Wed, 22 Jul 2015 15:25:39 -0700 Subject: [PATCH] Storing and using user id for pinned shelf apps. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need this to differentiate between starting apps for the primary and managed users. This in particular solves the issue when apps were executed as Owner while we are in Guest account (see the bug). Valid user ids for a shelf app are either primary or a managed (work) user for the profile. Launcher allows mixing apps for for primary and the managed users on its home screen. Apps for the managed user are marked with a suitcase badge. Launcher includes a user id in the drag and drop info that it sends to SystemUI, as “profile” extra added to the intent. Shelf now stores this user id and uses it to start apps. Bug: 22609426 Change-Id: Id4c6c1ac8617a83f4484f78db578955699d1a888 --- packages/SystemUI/res/values/strings.xml | 3 + .../systemui/statusbar/phone/AppInfo.java | 45 ++++++ .../statusbar/phone/NavigationBarApps.java | 135 ++++++++++++++---- .../phone/NavigationBarAppsModel.java | 38 +++-- .../statusbar/phone/NavigationBarRecents.java | 2 +- .../phone/NavigationBarAppsModelTest.java | 22 ++- 6 files changed, 198 insertions(+), 47 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e392c1134d421..0ede25f34b163 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1129,4 +1129,7 @@ Remove System UI Tuner from Settings and stop using all of its features?" + + Application is not installed on your device + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java new file mode 100644 index 0000000000000..67dd6736a9f62 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AppInfo.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 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.systemui.statusbar.phone; + +import android.content.ComponentName; + +/** + * Navigation bar app information. + */ +class AppInfo { + /** + * Unspecified serial number for the app's user. + */ + public static final long USER_UNSPECIFIED = -1; + + private final ComponentName mComponentName; + private final long mUserSerialNumber; + + public AppInfo(ComponentName componentName, long userSerialNumber) { + mComponentName = componentName; + mUserSerialNumber = userSerialNumber; + } + + public ComponentName getComponentName() { + return mComponentName; + } + + public long getUserSerialNumber() { + return mUserSerialNumber; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java index d0607562fcd71..71ee4e42f2e4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java @@ -18,19 +18,26 @@ package com.android.systemui.statusbar.phone; import android.animation.LayoutTransition; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.AppGlobals; import android.content.ClipData; import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; +import android.content.pm.ActivityInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.os.Bundle; +import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.util.AttributeSet; +import android.util.Log; import android.util.Slog; import android.view.DragEvent; import android.view.LayoutInflater; @@ -38,9 +45,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.Toast; import com.android.systemui.R; +import java.util.List; + /** * Container for application icons that appear in the navigation bar. Their appearance is similar * to the launcher hotseat. Clicking an icon launches the associated activity. A long click will @@ -51,11 +61,15 @@ class NavigationBarApps extends LinearLayout { private final static boolean DEBUG = false; private final static String TAG = "NavigationBarApps"; + /** + * Intent extra to store user serial number. + */ + static final String EXTRA_PROFILE = "profile"; + // There are separate NavigationBarApps view instances for landscape vs. portrait, but they // share the data model. private static NavigationBarAppsModel sAppsModel; - private final LauncherApps mLauncherApps; private final PackageManager mPackageManager; private final LayoutInflater mLayoutInflater; @@ -74,7 +88,6 @@ class NavigationBarApps extends LinearLayout { sAppsModel = new NavigationBarAppsModel(context); sAppsModel.initialize(); // Load the saved icons, if any. } - mLauncherApps = (LauncherApps) context.getSystemService("launcherapps"); mPackageManager = context.getPackageManager(); mLayoutInflater = LayoutInflater.from(context); @@ -121,12 +134,12 @@ class NavigationBarApps extends LinearLayout { ImageView button = createAppButton(); addView(button); - ComponentName activityName = sAppsModel.getApp(i); - CharSequence appLabel = getAppLabel(mPackageManager, activityName); - button.setContentDescription(getAppLabel(mPackageManager, activityName)); + AppInfo app = sAppsModel.getApp(i); + CharSequence appLabel = getAppLabel(mPackageManager, app.getComponentName()); + button.setContentDescription(appLabel); // Load the icon asynchronously. - new GetActivityIconTask(mPackageManager, button).execute(activityName); + new GetActivityIconTask(mPackageManager, button).execute(app.getComponentName()); } } @@ -150,8 +163,8 @@ class NavigationBarApps extends LinearLayout { public boolean onLongClick(View v) { mDragView = v; ImageView icon = (ImageView) v; - ComponentName activityName = sAppsModel.getApp(indexOfChild(v)); - startAppDrag(icon, activityName); + AppInfo app = sAppsModel.getApp(indexOfChild(v)); + startAppDrag(icon, app); return true; } } @@ -175,9 +188,13 @@ class NavigationBarApps extends LinearLayout { } /** Helper function to start dragging an app icon (either pinned or recent). */ - static void startAppDrag(ImageView icon, ComponentName activityName) { + static void startAppDrag(ImageView icon, AppInfo appInfo) { // The drag data is an Intent to launch the activity. - Intent mainIntent = Intent.makeMainActivity(activityName); + Intent mainIntent = Intent.makeMainActivity(appInfo.getComponentName()); + long userSerialNumber = appInfo.getUserSerialNumber(); + if (userSerialNumber != AppInfo.USER_UNSPECIFIED) { + mainIntent.putExtra(EXTRA_PROFILE, userSerialNumber); + } ClipData dragData = ClipData.newIntent("", mainIntent); // Use the ImageView to create the shadow. View.DragShadowBuilder shadow = new AppIconDragShadowBuilder(icon); @@ -294,7 +311,7 @@ class NavigationBarApps extends LinearLayout { addView(mDragView, targetIndex); // Update the data model. - ComponentName app = sAppsModel.removeApp(dragViewIndex); + AppInfo app = sAppsModel.removeApp(dragViewIndex); sAppsModel.addApp(targetIndex, app); } @@ -314,15 +331,15 @@ class NavigationBarApps extends LinearLayout { } // The drag view was a placeholder. Unpack the drop. - ComponentName activityName = getActivityNameFromDragEvent(event); - if (activityName == null) { + AppInfo appInfo = getAppFromDragEvent(event); + if (appInfo == null) { // This wasn't a valid drop. Clean up the placeholder and model. removePlaceholderDragViewIfNeeded(); - return true; + return false; } // The drop had valid data. Update the placeholder with a real activity and icon. - updateAppAt(dragViewIndex, activityName); + updateAppAt(dragViewIndex, appInfo); endDrag(); return true; } @@ -334,8 +351,8 @@ class NavigationBarApps extends LinearLayout { sAppsModel.savePrefs(); } - /** Returns an app launch Intent from a DragEvent, or null if the data wasn't valid. */ - private ComponentName getActivityNameFromDragEvent(DragEvent event) { + /** Returns an app info from a DragEvent, or null if the data wasn't valid. */ + private AppInfo getAppFromDragEvent(DragEvent event) { ClipData data = event.getClipData(); if (data == null) { return null; @@ -347,14 +364,16 @@ class NavigationBarApps extends LinearLayout { if (intent == null) { return null; } - return intent.getComponent(); + + long userSerialNumber = intent.getLongExtra(EXTRA_PROFILE, AppInfo.USER_UNSPECIFIED); + return new AppInfo(intent.getComponent(), userSerialNumber); } /** Updates the app at a given view index. */ - private void updateAppAt(int index, ComponentName activityName) { - sAppsModel.setApp(index, activityName); + private void updateAppAt(int index, AppInfo appInfo) { + sAppsModel.setApp(index, appInfo); ImageView button = (ImageView) getChildAt(index); - new GetActivityIconTask(mPackageManager, button).execute(activityName); + new GetActivityIconTask(mPackageManager, button).execute(appInfo.getComponentName()); } /** Removes the empty placeholder view and cleans up the data model. */ @@ -411,11 +430,26 @@ class NavigationBarApps extends LinearLayout { private class AppClickListener implements View.OnClickListener { @Override public void onClick(View v) { - ComponentName activityName = sAppsModel.getApp(indexOfChild(v)); + AppInfo appInfo = sAppsModel.getApp(indexOfChild(v)); + ComponentName component = appInfo.getComponentName(); - // TODO: Support apps from multiple user profiles. The profile will need to be stored in - // the data model for each app shortcut. - UserHandle user = UserHandle.OWNER; + UserManager userManager = + (UserManager) getContext().getSystemService(Context.USER_SERVICE); + + long appUserSerialNumber = appInfo.getUserSerialNumber(); + + UserHandle appUser = null; + if (appUserSerialNumber != AppInfo.USER_UNSPECIFIED) { + appUser = userManager.getUserForSerialNumber(appUserSerialNumber); + } + + int appUserId; + if (appUser != null) { + appUserId = appUser.getIdentifier(); + } else { + appUserId = ActivityManager.getCurrentUser(); + appUser = new UserHandle(appUserId); + } // Play a scale-up animation while launching the activity. // TODO: Consider playing a different animation, or no animation, if the activity is @@ -427,8 +461,53 @@ class NavigationBarApps extends LinearLayout { ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getWidth(), v.getHeight()); Bundle optsBundle = opts.toBundle(); - // Launch the activity. - mLauncherApps.startMainActivity(activityName, user, sourceBounds, optsBundle); + // Launch the activity. This code is based on LauncherAppsService.startActivityAsUser code. + Intent launchIntent = new Intent(Intent.ACTION_MAIN); + launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); + launchIntent.setSourceBounds(sourceBounds); + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + launchIntent.setPackage(component.getPackageName()); + + IPackageManager pm = AppGlobals.getPackageManager(); + try { + ActivityInfo info = pm.getActivityInfo(component, 0, appUserId); + if (info == null) { + Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Can't start activity " + component + " because it's not installed."); + return; + } + + if (!info.exported) { + Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Can't start activity " + component + " because it doesn't have 'exported' attribute."); + return; + } + } catch (RemoteException e) { + Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Failed to get activity info for " + component, e); + return; + } + + // Check that the component actually has Intent.CATEGORY_LAUCNCHER + // as calling startActivityAsUser ignores the category and just + // resolves based on the component if present. + List apps = getContext().getPackageManager().queryIntentActivitiesAsUser(launchIntent, + 0 /* flags */, appUserId); + final int size = apps.size(); + for (int i = 0; i < size; ++i) { + ActivityInfo activityInfo = apps.get(i).activityInfo; + if (activityInfo.packageName.equals(component.getPackageName()) && + activityInfo.name.equals(component.getClassName())) { + // Found an activity with category launcher that matches + // this component so ok to launch. + launchIntent.setComponent(component); + mContext.startActivityAsUser(launchIntent, optsBundle, appUser); + return; + } + } + + Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Attempt to launch activity without category Intent.CATEGORY_LAUNCHER " + component); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java index d74a5223283d7..9f80d33aed889 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; @@ -55,11 +56,14 @@ class NavigationBarAppsModel { // Preference name prefix for each app's info. The actual pref has an integer appended to it. private final static String APP_PREF_PREFIX = "app_"; + // User serial number prefix for each app's info. The actual pref has an integer appended to it. + private final static String APP_USER_PREFIX = "app_user_"; + private final LauncherApps mLauncherApps; private final SharedPreferences mPrefs; - // Apps are represented as an ordered list of component names. - private final List mApps = new ArrayList(); + // Apps are represented as an ordered list of app infos. + private final List mApps = new ArrayList(); public NavigationBarAppsModel(Context context) { mLauncherApps = (LauncherApps) context.getSystemService("launcherapps"); @@ -97,22 +101,22 @@ class NavigationBarAppsModel { } /** Returns the app at the given index. */ - public ComponentName getApp(int index) { + public AppInfo getApp(int index) { return mApps.get(index); } /** Adds the app before the given index. */ - public void addApp(int index, ComponentName name) { - mApps.add(index, name); + public void addApp(int index, AppInfo appInfo) { + mApps.add(index, appInfo); } /** Sets the app at the given index. */ - public void setApp(int index, ComponentName name) { - mApps.set(index, name); + public void setApp(int index, AppInfo appInfo) { + mApps.set(index, appInfo); } /** Remove the app at the given index. */ - public ComponentName removeApp(int index) { + public AppInfo removeApp(int index) { return mApps.remove(index); } @@ -125,8 +129,10 @@ class NavigationBarAppsModel { int appCount = mApps.size(); edit.putInt(APP_COUNT_PREF, appCount); for (int i = 0; i < appCount; i++) { - String componentNameString = mApps.get(i).flattenToString(); + final AppInfo appInfo = mApps.get(i); + String componentNameString = appInfo.getComponentName().flattenToString(); edit.putString(prefNameForApp(i), componentNameString); + edit.putLong(prefUserForApp(i), appInfo.getUserSerialNumber()); } // Start an asynchronous disk write. edit.apply(); @@ -143,7 +149,8 @@ class NavigationBarAppsModel { continue; } ComponentName componentName = ComponentName.unflattenFromString(prefValue); - mApps.add(componentName); + long userSerialNumber = mPrefs.getLong(prefUserForApp(i), AppInfo.USER_UNSPECIFIED); + mApps.add(new AppInfo(componentName, userSerialNumber)); } } @@ -151,11 +158,12 @@ class NavigationBarAppsModel { private void addDefaultApps() { // Get a list of all app activities. List apps = - mLauncherApps.getActivityList(null /* packageName */, UserHandle.OWNER); + mLauncherApps.getActivityList( + null /* packageName */, new UserHandle(ActivityManager.getCurrentUser())); int appCount = apps.size(); for (int i = 0; i < NUM_INITIAL_APPS && i < appCount; i++) { LauncherActivityInfo activityInfo = apps.get(i); - mApps.add(activityInfo.getComponentName()); + mApps.add(new AppInfo(activityInfo.getComponentName(), AppInfo.USER_UNSPECIFIED)); } } @@ -163,4 +171,10 @@ class NavigationBarAppsModel { private static String prefNameForApp(int index) { return APP_PREF_PREFIX + Integer.toString(index); } + + /** Returns the pref name for the app's user at a given index. */ + private static String prefUserForApp(int index) { + return APP_USER_PREFIX + Integer.toString(index); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarRecents.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarRecents.java index c7fae9207647b..eaa1c20c9b0cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarRecents.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarRecents.java @@ -232,7 +232,7 @@ class NavigationBarRecents extends LinearLayout { } if (DEBUG) Slog.d(TAG, "Start drag with " + intent); - NavigationBarApps.startAppDrag(icon, intent.getComponent()); + NavigationBarApps.startAppDrag(icon, new AppInfo (intent.getComponent(), AppInfo.USER_UNSPECIFIED)); return true; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java index 84aaac7f4c664..f46e76c701754 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java @@ -60,8 +60,11 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { // Assume several apps are stored. when(mMockPrefs.getInt("app_count", -1)).thenReturn(3); when(mMockPrefs.getString("app_0", null)).thenReturn("package0/class0"); + when(mMockPrefs.getLong("app_user_0", -1)).thenReturn(-1L); when(mMockPrefs.getString("app_1", null)).thenReturn("package1/class1"); + when(mMockPrefs.getLong("app_user_1", -1)).thenReturn(45L); when(mMockPrefs.getString("app_2", null)).thenReturn("package2/class2"); + when(mMockPrefs.getLong("app_user_2", -1)).thenReturn(239L); mModel.initialize(); } @@ -70,9 +73,12 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { public void testInitializeFromPrefs() { initializeModelFromPrefs(); assertEquals(3, mModel.getAppCount()); - assertEquals("package0/class0", mModel.getApp(0).flattenToString()); - assertEquals("package1/class1", mModel.getApp(1).flattenToString()); - assertEquals("package2/class2", mModel.getApp(2).flattenToString()); + assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString()); + assertEquals(-1L, mModel.getApp(0).getUserSerialNumber()); + assertEquals("package1/class1", mModel.getApp(1).getComponentName().flattenToString()); + assertEquals(45L, mModel.getApp(1).getUserSerialNumber()); + assertEquals("package2/class2", mModel.getApp(2).getComponentName().flattenToString()); + assertEquals(239L, mModel.getApp(2).getUserSerialNumber()); } /** Tests initializing the model when the SharedPreferences aren't available. */ @@ -94,8 +100,10 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { // Initializing the model should load the installed activities. mModel.initialize(); assertEquals(2, mModel.getAppCount()); - assertEquals("package1/class1", mModel.getApp(0).flattenToString()); - assertEquals("package2/class2", mModel.getApp(1).flattenToString()); + assertEquals("package1/class1", mModel.getApp(0).getComponentName().flattenToString()); + assertEquals(-1L, mModel.getApp(0).getUserSerialNumber()); + assertEquals("package2/class2", mModel.getApp(1).getComponentName().flattenToString()); + assertEquals(-1L, mModel.getApp(1).getUserSerialNumber()); } /** Tests initializing the model if one of the prefs is missing. */ @@ -106,6 +114,7 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { // Assume two apps are nominally stored. when(mMockPrefs.getInt("app_count", -1)).thenReturn(2); when(mMockPrefs.getString("app_0", null)).thenReturn("package0/class0"); + when(mMockPrefs.getLong("app_user_0", -1)).thenReturn(239L); // But assume one pref is missing. when(mMockPrefs.getString("app_1", null)).thenReturn(null); @@ -113,7 +122,8 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { // Initializing the model should load from prefs and skip the missing one. mModel.initialize(); assertEquals(1, mModel.getAppCount()); - assertEquals("package0/class0", mModel.getApp(0).flattenToString()); + assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString()); + assertEquals(239L, mModel.getApp(0).getUserSerialNumber()); } /** Tests saving the model to SharedPreferences. */