[go: nahoru, domu]

blob: 3a11957ec59a38c0472c79a42e8203f34efd8127 [file] [log] [blame]
/*
* Copyright (C) 2017 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 androidx.core.content.pm;
import static android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP;
import static android.graphics.drawable.Icon.TYPE_BITMAP;
import static androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_DYNAMIC;
import static androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_MANIFEST;
import static androidx.core.content.pm.ShortcutManagerCompat.FLAG_MATCH_PINNED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.BaseInstrumentationTestCase;
import androidx.core.app.TestActivity;
import androidx.core.content.FileProvider;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.test.R;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class ShortcutManagerCompatTest extends BaseInstrumentationTestCase<TestActivity> {
private static final String SHORTCUT_ICON_PATH = "shortcut_icons";
private static final String TEST_AUTHORITY = "moocow";
Context mContext;
ShortcutInfoCompat mInfoCompat;
ShortcutInfoCompatSaver<Void> mShortcutInfoCompatSaver;
public ShortcutManagerCompatTest() {
super(TestActivity.class);
}
@Before
public void setup() {
mContext = spy(mActivityTestRule.getActivity());
mInfoCompat = new ShortcutInfoCompat.Builder(mContext, "test-id")
.setIcon(IconCompat.createWithBitmap(Bitmap.createBitmap(
10, 10, Bitmap.Config.ARGB_8888)))
.setShortLabel("Test shortcut")
.setIntent(new Intent("Dummy"))
.build();
mShortcutInfoCompatSaver = mock(ShortcutInfoCompatSaver.class);
ShortcutManagerCompat.setShortcutInfoCompatSaver(mShortcutInfoCompatSaver);
}
@Test
@MediumTest
@SdkSuppress(minSdkVersion = 26)
public void testIsRequestPinShortcutSupported_v26() throws Throwable {
ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
when(mockShortcutManager.isRequestPinShortcutSupported()).thenReturn(true, false, true);
assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
assertFalse(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
verify(mockShortcutManager, times(3)).isRequestPinShortcutSupported();
}
@Test
@MediumTest
@SdkSuppress(minSdkVersion = 26)
public void testRequestPinShortcut_v26() throws Throwable {
ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
when(mockShortcutManager.requestPinShortcut(
any(ShortcutInfo.class), nullable(IntentSender.class))).thenReturn(true);
assertTrue(ShortcutManagerCompat.requestPinShortcut(mContext, mInfoCompat, null));
ArgumentCaptor<ShortcutInfo> captor = ArgumentCaptor.forClass(ShortcutInfo.class);
verify(mockShortcutManager, times(1)).requestPinShortcut(captor.capture(),
(IntentSender) isNull());
assertEquals("test-id", captor.getValue().getId());
}
@Test
@MediumTest
@SdkSuppress(minSdkVersion = 26)
public void testCreateShortcutResultIntent_v26() throws Throwable {
ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
when(mockShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class)))
.thenReturn(new Intent("some-dummy-action"));
Intent result = ShortcutManagerCompat.createShortcutResultIntent(mContext, mInfoCompat);
verifyLegacyIntent(result);
assertEquals("some-dummy-action", result.getAction());
ArgumentCaptor<ShortcutInfo> captor = ArgumentCaptor.forClass(ShortcutInfo.class);
verify(mockShortcutManager, times(1)).createShortcutResultIntent(captor.capture());
assertEquals("test-id", captor.getValue().getId());
}
@MediumTest
@Test
@SdkSuppress(maxSdkVersion = 25)
public void testIsRequestPinShortcutSupported_v4() throws Throwable {
setMocks(mockResolveInfo(null));
assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
// We do not have the permission
setMocks(mockResolveInfo("com.android.permission.something-we-dont-have"));
assertFalse(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
// There are no receivers
setMocks();
assertFalse(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
// At least one receiver is supported
setMocks(mockResolveInfo("com.android.permission.something-we-dont-have"),
mockResolveInfo(null));
assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
// We have the permission
setMocks(mockResolveInfo(ShortcutManagerCompat.INSTALL_SHORTCUT_PERMISSION));
assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
}
@LargeTest
@Test
@SdkSuppress(maxSdkVersion = 25)
public void testRequestPinShortcut_v4_noCallback() {
setMocks(mockResolveInfo(null));
assertTrue(ShortcutManagerCompat.requestPinShortcut(mContext, mInfoCompat, null));
final ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
verify(mContext).sendBroadcast(argument.capture());
verifyLegacyIntent(argument.getValue());
}
@LargeTest
@Test
@SdkSuppress(maxSdkVersion = 25)
public void testRequestPinShortcut_v4_withCallback() {
setMocks(mockResolveInfo(null));
assertTrue(ShortcutManagerCompat.requestPinShortcut(mContext, mInfoCompat,
PendingIntent.getBroadcast(mContext, 0, new Intent("shortcut-callback"),
PendingIntent.FLAG_ONE_SHOT).getIntentSender()));
final ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
verify(mContext).sendOrderedBroadcast(argument.capture(), nullable(String.class),
any(BroadcastReceiver.class), nullable(Handler.class), anyInt(),
nullable(String.class), nullable(Bundle.class));
verifyLegacyIntent(argument.getValue());
}
@MediumTest
@Test
@SdkSuppress(maxSdkVersion = 25)
public void testCreateShortcutResultIntent_v4() throws Throwable {
verifyLegacyIntent(ShortcutManagerCompat.createShortcutResultIntent(mContext, mInfoCompat));
}
@LargeTest
@Test
public void testShortcut() {
final List<String> shortcutIds = Lists.newArrayList("test-id");
final String disableMessage = "disabled";
final List<ShortcutInfoCompat> shortcuts = Lists.newArrayList(mInfoCompat);
final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
reset(mockShortcutManager);
reset(mShortcutInfoCompatSaver);
ShortcutManagerCompat.enableShortcuts(mContext, shortcuts);
if (Build.VERSION.SDK_INT >= 25) {
verify(mockShortcutManager).enableShortcuts(shortcutIds);
}
verify(mShortcutInfoCompatSaver).addShortcuts(shortcuts);
reset(mockShortcutManager);
reset(mShortcutInfoCompatSaver);
ShortcutManagerCompat.removeLongLivedShortcuts(mContext, shortcutIds);
if (Build.VERSION.SDK_INT >= 30) {
verify(mockShortcutManager).removeLongLivedShortcuts(shortcutIds);
} else if (Build.VERSION.SDK_INT >= 25) {
verify(mockShortcutManager).removeDynamicShortcuts(shortcutIds);
}
verify(mShortcutInfoCompatSaver).removeShortcuts(shortcutIds);
reset(mockShortcutManager);
reset(mShortcutInfoCompatSaver);
ShortcutManagerCompat.disableShortcuts(mContext, shortcutIds, disableMessage);
if (Build.VERSION.SDK_INT >= 25) {
verify(mockShortcutManager).disableShortcuts(shortcutIds, disableMessage);
}
verify(mShortcutInfoCompatSaver).removeShortcuts(shortcutIds);
reset(mockShortcutManager);
reset(mShortcutInfoCompatSaver);
when(mockShortcutManager.setDynamicShortcuts(ArgumentMatchers.<ShortcutInfo>anyList()))
.thenReturn(true);
ShortcutManagerCompat.setDynamicShortcuts(mContext, shortcuts);
if (Build.VERSION.SDK_INT >= 25) {
verify(mockShortcutManager)
.setDynamicShortcuts(ArgumentMatchers.<ShortcutInfo>anyList());
}
verify(mShortcutInfoCompatSaver).removeAllShortcuts();
verify(mShortcutInfoCompatSaver).addShortcuts(shortcuts);
}
@MediumTest
@Test
public void testGetShortcut() throws Throwable {
final int flag = FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED;
final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
ShortcutManagerCompat.getShortcuts(mContext, flag);
if (Build.VERSION.SDK_INT >= 30) {
verify(mockShortcutManager).getShortcuts(flag);
} else if (Build.VERSION.SDK_INT >= 25) {
verify(mockShortcutManager).getManifestShortcuts();
verify(mockShortcutManager).getDynamicShortcuts();
verify(mockShortcutManager).getPinnedShortcuts();
} else {
verify(mShortcutInfoCompatSaver).getShortcuts();
}
}
@MediumTest
@Test
public void testDynamicShortcuts() {
if (Build.VERSION.SDK_INT >= 25) {
ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
doReturn(mockShortcutManager).when(mContext).getSystemService(
eq(Context.SHORTCUT_SERVICE));
when(mockShortcutManager.addDynamicShortcuts(ArgumentMatchers.<ShortcutInfo>anyList()))
.thenReturn(true);
}
assertTrue(ShortcutManagerCompat.addDynamicShortcuts(mContext, getShortcuts()));
removeShortcuts();
}
@MediumTest
@Test
@SdkSuppress(minSdkVersion = 25)
@FlakyTest
public void testPushDynamicShortcuts() throws Throwable {
// setup mock objects
final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
doReturn(mockShortcutManager).when(mContext).getSystemService(
eq(Context.SHORTCUT_SERVICE));
when(mockShortcutManager.addDynamicShortcuts(ArgumentMatchers.<ShortcutInfo>anyList()))
.thenReturn(true);
when(mockShortcutManager.getMaxShortcutCountPerActivity()).thenReturn(4);
doReturn(getShortcuts()).when(mockShortcutManager).getDynamicShortcuts();
doReturn(getShortcuts()).when(mShortcutInfoCompatSaver).getShortcuts();
// push a new shortcut
final ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(
mContext, "bitmap-shortcut")
.setShortLabel("bitmap")
.setIcon(createBitmapIcon())
.setIntent(new Intent().setAction(Intent.ACTION_DEFAULT))
.setRank(0)
.build();
ShortcutManagerCompat.pushDynamicShortcut(mContext, shortcutInfo);
if (Build.VERSION.SDK_INT >= 30) {
verify(mockShortcutManager).pushDynamicShortcut(any(ShortcutInfo.class));
} else if (Build.VERSION.SDK_INT >= 25) {
// verify the shortcut with lowest rank has been removed
final ArgumentCaptor<List<String>> stringCaptor =
ArgumentCaptor.forClass(ArrayList.class);
verify(mockShortcutManager).removeDynamicShortcuts(stringCaptor.capture());
verifyShortcutRemoved(stringCaptor);
// verify the new shortcut has been added
final ArgumentCaptor<List<ShortcutInfo>> shortcutInfoCaptor =
ArgumentCaptor.forClass(ArrayList.class);
verify(mockShortcutManager).addDynamicShortcuts(shortcutInfoCaptor.capture());
final List<ShortcutInfo> actualShortcutInfos = shortcutInfoCaptor.getValue();
assertEquals(1, actualShortcutInfos.size());
assertEquals(shortcutInfo, actualShortcutInfos.get(0));
}
// verify the shortcut with lowest rank has been removed
final ArgumentCaptor<List<String>> stringCaptor = ArgumentCaptor.forClass(ArrayList.class);
verify(mShortcutInfoCompatSaver).removeShortcuts(stringCaptor.capture());
verifyShortcutRemoved(stringCaptor);
// verify the new shortcut has been added
final ArgumentCaptor<List<ShortcutInfoCompat>> shortcutInfoCaptor =
ArgumentCaptor.forClass(ArrayList.class);
verify(mShortcutInfoCompatSaver).addShortcuts(shortcutInfoCaptor.capture());
final List<ShortcutInfoCompat> actualShortcutInfos = shortcutInfoCaptor.getValue();
assertEquals(1, actualShortcutInfos.size());
assertEquals(shortcutInfo, actualShortcutInfos.get(0));
}
private void verifyShortcutRemoved(final ArgumentCaptor<List<String>> stringCaptor) {
final List<String> actualStrings = stringCaptor.getValue();
assertEquals(1, actualStrings.size());
assertEquals("uri-bitmap-shortcut", actualStrings.get(0));
}
@MediumTest
@Test
public void testConvertUriIconsToBitmapIcons() {
ArrayList<ShortcutInfoCompat> shortcuts = getShortcuts();
assertEquals(5, shortcuts.size());
ShortcutManagerCompat.convertUriIconsToBitmapIcons(mContext, shortcuts);
assertEquals(4, shortcuts.size()); // shortcut with invalid icon uri was removed
for (ShortcutInfoCompat info : shortcuts) {
assertTrue(info.mIcon.mType == TYPE_BITMAP
|| info.mIcon.mType == TYPE_ADAPTIVE_BITMAP);
assertNotNull(info.mIcon.getBitmap());
}
}
@LargeTest
@Test
public void testGetIconDimension() {
assertNotEquals(0, ShortcutManagerCompat.getIconMaxWidth(mContext));
assertNotEquals(0, ShortcutManagerCompat.getIconMaxHeight(mContext));
}
private void verifyLegacyIntent(Intent intent) {
assertNotNull(intent);
assertEquals("Test shortcut", intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME));
assertEquals("Dummy", ((Intent) intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT))
.getAction());
}
private void setMocks(ResolveInfo... infos) {
PackageManager pm = mock(PackageManager.class);
when(pm.queryBroadcastReceivers(any(Intent.class), anyInt()))
.thenReturn(Arrays.asList(infos));
reset(mContext);
doReturn(pm).when(mContext).getPackageManager();
doNothing().when(mContext).sendBroadcast(any(Intent.class));
doNothing().when(mContext).sendOrderedBroadcast(any(Intent.class), nullable(String.class),
any(BroadcastReceiver.class), nullable(Handler.class), anyInt(),
nullable(String.class), nullable(Bundle.class));
}
private ResolveInfo mockResolveInfo(String permission) {
ActivityInfo aInfo = new ActivityInfo();
aInfo.packageName = mContext.getPackageName();
aInfo.permission = permission;
ResolveInfo rInfo = new ResolveInfo();
rInfo.activityInfo = aInfo;
return rInfo;
}
private ArrayList<ShortcutInfoCompat> getShortcuts() {
ArrayList<ShortcutInfoCompat> shortcuts = new ArrayList<>();
shortcuts.add(new ShortcutInfoCompat.Builder(mContext, "bitmap-shortcut")
.setShortLabel("bitmap")
.setIcon(createBitmapIcon())
.setIntent(new Intent().setAction(Intent.ACTION_DEFAULT))
.setRank(0)
.build());
shortcuts.add(new ShortcutInfoCompat.Builder(mContext, "adaptive-bitmap-shortcut")
.setShortLabel("adaptive bitmap")
.setIcon(createAdaptiveBitmapIcon())
.setIntent(new Intent().setAction(Intent.ACTION_DEFAULT))
.setRank(2)
.build());
shortcuts.add(new ShortcutInfoCompat.Builder(mContext, "uri-bitmap-shortcut")
.setShortLabel("uri bitmap")
.setIcon(createUriIcon())
.setIntent(new Intent().setAction(Intent.ACTION_DEFAULT))
.setRank(4)
.build());
shortcuts.add(new ShortcutInfoCompat.Builder(mContext, "uri-adaptive-bitmap-shortcut")
.setShortLabel("uri adaptive bitmap")
.setIcon(createUriAdaptiveIcon())
.setIntent(new Intent().setAction(Intent.ACTION_DEFAULT))
.setRank(4)
.build());
shortcuts.add(new ShortcutInfoCompat.Builder(mContext,
"uri-adaptive-bitmap-shortcut-with-invalid-uri")
.setShortLabel("uri adaptive bitmap with invalid uri")
.setIcon(IconCompat.createWithAdaptiveBitmapContentUri(
"http://non-existing-uri"))
.setIntent(new Intent().setAction(Intent.ACTION_DEFAULT))
.setRank(4)
.build());
return shortcuts;
}
private void removeShortcuts() {
ShortcutManagerCompat.removeAllDynamicShortcuts(mContext);
}
private Bitmap createRawBitmap(String text) {
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
float density = mContext.getResources().getDisplayMetrics().density;
int x = (int) (72 * density);
int y = (int) (72 * density);
Bitmap bmp = Bitmap.createBitmap(x, y, conf);
Canvas canvas = new Canvas(bmp);
canvas.drawColor(Color.BLUE);
Paint mTextPaint = new Paint();
mTextPaint.setColor(Color.RED);
mTextPaint.setTextSize(mContext.getResources().getDimension(R.dimen.text_medium_size));
mTextPaint.setTextAlign(Paint.Align.CENTER);
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2)
- ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
canvas.drawText(text, xPos, yPos, mTextPaint);
return bmp;
}
private Uri bitmapToUri(Bitmap bmp, String name) {
File root = new File(mContext.getFilesDir(), SHORTCUT_ICON_PATH);
if (!root.exists()) {
root.mkdir();
}
File file = new File(root, name);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try (FileOutputStream out = new FileOutputStream(file)) {
bmp.compress(Bitmap.CompressFormat.JPEG, 100, out);
} catch (IOException e) {
e.printStackTrace();
}
return FileProvider.getUriForFile(mContext, TEST_AUTHORITY, file);
}
private IconCompat createBitmapIcon() {
return IconCompat.createWithBitmap(createRawBitmap("B"));
}
private IconCompat createAdaptiveBitmapIcon() {
return IconCompat.createWithAdaptiveBitmap(createRawBitmap("AB"));
}
private IconCompat createUriIcon() {
Uri uri = bitmapToUri(createRawBitmap("U"), "uri-icon");
return IconCompat.createWithContentUri(uri);
}
private IconCompat createUriAdaptiveIcon() {
Uri uri = bitmapToUri(createRawBitmap("AU"), "uri-adaptive-icon");
return IconCompat.createWithAdaptiveBitmapContentUri(uri);
}
}