1 /*
2  * Copyright (C) 2022 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 
17 package com.android.carrierdefaultapp;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.mockito.Mockito.any;
25 import static org.mockito.Mockito.anyInt;
26 import static org.mockito.Mockito.anyString;
27 import static org.mockito.Mockito.clearInvocations;
28 import static org.mockito.Mockito.doCallRealMethod;
29 import static org.mockito.Mockito.doReturn;
30 import static org.mockito.Mockito.eq;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.spy;
33 import static org.mockito.Mockito.verify;
34 
35 import android.annotation.NonNull;
36 import android.app.Notification;
37 import android.app.NotificationManager;
38 import android.app.PendingIntent;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.pm.ApplicationInfo;
42 import android.content.pm.PackageManager;
43 import android.content.res.Configuration;
44 import android.content.res.Resources;
45 import android.os.UserHandle;
46 import android.telephony.SubscriptionManager;
47 import android.telephony.TelephonyManager;
48 
49 import androidx.test.runner.AndroidJUnit4;
50 
51 import com.android.phone.slice.SlicePurchaseController;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.Mock;
58 import org.mockito.MockitoAnnotations;
59 
60 import java.net.URL;
61 import java.util.Locale;
62 
63 @RunWith(AndroidJUnit4.class)
64 public class SlicePurchaseBroadcastReceiverTest {
65     private static final int PHONE_ID = 0;
66     private static final String CARRIER = "Some Carrier";
67     private static final String EXTRA = "EXTRA";
68 
69     @Mock Intent mIntent;
70     @Mock Intent mDataIntent;
71     @Mock PendingIntent mPendingIntent;
72     @Mock PendingIntent mCanceledIntent;
73     @Mock PendingIntent mContentIntent1;
74     @Mock PendingIntent mContentIntent2;
75     @Mock PendingIntent mNotificationShownIntent;
76     @Mock PendingIntent mNotificationsDisabledIntent;
77     @Mock Context mContext;
78     @Mock Resources mResources;
79     @Mock Configuration mConfiguration;
80     @Mock NotificationManager mNotificationManager;
81     @Mock ApplicationInfo mApplicationInfo;
82     @Mock PackageManager mPackageManager;
83 
84     private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
85     private Resources mSpiedResources;
86 
87     @Before
setUp()88     public void setUp() throws Exception {
89         MockitoAnnotations.initMocks(this);
90         mSpiedResources = spy(Resources.getSystem());
91 
92         doReturn("").when(mResources).getString(anyInt());
93         doReturn(mNotificationManager).when(mContext)
94                 .getSystemService(eq(NotificationManager.class));
95         doReturn(true).when(mNotificationManager).areNotificationsEnabled();
96         doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
97         doReturn(mPackageManager).when(mContext).getPackageManager();
98         doReturn(mSpiedResources).when(mContext).getResources();
99 
100         mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
101     }
102 
103     @Test
testSendSlicePurchaseAppResponse()104     public void testSendSlicePurchaseAppResponse() throws Exception {
105         SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
106         verify(mPendingIntent, never()).send();
107 
108         doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
109                 eq(EXTRA), eq(PendingIntent.class));
110         SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
111         verify(mPendingIntent).send();
112     }
113 
114     @Test
testSendSlicePurchaseAppResponseWithData()115     public void testSendSlicePurchaseAppResponseWithData() throws Exception {
116         SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
117                 mContext, mIntent, EXTRA, mDataIntent);
118         verify(mPendingIntent, never()).send(eq(mContext), eq(0), any(Intent.class));
119 
120         doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
121                 eq(EXTRA), eq(PendingIntent.class));
122         SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
123                 mContext, mIntent, EXTRA, mDataIntent);
124         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
125         verify(mPendingIntent).send(eq(mContext), eq(0), captor.capture());
126         assertEquals(mDataIntent, captor.getValue());
127     }
128 
129     @Test
testIsIntentValid()130     public void testIsIntentValid() {
131         assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
132 
133         // set up intent
134         doReturn(PHONE_ID).when(mIntent).getIntExtra(
135                 eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
136         doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
137                 eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
138         doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
139                 eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
140         doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
141                 eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
142         doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
143         assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
144 
145         // set up pending intent
146         doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
147         doReturn(true).when(mPendingIntent).isBroadcast();
148         doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
149                 anyString(), eq(PendingIntent.class));
150         assertTrue(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
151     }
152 
153     @Test
testGetPurchaseUrl()154     public void testGetPurchaseUrl() {
155         String[] invalidUrls = new String[] {
156                 null,
157                 "",
158                 "www.google.com",
159                 "htt://www.google.com",
160                 "http//www.google.com",
161                 "http:/www.google.com",
162                 "file:///android_asset/",
163                 "file:///android_asset/slice_store_test.html"
164         };
165 
166         // test invalid URLs
167         for (String url : invalidUrls) {
168             URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, null, false);
169             assertNull(purchaseUrl);
170         }
171 
172         // test asset URL
173         assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE,
174                 SlicePurchaseBroadcastReceiver.getPurchaseUrl(
175                         SlicePurchaseController.SLICE_PURCHASE_TEST_FILE, null, false).toString());
176 
177         // test normal URL
178         String validUrl = "http://www.google.com";
179         assertEquals(validUrl,
180                 SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, false).toString());
181 
182         // test normal URL with user data but no append
183         String userData = "encryptedUserData=data";
184         assertEquals(validUrl,
185                 SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, false)
186                         .toString());
187 
188         // test normal URL with user data and append
189         assertEquals(validUrl + "?" + userData,
190                 SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, true).toString());
191 
192         // test normal URL without user data and append
193         assertEquals(validUrl,
194                 SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, true).toString());
195     }
196 
197     @Test
testDisplayPerformanceBoostNotification()198     public void testDisplayPerformanceBoostNotification() throws Exception {
199         displayPerformanceBoostNotification();
200 
201         // verify performance boost notification was shown
202         ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
203         verify(mNotificationManager).notifyAsUser(
204                 eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
205                 eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
206                 captor.capture(),
207                 eq(UserHandle.ALL));
208 
209         // verify notification fields
210         Notification notification = captor.getValue();
211         assertEquals(mContentIntent1, notification.contentIntent);
212         assertEquals(mPendingIntent, notification.deleteIntent);
213         assertEquals(2, notification.actions.length);
214         assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
215         assertEquals(mContentIntent2, notification.actions[1].actionIntent);
216 
217         // verify SlicePurchaseController was notified
218         verify(mNotificationShownIntent).send();
219     }
220 
displayPerformanceBoostNotification()221     private void displayPerformanceBoostNotification() {
222         // set up pending intents
223         doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
224         doReturn(true).when(mPendingIntent).isBroadcast();
225         doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
226                 anyString(), eq(PendingIntent.class));
227         createValidPendingIntent(mNotificationShownIntent,
228                 SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
229         createValidPendingIntent(mNotificationsDisabledIntent,
230                 SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
231 
232         // spy notification intents to prevent PendingIntent issues
233         doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
234                 eq(mContext), eq(mIntent), eq(1));
235         doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
236                 eq(mContext), eq(mIntent), eq(2));
237         doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
238                 eq(mContext), eq(mIntent));
239 
240         // spy resources to prevent resource not found issues
241         doReturn(mResources).when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
242 
243         // send ACTION_START_SLICE_PURCHASE_APP
244         doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
245         doReturn(PHONE_ID).when(mIntent).getIntExtra(
246                 eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
247         doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
248                 eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
249         doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
250                 eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
251         doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
252                 eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
253         doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
254         mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
255     }
256 
createValidPendingIntent(@onNull PendingIntent intent, @NonNull String extra)257     private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) {
258         doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage();
259         doReturn(true).when(intent).isBroadcast();
260         doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class));
261     }
262 
263     @Test
testNotificationCanceled()264     public void testNotificationCanceled() {
265         // send ACTION_NOTIFICATION_CANCELED
266         doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
267         doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
268                 eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
269         mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
270 
271         // verify notification was canceled
272         verify(mNotificationManager).cancelAsUser(
273                 eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
274                 eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
275                 eq(UserHandle.ALL));
276     }
277 
278     @Test
testNotificationTimeout()279     public void testNotificationTimeout() throws Exception {
280         displayPerformanceBoostNotification();
281 
282         // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
283         doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
284                 .getAction();
285         doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
286                 eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
287         mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
288 
289         // verify notification was canceled
290         verify(mNotificationManager).cancelAsUser(
291                 eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
292                 eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
293                 eq(UserHandle.ALL));
294     }
295 
296     @Test
testLocaleChanged()297     public void testLocaleChanged() throws Exception {
298         // get previous locale
299         doReturn(mConfiguration).when(mSpiedResources).getConfiguration();
300         Locale before = getLocale();
301 
302         // display notification
303         displayPerformanceBoostNotification();
304         clearInvocations(mNotificationManager);
305         clearInvocations(mNotificationShownIntent);
306 
307         // change current locale from previous value
308         Locale newLocale = Locale.forLanguageTag("en-US");
309         if (before.equals(newLocale)) {
310             newLocale = Locale.forLanguageTag("ko-KR");
311         }
312         doReturn(newLocale).when(mSlicePurchaseBroadcastReceiver).getCurrentLocale();
313 
314         // send ACTION_LOCALE_CHANGED
315         doReturn(Intent.ACTION_LOCALE_CHANGED).when(mIntent).getAction();
316         mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
317 
318         // verify notification was updated and SlicePurchaseController was not notified
319         verify(mNotificationManager).cancelAsUser(
320                 eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
321                 eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
322                 eq(UserHandle.ALL));
323         verify(mNotificationManager).notifyAsUser(
324                 eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
325                 eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
326                 any(Notification.class),
327                 eq(UserHandle.ALL));
328         verify(mNotificationShownIntent, never()).send();
329 
330         // verify locale was changed successfully
331         doCallRealMethod().when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
332         assertEquals(newLocale, getLocale());
333     }
334 
getLocale()335     private Locale getLocale() {
336         try {
337             mSlicePurchaseBroadcastReceiver.getResources(mContext);
338             fail("getLocale should not have completed successfully.");
339         } catch (NullPointerException expected) { }
340         ArgumentCaptor<Locale> captor = ArgumentCaptor.forClass(Locale.class);
341         verify(mConfiguration).setLocale(captor.capture());
342         clearInvocations(mConfiguration);
343         return captor.getValue();
344     }
345 
346     @Test
testNotificationsDisabled()347     public void testNotificationsDisabled() throws Exception {
348         doReturn(false).when(mNotificationManager).areNotificationsEnabled();
349 
350         displayPerformanceBoostNotification();
351 
352         // verify notification was not shown
353         verify(mNotificationManager, never()).notifyAsUser(
354                 eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
355                 eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
356                 any(),
357                 eq(UserHandle.ALL));
358         verify(mNotificationShownIntent, never()).send();
359 
360         // verify SlicePurchaseController was notified that notifications are disabled
361         verify(mNotificationsDisabledIntent).send();
362     }
363 }
364