Introducing ShortcutManager

What's supported:
- Most APIs are implemented, except for SM.updateShortcuts(),
the icon APIs in LA, and LA.startShortcut().
- Persisting information, except for icons
- Throttling

In addition, now PersistableBundle has a public copy
constructor from a Bundle. (Do we want to @hide it?)

TODOs:
- Add icon support
- Implement missing APIs
- Listen to PACKAGE_* broadcasts and do clean-up
- Support multi-launcher apps (pinned shortcuts per launcher)
- Dev option to reset throttling
- Load throttling config from Settings
- Backup & restore
- Figure out LauncherApps permissions (BIND_APPWIDGETS??)
- Other minor TODOs in the code
- Better javadoc

Note: This requires Idf2f9ae816e1f3d822a6286a4cf738c14e29a45e

Bug 27325877

Change-Id: Ia5aa555a4759df5f79a859338f1dc5e624cd0e35
This commit is contained in:
Makoto Onuki
2016-03-04 13:39:41 -08:00
parent b74926232e
commit 6f7362d925
26 changed files with 4492 additions and 12 deletions

View File

@@ -149,6 +149,7 @@ LOCAL_SRC_FILES += \
core/java/android/content/pm/IPackageMoveObserver.aidl \
core/java/android/content/pm/IPackageStatsObserver.aidl \
core/java/android/content/pm/IOnPermissionsChangeListener.aidl \
core/java/android/content/pm/IShortcutService.aidl \
core/java/android/database/IContentObserver.aidl \
../av/camera/aidl/android/hardware/ICameraService.aidl \
../av/camera/aidl/android/hardware/ICameraServiceListener.aidl \
@@ -650,6 +651,7 @@ aidl_files := \
frameworks/base/core/java/android/content/pm/ProviderInfo.aidl \
frameworks/base/core/java/android/content/pm/PackageStats.aidl \
frameworks/base/core/java/android/content/pm/PermissionGroupInfo.aidl \
frameworks/base/core/java/android/content/pm/ShortcutInfo.aidl \
frameworks/base/core/java/android/content/pm/LabeledIntent.aidl \
frameworks/base/core/java/android/content/ComponentName.aidl \
frameworks/base/core/java/android/content/SyncStats.aidl \

View File

@@ -8138,6 +8138,7 @@ package android.content {
field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
@@ -9443,13 +9444,19 @@ package android.content.pm {
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle);
method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
method public void registerCallback(android.content.pm.LauncherApps.Callback);
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
}
@@ -9462,6 +9469,18 @@ package android.content.pm {
method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle);
}
public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery();
method public void setActivity(android.content.ComponentName);
method public void setChangedSince(long);
method public void setPackage(java.lang.String);
method public void setQueryFlags(int);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
field public static final int FLAG_GET_PINNED = 2; // 0x2
}
public class PackageInfo implements android.os.Parcelable {
@@ -9961,6 +9980,56 @@ package android.content.pm {
field public java.lang.String permission;
}
public class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
method public android.content.ComponentName getActivityComponent();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.String getPackageName();
method public java.lang.String getTitle();
method public int getWeight();
method public boolean hasIconFile();
method public boolean hasIconResource();
method public boolean isDynamic();
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3
field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DYNAMIC = 1; // 0x1
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
field public static final int FLAG_PINNED = 2; // 0x2
}
public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setWeight(int);
}
public class ShortcutManager {
method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo);
method public void deleteAllDynamicShortcuts();
method public void deleteDynamicShortcut(java.lang.String);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
method public int getMaxDynamicShortcutCount();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime();
method public int getRemainingCallCount();
method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
}
public class Signature implements android.os.Parcelable {
ctor public Signature(byte[]);
ctor public Signature(java.lang.String);
@@ -29003,6 +29072,7 @@ package android.os {
ctor public PersistableBundle();
ctor public PersistableBundle(int);
ctor public PersistableBundle(android.os.PersistableBundle);
ctor public PersistableBundle(android.os.Bundle);
method public java.lang.Object clone();
method public int describeContents();
method public android.os.PersistableBundle getPersistableBundle(java.lang.String);

View File

@@ -8444,6 +8444,7 @@ package android.content {
field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
@@ -9777,13 +9778,19 @@ package android.content.pm {
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle);
method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
method public void registerCallback(android.content.pm.LauncherApps.Callback);
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
}
@@ -9796,6 +9803,18 @@ package android.content.pm {
method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle);
}
public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery();
method public void setActivity(android.content.ComponentName);
method public void setChangedSince(long);
method public void setPackage(java.lang.String);
method public void setQueryFlags(int);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
field public static final int FLAG_GET_PINNED = 2; // 0x2
}
public class PackageInfo implements android.os.Parcelable {
@@ -10355,6 +10374,56 @@ package android.content.pm {
field public java.lang.String permission;
}
public class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
method public android.content.ComponentName getActivityComponent();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.String getPackageName();
method public java.lang.String getTitle();
method public int getWeight();
method public boolean hasIconFile();
method public boolean hasIconResource();
method public boolean isDynamic();
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3
field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DYNAMIC = 1; // 0x1
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
field public static final int FLAG_PINNED = 2; // 0x2
}
public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setWeight(int);
}
public class ShortcutManager {
method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo);
method public void deleteAllDynamicShortcuts();
method public void deleteDynamicShortcut(java.lang.String);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
method public int getMaxDynamicShortcutCount();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime();
method public int getRemainingCallCount();
method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
}
public class Signature implements android.os.Parcelable {
ctor public Signature(byte[]);
ctor public Signature(java.lang.String);
@@ -31288,6 +31357,7 @@ package android.os {
ctor public PersistableBundle();
ctor public PersistableBundle(int);
ctor public PersistableBundle(android.os.PersistableBundle);
ctor public PersistableBundle(android.os.Bundle);
method public java.lang.Object clone();
method public int describeContents();
method public android.os.PersistableBundle getPersistableBundle(java.lang.String);

View File

@@ -8144,6 +8144,7 @@ package android.content {
field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
@@ -9452,13 +9453,19 @@ package android.content.pm {
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle);
method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle);
method public void registerCallback(android.content.pm.LauncherApps.Callback);
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
}
@@ -9471,6 +9478,18 @@ package android.content.pm {
method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle);
}
public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery();
method public void setActivity(android.content.ComponentName);
method public void setChangedSince(long);
method public void setPackage(java.lang.String);
method public void setQueryFlags(int);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
field public static final int FLAG_GET_PINNED = 2; // 0x2
}
public class PackageInfo implements android.os.Parcelable {
@@ -9971,6 +9990,56 @@ package android.content.pm {
field public java.lang.String permission;
}
public class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
method public android.content.ComponentName getActivityComponent();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.String getPackageName();
method public java.lang.String getTitle();
method public int getWeight();
method public boolean hasIconFile();
method public boolean hasIconResource();
method public boolean isDynamic();
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3
field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DYNAMIC = 1; // 0x1
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
field public static final int FLAG_PINNED = 2; // 0x2
}
public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setWeight(int);
}
public class ShortcutManager {
method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo);
method public void deleteAllDynamicShortcuts();
method public void deleteDynamicShortcut(java.lang.String);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
method public int getMaxDynamicShortcutCount();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime();
method public int getRemainingCallCount();
method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
}
public class Signature implements android.os.Parcelable {
ctor public Signature(byte[]);
ctor public Signature(java.lang.String);
@@ -29014,6 +29083,7 @@ package android.os {
ctor public PersistableBundle();
ctor public PersistableBundle(int);
ctor public PersistableBundle(android.os.PersistableBundle);
ctor public PersistableBundle(android.os.Bundle);
method public java.lang.Object clone();
method public int describeContents();
method public android.os.PersistableBundle getPersistableBundle(java.lang.String);

View File

@@ -37,7 +37,9 @@ import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
import android.content.pm.ILauncherApps;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -748,6 +750,15 @@ final class SystemServiceRegistry {
Log.i(TAG, "Creating new instance of SoundTriggerManager object.");
return new SoundTriggerManager(ctx, ISoundTriggerService.Stub.asInterface(b));
}});
registerService(Context.SHORTCUT_SERVICE, ShortcutManager.class,
new CachedServiceFetcher<ShortcutManager>() {
@Override
public ShortcutManager createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(Context.SHORTCUT_SERVICE);
return new ShortcutManager(ctx,
IShortcutService.Stub.asInterface(b));
}});
}
/**

View File

@@ -2680,6 +2680,7 @@ public abstract class Context {
RADIO_SERVICE,
HARDWARE_PROPERTIES_SERVICE,
//@hide: SOUND_TRIGGER_SERVICE,
SHORTCUT_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -3575,6 +3576,14 @@ public abstract class Context {
*/
public static final String HARDWARE_PROPERTIES_SERVICE = "hardwareproperties";
/**
* TODO Javadoc
*
* @see #getSystemService
* @see android.content.pm.ShortcutManager
*/
public static final String SHORTCUT_SERVICE = "shortcut";
/**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.

View File

@@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
@@ -42,4 +43,13 @@ interface ILauncherApps {
boolean isPackageEnabled(String packageName, in UserHandle user);
boolean isActivityEnabled(in ComponentName component, in UserHandle user);
ApplicationInfo getApplicationInfo(String packageName, int flags, in UserHandle user);
ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
in ComponentName componentName, int flags, in UserHandle user);
ParceledListSlice getShortcutInfo(String callingPackage, String packageName, in List<String> ids,
in UserHandle user);
void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
in UserHandle user);
void startShortcut(String callingPackage, in ShortcutInfo shortcut, in Rect sourceBounds,
in Bundle startActivityOptions, in UserHandle user);
}

View File

@@ -16,6 +16,7 @@
package android.content.pm;
import android.content.pm.ParceledListSlice;
import android.os.UserHandle;
/**
@@ -29,4 +30,5 @@ oneway interface IOnAppsChangedListener {
void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing);
void onPackagesSuspended(in UserHandle user, in String[] packageNames);
void onPackagesUnsuspended(in UserHandle user, in String[] packageNames);
void onShortcutChanged(in UserHandle user, String packageName, in ParceledListSlice shortcuts);
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2016 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.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
/**
* {@hide}
*/
interface IShortcutService {
boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
int userId);
ParceledListSlice getDynamicShortcuts(String packageName, int userId);
boolean addDynamicShortcut(String packageName, in ShortcutInfo shortcutInfo, int userId);
void deleteDynamicShortcut(String packageName, in String shortcutId, int userId);
void deleteAllDynamicShortcuts(String packageName, int userId);
ParceledListSlice getPinnedShortcuts(String packageName, int userId);
boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId);
int getMaxDynamicShortcutCount(String packageName, int userId);
int getRemainingCallCount(String packageName, int userId);
long getRateLimitResetTime(String packageName, int userId);
void resetThrottling(); // system only API for developer opsions
}

View File

@@ -16,23 +16,29 @@
package android.content.pm;
import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -148,6 +154,91 @@ public class LauncherApps {
*/
public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
}
/**
* Indicates that one or more shortcuts (which may be dynamic and/or pinned)
* have been added, updated or removed.
*
* @param packageName The name of the package that has the shortcuts.
* @param shortcuts all shortcuts from the package (dynamic and/or pinned).
* @param user The UserHandle of the profile that generated the change.
*/
public void onShortcutsChanged(@NonNull String packageName,
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
}
}
/**
* Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
*/
public static class ShortcutQuery {
/**
* Include dynamic shortcuts in the result.
*/
public static final int FLAG_GET_DYNAMIC = 1 << 0;
/**
* Include pinned shortcuts in the result.
*/
public static final int FLAG_GET_PINNED = 1 << 1;
/**
* Requests "key" fields only.
*/
public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
/** @hide */
@IntDef(flag = true,
value = {
FLAG_GET_DYNAMIC,
FLAG_GET_PINNED,
FLAG_GET_KEY_FIELDS_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface QueryFlags {}
long mChangedSince;
@Nullable
String mPackage;
@Nullable
ComponentName mActivity;
@QueryFlags
int mQueryFlags;
public ShortcutQuery() {
}
/**
* If non-zero, returns only shortcuts that have been added or updated since the timestamp,
* which is a milliseconds since the Epoch.
*/
public void setChangedSince(long changedSince) {
mChangedSince = changedSince;
}
/**
* If non-null, returns only shortcuts from the package.
*/
public void setPackage(@Nullable String packageName) {
mPackage = packageName;
}
/**
* If non-null, returns only shortcuts associated with the activity.
*/
public void setActivity(@Nullable ComponentName activity) {
mActivity = activity;
}
/**
* Set query options.
*/
public void setQueryFlags(@QueryFlags int queryFlags) {
mQueryFlags = queryFlags;
}
}
/** @hide */
@@ -302,6 +393,125 @@ public class LauncherApps {
}
}
/**
* Returns the IDs of {@link ShortcutInfo}s that match {@code query}.
*
* <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
*
* @param query result includes shortcuts matching this query.
* @param user The UserHandle of the profile.
*
* @return the IDs of {@link ShortcutInfo}s that match the query.
*/
@RequiresPermission(permission.BIND_APPWIDGET)
@Nullable
public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
@NonNull UserHandle user) {
try {
return mService.getShortcuts(mContext.getPackageName(),
query.mChangedSince, query.mPackage, query.mActivity, query.mQueryFlags, user)
.getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns {@link ShortcutInfo}s with the given IDs from a package.
*
* <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
*
* @param packageName The target package.
* @param ids IDs of the shortcuts to retrieve.
* @param user The UserHandle of the profile.
*
* @return list of {@link ShortcutInfo} associated with the package.
*/
@RequiresPermission(permission.BIND_APPWIDGET)
@Nullable
public List<ShortcutInfo> getShortcutInfo(@NonNull String packageName,
@NonNull List<String> ids, @NonNull UserHandle user) {
try {
return mService.getShortcutInfo(mContext.getPackageName(), packageName, ids, user)
.getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Pin shortcuts on a package.
*
* <p>This API is <b>NOT</b> cumulative; this will replace all pinned shortcuts for the package.
* However, different launchers may have different set of pinned shortcuts.
*
* <p>Callers must have the {@link permission#BIND_APPWIDGET} permission.
*
* @param packageName The target package name.
* @param shortcutIds The IDs of the shortcut to be pinned.
* @param user The UserHandle of the profile.
*/
@RequiresPermission(permission.BIND_APPWIDGET)
public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
@NonNull UserHandle user) {
try {
mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the icon resource ID, if {@code shortcut} has one
* (i.e. when {@link ShortcutInfo#hasIconResource()} returns {@code true}).
*
* <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
*
* @param shortcut The target shortcut.
* @param user The UserHandle of the profile.
*/
@RequiresPermission(permission.BIND_APPWIDGET)
public int getShortcutIconResId(@NonNull ShortcutInfo shortcut, @NonNull UserHandle user) {
throw new RuntimeException("not implemented yet");
}
/**
* Return the icon as {@link ParcelFileDescriptor}, when it's stored as a file
* (i.e. when {@link ShortcutInfo#hasIconFile()} returns {@code true}).
*
* <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
*
* @param shortcut The target shortcut.
* @param user The UserHandle of the profile.
*/
@RequiresPermission(permission.BIND_APPWIDGET)
public ParcelFileDescriptor getShortcutIconFd(
@NonNull ShortcutInfo shortcut, @NonNull UserHandle user) {
throw new RuntimeException("not implemented yet");
}
/**
* Launches a shortcut.
*
* <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission.
*
* @param shortcut The target shortcut.
* @param sourceBounds The Rect containing the source bounds of the clicked icon.
* @param startActivityOptions Options to pass to startActivity.
* @param user The UserHandle of the profile.
*/
@RequiresPermission(permission.BIND_APPWIDGET)
public void startShortcut(@NonNull ShortcutInfo shortcut,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
@NonNull UserHandle user) {
try {
mService.startShortcut(mContext.getPackageName(), shortcut, sourceBounds,
startActivityOptions, user);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Registers a callback for changes to packages in current and managed profiles.
@@ -474,6 +684,20 @@ public class LauncherApps {
}
}
}
@Override
public void onShortcutChanged(UserHandle user, String packageName,
ParceledListSlice shortcuts) {
if (DEBUG) {
Log.d(TAG, "onShortcutChanged " + user.getIdentifier() + "," + packageName);
}
final List<ShortcutInfo> list = shortcuts.getList();
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnShortcutChanged(packageName, user, list);
}
}
}
};
private static class CallbackMessageHandler extends Handler {
@@ -484,6 +708,7 @@ public class LauncherApps {
private static final int MSG_UNAVAILABLE = 5;
private static final int MSG_SUSPENDED = 6;
private static final int MSG_UNSUSPENDED = 7;
private static final int MSG_SHORTCUT_CHANGED = 8;
private LauncherApps.Callback mCallback;
@@ -492,6 +717,7 @@ public class LauncherApps {
String packageName;
boolean replacing;
UserHandle user;
List<ShortcutInfo> shortcuts;
}
public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
@@ -527,6 +753,9 @@ public class LauncherApps {
case MSG_UNSUSPENDED:
mCallback.onPackagesUnsuspended(info.packageNames, info.user);
break;
case MSG_SHORTCUT_CHANGED:
mCallback.onShortcutsChanged(info.packageName, info.shortcuts, info.user);
break;
}
}
@@ -582,5 +811,14 @@ public class LauncherApps {
info.user = user;
obtainMessage(MSG_UNSUSPENDED, info).sendToTarget();
}
public void postOnShortcutChanged(String packageName, UserHandle user,
List<ShortcutInfo> shortcuts) {
CallbackInfo info = new CallbackInfo();
info.packageName = packageName;
info.user = user;
info.shortcuts = shortcuts;
obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget();
}
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (C) 2016 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;
parcelable ShortcutInfo;

View File

@@ -0,0 +1,680 @@
/*
* Copyright (C) 2016 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.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.UserHandle;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* TODO Enhance javadoc
*
* Represents a shortcut form an application.
*
* Notes...
* - If an {@link Icon} is of a resource, then we'll just persist the package name and resource ID.
*
* Otherwise, the bitmap will be fetched when it's registered to ShortcutManager, then *shrunk*
* if necessary, and persisted.
*
* We will disallow byte[] icons, because they can easily go over binder size limit.
*
* TODO Move save/load to this class
*/
public class ShortcutInfo implements Parcelable {
/* @hide */
public static final int FLAG_DYNAMIC = 1 << 0;
/* @hide */
public static final int FLAG_PINNED = 1 << 1;
/* @hide */
public static final int FLAG_HAS_ICON_RES = 1 << 2;
/* @hide */
public static final int FLAG_HAS_ICON_FILE = 1 << 3;
/** @hide */
@IntDef(flag = true,
value = {
FLAG_DYNAMIC,
FLAG_PINNED,
FLAG_HAS_ICON_RES,
FLAG_HAS_ICON_FILE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
// Cloning options.
/* @hide */
private static final int CLONE_REMOVE_ICON = 1 << 0;
/* @hide */
private static final int CLONE_REMOVE_INTENT = 1 << 1;
/* @hide */
public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2;
/* @hide */
public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON;
/* @hide */
public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT;
/** @hide */
@IntDef(flag = true,
value = {
CLONE_REMOVE_ICON,
CLONE_REMOVE_INTENT,
CLONE_REMOVE_NON_KEY_INFO,
CLONE_REMOVE_FOR_CREATOR,
CLONE_REMOVE_FOR_LAUNCHER
})
@Retention(RetentionPolicy.SOURCE)
public @interface CloneFlags {}
private final String mId;
@NonNull
private final String mPackageName;
@Nullable
private ComponentName mActivityComponent;
@Nullable
private Icon mIcon;
@NonNull
private String mTitle;
@NonNull
private Intent mIntent;
// Internal use only.
@NonNull
private PersistableBundle mIntentPersistableExtras;
private int mWeight;
@Nullable
private PersistableBundle mExtras;
private long mLastChangedTimestamp;
// Internal use only.
@ShortcutFlags
private int mFlags;
// Internal use only.
private int mIconResourceId;
// Internal use only.
@Nullable
private String mBitmapPath;
private ShortcutInfo(Builder b) {
mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
// Note we can't do other null checks here because SM.updateShortcuts() takes partial
// information.
mPackageName = b.mContext.getPackageName();
mActivityComponent = b.mActivityComponent;
mIcon = b.mIcon;
mTitle = b.mTitle;
mIntent = b.mIntent;
mWeight = b.mWeight;
mExtras = b.mExtras;
updateTimestamp();
}
/**
* Throws if any of the mandatory fields is not set.
*
* @hide
*/
public void enforceMandatoryFields() {
Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided");
Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
}
/**
* Copy constructor.
*/
private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) {
mId = source.mId;
mPackageName = source.mPackageName;
mActivityComponent = source.mActivityComponent;
mFlags = source.mFlags;
mLastChangedTimestamp = source.mLastChangedTimestamp;
if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
mIcon = source.mIcon;
}
mTitle = source.mTitle;
if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
}
mWeight = source.mWeight;
mExtras = source.mExtras;
mIconResourceId = source.mIconResourceId;
mBitmapPath = source.mBitmapPath;
}
}
/**
* Copy a {@link ShortcutInfo}, optionally removing fields.
* @hide
*/
public ShortcutInfo clone(@CloneFlags int cloneFlags) {
return new ShortcutInfo(this, cloneFlags);
}
/**
* Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information
* will be overwritten. The timestamp will be updated.
*
* - Flags will not change
* - mBitmapPath will not change
* - Current time will be set to timestamp
*
* @hide
*/
public void copyNonNullFieldsFrom(ShortcutInfo source) {
Preconditions.checkState(mId == source.mId, "ID must match");
Preconditions.checkState(mPackageName.equals(source.mPackageName),
"Package namae must match");
if (source.mActivityComponent != null) {
mActivityComponent = source.mActivityComponent;
}
if (source.mIcon != null) {
mIcon = source.mIcon;
}
if (source.mTitle != null) {
mTitle = source.mTitle;
}
if (source.mIntent != null) {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
}
if (source.mWeight != 0) {
mWeight = source.mWeight;
}
if (source.mExtras != null) {
mExtras = source.mExtras;
}
updateTimestamp();
}
/**
* Builder class for {@link ShortcutInfo} objects.
*/
public static class Builder {
private final Context mContext;
private String mId;
private ComponentName mActivityComponent;
private Icon mIcon;
private String mTitle;
private Intent mIntent;
private int mWeight;
private PersistableBundle mExtras;
/** Constructor. */
public Builder(Context context) {
mContext = context;
}
/**
* Sets the ID of the shortcut. This is a mandatory field.
*/
@NonNull
public Builder setId(@NonNull String id) {
mId = Preconditions.checkStringNotEmpty(id, "id");
return this;
}
/**
* Optionally sets the target activity.
*/
@NonNull
public Builder setActivityComponent(@NonNull ComponentName activityComponent) {
mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent");
return this;
}
/**
* Optionally sets an icon.
*
* - Tint is not supported TODO Either check and throw, or support it.
* - URI icons will be converted into Bitmap icons at the registration time.
*
* TODO Only allow Bitmap, Resource and URI types. byte[] type can easily go over
* binder size limit.
*/
@NonNull
public Builder setIcon(Icon icon) {
mIcon = icon;
return this;
}
/**
* Sets the title of a shortcut. This is a mandatory field.
*/
@NonNull
public Builder setTitle(@NonNull String title) {
mTitle = Preconditions.checkStringNotEmpty(title, "title");
return this;
}
/**
* Sets the intent of a shortcut. This is a mandatory field. The extras must only contain
* persistable information. (See {@link PersistableBundle}).
*/
@NonNull
public Builder setIntent(@NonNull Intent intent) {
mIntent = Preconditions.checkNotNull(intent, "intent");
return this;
}
/**
* Optionally sets the weight of a shortcut, which will be used by Launcher for sorting.
* The larger the weight, the more "important" a shortcut is.
*/
@NonNull
public Builder setWeight(int weight) {
mWeight = weight;
return this;
}
/**
* Optional values that application can set.
* TODO: reserve keys starting with "android."
*/
@NonNull
public Builder setExtras(@NonNull PersistableBundle extras) {
mExtras = extras;
return this;
}
/**
* Creates a {@link ShortcutInfo} instance.
*/
@NonNull
public ShortcutInfo build() {
return new ShortcutInfo(this);
}
}
/**
* Return the ID of the shortcut.
*/
@NonNull
public String getId() {
return mId;
}
/**
* Return the ID of the shortcut.
*/
@NonNull
public String getPackageName() {
return mPackageName;
}
/**
* Return the target activity, which may be null, in which case the shortcut is not associated
* with a specific activity.
*/
@Nullable
public ComponentName getActivityComponent() {
return mActivityComponent;
}
/**
* Icon.
*
* For performance reasons, this will <b>NOT</b> be available when an instance is returned
* by {@link ShortcutManager} or {@link LauncherApps}. A launcher application needs to use
* other APIs in LauncherApps to fetch the bitmap. TODO Add a precondition for it.
*
* @hide
*/
@Nullable
public Icon getIcon() {
return mIcon;
}
/**
* Return the shortcut title.
*/
@NonNull
public String getTitle() {
return mTitle;
}
/**
* Return the intent.
* TODO Set mIntentPersistableExtras and before returning.
*/
@NonNull
public Intent getIntent() {
return mIntent;
}
/** @hide */
@Nullable
public PersistableBundle getIntentPersistableExtras() {
return mIntentPersistableExtras;
}
/**
* Return the weight of a shortcut, which will be used by Launcher for sorting.
* The larger the weight, the more "important" a shortcut is.
*/
public int getWeight() {
return mWeight;
}
/**
* Optional values that application can set.
*/
@Nullable
public PersistableBundle getExtras() {
return mExtras;
}
/**
* Last time when any of the fields was updated.
*/
public long getLastChangedTimestamp() {
return mLastChangedTimestamp;
}
/** @hide */
@ShortcutFlags
public int getFlags() {
return mFlags;
}
/** @hide*/
public void setFlags(@ShortcutFlags int flags) {
mFlags = flags;
}
/** @hide*/
public void addFlags(@ShortcutFlags int flags) {
mFlags |= flags;
}
/** @hide*/
public void clearFlags(@ShortcutFlags int flags) {
mFlags &= ~flags;
}
/** @hide*/
public boolean hasFlags(@ShortcutFlags int flags) {
return (mFlags & flags) == flags;
}
/** Return whether a shortcut is dynamic. */
public boolean isDynamic() {
return hasFlags(FLAG_DYNAMIC);
}
/** Return whether a shortcut is pinned. */
public boolean isPinned() {
return hasFlags(FLAG_PINNED);
}
/**
* Return whether a shortcut's icon is a resource in the owning package.
*
* @see LauncherApps#getShortcutIconResId(ShortcutInfo, UserHandle)
*/
public boolean hasIconResource() {
return hasFlags(FLAG_HAS_ICON_RES);
}
/**
* Return whether a shortcut's icon is stored as a file.
*
* @see LauncherApps#getShortcutIconFd(ShortcutInfo, UserHandle)
*/
public boolean hasIconFile() {
return hasFlags(FLAG_HAS_ICON_FILE);
}
/** @hide */
public void updateTimestamp() {
mLastChangedTimestamp = System.currentTimeMillis();
}
/** @hide */
// VisibleForTesting
public void setTimestamp(long value) {
mLastChangedTimestamp = value;
}
/** @hide */
public void setIcon(Icon icon) {
mIcon = icon;
}
/** @hide */
public void setTitle(String title) {
mTitle = title;
}
/** @hide */
public void setIntent(Intent intent) {
mIntent = intent;
}
/** @hide */
public void setIntentPersistableExtras(PersistableBundle intentPersistableExtras) {
mIntentPersistableExtras = intentPersistableExtras;
}
/** @hide */
public void setWeight(int weight) {
mWeight = weight;
}
/** @hide */
public void setExtras(PersistableBundle extras) {
mExtras = extras;
}
/** @hide */
public int getIconResourceId() {
return mIconResourceId;
}
/** @hide */
public String getBitmapPath() {
return mBitmapPath;
}
/** @hide */
public void setBitmapPath(String bitmapPath) {
mBitmapPath = bitmapPath;
}
private ShortcutInfo(Parcel source) {
final ClassLoader cl = getClass().getClassLoader();
mId = source.readString();
mPackageName = source.readString();
mActivityComponent = source.readParcelable(cl);
mIcon = source.readParcelable(cl);
mTitle = source.readString();
mIntent = source.readParcelable(cl);
mIntentPersistableExtras = source.readParcelable(cl);
mWeight = source.readInt();
mExtras = source.readParcelable(cl);
mLastChangedTimestamp = source.readLong();
mFlags = source.readInt();
mIconResourceId = source.readInt();
mBitmapPath = source.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mPackageName);
dest.writeParcelable(mActivityComponent, flags);
dest.writeParcelable(mIcon, flags);
dest.writeString(mTitle);
dest.writeParcelable(mIntent, flags);
dest.writeParcelable(mIntentPersistableExtras, flags);
dest.writeInt(mWeight);
dest.writeParcelable(mExtras, flags);
dest.writeLong(mLastChangedTimestamp);
dest.writeInt(mFlags);
dest.writeInt(mIconResourceId);
dest.writeString(mBitmapPath);
}
public static final Creator<ShortcutInfo> CREATOR =
new Creator<ShortcutInfo>() {
public ShortcutInfo createFromParcel(Parcel source) {
return new ShortcutInfo(source);
}
public ShortcutInfo[] newArray(int size) {
return new ShortcutInfo[size];
}
};
@Override
public int describeContents() {
return 0;
}
/**
* Return a string representation, intended for logging. Some fields will be retracted.
*/
@Override
public String toString() {
return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
}
/** @hide */
public String toInsecureString() {
return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
}
private String toStringInner(boolean secure, boolean includeInternalData) {
final StringBuilder sb = new StringBuilder();
sb.append("ShortcutInfo {");
sb.append("id=");
sb.append(secure ? "***" : mId);
sb.append(", packageName=");
sb.append(mPackageName);
if (isDynamic()) {
sb.append(", dynamic");
}
if (isPinned()) {
sb.append(", pinned");
}
sb.append(", activity=");
sb.append(mActivityComponent);
sb.append(", title=");
sb.append(secure ? "***" : mTitle);
sb.append(", icon=");
sb.append(mIcon);
sb.append(", weight=");
sb.append(mWeight);
sb.append(", timestamp=");
sb.append(mLastChangedTimestamp);
sb.append(", intent=");
sb.append(mIntent);
sb.append(", intentExtras=");
sb.append(secure ? "***" : mIntentPersistableExtras);
sb.append(", extras=");
sb.append(mExtras);
if (includeInternalData) {
sb.append(", flags=");
sb.append(mFlags);
sb.append(", iconRes=");
sb.append(mIconResourceId);
sb.append(", bitmapPath=");
sb.append(mBitmapPath);
}
sb.append("}");
return sb.toString();
}
/** @hide */
public ShortcutInfo(String id, String packageName, ComponentName activityComponent,
Icon icon, String title, Intent intent, PersistableBundle intentPersistableExtras,
int weight, PersistableBundle extras, long lastChangedTimestamp,
int flags, int iconResId, String bitmapPath) {
mId = id;
mPackageName = packageName;
mActivityComponent = activityComponent;
mIcon = icon;
mTitle = title;
mIntent = intent;
mIntentPersistableExtras = intentPersistableExtras;
mWeight = weight;
mExtras = extras;
mLastChangedTimestamp = lastChangedTimestamp;
mFlags = flags;
mIconResourceId = iconResId;
mBitmapPath = bitmapPath;
}
}

View File

@@ -0,0 +1,247 @@
/*
* Copyright (C) 2016 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.annotation.NonNull;
import android.content.Context;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
/**
* TODO Enhance javadoc
*
* {@link ShortcutManager} manages shortcuts created by applications.
*
* <h3>Dynamic shortcuts and pinned shortcuts</h3>
*
* An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and
* {@link #addDynamicShortcut(ShortcutInfo)}. There can be at most
* {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same
* application.
* A dynamic shortcut can be deleted with {@link #deleteDynamicShortcut(String)}, and apps
* can also use {@link #deleteAllDynamicShortcuts()} to delete all dynamic shortcuts.
*
* <p>The shortcuts that are currently published by the above APIs are called "dynamic", because
* they can be removed by the creator application at any time. The user may "pin" dynamic shortcuts
* on Launcher to make "pinned" shortcuts. Pinned shortcuts <b>cannot</b> be removed by the creator
* app. An application can obtain all pinned shortcuts from itself with
* {@link #getPinnedShortcuts()}. Applications should keep the pinned shortcut information
* up-to-date using {@link #updateShortcuts(List)}.
*
* <p>The number of pinned shortcuts does not affect the number of dynamic shortcuts that can be
* published by an application at a time.
* No matter how many pinned shortcuts that Launcher has for an application, the
* application can still always publish {@link #getMaxDynamicShortcutCount()} number of dynamic
* shortcuts.
*
* <h3>Shortcut IDs</h3>
*
* Each shortcut must have an ID, which must be unique within each application. When a shortcut is
* published, existing shortcuts with the same ID will be updated. Note this may include a
* pinned shortcut.
*
* <h3>Rate limiting</h3>
*
* Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcut(ShortcutInfo)},
* and {@link #updateShortcuts(List)} will be
* rate-limited. An application can call these methods at most
* {@link #getRemainingCallCount()} times until the rate-limiting counter is reset,
* which happens at a certain time every day.
*
* <p>An applications can use {@link #getRateLimitResetTime()} to get the next reset time.
*
* <h3>Backup and Restore</h3>
*
* Shortcuts will be backed up and restored across devices. This means all information, including
* IDs, must be meaningful on a different device.
*
* TODO: Define a Broadcast to let apps update shortcuts on a restored device.
*
* <h3>APIs for launcher</h3>
*
* Launcher applications should use {@link LauncherApps} to get shortcuts that are published from
* applications. Launcher applications can also pin shortcuts with
* {@link LauncherApps#pinShortcuts(String, List, UserHandle)}.
*/
public class ShortcutManager {
private static final String TAG = "ShortcutManager";
private final Context mContext;
private final IShortcutService mService;
/**
* @hide
*/
public ShortcutManager(Context context, IShortcutService service) {
mContext = context;
mService = service;
}
/**
* Publish a list of shortcuts. All existing dynamic shortcuts from the caller application
* will be replaced.
*
* <p>This API will be rate-limited.
*
* @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
*
* @throws IllegalArgumentException if {@code shortcutInfoList} contains more than
* {@link #getMaxDynamicShortcutCount()} shortcuts.
*/
public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
try {
return mService.setDynamicShortcuts(mContext.getPackageName(),
new ParceledListSlice(shortcutInfoList), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return all dynamic shortcuts from the caller application. The number of result items
* will not exceed the value returned by {@link #getMaxDynamicShortcutCount()}.
*/
@NonNull
public List<ShortcutInfo> getDynamicShortcuts() {
try {
return mService.getDynamicShortcuts(mContext.getPackageName(), injectMyUserId())
.getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Publish a single dynamic shortcut. If there's already dynamic or pinned shortcuts with
* the same ID, they will all be updated.
*
* <p>This API will be rate-limited.
*
* @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
*
* @throws IllegalArgumentException if the caller application has already published the
* max number of dynamic shortcuts.
*/
public boolean addDynamicShortcut(@NonNull ShortcutInfo shortcutInfo) {
try {
return mService.addDynamicShortcut(
mContext.getPackageName(), shortcutInfo, injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Delete a single dynamic shortcut by ID.
*/
public void deleteDynamicShortcut(@NonNull String shortcutId) {
try {
mService.deleteDynamicShortcut(mContext.getPackageName(), shortcutId, injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Delete all dynamic shortcuts from the caller application.
*/
public void deleteAllDynamicShortcuts() {
try {
mService.deleteAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return all pinned shortcuts from the caller application.
*/
@NonNull
public List<ShortcutInfo> getPinnedShortcuts() {
try {
return mService.getPinnedShortcuts(mContext.getPackageName(), injectMyUserId())
.getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Update all existing shortcuts with the same IDs. Shortcuts may be pinned and/or dynamic.
*
* <p>This API will be rate-limited.
*
* @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
*/
public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) {
try {
return mService.updateShortcuts(mContext.getPackageName(),
new ParceledListSlice(shortcutInfoList), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the max number of dynamic shortcuts that each application can have at a time.
*/
public int getMaxDynamicShortcutCount() {
try {
return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the number of times the caller application can call the rate-limited APIs
* before the rate limit counter is reset.
*
* @see #getRateLimitResetTime()
*/
public int getRemainingCallCount() {
try {
return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return when the rate limit count will be reset next time, in milliseconds since the epoch.
*
* @see #getRemainingCallCount()
* @see System#currentTimeMillis()
*/
public long getRateLimitResetTime() {
try {
return mService.getRateLimitResetTime(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide injection point */
@VisibleForTesting
protected int injectMyUserId() {
return UserHandle.myUserId();
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2016 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.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.LauncherApps.ShortcutQuery;
import java.util.List;
/**
* Entry points used by {@link LauncherApps}.
*
* <p>No permission / argument checks will be performed inside.
* Callers must check the calling app permission and the calling package name.
* @hide
*/
public abstract class ShortcutServiceInternal {
public interface ShortcutChangeListener {
void onShortcutChanged(@NonNull String packageName,
@NonNull List<ShortcutInfo> shortcuts, @UserIdInt int userId);
}
public abstract List<ShortcutInfo>
getShortcuts(@NonNull String callingPackage, long changedSince,
@Nullable String packageName, @Nullable ComponentName componentName,
@ShortcutQuery.QueryFlags int flags,
int userId);
public abstract List<ShortcutInfo>
getShortcutInfo(@NonNull String callingPackage,
@NonNull String packageName, @Nullable List<String> ids, int userId);
public abstract void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName,
@NonNull List<String> shortcutIds, int userId);
public abstract Intent createShortcutIntent(@NonNull String callingPackage,
@NonNull ShortcutInfo shortcut, int userId);
public abstract void addListener(@NonNull ShortcutChangeListener listener);
}

View File

@@ -235,6 +235,12 @@ public class BaseBundle {
return mParcelledData != null;
}
/** @hide */
ArrayMap<String, Object> getMap() {
unparcel();
return mMap;
}
/**
* Returns the number of mappings contained in this Bundle.
*

View File

@@ -24,9 +24,6 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* A mapping from String values to various types that can be saved to persistent and later
@@ -82,6 +79,18 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
super(b);
}
/**
* Constructs a PersistableBundle from a Bundle.
*
* @param b a Bundle to be copied.
*
* @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
*/
public PersistableBundle(Bundle b) {
this(b.getMap());
}
/**
* Constructs a PersistableBundle containing the mappings passed in.
*
@@ -101,6 +110,8 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
if (value instanceof ArrayMap) {
// Fix up any Maps by replacing them with PersistableBundles.
mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
} else if (value instanceof Bundle) {
mMap.setValueAt(i, new PersistableBundle(((Bundle) value)));
} else if (!isValidType(value)) {
throw new IllegalArgumentException("Bad value in PersistableBundle key="
+ mMap.keyAt(i) + " value=" + value);

View File

@@ -16,6 +16,8 @@
package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
@@ -27,8 +29,12 @@ import android.content.pm.IOnAppsChangedListener;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.net.Uri;
@@ -44,15 +50,16 @@ import android.util.Log;
import android.util.Slog;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.util.List;
/**
* Service that manages requests and callbacks for launchers that support
* managed profiles.
* managed profiles.
*/
public class LauncherAppsService extends SystemService {
private final LauncherAppsImpl mLauncherAppsImpl;
@@ -67,21 +74,25 @@ public class LauncherAppsService extends SystemService {
publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl);
}
class LauncherAppsImpl extends ILauncherApps.Stub {
static class LauncherAppsImpl extends ILauncherApps.Stub {
private static final boolean DEBUG = false;
private static final String TAG = "LauncherAppsService";
private final Context mContext;
private final PackageManager mPm;
private final UserManager mUm;
private final ShortcutServiceInternal mShortcutServiceInternal;
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
private MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
public LauncherAppsImpl(Context context) {
mContext = context;
mPm = mContext.getPackageManager();
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mShortcutServiceInternal = Preconditions.checkNotNull(
LocalServices.getService(ShortcutServiceInternal.class));
mShortcutServiceInternal.addListener(mPackageMonitor);
}
/*
@@ -174,6 +185,20 @@ public class LauncherAppsService extends SystemService {
}
}
private void verifyCallingPackage(String callingPackage) {
int packageUid = -1;
try {
packageUid = mPm.getPackageUid(callingPackage,
PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
Log.e(TAG, "Package not found: " + callingPackage);
}
if (packageUid != Binder.getCallingUid()) {
throw new SecurityException("Calling package name mismatch");
}
}
/**
* Checks if the user is enabled.
*/
@@ -264,6 +289,57 @@ public class LauncherAppsService extends SystemService {
}
}
private void enforceShortcutPermission(UserHandle user) {
ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
// STOPSHIP Implement it
}
@Override
public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
String packageName, ComponentName componentName, int flags, UserHandle user)
throws RemoteException {
enforceShortcutPermission(user);
verifyCallingPackage(callingPackage);
return new ParceledListSlice<>(
mShortcutServiceInternal.getShortcuts(callingPackage, changedSince, packageName,
componentName, flags, user.getIdentifier()));
}
@Override
public ParceledListSlice getShortcutInfo(String callingPackage, String packageName,
List<String> ids, UserHandle user) throws RemoteException {
enforceShortcutPermission(user);
verifyCallingPackage(callingPackage);
return new ParceledListSlice<>(
mShortcutServiceInternal.getShortcutInfo(callingPackage, packageName,
ids, user.getIdentifier()));
}
@Override
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle user) throws RemoteException {
enforceShortcutPermission(user);
verifyCallingPackage(callingPackage);
mShortcutServiceInternal.pinShortcuts(callingPackage, packageName,
ids, user.getIdentifier());
}
@Override
public void startShortcut(String callingPackage, ShortcutInfo shortcut, Rect sourceBounds,
Bundle startActivityOptions, UserHandle user) throws RemoteException {
enforceShortcutPermission(user);
verifyCallingPackage(callingPackage);
final Intent intent = mShortcutServiceInternal.createShortcutIntent(callingPackage,
shortcut, user.getIdentifier());
// TODO
Slog.e(TAG, "startShortcut() not implemented yet, but the intent is " + intent);
throw new RuntimeException("not implemented yet");
}
@Override
public boolean isActivityEnabled(ComponentName component, UserHandle user)
throws RemoteException {
@@ -355,7 +431,7 @@ public class LauncherAppsService extends SystemService {
}
private class MyPackageMonitor extends PackageMonitor {
private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener {
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
@@ -390,6 +466,8 @@ public class LauncherAppsService extends SystemService {
}
}
// TODO Simplify with lambdas.
@Override
public void onPackageAdded(String packageName, int uid) {
UserHandle user = new UserHandle(getChangingUserId());
@@ -523,6 +601,25 @@ public class LauncherAppsService extends SystemService {
super.onPackagesUnsuspended(packages);
}
@Override
public void onShortcutChanged(@NonNull String packageName,
@NonNull List<ShortcutInfo> shortcuts, @UserIdInt int userId) {
final UserHandle user = UserHandle.of(userId);
final int n = mListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(user, listeningUser, "onShortcutChanged")) continue;
try {
listener.onShortcutChanged(user, packageName,
new ParceledListSlice<>(shortcuts));
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
mListeners.finishBroadcast();
}
}
class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> {

File diff suppressed because it is too large Load Diff

View File

@@ -494,7 +494,6 @@ class Owners {
abstract void writeInner(XmlSerializer out) throws IOException;
abstract boolean readInner(XmlPullParser parser, int depth, String tag);
}
private class DeviceOwnerReadWriter extends FileReadWriter {

View File

@@ -83,6 +83,7 @@ import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
import com.android.server.pm.OtaDexoptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
@@ -726,6 +727,9 @@ public final class SystemServer {
// Always start the Device Policy Manager, so that the API is compatible with
// API8.
mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
// TODO is this a good place?
mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
}
if (!disableSystemUI) {

View File

@@ -106,6 +106,10 @@
<service android:name="com.android.server.job.MockPriorityJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
<activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity" />
<activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity2" />
<activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity3" />
</application>
<instrumentation

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2016 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.pm.ShortcutInfo;
import android.test.AndroidTestCase;
import com.android.server.testutis.TestUtils;
/**
* Tests for {@link ShortcutInfo}.
m FrameworksServicesTests &&
adb install \
-r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
public class ShortcutInfoTest extends AndroidTestCase {
public void testNoId() {
TestUtils.assertExpectException(
IllegalArgumentException.class,
"ID must be provided",
() -> new ShortcutInfo.Builder(mContext).build());
}
// TODO Add more tests.
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2016 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.testutis;
import android.test.MoreAsserts;
import junit.framework.Assert;
public class TestUtils {
private TestUtils() {
}
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
Runnable r) {
assertExpectException(expectedExceptionType, null, r);
}
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
String expectedExceptionMessageRegex, Runnable r) {
try {
r.run();
Assert.fail("Expected exception type " + expectedExceptionType.getClass().getName()
+ " was not thrown");
} catch (Throwable e) {
Assert.assertTrue(
"Expected exception type was " + expectedExceptionType.getClass().getName()
+ " but caught " + e.getClass().getName(),
expectedExceptionType.isAssignableFrom(e.getClass()));
if (expectedExceptionMessageRegex != null) {
MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
}
}
}
}