1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.content.pm;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNotNull;
22import static org.junit.Assert.assertTrue;
23import static org.mockito.Mockito.any;
24import static org.mockito.Mockito.anyInt;
25import static org.mockito.Mockito.doReturn;
26import static org.mockito.Mockito.eq;
27import static org.mockito.Mockito.isNull;
28import static org.mockito.Mockito.mock;
29import static org.mockito.Mockito.reset;
30import static org.mockito.Mockito.spy;
31import static org.mockito.Mockito.times;
32import static org.mockito.Mockito.verify;
33import static org.mockito.Mockito.when;
34
35import android.annotation.TargetApi;
36import android.app.PendingIntent;
37import android.content.BroadcastReceiver;
38import android.content.Context;
39import android.content.Intent;
40import android.content.IntentFilter;
41import android.content.IntentSender;
42import android.content.pm.ActivityInfo;
43import android.content.pm.PackageManager;
44import android.content.pm.ResolveInfo;
45import android.content.pm.ShortcutInfo;
46import android.content.pm.ShortcutManager;
47import android.graphics.Bitmap;
48import android.os.Build;
49import android.support.test.filters.LargeTest;
50import android.support.test.filters.SmallTest;
51import android.support.test.runner.AndroidJUnit4;
52import android.support.v4.BaseInstrumentationTestCase;
53import android.support.v4.app.TestSupportActivity;
54import android.support.v4.graphics.drawable.IconCompat;
55
56import org.junit.Before;
57import org.junit.Test;
58import org.junit.runner.RunWith;
59import org.mockito.ArgumentCaptor;
60
61import java.util.Arrays;
62import java.util.concurrent.CountDownLatch;
63import java.util.concurrent.TimeUnit;
64
65@RunWith(AndroidJUnit4.class)
66public class ShortcutManagerCompatTest extends BaseInstrumentationTestCase<TestSupportActivity> {
67
68    Context mContext;
69    ShortcutInfoCompat mInfoCompat;
70
71    public ShortcutManagerCompatTest() {
72        super(TestSupportActivity.class);
73    }
74
75    @Before
76    public void setup() {
77        mContext = spy(mActivityTestRule.getActivity());
78        mInfoCompat = new ShortcutInfoCompat.Builder(mContext, "test-id")
79                .setIcon(IconCompat.createWithBitmap(Bitmap.createBitmap(
80                        10, 10, Bitmap.Config.ARGB_8888)))
81                .setShortLabel("Test shortcut")
82                .setIntent(new Intent("Dummy"))
83                .build();
84    }
85
86    @Test
87    @SmallTest
88    @TargetApi(26)
89    public void testIsRequestPinShortcutSupported_v26() throws Throwable {
90        if (!(Build.VERSION.SDK_INT >= 26)) {
91            return;
92        }
93
94        ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
95        doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
96        when(mockShortcutManager.isRequestPinShortcutSupported()).thenReturn(true, false, true);
97
98        assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
99        assertFalse(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
100        assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
101        verify(mockShortcutManager, times(3)).isRequestPinShortcutSupported();
102    }
103
104    @Test
105    @SmallTest
106    @TargetApi(26)
107    public void testRequestPinShortcut_v26()  throws Throwable {
108        if (!(Build.VERSION.SDK_INT >= 26)) {
109            return;
110        }
111
112        ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
113        doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
114        when(mockShortcutManager.requestPinShortcut(
115                any(ShortcutInfo.class), any(IntentSender.class))).thenReturn(true);
116
117        assertTrue(ShortcutManagerCompat.requestPinShortcut(mContext, mInfoCompat, null));
118        ArgumentCaptor<ShortcutInfo> captor = ArgumentCaptor.forClass(ShortcutInfo.class);
119        verify(mockShortcutManager, times(1)).requestPinShortcut(captor.capture(),
120                (IntentSender) isNull());
121        assertEquals("test-id", captor.getValue().getId());
122    }
123
124    @Test
125    @SmallTest
126    @TargetApi(26)
127    public void testCreateShortcutResultIntent_v26()  throws Throwable {
128        if (!(Build.VERSION.SDK_INT >= 26)) {
129            return;
130        }
131
132        ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
133        doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
134
135        when(mockShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class)))
136                .thenReturn(new Intent("some-dummy-action"));
137
138        Intent result = ShortcutManagerCompat.createShortcutResultIntent(mContext, mInfoCompat);
139        verifyLegacyIntent(result);
140        assertEquals("some-dummy-action", result.getAction());
141
142        ArgumentCaptor<ShortcutInfo> captor = ArgumentCaptor.forClass(ShortcutInfo.class);
143        verify(mockShortcutManager, times(1)).createShortcutResultIntent(captor.capture());
144        assertEquals("test-id", captor.getValue().getId());
145    }
146
147    @SmallTest
148    @Test
149    public void testIsRequestPinShortcutSupported_v4() throws Throwable {
150        if (Build.VERSION.SDK_INT >= 26) {
151            return;
152        }
153        setMockPm(mockResolveInfo(null));
154        assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
155
156        // We do not have the permission
157        setMockPm(mockResolveInfo("com.android.permission.something-we-dont-have"));
158        assertFalse(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
159
160        // There are no receivers
161        setMockPm();
162        assertFalse(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
163
164        // At least one receiver is supported
165        setMockPm(mockResolveInfo("com.android.permission.something-we-dont-have"),
166                mockResolveInfo(null));
167        assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
168
169        // We have the permission
170        setMockPm(mockResolveInfo(ShortcutManagerCompat.INSTALL_SHORTCUT_PERMISSION));
171        assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
172    }
173
174    @LargeTest
175    @Test
176    public void testRequestPinShortcut_v4_noCallback()  throws Throwable {
177        if (Build.VERSION.SDK_INT >= 26) {
178            return;
179        }
180
181        setMockPm(mockResolveInfo(null));
182
183        BlockingBroadcastReceiver receiver =
184                new BlockingBroadcastReceiver(ShortcutManagerCompat.ACTION_INSTALL_SHORTCUT);
185        assertTrue(ShortcutManagerCompat.requestPinShortcut(mContext, mInfoCompat, null));
186        verifyLegacyIntent(receiver.blockingGetIntent());
187    }
188
189    @LargeTest
190    @Test
191    public void testRequestPinShortcut_v4_withCallback()  throws Throwable {
192        if (Build.VERSION.SDK_INT >= 26) {
193            return;
194        }
195
196        setMockPm(mockResolveInfo(null));
197
198        BlockingBroadcastReceiver receiver =
199                new BlockingBroadcastReceiver(ShortcutManagerCompat.ACTION_INSTALL_SHORTCUT);
200        BlockingBroadcastReceiver callback =
201                new BlockingBroadcastReceiver("shortcut-callback");
202
203        assertTrue(ShortcutManagerCompat.requestPinShortcut(mContext, mInfoCompat,
204                PendingIntent.getBroadcast(mContext, 0, new Intent("shortcut-callback"),
205                        PendingIntent.FLAG_ONE_SHOT).getIntentSender()));
206        verifyLegacyIntent(receiver.blockingGetIntent());
207        assertNotNull(callback.blockingGetIntent());
208    }
209
210    @SmallTest
211    @Test
212    public void testCreateShortcutResultIntent_v4() throws Throwable {
213        if (Build.VERSION.SDK_INT >= 26) {
214            return;
215        }
216
217        verifyLegacyIntent(ShortcutManagerCompat.createShortcutResultIntent(mContext, mInfoCompat));
218    }
219
220    private void verifyLegacyIntent(Intent intent) {
221        assertNotNull(intent);
222        assertEquals("Test shortcut", intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME));
223        assertEquals("Dummy", ((Intent) intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT))
224                .getAction());
225    }
226
227    private void setMockPm(ResolveInfo... infos) {
228        PackageManager pm = mock(PackageManager.class);
229        when(pm.queryBroadcastReceivers(any(Intent.class), anyInt()))
230                .thenReturn(Arrays.asList(infos));
231        reset(mContext);
232        doReturn(pm).when(mContext).getPackageManager();
233    }
234
235    private ResolveInfo mockResolveInfo(String permission) {
236        ActivityInfo aInfo = new ActivityInfo();
237        aInfo.packageName = mContext.getPackageName();
238        aInfo.permission = permission;
239        ResolveInfo rInfo = new ResolveInfo();
240        rInfo.activityInfo = aInfo;
241        return rInfo;
242    }
243
244    private class BlockingBroadcastReceiver extends BroadcastReceiver {
245
246        private final CountDownLatch mLatch = new CountDownLatch(1);
247        private Intent mIntent;
248
249        BlockingBroadcastReceiver(String action) {
250            mContext.registerReceiver(this, new IntentFilter(action));
251        }
252
253        @Override
254        public void onReceive(Context context, Intent intent) {
255            mIntent = intent;
256            mLatch.countDown();
257        }
258
259        public Intent blockingGetIntent() throws InterruptedException {
260            mLatch.await(5, TimeUnit.SECONDS);
261            mContext.unregisterReceiver(this);
262            return mIntent;
263        }
264    }
265}
266