diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 4c22ee6053cb9..c4bb17cb01dc8 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -34,7 +34,8 @@ android:label="@string/app_name" android:directBootAware="true" android:usesCleartextTraffic="true" - android:icon="@mipmap/ic_launcher_android"> + android:icon="@mipmap/ic_launcher_android" + android:debuggable="true"> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java index 5d1e1644a5b48..220aa33bf497e 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java @@ -58,8 +58,8 @@ import java.util.concurrent.TimeUnit; public class SlicePurchaseActivity extends Activity { private static final String TAG = "SlicePurchaseActivity"; - private @NonNull WebView mWebView; - private @NonNull Context mApplicationContext; + @NonNull private WebView mWebView; + @NonNull private Context mApplicationContext; private int mSubId; @TelephonyManager.PremiumCapability protected int mCapability; diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java index c2ad2c56079bd..dd7b218959bf4 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java @@ -33,6 +33,7 @@ import android.text.TextUtils; import android.util.Log; import android.webkit.WebView; +import com.android.internal.annotations.VisibleForTesting; import com.android.phone.slice.SlicePurchaseController; import java.lang.ref.WeakReference; @@ -291,7 +292,8 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ * * @return The intent to start {@link SlicePurchaseActivity}. */ - @NonNull private PendingIntent createContentIntent(@NonNull Context context, + @VisibleForTesting + @NonNull public PendingIntent createContentIntent(@NonNull Context context, @NonNull Intent intent, int requestCode) { Intent i = new Intent(context, SlicePurchaseActivity.class); i.setComponent(ComponentName.unflattenFromString( @@ -314,7 +316,8 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ * * @return The canceled intent. */ - @NonNull private PendingIntent createCanceledIntent(@NonNull Context context, + @VisibleForTesting + @NonNull public PendingIntent createCanceledIntent(@NonNull Context context, @NonNull Intent intent) { Intent i = new Intent(ACTION_NOTIFICATION_CANCELED); i.setComponent(ComponentName.unflattenFromString( diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp index 54c9016ee53a0..cdf795752e079 100644 --- a/packages/CarrierDefaultApp/tests/unit/Android.bp +++ b/packages/CarrierDefaultApp/tests/unit/Android.bp @@ -27,11 +27,13 @@ android_test { libs: [ "android.test.runner", "android.test.base", + "SlicePurchaseController", ], static_libs: [ "androidx.test.rules", - "mockito-target-minus-junit4", + "mockito-target-inline-minus-junit4", ], + jni_libs: ["libdexmakerjvmtiagent"], // Include all test java files. srcs: ["src/**/*.java"], platform_apis: true, diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java new file mode 100644 index 0000000000000..cecc86d8e330b --- /dev/null +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022 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.carrierdefaultapp; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Looper; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.test.ActivityUnitTestCase; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.phone.slice.SlicePurchaseController; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class SlicePurchaseActivityTest extends ActivityUnitTestCase { + private static final String TAG = "SlicePurchaseActivityTest"; + private static final String URL = "file:///android_asset/slice_purchase_test.html"; + private static final int PHONE_ID = 0; + + @Mock PendingIntent mPendingIntent; + @Mock PendingIntent mCanceledIntent; + @Mock CarrierConfigManager mCarrierConfigManager; + @Mock NotificationManager mNotificationManager; + @Mock PersistableBundle mPersistableBundle; + + private SlicePurchaseActivity mSlicePurchaseActivity; + private Context mContext; + + public SlicePurchaseActivityTest() { + super(SlicePurchaseActivity.class); + } + + @Before + public void setUp() throws Exception { + injectInstrumentation(InstrumentationRegistry.getInstrumentation()); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + super.setUp(); + MockitoAnnotations.initMocks(this); + + // setup context + mContext = spy(getInstrumentation().getTargetContext()); + doReturn(mCarrierConfigManager).when(mContext) + .getSystemService(eq(CarrierConfigManager.class)); + doReturn(URL).when(mPersistableBundle).getString( + CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING); + doReturn(mPersistableBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt()); + doReturn(mNotificationManager).when(mContext) + .getSystemService(eq(NotificationManager.class)); + doReturn(mContext).when(mContext).getApplicationContext(); + setActivityContext(mContext); + + // set up intent + Intent intent = new Intent(); + intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID); + intent.putExtra(SlicePurchaseController.EXTRA_SUB_ID, + SubscriptionManager.getDefaultDataSubscriptionId()); + intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY, + TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY); + intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG); + Intent spiedIntent = spy(intent); + + // set up pending intents + doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage(); + doReturn(true).when(mPendingIntent).isBroadcast(); + doReturn(mPendingIntent).when(spiedIntent).getParcelableExtra( + anyString(), eq(PendingIntent.class)); + doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mCanceledIntent).getCreatorPackage(); + doReturn(true).when(mCanceledIntent).isBroadcast(); + doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra( + eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class)); + + mSlicePurchaseActivity = startActivity(spiedIntent, null, null); + } + + @After + public void tearDown() throws Exception { + mSlicePurchaseActivity.onDestroy(); + super.tearDown(); + } + + @Test + public void testOnPurchaseSuccessful() throws Exception { + int duration = 5 * 60 * 1000; // 5 minutes + int invalidDuration = -1; + mSlicePurchaseActivity.onPurchaseSuccessful(duration); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture()); + Intent intent = intentCaptor.getValue(); + assertEquals(duration, intent.getLongExtra( + SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration)); + } + + @Test + public void testOnPurchaseFailed() throws Exception { + int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE; + String failureReason = "Server unreachable"; + mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture()); + Intent intent = intentCaptor.getValue(); + assertEquals(failureCode, intent.getIntExtra( + SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode)); + assertEquals(failureReason, intent.getStringExtra( + SlicePurchaseController.EXTRA_FAILURE_REASON)); + } + + @Test + public void testOnUserCanceled() throws Exception { + mSlicePurchaseActivity.onDestroy(); + verify(mCanceledIntent).send(); + } +} diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java new file mode 100644 index 0000000000000..5765e5b956a92 --- /dev/null +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2022 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.carrierdefaultapp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.UserHandle; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.DisplayMetrics; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.phone.slice.SlicePurchaseController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class SlicePurchaseBroadcastReceiverTest { + private static final int PHONE_ID = 0; + private static final String TAG = "SlicePurchaseBroadcastReceiverTest"; + private static final String EXTRA = "EXTRA"; + + @Mock Intent mIntent; + @Mock Intent mDataIntent; + @Mock PendingIntent mPendingIntent; + @Mock PendingIntent mCanceledIntent; + @Mock PendingIntent mContentIntent1; + @Mock PendingIntent mContentIntent2; + @Mock Context mContext; + @Mock Resources mResources; + @Mock NotificationManager mNotificationManager; + @Mock ApplicationInfo mApplicationInfo; + @Mock PackageManager mPackageManager; + @Mock DisplayMetrics mDisplayMetrics; + @Mock SlicePurchaseActivity mSlicePurchaseActivity; + + private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver; + private ArgumentCaptor mIntentCaptor; + private ArgumentCaptor mNotificationCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(mNotificationManager).when(mContext) + .getSystemService(eq(NotificationManager.class)); + + mIntentCaptor = ArgumentCaptor.forClass(Intent.class); + mNotificationCaptor = ArgumentCaptor.forClass(Notification.class); + mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver()); + } + + @Test + public void testSendSlicePurchaseAppResponse() throws Exception { + SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA); + verify(mPendingIntent, never()).send(); + + doReturn(mPendingIntent).when(mIntent).getParcelableExtra( + eq(EXTRA), eq(PendingIntent.class)); + SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA); + verify(mPendingIntent).send(); + } + + @Test + public void testSendSlicePurchaseAppResponseWithData() throws Exception { + SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData( + mContext, mIntent, EXTRA, mDataIntent); + verify(mPendingIntent, never()).send(eq(mContext), eq(0), any(Intent.class)); + + doReturn(mPendingIntent).when(mIntent).getParcelableExtra( + eq(EXTRA), eq(PendingIntent.class)); + SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData( + mContext, mIntent, EXTRA, mDataIntent); + verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture()); + assertEquals(mDataIntent, mIntentCaptor.getValue()); + } + + @Test + public void testIsIntentValid() { + assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)); + + // set up intent + doReturn(PHONE_ID).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt()); + doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt()); + doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt()); + doReturn(TAG).when(mIntent).getStringExtra( + eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)); + assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)); + + // set up pending intent + doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage(); + doReturn(true).when(mPendingIntent).isBroadcast(); + doReturn(mPendingIntent).when(mIntent).getParcelableExtra( + anyString(), eq(PendingIntent.class)); + assertTrue(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)); + } + + @Test + public void testDisplayBoosterNotification() { + // set up intent + doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction(); + doReturn(PHONE_ID).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt()); + doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt()); + doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt()); + doReturn(TAG).when(mIntent).getStringExtra( + eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)); + + // set up pending intent + doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage(); + doReturn(true).when(mPendingIntent).isBroadcast(); + doReturn(mPendingIntent).when(mIntent).getParcelableExtra( + anyString(), eq(PendingIntent.class)); + + // set up notification + doReturn(mResources).when(mContext).getResources(); + doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics(); + doReturn("").when(mResources).getString(anyInt()); + doReturn(mApplicationInfo).when(mContext).getApplicationInfo(); + doReturn(mPackageManager).when(mContext).getPackageManager(); + + // set up intents created by broadcast receiver + doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent( + eq(mContext), eq(mIntent), eq(1)); + doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent( + eq(mContext), eq(mIntent), eq(2)); + doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent( + eq(mContext), eq(mIntent)); + + // send ACTION_START_SLICE_PURCHASE_APP + mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent); + + // verify network boost notification was shown + verify(mNotificationManager).notifyAsUser( + eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG), + eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY), + mNotificationCaptor.capture(), + eq(UserHandle.ALL)); + + Notification notification = mNotificationCaptor.getValue(); + assertEquals(mContentIntent1, notification.contentIntent); + assertEquals(mPendingIntent, notification.deleteIntent); + assertEquals(2, notification.actions.length); + assertEquals(mCanceledIntent, notification.actions[0].actionIntent); + assertEquals(mContentIntent2, notification.actions[1].actionIntent); + } + + + @Test + public void testNotificationCanceled() { + // set up intent + doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction(); + doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt()); + + // send ACTION_NOTIFICATION_CANCELED + mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent); + + // verify notification was canceled + verify(mNotificationManager).cancelAsUser( + eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG), + eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY), + eq(UserHandle.ALL)); + } + + @Test + public void testNotificationTimeout() { + // set up intent + doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent) + .getAction(); + doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt()); + + // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT + mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent); + + // verify notification was canceled + verify(mNotificationManager).cancelAsUser( + eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG), + eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY), + eq(UserHandle.ALL)); + } + + @Test + // TODO: WebView/Activity should not close on timeout. + // This test should be removed once implementation is fixed. + public void testActivityTimeout() { + // create and track activity + SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity( + TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity); + + // set up intent + doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent) + .getAction(); + doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra( + eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt()); + + // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT + mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent); + + // verify activity was canceled + verify(mSlicePurchaseActivity).finishAndRemoveTask(); + + // untrack activity + SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity( + TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY); + } +}