| /* |
| * Copyright (C) 2015 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.server.telecom.tests; |
| |
| import static junit.framework.Assert.assertEquals; |
| import static junit.framework.Assert.assertTrue; |
| |
| import android.app.BroadcastOptions; |
| import android.app.Notification; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.content.ComponentName; |
| import android.content.ContentProvider; |
| import android.content.Context; |
| import android.content.IContentProvider; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.ICancellationSignal; |
| import android.os.Looper; |
| import android.os.UserHandle; |
| import android.provider.CallLog; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.TelecomManager; |
| import android.telephony.TelephonyManager; |
| import android.test.suitebuilder.annotation.SmallTest; |
| |
| import android.telecom.CallerInfo; |
| import com.android.server.telecom.CallerInfoLookupHelper; |
| import com.android.server.telecom.Constants; |
| import com.android.server.telecom.DefaultDialerCache; |
| import com.android.server.telecom.DeviceIdleControllerAdapter; |
| import com.android.server.telecom.MissedCallNotifier; |
| import com.android.server.telecom.PhoneAccountRegistrar; |
| import com.android.server.telecom.TelecomBroadcastIntentProcessor; |
| import com.android.server.telecom.TelecomSystem; |
| import com.android.server.telecom.components.TelecomBroadcastReceiver; |
| import com.android.server.telecom.ui.MissedCallNotifierImpl; |
| import com.android.server.telecom.ui.MissedCallNotifierImpl.NotificationBuilderFactory; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyLong; |
| import static org.mockito.ArgumentMatchers.nullable; |
| import static org.mockito.Matchers.any; |
| import static org.mockito.Matchers.anyString; |
| import static org.mockito.Matchers.eq; |
| import static org.mockito.Matchers.isNull; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| @RunWith(JUnit4.class) |
| public class MissedCallNotifierImplTest extends TelecomTestCase { |
| |
| private static class MockMissedCallCursorBuilder { |
| private class CallLogRow { |
| String number; |
| int presentation; |
| long date; |
| |
| public CallLogRow(String number, int presentation, long date) { |
| this.number = number; |
| this.presentation = presentation; |
| this.date = date; |
| } |
| } |
| |
| private List<CallLogRow> mRows; |
| |
| MockMissedCallCursorBuilder() { |
| mRows = new LinkedList<>(); |
| mRows.add(null); |
| } |
| |
| public MockMissedCallCursorBuilder addEntry(String number, int presentation, long date) { |
| mRows.add(new CallLogRow(number, presentation, date)); |
| return this; |
| } |
| |
| public Cursor build() { |
| Cursor c = mock(Cursor.class); |
| when(c.moveToNext()).thenAnswer(unused -> { |
| mRows.remove(0); |
| return mRows.size() > 0; |
| }); |
| when(c.getString(MissedCallNotifierImpl.CALL_LOG_COLUMN_NUMBER)) |
| .thenAnswer(unused -> mRows.get(0).number); |
| when(c.getInt(MissedCallNotifierImpl.CALL_LOG_COLUMN_NUMBER_PRESENTATION)) |
| .thenAnswer(unused -> mRows.get(0).presentation); |
| when(c.getLong(MissedCallNotifierImpl.CALL_LOG_COLUMN_DATE)) |
| .thenAnswer(unused -> mRows.get(0).date); |
| return c; |
| } |
| } |
| |
| private static final long TIMEOUT_DELAY = 5000; |
| private static final Uri TEL_CALL_HANDLE = Uri.parse("tel:+11915552620"); |
| private static final Uri SIP_CALL_HANDLE = Uri.parse("sip:testaddress@testdomain.com"); |
| private static final String CALLER_NAME = "Fake Name"; |
| private static final String MISSED_CALL_TITLE = "Missed Call"; |
| private static final String MISSED_CALLS_TITLE = "Missed Calls"; |
| private static final String MISSED_CALLS_MSG = "%s missed calls"; |
| private static final String USER_CALL_ACTIVITY_LABEL = "Phone"; |
| private static final String DEFAULT_DIALER_PACKAGE = "com.android.server.telecom.test"; |
| |
| private static final int REQUEST_ID = 0; |
| private static final long CALL_TIMESTAMP; |
| static { |
| CALL_TIMESTAMP = System.currentTimeMillis() - 60 * 1000 * 5; |
| } |
| |
| private static final UserHandle PRIMARY_USER = UserHandle.of(0); |
| private static final UserHandle SECONARY_USER = UserHandle.of(12); |
| private static final int NO_CAPABILITY = 0; |
| private static final int TEST_TIMEOUT = 1000; |
| private static final long TEST_POWER_EXEMPT_TIME_MS = 1000; |
| private static final ComponentName COMPONENT_NAME = new ComponentName( |
| "com.anything", "com.whatever"); |
| |
| @Mock |
| private NotificationManager mNotificationManager; |
| |
| @Mock |
| private PhoneAccountRegistrar mPhoneAccountRegistrar; |
| |
| @Mock |
| private TelecomManager mTelecomManager; |
| |
| @Mock TelecomSystem mTelecomSystem; |
| @Mock private DefaultDialerCache mDefaultDialerCache; |
| @Mock private DeviceIdleControllerAdapter mDeviceIdleControllerAdapter; |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| super.setUp(); |
| MockitoAnnotations.initMocks(this); |
| |
| mContext = mComponentContextFixture.getTestDouble().getApplicationContext(); |
| mNotificationManager = (NotificationManager) mContext.getSystemService( |
| Context.NOTIFICATION_SERVICE); |
| TelephonyManager fakeTelephonyManager = (TelephonyManager) mContext.getSystemService( |
| Context.TELEPHONY_SERVICE); |
| when(fakeTelephonyManager.getNetworkCountryIso()).thenReturn("US"); |
| doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo(); |
| doReturn("com.android.server.telecom.tests").when(mContext).getPackageName(); |
| |
| mComponentContextFixture.putResource(R.string.notification_missedCallTitle, |
| MISSED_CALL_TITLE); |
| mComponentContextFixture.putResource(R.string.notification_missedCallsTitle, |
| MISSED_CALLS_TITLE); |
| mComponentContextFixture.putResource(R.string.notification_missedCallsMsg, |
| MISSED_CALLS_MSG); |
| mComponentContextFixture.putResource(R.string.userCallActivityLabel, |
| USER_CALL_ACTIVITY_LABEL); |
| mComponentContextFixture.setTelecomManager(mTelecomManager); |
| } |
| |
| @Override |
| @After |
| public void tearDown() throws Exception { |
| TelecomSystem.setInstance(null); |
| when(mTelecomSystem.isBootComplete()).thenReturn(false); |
| super.tearDown(); |
| } |
| |
| @SmallTest |
| @Test |
| public void testCancelNotificationInPrimaryUser() { |
| cancelNotificationTestInternal(PRIMARY_USER); |
| } |
| |
| @SmallTest |
| @Test |
| public void testCancelNotificationInSecondaryUser() { |
| cancelNotificationTestInternal(SECONARY_USER); |
| } |
| |
| @SmallTest |
| @Test |
| public void testDefaultDialerClear() { |
| MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer(); |
| missedCallNotifier.clearMissedCalls(PRIMARY_USER); |
| |
| ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); |
| verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(), |
| anyString(), any()); |
| Intent sentIntent = intentArgumentCaptor.getValue(); |
| assertEquals(0, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testDefaultDialerIncrement() { |
| MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer(); |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, |
| CALL_TIMESTAMP, phoneAccount.getAccountHandle()); |
| |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); |
| verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(), |
| anyString(), any()); |
| |
| Intent sentIntent = intentArgumentCaptor.getValue(); |
| assertEquals(1, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1)); |
| } |
| |
| private MissedCallNotifier setupMissedCallNotificationThroughDefaultDialer() { |
| mComponentContextFixture.addIntentReceiver( |
| TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, COMPONENT_NAME); |
| when(mDefaultDialerCache.getDefaultDialerApplication(anyInt())).thenReturn( |
| DEFAULT_DIALER_PACKAGE); |
| |
| Notification.Builder builder1 = makeNotificationBuilder("builder1"); |
| Notification.Builder builder2 = makeNotificationBuilder("builder2"); |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builder1, builder1, builder2, builder2); |
| return makeMissedCallNotifier(fakeBuilderFactory, PRIMARY_USER); |
| } |
| |
| private void cancelNotificationTestInternal(UserHandle userHandle) { |
| Notification.Builder builder1 = makeNotificationBuilder("builder1"); |
| Notification.Builder builder2 = makeNotificationBuilder("builder2"); |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builder1, builder1, builder2, builder2); |
| |
| MissedCallNotifier missedCallNotifier = makeMissedCallNotifier(fakeBuilderFactory, |
| PRIMARY_USER); |
| PhoneAccount phoneAccount = makePhoneAccount(userHandle, NO_CAPABILITY); |
| MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, |
| CALL_TIMESTAMP, phoneAccount.getAccountHandle()); |
| |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| missedCallNotifier.clearMissedCalls(userHandle); |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| |
| ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass( |
| Integer.class); |
| verify(mNotificationManager, times(2)).notifyAsUser(nullable(String.class), |
| requestIdCaptor.capture(), nullable(Notification.class), eq(userHandle)); |
| verify(mNotificationManager).cancelAsUser(nullable(String.class), |
| eq(requestIdCaptor.getValue()), eq(userHandle)); |
| |
| // Verify that the second call to showMissedCallNotification behaves like it were the first. |
| verify(builder2).setContentText(CALLER_NAME); |
| } |
| |
| @SmallTest |
| @Test |
| public void testNotifyMultipleMissedCalls() { |
| Notification.Builder[] builders = new Notification.Builder[4]; |
| |
| for (int i = 0; i < 4; i++) { |
| builders[i] = makeNotificationBuilder("builder" + Integer.toString(i)); |
| } |
| |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, |
| CALL_TIMESTAMP, phoneAccount.getAccountHandle()); |
| |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builders); |
| |
| MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, |
| mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory, |
| mDeviceIdleControllerAdapter); |
| |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| |
| // The following captor is to capture the two notifications that got passed into |
| // notifyAsUser. This distinguishes between the builders used for the full notification |
| // (i.e. the one potentially containing sensitive information, such as phone numbers), |
| // and the builders used for the notifications shown on the lockscreen, which have been |
| // scrubbed of such sensitive info. The notifications which are used as arguments |
| // to notifyAsUser are the versions which contain sensitive information. |
| ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass( |
| Notification.class); |
| verify(mNotificationManager, times(2)).notifyAsUser(nullable(String.class), eq(1), |
| notificationArgumentCaptor.capture(), eq(PRIMARY_USER)); |
| HashSet<String> privateNotifications = new HashSet<>(); |
| for (Notification n : notificationArgumentCaptor.getAllValues()) { |
| privateNotifications.add(n.toString()); |
| } |
| |
| for (int i = 0; i < 4; i++) { |
| Notification.Builder builder = builders[i]; |
| verify(builder).setWhen(CALL_TIMESTAMP); |
| if (i >= 2) { |
| // The builders after the first two are for multiple missed calls. The notification |
| // for subsequent missed calls is expected to be different in terms of the text |
| // contents of the notification, and that is verified here. |
| if (privateNotifications.contains(builder.toString())) { |
| verify(builder).setContentText(String.format(MISSED_CALLS_MSG, 2)); |
| verify(builder).setContentTitle(MISSED_CALLS_TITLE); |
| } else { |
| verify(builder).setContentText(MISSED_CALLS_TITLE); |
| verify(builder).setContentTitle(USER_CALL_ACTIVITY_LABEL); |
| } |
| verify(builder, never()).addAction(any(Notification.Action.class)); |
| } else { |
| if (privateNotifications.contains(builder.toString())) { |
| verify(builder).setContentText(CALLER_NAME); |
| verify(builder).setContentTitle(MISSED_CALL_TITLE); |
| } else { |
| verify(builder).setContentText(MISSED_CALL_TITLE); |
| verify(builder).setContentTitle(USER_CALL_ACTIVITY_LABEL); |
| } |
| } |
| } |
| } |
| |
| @SmallTest |
| @Test |
| public void testNotifySingleCallInPrimaryUser() { |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); |
| } |
| |
| @SmallTest |
| @Test |
| public void testNotifySingleCallInSecondaryUser() { |
| PhoneAccount phoneAccount = makePhoneAccount(SECONARY_USER, NO_CAPABILITY); |
| notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); |
| } |
| |
| @SmallTest |
| @Test |
| public void testNotifySingleCallInSecondaryUserWithMultiUserCapability() { |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, |
| PhoneAccount.CAPABILITY_MULTI_USER); |
| notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); |
| } |
| |
| @SmallTest |
| @Test |
| public void testNotifySingleCallWhenCurrentUserIsSecondaryUser() { |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| notifySingleCallTestInternal(phoneAccount, SECONARY_USER); |
| } |
| |
| @SmallTest |
| @Test |
| public void testNotifySingleCall() { |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); |
| } |
| |
| private void notifySingleCallTestInternal(PhoneAccount phoneAccount, UserHandle currentUser) { |
| Notification.Builder builder1 = makeNotificationBuilder("builder1"); |
| Notification.Builder builder2 = makeNotificationBuilder("builder2"); |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builder1, builder2); |
| |
| MissedCallNotifier missedCallNotifier = makeMissedCallNotifier(fakeBuilderFactory, |
| currentUser); |
| |
| MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, |
| CALL_TIMESTAMP, phoneAccount.getAccountHandle()); |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| |
| ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass( |
| Notification.class); |
| |
| UserHandle expectedUserHandle; |
| if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { |
| expectedUserHandle = currentUser; |
| } else { |
| expectedUserHandle = phoneAccount.getAccountHandle().getUserHandle(); |
| } |
| verify(mNotificationManager).notifyAsUser(nullable(String.class), eq(1), |
| notificationArgumentCaptor.capture(), eq((expectedUserHandle))); |
| |
| Notification.Builder builder; |
| Notification.Builder publicBuilder; |
| |
| if (notificationArgumentCaptor.getValue().toString().equals("builder1")) { |
| builder = builder1; |
| publicBuilder = builder2; |
| } else { |
| builder = builder2; |
| publicBuilder = builder1; |
| } |
| |
| verify(builder).setWhen(CALL_TIMESTAMP); |
| verify(publicBuilder).setWhen(CALL_TIMESTAMP); |
| |
| verify(builder).setContentText(CALLER_NAME); |
| verify(publicBuilder).setContentText(MISSED_CALL_TITLE); |
| |
| verify(builder).setContentTitle(MISSED_CALL_TITLE); |
| verify(publicBuilder).setContentTitle(USER_CALL_ACTIVITY_LABEL); |
| |
| // Create two intents that correspond to call-back and respond back with SMS, and assert |
| // that these pending intents have in fact been registered. |
| Intent callBackIntent = new Intent( |
| TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, |
| TEL_CALL_HANDLE, |
| mContext, |
| TelecomBroadcastReceiver.class); |
| Intent smsIntent = new Intent( |
| TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION, |
| Uri.fromParts(Constants.SCHEME_SMSTO, TEL_CALL_HANDLE.getSchemeSpecificPart(), null), |
| mContext, |
| TelecomBroadcastReceiver.class); |
| |
| assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, |
| callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE)); |
| assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, |
| smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testNoSmsBackAfterMissedSipCall() { |
| Notification.Builder builder1 = makeNotificationBuilder("builder1"); |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builder1); |
| |
| MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, |
| mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory, |
| mDeviceIdleControllerAdapter); |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| |
| MissedCallNotifier.CallInfo fakeCall = |
| makeFakeCallInfo(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP, |
| phoneAccount.getAccountHandle()); |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| |
| // Create two intents that correspond to call-back and respond back with SMS, and assert |
| // that in the case of a SIP call, no SMS intent is generated. |
| Intent callBackIntent = new Intent( |
| TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, |
| SIP_CALL_HANDLE, |
| mContext, |
| TelecomBroadcastReceiver.class); |
| Intent smsIntent = new Intent( |
| TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION, |
| Uri.fromParts(Constants.SCHEME_SMSTO, SIP_CALL_HANDLE.getSchemeSpecificPart(), |
| null), |
| mContext, |
| TelecomBroadcastReceiver.class); |
| |
| assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, |
| callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE)); |
| assertNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, |
| smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testLoadOneCallFromDb() throws Exception { |
| CallerInfoLookupHelper mockCallerInfoLookupHelper = mock(CallerInfoLookupHelper.class); |
| MissedCallNotifier.CallInfoFactory mockCallInfoFactory = |
| mock(MissedCallNotifier.CallInfoFactory.class); |
| |
| Uri queryUri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, |
| PRIMARY_USER.getIdentifier()); |
| IContentProvider cp = getContentProviderForUser(PRIMARY_USER.getIdentifier()); |
| |
| Cursor mockMissedCallsCursor = new MockMissedCallCursorBuilder() |
| .addEntry(TEL_CALL_HANDLE.getSchemeSpecificPart(), |
| CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP) |
| .build(); |
| |
| when(cp.query(any(), eq(queryUri), nullable(String[].class), |
| nullable(Bundle.class), nullable(ICancellationSignal.class))) |
| .thenReturn(mockMissedCallsCursor); |
| |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| MissedCallNotifier.CallInfo fakeCallInfo = makeFakeCallInfo(TEL_CALL_HANDLE, |
| CALLER_NAME, CALL_TIMESTAMP, phoneAccount.getAccountHandle()); |
| when(mockCallInfoFactory.makeCallInfo(nullable(CallerInfo.class), |
| nullable(PhoneAccountHandle.class), nullable(Uri.class), eq(CALL_TIMESTAMP))) |
| .thenReturn(fakeCallInfo); |
| |
| Notification.Builder builder1 = makeNotificationBuilder("builder1"); |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builder1); |
| |
| MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, |
| mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory, |
| mDeviceIdleControllerAdapter); |
| |
| // AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below |
| // timeout-verify, so run this in a new handler to mitigate that. |
| Handler h = new Handler(Looper.getMainLooper()); |
| h.post(() -> missedCallNotifier.reloadFromDatabase( |
| mockCallerInfoLookupHelper, mockCallInfoFactory, PRIMARY_USER)); |
| waitForHandlerAction(h, TEST_TIMEOUT); |
| |
| // TelecomSystem.getInstance returns null in this test, so we expect that nothing will |
| // happen. |
| verify(mockCallerInfoLookupHelper, never()).startLookup(any(Uri.class), |
| any(CallerInfoLookupHelper.OnQueryCompleteListener.class)); |
| // Simulate a boot-complete |
| TelecomSystem.setInstance(mTelecomSystem); |
| when(mTelecomSystem.isBootComplete()).thenReturn(true); |
| h.post(() -> missedCallNotifier.reloadAfterBootComplete(mockCallerInfoLookupHelper, |
| mockCallInfoFactory)); |
| waitForHandlerAction(h, TEST_TIMEOUT); |
| |
| Uri escapedHandle = Uri.fromParts(PhoneAccount.SCHEME_TEL, |
| TEL_CALL_HANDLE.getSchemeSpecificPart(), null); |
| ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> listenerCaptor = |
| ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class); |
| verify(mockCallerInfoLookupHelper, timeout(TEST_TIMEOUT)).startLookup(eq(escapedHandle), |
| listenerCaptor.capture()); |
| |
| CallerInfo ci = new CallerInfo(); |
| listenerCaptor.getValue().onCallerInfoQueryComplete(escapedHandle, ci); |
| verify(mockCallInfoFactory).makeCallInfo(eq(ci), isNull(PhoneAccountHandle.class), |
| eq(escapedHandle), eq(CALL_TIMESTAMP)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testLoadTwoCallsFromDb() throws Exception { |
| TelecomSystem.setInstance(mTelecomSystem); |
| when(mTelecomSystem.isBootComplete()).thenReturn(true); |
| CallerInfoLookupHelper mockCallerInfoLookupHelper = mock(CallerInfoLookupHelper.class); |
| MissedCallNotifier.CallInfoFactory mockCallInfoFactory = |
| mock(MissedCallNotifier.CallInfoFactory.class); |
| |
| Cursor mockMissedCallsCursor = new MockMissedCallCursorBuilder() |
| .addEntry(TEL_CALL_HANDLE.getSchemeSpecificPart(), |
| CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP) |
| .addEntry(SIP_CALL_HANDLE.getSchemeSpecificPart(), |
| CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP) |
| .build(); |
| |
| Uri queryUri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, |
| PRIMARY_USER.getIdentifier()); |
| IContentProvider cp = getContentProviderForUser(PRIMARY_USER.getIdentifier()); |
| |
| when(cp.query(any(), eq(queryUri), nullable(String[].class), |
| nullable(Bundle.class), nullable(ICancellationSignal.class))) |
| .thenReturn(mockMissedCallsCursor); |
| |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| MissedCallNotifier.CallInfo fakeCallInfo = makeFakeCallInfo(TEL_CALL_HANDLE, |
| CALLER_NAME, CALL_TIMESTAMP, phoneAccount.getAccountHandle()); |
| when(mockCallInfoFactory.makeCallInfo(nullable(CallerInfo.class), |
| nullable(PhoneAccountHandle.class), nullable(Uri.class), eq(CALL_TIMESTAMP))) |
| .thenReturn(fakeCallInfo); |
| |
| Notification.Builder builder1 = makeNotificationBuilder("builder1"); |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builder1); |
| |
| MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, |
| mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory, |
| mDeviceIdleControllerAdapter); |
| |
| // AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below |
| // timeout-verify, so run this in a new handler to mitigate that. |
| Handler h = new Handler(Looper.getMainLooper()); |
| h.post(() -> missedCallNotifier.reloadFromDatabase( |
| mockCallerInfoLookupHelper, mockCallInfoFactory, PRIMARY_USER)); |
| waitForHandlerAction(h, TEST_TIMEOUT); |
| |
| Uri escapedTelHandle = Uri.fromParts(PhoneAccount.SCHEME_TEL, |
| TEL_CALL_HANDLE.getSchemeSpecificPart(), null); |
| Uri escapedSipHandle = Uri.fromParts(PhoneAccount.SCHEME_SIP, |
| SIP_CALL_HANDLE.getSchemeSpecificPart(), null); |
| |
| ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> listenerCaptor = |
| ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class); |
| verify(mockCallerInfoLookupHelper, timeout(TEST_TIMEOUT)).startLookup(eq(escapedTelHandle), |
| listenerCaptor.capture()); |
| verify(mockCallerInfoLookupHelper, timeout(TEST_TIMEOUT)).startLookup(eq(escapedSipHandle), |
| listenerCaptor.capture()); |
| |
| CallerInfo ci = new CallerInfo(); |
| listenerCaptor.getAllValues().get(0).onCallerInfoQueryComplete(escapedTelHandle, ci); |
| listenerCaptor.getAllValues().get(1).onCallerInfoQueryComplete(escapedSipHandle, ci); |
| |
| // Verify that two notifications were generated, both with the same id. |
| verify(mNotificationManager, times(2)).notifyAsUser(nullable(String.class), eq(1), |
| nullable(Notification.class), eq(PRIMARY_USER)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testDialerHandleMissedCall() { |
| // Configure Notifier to send missed call intent and let dialer handle |
| enableDialerHandlesMissedCall(); |
| |
| Notification.Builder builder1 = makeNotificationBuilder("builder1"); |
| MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = |
| makeNotificationBuilderFactory(builder1); |
| |
| MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, |
| mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory, |
| mDeviceIdleControllerAdapter); |
| PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); |
| |
| MissedCallNotifier.CallInfo fakeCall = |
| makeFakeCallInfo(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP, |
| phoneAccount.getAccountHandle()); |
| missedCallNotifier.showMissedCallNotification(fakeCall); |
| |
| ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); |
| ArgumentCaptor<Bundle> bundleCaptor = |
| ArgumentCaptor.forClass(Bundle.class); |
| verify(mDeviceIdleControllerAdapter).exemptAppTemporarilyForEvent( |
| eq(COMPONENT_NAME.getPackageName()), anyLong(), anyInt(), any()); |
| verify(mContext).sendBroadcastAsUser( |
| intentCaptor.capture(), |
| any(), |
| eq(android.Manifest.permission.READ_PHONE_STATE), bundleCaptor.capture()); |
| assertNotNull("Not expecting null intent", intentCaptor.getValue()); |
| assertEquals("Incorrect intent received", |
| TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, |
| intentCaptor.getValue().getAction()); |
| assertNotNull("Not expecting null options bundle", bundleCaptor.getValue()); |
| BroadcastOptions options = new BroadcastOptions(bundleCaptor.getValue()); |
| assertTrue("App must have a temporary exemption set.", |
| options.getTemporaryAppAllowlistDuration() > 0); |
| |
| // A notification should never be posted by Telecom |
| verify(mNotificationManager, never()).notifyAsUser(nullable(String.class), anyInt(), |
| nullable(Notification.class), eq(PRIMARY_USER)); |
| } |
| |
| private Notification.Builder makeNotificationBuilder(String label) { |
| Notification.Builder builder = spy(new Notification.Builder(mContext)); |
| Notification notification = mock(Notification.class); |
| when(notification.toString()).thenReturn(label); |
| when(builder.toString()).thenReturn(label); |
| doReturn(notification).when(builder).build(); |
| return builder; |
| } |
| |
| private MissedCallNotifier.CallInfo makeFakeCallInfo(Uri handle, String name, long timestamp, |
| PhoneAccountHandle phoneAccountHandle) { |
| MissedCallNotifier.CallInfo fakeCall = mock(MissedCallNotifier.CallInfo.class); |
| when(fakeCall.getHandle()).thenReturn(handle); |
| when(fakeCall.getHandleSchemeSpecificPart()).thenReturn(handle.getSchemeSpecificPart()); |
| when(fakeCall.getName()).thenReturn(name); |
| when(fakeCall.getCreationTimeMillis()).thenReturn(timestamp); |
| when(fakeCall.getPhoneAccountHandle()).thenReturn(phoneAccountHandle); |
| return fakeCall; |
| } |
| |
| private MissedCallNotifierImpl.NotificationBuilderFactory makeNotificationBuilderFactory( |
| Notification.Builder... builders) { |
| MissedCallNotifierImpl.NotificationBuilderFactory builderFactory = |
| mock(MissedCallNotifierImpl.NotificationBuilderFactory.class); |
| when(builderFactory.getBuilder(mContext)).thenReturn(builders[0], |
| Arrays.copyOfRange(builders, 1, builders.length)); |
| return builderFactory; |
| } |
| |
| private MissedCallNotifier makeMissedCallNotifier( |
| NotificationBuilderFactory fakeBuilderFactory, UserHandle currentUser) { |
| MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, |
| mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory, |
| mDeviceIdleControllerAdapter); |
| missedCallNotifier.setCurrentUserHandle(currentUser); |
| return missedCallNotifier; |
| } |
| |
| private PhoneAccount makePhoneAccount(UserHandle userHandle, int capability) { |
| PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(COMPONENT_NAME, "id", |
| userHandle); |
| PhoneAccount.Builder builder = new PhoneAccount.Builder(phoneAccountHandle, "test"); |
| builder.setCapabilities(capability); |
| PhoneAccount phoneAccount = builder.build(); |
| when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle)) |
| .thenReturn(phoneAccount); |
| return phoneAccount; |
| } |
| |
| private void enableDialerHandlesMissedCall() { |
| doReturn(COMPONENT_NAME.getPackageName()).when(mDefaultDialerCache). |
| getDefaultDialerApplication(anyInt()); |
| mComponentContextFixture.addIntentReceiver( |
| TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, COMPONENT_NAME); |
| } |
| |
| private IContentProvider getContentProviderForUser(int userId) { |
| return mContext.getContentResolver().acquireProvider(userId + "@call_log"); |
| } |
| |
| } |