From 9cd03e1e3c319f7633638615074da3028eb45c2b Mon Sep 17 00:00:00 2001 From: Brandon Maxwell Date: Tue, 2 Feb 2016 19:36:46 -0800 Subject: Fixes for FBE behavior changes + This change ensures that the Dialer doesn't try to use the ContactsPreferences object (which uses information in unavailable storage) while in File based encryption locked mode. Without these checks, the Dialer crashes on receiving an incoming call while FBE locked. + Added Factory method to create ContactsPreferences for tests/while FBE locked/while FBE unlocked + Added tests for CallCardPresenter and StatusBarNotifier FBE related code Bug=26822105 Change-Id: I5df93526e70b7350885c7261982945c32b7e86a0 --- .../com/android/incallui/CallCardPresenter.java | 37 ++++--- .../incallui/ContactsPreferencesFactory.java | 61 +++++++++++ .../com/android/incallui/StatusBarNotifier.java | 17 ++- .../android/incallui/CallCardPresenterTest.java | 121 +++++++++++++++++++++ .../incallui/ContactsPreferencesFactoryTest.java | 51 +++++++++ .../android/incallui/StatusBarNotifierTest.java | 98 +++++++++++++++++ 6 files changed, 366 insertions(+), 19 deletions(-) create mode 100644 InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java create mode 100644 InCallUI/tests/src/com/android/incallui/CallCardPresenterTest.java create mode 100644 InCallUI/tests/src/com/android/incallui/ContactsPreferencesFactoryTest.java create mode 100644 InCallUI/tests/src/com/android/incallui/StatusBarNotifierTest.java diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index f54c633b3..8156cc52f 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.Nullable; import android.telecom.Call.Details; import android.telecom.DisconnectCause; import android.telecom.PhoneAccount; @@ -42,6 +43,7 @@ import android.widget.ListAdapter; import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.compat.telecom.TelecomManagerCompat; import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.util.ContactDisplayUtils; import com.android.incallui.Call.State; import com.android.incallui.ContactInfoCache.ContactCacheEntry; @@ -83,7 +85,7 @@ public class CallCardPresenter extends Presenter private ContactCacheEntry mSecondaryContactInfo; private CallTimer mCallTimer; private Context mContext; - private ContactsPreferences mContactsPreferences; + @Nullable private ContactsPreferences mContactsPreferences; private boolean mSpinnerShowing = false; private boolean mHasShownToast = false; private InCallContactInteractions mInCallContactInteractions; @@ -136,7 +138,7 @@ public class CallCardPresenter extends Presenter public void init(Context context, Call call) { mContext = Preconditions.checkNotNull(context); mDistanceHelper = ObjectFactory.newDistanceHelper(mContext, this); - mContactsPreferences = new ContactsPreferences(mContext); + mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext); // Call may be null if disconnect happened already. if (call != null) { @@ -932,11 +934,15 @@ public class CallCardPresenter extends Presenter /** * Gets the name to display for the call. */ - private String getNameForCall(ContactCacheEntry contactInfo) { - String preferredName = ContactDisplayUtils.getPreferredDisplayName( - contactInfo.namePrimary, - contactInfo.nameAlternative, - mContactsPreferences.getDisplayOrder()); + @NeededForTesting + String getNameForCall(ContactCacheEntry contactInfo) { + String preferredName = contactInfo.namePrimary; + if (mContactsPreferences != null) { + preferredName = ContactDisplayUtils.getPreferredDisplayName( + contactInfo.namePrimary, + contactInfo.nameAlternative, + mContactsPreferences.getDisplayOrder()); + } if (TextUtils.isEmpty(preferredName)) { return contactInfo.number; } @@ -946,13 +952,18 @@ public class CallCardPresenter extends Presenter /** * Gets the number to display for a call. */ - private String getNumberForCall(ContactCacheEntry contactInfo) { - // If the name is empty, we use the number for the name...so dont show a second + @NeededForTesting + String getNumberForCall(ContactCacheEntry contactInfo) { + // If the name is empty, we use the number for the name...so don't show a second // number in the number field - if (TextUtils.isEmpty(ContactDisplayUtils.getPreferredDisplayName( - contactInfo.namePrimary, - contactInfo.nameAlternative, - mContactsPreferences.getDisplayOrder()))) { + String preferredName = contactInfo.namePrimary; + if (mContactsPreferences != null) { + preferredName = ContactDisplayUtils.getPreferredDisplayName( + contactInfo.namePrimary, + contactInfo.nameAlternative, + mContactsPreferences.getDisplayOrder()); + } + if (TextUtils.isEmpty(preferredName)) { return contactInfo.location; } return contactInfo.number; diff --git a/InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java b/InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java new file mode 100644 index 000000000..a9cc93bda --- /dev/null +++ b/InCallUI/src/com/android/incallui/ContactsPreferencesFactory.java @@ -0,0 +1,61 @@ +/* + * 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.incallui; + +import android.content.Context; +import android.support.annotation.Nullable; +import com.android.dialer.compat.UserManagerCompat; + +import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.testing.NeededForTesting; + +/** + * Factory class for {@link ContactsPreferences}. + */ +public class ContactsPreferencesFactory { + + private static boolean sUseTestInstance; + private static ContactsPreferences sTestInstance; + + /** + * Creates a new {@link ContactsPreferences} object if possible. + * + * @param context the context to use when creating the ContactsPreferences. + * @return a new ContactsPreferences object or {@code null} if the user is locked. + */ + @Nullable + public static ContactsPreferences newContactsPreferences(Context context) { + if (sUseTestInstance) { + return sTestInstance; + } + if (UserManagerCompat.isUserUnlocked(context)) { + return new ContactsPreferences(context); + } + return null; + } + + /** + * Sets the instance to be returned by all calls to {@link #newContactsPreferences(Context)}. + * + * @param testInstance the instance to return. + */ + @NeededForTesting + static void setTestInstance(ContactsPreferences testInstance) { + sUseTestInstance = true; + sTestInstance = testInstance; + } +} diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java index 985c88565..66091bd55 100644 --- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java +++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java @@ -36,6 +36,7 @@ import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.media.AudioAttributes; import android.net.Uri; +import android.support.annotation.Nullable; import android.telecom.Call.Details; import android.telecom.PhoneAccount; import android.telecom.TelecomManager; @@ -46,6 +47,7 @@ import android.text.TextUtils; import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.ContactsUtils.UserType; import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.util.BitmapUtil; import com.android.contacts.common.util.ContactDisplayUtils; import com.android.incallui.ContactInfoCache.ContactCacheEntry; @@ -72,7 +74,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, private static final long[] VIBRATE_PATTERN = new long[] {0, 1000, 1000}; private final Context mContext; - private final ContactsPreferences mContactsPreferences; + @Nullable private ContactsPreferences mContactsPreferences; private final ContactInfoCache mContactInfoCache; private final NotificationManager mNotificationManager; private final DialerRingtoneManager mDialerRingtoneManager; @@ -89,7 +91,7 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, public StatusBarNotifier(Context context, ContactInfoCache contactInfoCache) { Preconditions.checkNotNull(context); mContext = context; - mContactsPreferences = new ContactsPreferences(context); + mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext); mContactInfoCache = contactInfoCache; mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -389,18 +391,21 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, /** * Returns the main string to use in the notification. */ - private String getContentTitle(ContactCacheEntry contactInfo, Call call) { + @NeededForTesting + String getContentTitle(ContactCacheEntry contactInfo, Call call) { if (call.isConferenceCall() && !call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)) { return mContext.getResources().getString(R.string.card_title_conf_call); } - String preferredName = ContactDisplayUtils.getPreferredDisplayName(contactInfo.namePrimary, - contactInfo.nameAlternative, mContactsPreferences.getDisplayOrder()); + String preferredName = contactInfo.namePrimary; + if (mContactsPreferences != null) { + preferredName = ContactDisplayUtils.getPreferredDisplayName(contactInfo.namePrimary, + contactInfo.nameAlternative, mContactsPreferences.getDisplayOrder()); + } if (TextUtils.isEmpty(preferredName)) { return TextUtils.isEmpty(contactInfo.number) ? null : BidiFormatter.getInstance() .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR); } - return preferredName; } diff --git a/InCallUI/tests/src/com/android/incallui/CallCardPresenterTest.java b/InCallUI/tests/src/com/android/incallui/CallCardPresenterTest.java new file mode 100644 index 000000000..79545ce4b --- /dev/null +++ b/InCallUI/tests/src/com/android/incallui/CallCardPresenterTest.java @@ -0,0 +1,121 @@ +/* + * 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.incallui; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.contacts.common.preference.ContactsPreferences; +import com.android.incallui.ContactInfoCache.ContactCacheEntry; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@MediumTest +public class CallCardPresenterTest extends AndroidTestCase { + + private static final String NAME_PRIMARY = "Full Name"; + private static final String NAME_ALTERNATIVE = "Name, Full"; + private static final String LOCATION = "US"; + private static final String NUMBER = "8006459001"; + + @Mock private ContactsPreferences mContactsPreferences; + private ContactCacheEntry mUnlockedContactInfo; + private ContactCacheEntry mLockedContactInfo; + + @Override + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + + Mockito.when(mContactsPreferences.getDisplayOrder()) + .thenReturn(ContactsPreferences.DISPLAY_ORDER_PRIMARY); + + // Unlocked all contact info is available + mUnlockedContactInfo = new ContactCacheEntry(); + mUnlockedContactInfo.namePrimary = NAME_PRIMARY; + mUnlockedContactInfo.nameAlternative = NAME_ALTERNATIVE; + mUnlockedContactInfo.location = LOCATION; + mUnlockedContactInfo.number = NUMBER; + + // Locked only number and location are available + mLockedContactInfo = new ContactCacheEntry(); + mLockedContactInfo .location = LOCATION; + mLockedContactInfo .number = NUMBER; + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + ContactsPreferencesFactory.setTestInstance(null); + } + + public void testGetNameForCall_Unlocked() { + ContactsPreferencesFactory.setTestInstance(mContactsPreferences); + CallCardPresenter presenter = new CallCardPresenter(); + presenter.init(getContext(), null); + + assertEquals(NAME_PRIMARY, presenter.getNameForCall(mUnlockedContactInfo)); + } + + public void testGetNameForCall_Locked() { + ContactsPreferencesFactory.setTestInstance(null); + CallCardPresenter presenter = new CallCardPresenter(); + presenter.init(getContext(), null); + + assertEquals(NUMBER, presenter.getNameForCall(mLockedContactInfo)); + } + + public void testGetNameForCall_EmptyPreferredName() { + ContactCacheEntry contactInfo = new ContactCacheEntry(); + contactInfo.number = NUMBER; + + ContactsPreferencesFactory.setTestInstance(null); + CallCardPresenter presenter = new CallCardPresenter(); + presenter.init(getContext(), null); + + assertEquals(NUMBER, presenter.getNameForCall(contactInfo)); + } + + public void testGetNumberForCall_Unlocked() { + ContactsPreferencesFactory.setTestInstance(mContactsPreferences); + CallCardPresenter presenter = new CallCardPresenter(); + presenter.init(getContext(), null); + + assertEquals(NUMBER, presenter.getNumberForCall(mUnlockedContactInfo)); + } + + public void testGetNumberForCall_Locked() { + ContactsPreferencesFactory.setTestInstance(null); + CallCardPresenter presenter = new CallCardPresenter(); + presenter.init(getContext(), null); + + assertEquals(LOCATION, presenter.getNumberForCall(mLockedContactInfo)); + } + + public void testGetNumberForCall_EmptyPreferredName() { + ContactCacheEntry contactInfo = new ContactCacheEntry(); + contactInfo.location = LOCATION; + + ContactsPreferencesFactory.setTestInstance(null); + CallCardPresenter presenter = new CallCardPresenter(); + presenter.init(getContext(), null); + + assertEquals(LOCATION, presenter.getNumberForCall(contactInfo)); + } +} diff --git a/InCallUI/tests/src/com/android/incallui/ContactsPreferencesFactoryTest.java b/InCallUI/tests/src/com/android/incallui/ContactsPreferencesFactoryTest.java new file mode 100644 index 000000000..bf915553b --- /dev/null +++ b/InCallUI/tests/src/com/android/incallui/ContactsPreferencesFactoryTest.java @@ -0,0 +1,51 @@ +/* + * 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.incallui; + +import com.android.dialer.compat.UserManagerCompat; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.contacts.common.preference.ContactsPreferences; + +import org.mockito.Mockito; + +@SmallTest +public class ContactsPreferencesFactoryTest extends AndroidTestCase { + + public void testNewContactsPreferences_Unlocked() { + if (!UserManagerCompat.isUserUnlocked(getContext())) { + return; + } + assertNotNull(ContactsPreferencesFactory.newContactsPreferences(getContext())); + } + + public void testNewContactsPreferences_Locked() { + if (UserManagerCompat.isUserUnlocked(getContext())) { + return; + } + assertNull(ContactsPreferencesFactory.newContactsPreferences(getContext())); + } + + public void testNewContactsPreferences_TestInstance() { + ContactsPreferences testInstance = Mockito.mock(ContactsPreferences.class); + ContactsPreferencesFactory.setTestInstance(testInstance); + // Assert that it returns the same object always + assertSame(testInstance, ContactsPreferencesFactory.newContactsPreferences(getContext())); + assertSame(testInstance, ContactsPreferencesFactory.newContactsPreferences(getContext())); + } +} diff --git a/InCallUI/tests/src/com/android/incallui/StatusBarNotifierTest.java b/InCallUI/tests/src/com/android/incallui/StatusBarNotifierTest.java new file mode 100644 index 000000000..4c55ddcc0 --- /dev/null +++ b/InCallUI/tests/src/com/android/incallui/StatusBarNotifierTest.java @@ -0,0 +1,98 @@ +/* + * 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.incallui; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.contacts.common.preference.ContactsPreferences; +import com.android.incallui.ContactInfoCache.ContactCacheEntry; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@MediumTest +public class StatusBarNotifierTest extends AndroidTestCase { + + private static final String NAME_PRIMARY = "Full Name"; + private static final String NAME_ALTERNATIVE = "Name, Full"; + private static final String LOCATION = "US"; + private static final String NUMBER = "8006459001"; + + @Mock private Call mCall; + @Mock private ContactsPreferences mContactsPreferences; + private ContactCacheEntry mUnlockedContactInfo; + private ContactCacheEntry mLockedContactInfo; + + @Override + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + + Mockito.when(mContactsPreferences.getDisplayOrder()) + .thenReturn(ContactsPreferences.DISPLAY_ORDER_PRIMARY); + + // Unlocked all contact info is available + mUnlockedContactInfo = new ContactCacheEntry(); + mUnlockedContactInfo.namePrimary = NAME_PRIMARY; + mUnlockedContactInfo.nameAlternative = NAME_ALTERNATIVE; + mUnlockedContactInfo.location = LOCATION; + mUnlockedContactInfo.number = NUMBER; + + // Locked only number and location are available + mLockedContactInfo = new ContactCacheEntry(); + mLockedContactInfo .location = LOCATION; + mLockedContactInfo .number = NUMBER; + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + ContactsPreferencesFactory.setTestInstance(null); + } + + public void testGetContentTitle_ConferenceCall() { + ContactsPreferencesFactory.setTestInstance(null); + StatusBarNotifier statusBarNotifier = new StatusBarNotifier(mContext, null); + + Mockito.when(mCall.isConferenceCall()).thenReturn(true); + Mockito.when(mCall.hasProperty(Mockito.anyInt())).thenReturn(false); + + assertEquals(mContext.getResources().getString(R.string.card_title_conf_call), + statusBarNotifier.getContentTitle(null, mCall)); + } + + public void testGetContentTitle_Unlocked() { + ContactsPreferencesFactory.setTestInstance(mContactsPreferences); + StatusBarNotifier statusBarNotifier = new StatusBarNotifier(mContext, null); + assertEquals(NAME_PRIMARY, statusBarNotifier.getContentTitle(mUnlockedContactInfo, mCall)); + } + + public void testGetContentTitle_Locked() { + ContactsPreferencesFactory.setTestInstance(null); + StatusBarNotifier statusBarNotifier = new StatusBarNotifier(mContext, null); + assertEquals(NUMBER, statusBarNotifier.getContentTitle(mLockedContactInfo, mCall)); + } + + public void testGetContentTitle_EmptyPreferredName() { + ContactCacheEntry contactCacheEntry = new ContactCacheEntry(); + contactCacheEntry.number = NUMBER; + StatusBarNotifier statusBarNotifier = new StatusBarNotifier(mContext, null); + assertEquals(NUMBER, statusBarNotifier.getContentTitle(contactCacheEntry, mCall)); + } +} -- cgit v1.2.3