diff --git a/src/com/android/settings/shortcut/CreateShortcut.java b/src/com/android/settings/shortcut/CreateShortcut.java index a25e6247f4d..953dfc0eb0c 100644 --- a/src/com/android/settings/shortcut/CreateShortcut.java +++ b/src/com/android/settings/shortcut/CreateShortcut.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; @@ -33,6 +34,7 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.LayerDrawable; import android.net.ConnectivityManager; import android.os.AsyncTask; +import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -52,6 +54,7 @@ import androidx.annotation.VisibleForTesting; public class CreateShortcut extends LauncherActivity { + private static final String TAG = "CreateShortcut"; @VisibleForTesting static final String SHORTCUT_ID_PREFIX = "component-shortcut-"; @@ -76,11 +79,12 @@ public class CreateShortcut extends LauncherActivity { ShortcutManager sm = getSystemService(ShortcutManager.class); ActivityInfo activityInfo = resolveInfo.activityInfo; - Icon maskableIcon = activityInfo.icon != 0 ? Icon.createWithAdaptiveBitmap( - createIcon(activityInfo.icon, + Icon maskableIcon = activityInfo.icon != 0 && activityInfo.applicationInfo != null + ? Icon.createWithAdaptiveBitmap( + createIcon(activityInfo.applicationInfo, activityInfo.icon, R.layout.shortcut_badge_maskable, - getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable))) : - Icon.createWithResource(this, R.drawable.ic_launcher_settings); + getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable))) + : Icon.createWithResource(this, R.drawable.ic_launcher_settings); String shortcutId = SHORTCUT_ID_PREFIX + shortcutIntent.getComponent().flattenToShortString(); ShortcutInfo info = new ShortcutInfo.Builder(this, shortcutId) @@ -98,7 +102,9 @@ public class CreateShortcut extends LauncherActivity { intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); if (activityInfo.icon != 0) { - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(activityInfo.icon, + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon( + activityInfo.applicationInfo, + activityInfo.icon, R.layout.shortcut_badge, getResources().getDimensionPixelSize(R.dimen.shortcut_size))); } @@ -114,20 +120,29 @@ public class CreateShortcut extends LauncherActivity { info.activityInfo.name); } - private Bitmap createIcon(int resource, int layoutRes, int size) { - Context context = new ContextThemeWrapper(this, android.R.style.Theme_Material); - View view = LayoutInflater.from(context).inflate(layoutRes, null); - Drawable iconDrawable = getDrawable(resource); - if (iconDrawable instanceof LayerDrawable) { - iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1); - } - ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable); - - int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + private Bitmap createIcon(ApplicationInfo app, int resource, int layoutRes, int size) { + final Context context = new ContextThemeWrapper(this, android.R.style.Theme_Material); + final View view = LayoutInflater.from(context).inflate(layoutRes, null); + final int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); view.measure(spec, spec); - Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), + final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); + final Canvas canvas = new Canvas(bitmap); + + Drawable iconDrawable = null; + try { + iconDrawable = + getPackageManager().getResourcesForApplication(app).getDrawable(resource); + if (iconDrawable instanceof LayerDrawable) { + iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1); + } + ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon"); + Icon icon = Icon.createWithResource(this, R.drawable.ic_launcher_settings); + ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon); + } + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.draw(canvas); return bitmap; @@ -147,12 +162,15 @@ public class CreateShortcut extends LauncherActivity { * Perform query on package manager for list items. The default * implementation queries for activities. */ + @Override protected List onQueryPackageManager(Intent queryIntent) { List activities = getPackageManager().queryIntentActivities(queryIntent, PackageManager.GET_META_DATA); final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - if (activities == null) return null; + if (activities == null) { + return null; + } for (int i = activities.size() - 1; i >= 0; i--) { ResolveInfo info = activities.get(i); if (info.activityInfo.name.endsWith(TetherSettingsActivity.class.getSimpleName())) { @@ -160,6 +178,10 @@ public class CreateShortcut extends LauncherActivity { activities.remove(i); } } + if (!info.activityInfo.applicationInfo.isSystemApp()) { + Log.d(TAG, "Skipping non-system app: " + info.activityInfo); + activities.remove(i); + } } return activities; } diff --git a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java new file mode 100644 index 00000000000..c56819bef62 --- /dev/null +++ b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2018 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.settings.shortcut; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +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.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; + +import com.android.settings.Settings; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowConnectivityManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowPackageManager; + +import java.util.Arrays; +import java.util.List; + +/** + * Tests for {@link CreateShortcutTest} + */ +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = ShadowConnectivityManager.class) +public class CreateShortcutTest { + + private static final String SHORTCUT_ID_PREFIX = CreateShortcut.SHORTCUT_ID_PREFIX; + + private Context mContext; + private ShadowConnectivityManager mShadowConnectivityManager; + private ShadowPackageManager mPackageManager; + + @Mock + private ShortcutManager mShortcutManager; + @Captor + private ArgumentCaptor> mListCaptor; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mPackageManager = Shadow.extract(mContext.getPackageManager()); + mShadowConnectivityManager = ShadowConnectivityManager.getShadow(); + mShadowConnectivityManager.setTetheringSupported(true); + } + + @Test + public void createResultIntent() { + CreateShortcut orgActivity = Robolectric.setupActivity(CreateShortcut.class); + CreateShortcut activity = spy(orgActivity); + doReturn(mShortcutManager).when(activity).getSystemService(eq(Context.SHORTCUT_SERVICE)); + + when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class))) + .thenReturn(new Intent().putExtra("d1", "d2")); + + final Intent intent = CreateShortcut.getBaseIntent() + .setClass(activity, Settings.ManageApplicationsActivity.class); + final ResolveInfo ri = activity.getPackageManager().resolveActivity(intent, 0); + final Intent result = activity.createResultIntent(intent, ri, "dummy"); + + assertThat(result.getStringExtra("d1")).isEqualTo("d2"); + assertThat((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull(); + + ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(ShortcutInfo.class); + verify(mShortcutManager, times(1)) + .createShortcutResultIntent(infoCaptor.capture()); + assertThat(infoCaptor.getValue().getId()) + .isEqualTo(SHORTCUT_ID_PREFIX + intent.getComponent().flattenToShortString()); + } + + @Test + public void shortcutsUpdateTask() { + mContext = spy(RuntimeEnvironment.application); + doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE)); + final Intent shortcut1 = CreateShortcut.getBaseIntent().setComponent( + new ComponentName(mContext, Settings.ManageApplicationsActivity.class)); + final ResolveInfo ri1 = mock(ResolveInfo.class); + final Intent shortcut2 = CreateShortcut.getBaseIntent().setComponent( + new ComponentName(mContext, Settings.SoundSettingsActivity.class)); + final ResolveInfo ri2 = mock(ResolveInfo.class); + when(ri1.loadLabel(any(PackageManager.class))).thenReturn("label1"); + when(ri2.loadLabel(any(PackageManager.class))).thenReturn("label2"); + mPackageManager.addResolveInfoForIntent(shortcut1, ri1); + mPackageManager.addResolveInfoForIntent(shortcut2, ri2); + + final List pinnedShortcuts = Arrays.asList( + makeShortcut("d1"), + makeShortcut("d2"), + makeShortcut(Settings.ManageApplicationsActivity.class), + makeShortcut("d3"), + makeShortcut(Settings.SoundSettingsActivity.class)); + when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts); + + new CreateShortcut.ShortcutsUpdateTask(mContext).doInBackground(); + + verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture()); + + final List updates = mListCaptor.getValue(); + + assertThat(updates).hasSize(2); + assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId()); + assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId()); + } + + @Test + public void queryActivities_shouldOnlyIncludeSystemApp() { + final ResolveInfo ri1 = new ResolveInfo(); + ri1.activityInfo = new ActivityInfo(); + ri1.activityInfo.name = "activity1"; + ri1.activityInfo.applicationInfo = new ApplicationInfo(); + ri1.activityInfo.applicationInfo.flags = 0; + final ResolveInfo ri2 = new ResolveInfo(); + ri2.activityInfo = new ActivityInfo(); + ri2.activityInfo.name = "activity2"; + ri2.activityInfo.applicationInfo = new ApplicationInfo(); + ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + mPackageManager.addResolveInfoForIntent(CreateShortcut.getBaseIntent(), + Arrays.asList(ri1, ri2)); + + TestClass orgActivity = Robolectric.setupActivity(TestClass.class); + TestClass activity = spy(orgActivity); + + List info = activity.onQueryPackageManager(CreateShortcut.getBaseIntent()); + assertThat(info).hasSize(1); + assertThat(info.get(0)).isEqualTo(ri2); + } + + private ShortcutInfo makeShortcut(Class className) { + ComponentName cn = new ComponentName(mContext, className); + return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString()); + } + + private ShortcutInfo makeShortcut(String id) { + return new ShortcutInfo.Builder(mContext, id).build(); + } + + private static class TestClass extends CreateShortcut { + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java index 62a4e720131..05687f48944 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java @@ -19,8 +19,10 @@ package com.android.settings.testutils.shadow; import android.net.ConnectivityManager; import android.util.SparseBooleanArray; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadow.api.Shadow; @Implements(value = ConnectivityManager.class, inheritImplementationMethods = true) public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowConnectivityManager { @@ -45,4 +47,9 @@ public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowCon public boolean isTetheringSupported() { return mTetheringSupported; } + + public static ShadowConnectivityManager getShadow() { + return Shadow.extract( + RuntimeEnvironment.application.getSystemService(ConnectivityManager.class)); + } } diff --git a/tests/unit/src/com/android/settings/shortcut/CreateShortcutTest.java b/tests/unit/src/com/android/settings/shortcut/CreateShortcutTest.java deleted file mode 100644 index 5ec008b3d5d..00000000000 --- a/tests/unit/src/com/android/settings/shortcut/CreateShortcutTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.settings.shortcut; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; -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.Instrumentation; -import android.content.ComponentName; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.matcher.ViewMatchers; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.settings.R; -import com.android.settings.Settings; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Arrays; -import java.util.List; - -/** - * Tests for {@link CreateShortcutTest} - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class CreateShortcutTest { - - private static final String SHORTCUT_ID_PREFIX = CreateShortcut.SHORTCUT_ID_PREFIX; - - private Instrumentation mInstrumentation; - private Context mContext; - - @Mock - ShortcutManager mShortcutManager; - @Captor - ArgumentCaptor> mListCaptor; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mContext = mInstrumentation.getTargetContext(); - } - - @Test - public void test_layoutDoesNotHaveCancelButton() { - mInstrumentation.startActivitySync(new Intent(Intent.ACTION_CREATE_SHORTCUT) - .setClassName(mContext, CreateShortcut.class.getName()) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - onView(ViewMatchers.withText(R.string.cancel)).check(doesNotExist()); - } - - @Test - public void createResultIntent() { - CreateShortcut orgActivity = (CreateShortcut) mInstrumentation.startActivitySync( - new Intent(Intent.ACTION_CREATE_SHORTCUT) - .setClassName(mContext, CreateShortcut.class.getName()) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - CreateShortcut activity = spy(orgActivity); - doReturn(mShortcutManager).when(activity).getSystemService(eq(Context.SHORTCUT_SERVICE)); - - when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class))) - .thenReturn(new Intent().putExtra("d1", "d2")); - - Intent intent = CreateShortcut.getBaseIntent() - .setClass(activity, Settings.ManageApplicationsActivity.class); - ResolveInfo ri = activity.getPackageManager().resolveActivity(intent, 0); - Intent result = activity.createResultIntent(intent, ri, "dummy"); - assertEquals("d2", result.getStringExtra("d1")); - assertNotNull(result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)); - - ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(ShortcutInfo.class); - verify(mShortcutManager, times(1)) - .createShortcutResultIntent(infoCaptor.capture()); - String expectedId = SHORTCUT_ID_PREFIX + intent.getComponent().flattenToShortString(); - assertEquals(expectedId, infoCaptor.getValue().getId()); - } - - @Test - public void shortcutsUpdateTask() { - mContext = spy(new ContextWrapper(mInstrumentation.getTargetContext())); - doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE)); - - List pinnedShortcuts = Arrays.asList( - makeShortcut("d1"), makeShortcut("d2"), - makeShortcut(Settings.ManageApplicationsActivity.class), - makeShortcut("d3"), - makeShortcut(Settings.SoundSettingsActivity.class)); - when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts); - new CreateShortcut.ShortcutsUpdateTask(mContext).doInBackground(); - - verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture()); - - List updates = mListCaptor.getValue(); - assertEquals(2, updates.size()); - assertEquals(pinnedShortcuts.get(2).getId(), updates.get(0).getId()); - assertEquals(pinnedShortcuts.get(4).getId(), updates.get(1).getId()); - } - - private ShortcutInfo makeShortcut(Class className) { - ComponentName cn = new ComponentName(mContext, className); - return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString()); - } - - private ShortcutInfo makeShortcut(String id) { - return new ShortcutInfo.Builder(mContext, id).build(); - } -}