Adding AppWidgetManager.isRequestPinAppWidgetSupported

Launchers can choose to implement support pin shortcut or pin appwidget
or both by specifying different actions in the intent filter

Test: Automated tests and CTS
Bug: 34174006
Change-Id: I4456dbb943cfba11d465556f0732f0ed629ab3ba
This commit is contained in:
Sunny Goyal
2017-01-24 11:53:54 -08:00
parent 29d20fc0f9
commit 7f7372ae2a
16 changed files with 127 additions and 40 deletions

View File

@@ -6841,6 +6841,7 @@ package android.appwidget {
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public boolean isRequestPinAppWidgetSupported();
method public void notifyAppWidgetViewDataChanged(int[], int);
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -9836,7 +9837,8 @@ package android.content.pm {
method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}

View File

@@ -7177,6 +7177,7 @@ package android.appwidget {
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public boolean isRequestPinAppWidgetSupported();
method public void notifyAppWidgetViewDataChanged(int[], int);
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -10269,7 +10270,8 @@ package android.content.pm {
method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}

View File

@@ -6863,6 +6863,7 @@ package android.appwidget {
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public boolean isRequestPinAppWidgetSupported();
method public void notifyAppWidgetViewDataChanged(int[], int);
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -9864,7 +9865,8 @@ package android.content.pm {
method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}

View File

@@ -1071,6 +1071,18 @@ public class AppWidgetManager {
}
}
/**
* Return {@code TRUE} if the default launcher supports
* {@link #requestPinAppWidget(ComponentName, PendingIntent)}
*/
public boolean isRequestPinAppWidgetSupported() {
try {
return mService.isRequestPinAppWidgetSupported();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Request to pin an app widget on the current launcher. It's up to the launcher to accept this
* request (optionally showing a user confirmation). If the request is accepted, the caller will
@@ -1095,6 +1107,7 @@ public class AppWidgetManager {
*
* @see android.content.pm.ShortcutManager#isRequestPinShortcutSupported()
* @see android.content.pm.ShortcutManager#requestPinShortcut(ShortcutInfo, IntentSender)
* @see #isRequestPinAppWidgetSupported()
*
* @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
* service or when the user is locked.

View File

@@ -71,5 +71,5 @@ interface IShortcutService {
void applyRestore(in byte[] payload, int user);
boolean isRequestPinShortcutSupported(int user);
boolean isRequestPinItemSupported(int user, int requestType);
}

View File

@@ -93,22 +93,40 @@ public class LauncherApps {
* @see #EXTRA_PIN_ITEM_REQUEST
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CONFIRM_PIN_ITEM =
"android.content.pm.action.CONFIRM_PIN_ITEM";
public static final String ACTION_CONFIRM_PIN_SHORTCUT =
"android.content.pm.action.CONFIRM_PIN_SHORTCUT";
/**
* An extra for {@link #ACTION_CONFIRM_PIN_ITEM} containing a
* {@link ShortcutInfo} of the shortcut the publisher app asked to pin.
* Activity Action: For the default launcher to show the confirmation dialog to create
* a pinned app widget.
*
* <p>See the {@link android.appwidget.AppWidgetManager#requestPinAppWidget} javadoc for
* details.
*
* <p>
* Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object,
* and call {@link PinItemRequest#accept(Bundle)}
* if the user accepts. If the user doesn't accept, no further action is required.
*
* @see #EXTRA_PIN_ITEM_REQUEST
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CONFIRM_PIN_APPWIDGET =
"android.content.pm.action.CONFIRM_PIN_APPWIDGET";
/**
* An extra for {@link #ACTION_CONFIRM_PIN_SHORTCUT} &amp; {@link #ACTION_CONFIRM_PIN_APPWIDGET}
* containing a {@link PinItemRequest} of appropriate type asked to pin.
*
* <p>A helper function {@link #getPinItemRequest(Intent)} can be used
* instead of using this constant directly.
*
* @see #ACTION_CONFIRM_PIN_ITEM
* @see #ACTION_CONFIRM_PIN_SHORTCUT
* @see #ACTION_CONFIRM_PIN_APPWIDGET
*/
public static final String EXTRA_PIN_ITEM_REQUEST =
"android.content.pm.extra.PIN_ITEM_REQUEST";
private Context mContext;
private ILauncherApps mService;
private PackageManager mPm;
@@ -1203,8 +1221,9 @@ public class LauncherApps {
}
/**
* Represents a "pin shortcut" request made by an app, which is sent with
* an {@link #ACTION_CONFIRM_PIN_ITEM} intent to the default launcher app.
* Represents a "pin shortcut" or a "pin appwidget" request made by an app, which is sent with
* an {@link #ACTION_CONFIRM_PIN_SHORTCUT} or {@link #ACTION_CONFIRM_PIN_APPWIDGET} intent
* respectively to the default launcher app.
*
* <p>Note the launcher may receive a request to pin a shortcut that is already pinned, because
* the user may actually want to have multiple icons of the same shortcut on the launcher.
@@ -1213,6 +1232,9 @@ public class LauncherApps {
* even if the launcher does not call it, the shortcut is already pinned. Also in this case,
* the {@code options} argument to {@link #accept(Bundle)} will be ignored.
*
* <p>For AppWidget pin requests launcher should send back the appwidget id as an extra for
* {@link #accept(Bundle)} as {@link android.appwidget.AppWidgetManager#EXTRA_APPWIDGET_ID}.
*
* @see #EXTRA_PIN_ITEM_REQUEST
* @see #getPinItemRequest(Intent)
*/

View File

@@ -830,7 +830,8 @@ public class ShortcutManager {
*/
public boolean isRequestPinShortcutSupported() {
try {
return mService.isRequestPinShortcutSupported(injectMyUserId());
return mService.isRequestPinItemSupported(injectMyUserId(),
LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -74,4 +74,6 @@ public abstract class ShortcutServiceInternal {
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable IntentSender resultIntent,
int userId);
public abstract boolean isRequestPinItemSupported(int callingUserId, int requestType);
}

View File

@@ -69,5 +69,6 @@ interface IAppWidgetService {
boolean isBoundWidgetPackage(String packageName, int userId);
boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
in IntentSender resultIntent);
boolean isRequestPinAppWidgetSupported();
}

View File

@@ -42,6 +42,7 @@ import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -1546,6 +1547,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
@Override
public boolean isRequestPinAppWidgetSupported() {
return LocalServices.getService(ShortcutServiceInternal.class)
.isRequestPinItemSupported(UserHandle.getCallingUserId(),
LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET);
}
@Override
public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
IntentSender resultSender) {

View File

@@ -25,7 +25,6 @@ import android.content.pm.IPinItemRequest;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.ShortcutInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
@@ -168,8 +167,8 @@ class ShortcutRequestPinProcessor {
mLock = lock;
}
public boolean isRequestPinnedShortcutSupported(int callingUserId) {
return getRequestPinShortcutConfirmationActivity(callingUserId) != null;
public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
return getRequestPinConfirmationActivity(callingUserId, requestType) != null;
}
/**
@@ -185,8 +184,10 @@ class ShortcutRequestPinProcessor {
// First, make sure the launcher supports it.
// Find the confirmation activity in the default launcher.
final int requestType = inShortcut != null ?
PinItemRequest.REQUEST_TYPE_SHORTCUT : PinItemRequest.REQUEST_TYPE_APPWIDGET;
final Pair<ComponentName, Integer> confirmActivity =
getRequestPinShortcutConfirmationActivity(userId);
getRequestPinConfirmationActivity(userId, requestType);
// If the launcher doesn't support it, just return a rejected result and finish.
if (confirmActivity == null) {
@@ -210,7 +211,8 @@ class ShortcutRequestPinProcessor {
request = new PinItemRequest(inAppWidget,
new PinItemRequestInner(this, resultIntent, launcherUid));
}
return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request);
return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request,
requestType);
}
/**
@@ -330,9 +332,13 @@ class ShortcutRequestPinProcessor {
}
private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId,
PinItemRequest request) {
PinItemRequest request, int requestType) {
final String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
// Start the activity.
final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM);
final Intent confirmIntent = new Intent(action);
confirmIntent.setComponent(activity);
confirmIntent.putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request);
confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -351,13 +357,13 @@ class ShortcutRequestPinProcessor {
}
/**
* Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} in the
* Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} in the
* default launcher.
*/
@Nullable
@VisibleForTesting
Pair<ComponentName, Integer> getRequestPinShortcutConfirmationActivity(
int callingUserId) {
Pair<ComponentName, Integer> getRequestPinConfirmationActivity(
int callingUserId, int requestType) {
// Find the default launcher.
final int launcherUserId = mService.getParentOrSelfUserId(callingUserId);
final ComponentName defaultLauncher = mService.getDefaultLauncher(launcherUserId);
@@ -367,7 +373,7 @@ class ShortcutRequestPinProcessor {
return null;
}
final ComponentName activity = mService.injectGetPinConfirmationActivity(
defaultLauncher.getPackageName(), launcherUserId);
defaultLauncher.getPackageName(), launcherUserId, requestType);
return (activity == null) ? null : Pair.create(activity, launcherUserId);
}

View File

@@ -2116,10 +2116,11 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Override
public boolean isRequestPinShortcutSupported(int callingUserId) {
public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
final long token = injectClearCallingIdentity();
try {
return mShortcutRequestPinProcessor.isRequestPinnedShortcutSupported(callingUserId);
return mShortcutRequestPinProcessor
.isRequestPinItemSupported(callingUserId, requestType);
} finally {
injectRestoreCallingIdentity(token);
}
@@ -2620,6 +2621,11 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkNotNull(appWidget);
return requestPinItem(callingPackage, userId, null, appWidget, resultIntent);
}
@Override
public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType);
}
}
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -3241,16 +3247,19 @@ public class ShortcutService extends IShortcutService.Stub {
}
/**
* Get the {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} activity in a given package.
* Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or
* {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on
* the requestType.
*/
@Nullable
ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
int launcherUserId) {
int launcherUserId, int requestType) {
Preconditions.checkNotNull(launcherPackageName);
String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM);
confirmIntent.setPackage(launcherPackageName);
final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName);
final List<ResolveInfo> candidates = queryActivities(
confirmIntent, launcherUserId, /* exportedOnly =*/ false);
for (ResolveInfo ri : candidates) {

View File

@@ -36,6 +36,7 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutServiceInternal;
import android.os.Handler;
import android.os.UserHandle;
@@ -121,6 +122,18 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
assertEquals(provider, providerCaptor.getValue().provider);
}
public void testIsRequestPinAppWidgetSupported() {
ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
// Set up users.
when(mMockShortcutService.isRequestPinItemSupported(anyInt(), anyInt()))
.thenReturn(true, false);
assertTrue(mManager.isRequestPinAppWidgetSupported());
assertFalse(mManager.isRequestPinAppWidgetSupported());
verify(mMockShortcutService, times(2)).isRequestPinItemSupported(anyInt(),
eq(LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET));
}
public void testProviderUpdatesReceived() throws Exception {
int widgetId = setupHostAndWidget();
RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);

View File

@@ -398,7 +398,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
@Override
ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
int launcherUserId) {
int launcherUserId, int requestType) {
return mPinConfirmActivityFetcher.apply(launcherPackageName, launcherUserId);
}

View File

@@ -93,21 +93,24 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
Pair<ComponentName, Integer> actual;
// User 0
actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_0);
actual = mProcessor.getRequestPinConfirmationActivity(USER_0,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_1, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_0, (int) actual.second);
// User 10
actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_2, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_10, (int) actual.second);
// User P0 -> managed profile, return user-0's launcher.
actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0);
actual = mProcessor.getRequestPinConfirmationActivity(USER_P0,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_1, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
@@ -133,15 +136,18 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
// User 10 -- still has confirm activity.
actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_2, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_10, (int) actual.second);
// But user-0 and user p0 no longer has a confirmation activity.
assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_0));
assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0));
assertNull(mProcessor.getRequestPinConfirmationActivity(USER_0,
PinItemRequest.REQUEST_TYPE_SHORTCUT));
assertNull(mProcessor.getRequestPinConfirmationActivity(USER_P0,
PinItemRequest.REQUEST_TYPE_SHORTCUT));
// Check from the public API.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -204,7 +210,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
}
private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction());
assertEquals(LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT, actualIntent.getAction());
assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
actualIntent.getComponent().getClassName());

View File

@@ -72,7 +72,7 @@ public class ShortcutManagerTest9 extends BaseShortcutManagerTest {
}
private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction());
assertEquals(LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET, actualIntent.getAction());
assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
actualIntent.getComponent().getClassName());