diff options
48 files changed, 679 insertions, 312 deletions
diff --git a/java/com/android/contacts/common/list/PhoneNumberListAdapter.java b/java/com/android/contacts/common/list/PhoneNumberListAdapter.java index b7427985b..dee51091f 100644 --- a/java/com/android/contacts/common/list/PhoneNumberListAdapter.java +++ b/java/com/android/contacts/common/list/PhoneNumberListAdapter.java @@ -27,6 +27,7 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Directory; +import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; @@ -357,7 +358,8 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { bindPhoneNumber(view, cursor, directory.isDisplayNumber(), position); } - protected void bindPhoneNumber( + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + public void bindPhoneNumber( ContactListItemView view, Cursor cursor, boolean displayNumber, int position) { CharSequence label = null; if (displayNumber && !cursor.isNull(PhoneQuery.PHONE_TYPE)) { @@ -397,7 +399,8 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { } } - if (LightbringerComponent.get(mContext).getLightbringer().isReachable(mContext, number)) { + if (action == ContactListItemView.NONE + && LightbringerComponent.get(mContext).getLightbringer().isReachable(mContext, number)) { action = ContactListItemView.LIGHTBRINGER; } diff --git a/java/com/android/contacts/common/res/drawable/ic_work_profile.xml b/java/com/android/contacts/common/res/drawable/ic_work_profile.xml index fc21100c0..445288595 100644 --- a/java/com/android/contacts/common/res/drawable/ic_work_profile.xml +++ b/java/com/android/contacts/common/res/drawable/ic_work_profile.xml @@ -1,16 +1,26 @@ -<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2017 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. +--> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:height="16dp" - android:viewportHeight="48" - android:viewportWidth="48" - android:width="16dp"> + android:width="16dp" + android:height="16dp" + android:viewportWidth="16.0" + android:viewportHeight="16.0"> - <path - android:fillColor="#757575" - android:pathData="M28 33h-8v-3H6v8c0 2.2 1.8 4 4 4h28c2.2 0 4-1.8 -4-4v-8H28v3zm12-21h-7V9l-3-3H18l-3 3.1V12H8c-2.2 0-4 1.8-4 4v8c0 2.2 1.8 4 4 -4h12v-3h8v3h12c2.2 0 4-1.8 4-4v-8c0-2.2-1.8-4-4-4zm-10 0H18V9h12v3z"/> - <path - android:pathData="M0 0h48v48H0z"/> + <path + android:pathData="M13.33,4h-2.67V2.67c0,-0.74 -0.59,-1.33 -1.33,-1.33H6.67c-0.74,0 -1.33,0.59 -1.33,1.33V4H2.67C1.93,4 1.34,4.59 1.34,5.33l-0.01,7.33c0,0.74 0.59,1.33 1.33,1.33h10.67c0.74,0 1.33,-0.59 1.33,-1.33V5.33C14.67,4.59 14.07,4 13.33,4zM8,10c-0.73,0 -1.33,-0.6 -1.33,-1.33S7.27,7.33 8,7.33s1.33,0.6 1.33,1.33S8.73,10 8,10zM9.33,4H6.67V2.67h2.67V4z" + android:fillColor="#757575"/> </vector> diff --git a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java index b1ad0d9a2..78ec7a695 100644 --- a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java +++ b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java @@ -66,9 +66,8 @@ public class CallLogAsyncTaskUtil { .update(voicemailUri, values, Voicemails.IS_READ + " = 0", null) > 0) { uploadVoicemailLocalChangesToServer(context); + CallLogNotificationsService.markAllNewVoicemailsAsOld(context); } - - CallLogNotificationsService.markAllNewVoicemailsAsOld(context); return null; } }); diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index 23a00d745..f7ea63c90 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -551,12 +551,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder private void bindActionButtons() { boolean canPlaceCallToNumber = PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation); + // Hide the call buttons by default. We then set it to be visible when appropriate below. + // This saves us having to remember to set it to GONE in multiple places. + callButtonView.setVisibility(View.GONE); + videoCallButtonView.setVisibility(View.GONE); + if (isFullyUndialableVoicemail()) { // Sometimes the voicemail server will report the message is from some non phone number // source. If the number does not contains any dialable digit treat it as it is from a unknown // number, remove all action buttons but still show the voicemail playback layout. - callButtonView.setVisibility(View.GONE); - videoCallButtonView.setVisibility(View.GONE); detailsButtonView.setVisibility(View.GONE); createNewContactButtonView.setVisibility(View.GONE); addToExistingContactButtonView.setVisibility(View.GONE); @@ -585,11 +588,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text)); if (canPlaceCallToNumber) { - // Set up the call button but hide it by default (the primary action is to call so it is - // redundant). We then set it to be visible when appropriate below. This saves us having to - // remember to set it to GONE in multiple places. callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number)); - callButtonView.setVisibility(View.GONE); callTypeOrLocationView.setVisibility(View.GONE); } @@ -617,8 +616,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } else if (lightbringerReady) { videoCallButtonView.setTag(IntentProvider.getLightbringerIntentProvider(number)); videoCallButtonView.setVisibility(View.VISIBLE); - } else { - videoCallButtonView.setVisibility(View.GONE); } // For voicemail calls, show the voicemail playback layout; hide otherwise. @@ -947,12 +944,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder String accountLabel = mCallLogCache.getAccountLabel(accountHandle); if (!TextUtils.isEmpty(accountLabel)) { SimDetails.Builder simDetails = SimDetails.newBuilder().setNetwork(accountLabel); - int color = mCallLogCache.getAccountColor(accountHandle); - if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) { - simDetails.setColor(R.color.secondary_text_color); - } else { - simDetails.setColor(color); - } + simDetails.setColor(mCallLogCache.getAccountColor(accountHandle)); contact.setSimDetails(simDetails.build()); } return contact.build(); diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java index 84aedf880..0490b9932 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java @@ -25,6 +25,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; +import android.telecom.PhoneAccountHandle; +import com.android.dialer.app.voicemail.LegacyVoicemailNotificationReceiver; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; @@ -48,7 +50,8 @@ import com.android.dialer.util.PermissionsUtil; */ public class CallLogNotificationsService extends IntentService { - private static final String ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD = + @VisibleForTesting + static final String ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD = "com.android.dialer.calllog.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD"; private static final String ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD = @@ -68,6 +71,10 @@ public class CallLogNotificationsService extends IntentService { public static final String ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION = "com.android.dialer.calllog.CALL_BACK_FROM_MISSED_CALL_NOTIFICATION"; + /** Action mark legacy voicemail as dismissed. */ + public static final String ACTION_LEGACY_VOICEMAIL_DISMISSED = + "com.android.dialer.calllog.ACTION_LEGACY_VOICEMAIL_DISMISSED"; + /** * Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent a post call note. * @@ -83,6 +90,8 @@ public class CallLogNotificationsService extends IntentService { */ private static final String EXTRA_POST_CALL_NUMBER = "POST_CALL_NUMBER"; + private static final String EXTRA_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE"; + public static final int UNKNOWN_MISSED_CALL_COUNT = -1; public CallLogNotificationsService() { @@ -149,6 +158,14 @@ public class CallLogNotificationsService extends IntentService { return PendingIntent.getService(context, 0, intent, 0); } + public static PendingIntent createLegacyVoicemailDismissedPendingIntent( + @NonNull Context context, PhoneAccountHandle phoneAccountHandle) { + Intent intent = new Intent(context, CallLogNotificationsService.class); + intent.setAction(ACTION_LEGACY_VOICEMAIL_DISMISSED); + intent.putExtra(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle); + return PendingIntent.getService(context, 0, intent, 0); + } + @Override protected void onHandleIntent(Intent intent) { if (intent == null) { @@ -174,6 +191,10 @@ public class CallLogNotificationsService extends IntentService { VoicemailQueryHandler.markSingleNewVoicemailAsRead(this, voicemailUri); VisualVoicemailNotifier.cancelSingleVoicemailNotification(this, voicemailUri); break; + case ACTION_LEGACY_VOICEMAIL_DISMISSED: + LegacyVoicemailNotificationReceiver.setDismissed( + this, intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE), true); + break; case ACTION_INCOMING_POST_CALL: String note = intent.getStringExtra(EXTRA_POST_CALL_NOTE); String phoneNumber = intent.getStringExtra(EXTRA_POST_CALL_NUMBER); diff --git a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java index 428c71677..c64e03e4e 100644 --- a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java @@ -122,7 +122,10 @@ public final class LegacyVoicemailNotifier { .setSound(pinnedTelephonyManager.getVoicemailRingtoneUri(handle)) .setOngoing(isOngoing) .setOnlyAlertOnce(isRefresh) - .setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle)); + .setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle)) + .setDeleteIntent( + CallLogNotificationsService.createLegacyVoicemailDismissedPendingIntent( + context, handle)); if (pinnedTelephonyManager.isVoicemailVibrationEnabled(handle)) { builder.setDefaults(Notification.DEFAULT_VIBRATE); diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java index 3f03db1e8..dbb6c8b5c 100644 --- a/java/com/android/dialer/app/list/ListsFragment.java +++ b/java/com/android/dialer/app/list/ListsFragment.java @@ -75,7 +75,6 @@ public class ListsFragment extends Fragment implements OnPageChangeListener, Lis private SharedPreferences mPrefs; private boolean mHasFetchedVoicemailStatus; private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched; - private VoicemailStatusHelper mVoicemailStatusHelper; private final ArrayList<OnPageChangeListener> mOnPageChangeListeners = new ArrayList<>(); /** The position of the currently selected tab. */ private int mTabIndex = TAB_INDEX_SPEED_DIAL; @@ -99,7 +98,6 @@ public class ListsFragment extends Fragment implements OnPageChangeListener, Lis LogUtil.d("ListsFragment.onCreate", null); Trace.beginSection(TAG + " onCreate"); super.onCreate(savedInstanceState); - mVoicemailStatusHelper = new VoicemailStatusHelper(); mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); Trace.endSection(); } @@ -294,7 +292,7 @@ public class ListsFragment extends Fragment implements OnPageChangeListener, Lis // Update hasActiveVoicemailProvider, which controls the number of tabs displayed. boolean hasActiveVoicemailProvider = - mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; + VoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; if (hasActiveVoicemailProvider != mAdapter.hasActiveVoicemailProvider()) { mAdapter.setHasActiveVoicemailProvider(hasActiveVoicemailProvider); mAdapter.notifyDataSetChanged(); diff --git a/java/com/android/dialer/app/list/PhoneFavoriteTileView.java b/java/com/android/dialer/app/list/PhoneFavoriteTileView.java index 455085d85..29147e7b3 100644 --- a/java/com/android/dialer/app/list/PhoneFavoriteTileView.java +++ b/java/com/android/dialer/app/list/PhoneFavoriteTileView.java @@ -18,6 +18,7 @@ package com.android.dialer.app.list; import android.content.ClipData; import android.content.Context; +import android.net.Uri; import android.provider.ContactsContract.PinnedPositions; import android.text.TextUtils; import android.util.AttributeSet; @@ -26,10 +27,12 @@ import android.widget.ImageView; import com.android.contacts.common.MoreContactUtils; import com.android.contacts.common.list.ContactEntry; import com.android.contacts.common.list.ContactTileView; +import com.android.contacts.common.model.ContactLoader; import com.android.dialer.app.R; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.callintent.SpeedDialContactType; +import com.android.dialer.common.LogUtil; import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest; import com.android.dialer.lettertile.LetterTileDrawable; import com.android.dialer.logging.InteractionEvent; @@ -94,6 +97,7 @@ public abstract class PhoneFavoriteTileView extends ContactTileView { isPinned = (entry.pinned != PinnedPositions.UNPINNED); isStarred = entry.isFavorite; if (entry != null) { + sendViewNotification(getContext(), entry.lookupUri); // Grab the phone-number to call directly. See {@link onClick()}. mPhoneNumberString = entry.phoneNumber; @@ -186,4 +190,22 @@ public abstract class PhoneFavoriteTileView extends ContactTileView { public void setPosition(int position) { this.position = position; } + + /** + * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are + * viewing a particular contact, so that it can download the high-res photo. + */ + private static void sendViewNotification(Context context, Uri contactUri) { + ContactLoader loader = new ContactLoader(context, contactUri, true /* postViewNotification */); + loader.registerListener( + 0, + (loader1, contact) -> { + try { + loader1.reset(); + } catch (RuntimeException e) { + LogUtil.e("PhoneFavoriteTileView.onLoadComplete", "error resetting loader", e); + } + }); + loader.startLoading(); + } } diff --git a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java index 4100521ab..a81d8665a 100644 --- a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java +++ b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java @@ -23,15 +23,15 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Build.VERSION_CODES; -import android.preference.PreferenceManager; +import android.support.annotation.VisibleForTesting; import android.support.v4.os.BuildCompat; -import android.support.v4.os.UserManagerCompat; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import com.android.dialer.app.calllog.LegacyVoicemailNotifier; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.PerAccountSharedPreferences; +import com.android.dialer.util.DialerUtils; import com.android.voicemail.VoicemailComponent; /** @@ -43,15 +43,14 @@ import com.android.voicemail.VoicemailComponent; public class LegacyVoicemailNotificationReceiver extends BroadcastReceiver { private static final String LEGACY_VOICEMAIL_COUNT = "legacy_voicemail_count"; + @VisibleForTesting static final String LEGACY_VOICEMAIL_DISMISSED = "legacy_voicemail_dismissed"; /** - * Hidden extra for {@link TelephonyManager#ACTION_SHOW_VOICEMAIL_NOTIFICATION} for whether the - * notification is just a refresh or for a new voicemail. The phone should not play a ringtone or - * vibrate during a refresh if the notification is already showing. - * - * <p>TODO(b/62202833): make public + * Whether the notification is just a refresh or for a new voicemail. The phone should not play a + * ringtone or vibrate during a refresh if the notification is already showing. This is Hidden in + * O and public in O MR1. */ - private static final String EXTRA_IS_REFRESH = "is_refresh"; + @VisibleForTesting static final String EXTRA_IS_REFRESH = "is_refresh"; @Override public void onReceive(Context context, Intent intent) { @@ -72,8 +71,21 @@ public class LegacyVoicemailNotificationReceiver extends BroadcastReceiver { PhoneAccountHandle phoneAccountHandle = Assert.isNotNull(intent.getParcelableExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE)); int count = intent.getIntExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, -1); + boolean isRefresh = intent.getBooleanExtra(EXTRA_IS_REFRESH, false); + LogUtil.i("LegacyVoicemailNotificationReceiver.onReceive", "isRefresh: " + isRefresh); + PerAccountSharedPreferences preferences = getSharedPreferences(context, phoneAccountHandle); + if (isRefresh) { + if (preferences.getBoolean(LEGACY_VOICEMAIL_DISMISSED, false)) { + LogUtil.i( + "LegacyVoicemailNotificationReceiver.onReceive", + "notification dismissed, ignoring refresh"); + return; + } + } else { + setDismissed(context, phoneAccountHandle, false); + } - if (!hasVoicemailCountChanged(context, phoneAccountHandle, count)) { + if (!hasVoicemailCountChanged(preferences, count)) { LogUtil.i( "LegacyVoicemailNotificationReceiver.onReceive", "voicemail count hasn't changed, ignoring"); @@ -116,27 +128,24 @@ public class LegacyVoicemailNotificationReceiver extends BroadcastReceiver { voicemailNumber, callVoicemailIntent, voicemailSettingIntent, - intent.getBooleanExtra(EXTRA_IS_REFRESH, false)); + isRefresh); } - private static boolean hasVoicemailCountChanged( - Context context, PhoneAccountHandle phoneAccountHandle, int newCount) { - // Need credential encrypted storage to access preferences. - if (!UserManagerCompat.isUserUnlocked(context)) { - LogUtil.i( - "LegacyVoicemailNotificationReceiver.onReceive", - "User locked, bypassing voicemail count check"); - return true; - } + public static void setDismissed( + Context context, PhoneAccountHandle phoneAccountHandle, boolean dismissed) { + getSharedPreferences(context, phoneAccountHandle) + .edit() + .putBoolean(LEGACY_VOICEMAIL_DISMISSED, dismissed) + .apply(); + } + private static boolean hasVoicemailCountChanged( + PerAccountSharedPreferences preferences, int newCount) { if (newCount == -1) { // Carrier does not report voicemail count return true; } - PerAccountSharedPreferences preferences = - new PerAccountSharedPreferences( - context, phoneAccountHandle, PreferenceManager.getDefaultSharedPreferences(context)); // Carriers may send multiple notifications for the same voicemail. if (newCount != 0 && newCount == preferences.getInt(LEGACY_VOICEMAIL_COUNT, -1)) { return false; @@ -144,4 +153,13 @@ public class LegacyVoicemailNotificationReceiver extends BroadcastReceiver { preferences.edit().putInt(LEGACY_VOICEMAIL_COUNT, newCount).apply(); return true; } + + @VisibleForTesting + static PerAccountSharedPreferences getSharedPreferences( + Context context, PhoneAccountHandle phoneAccountHandle) { + return new PerAccountSharedPreferences( + context, + phoneAccountHandle, + DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(context)); + } } diff --git a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java index 5c9bc01c2..6d5015a22 100644 --- a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java +++ b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java @@ -57,6 +57,7 @@ import com.android.dialer.constants.Constants; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.phonenumbercache.CallLogQuery; +import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; import com.google.common.io.ByteStreams; import java.io.File; @@ -515,6 +516,11 @@ public class VoicemailPlaybackPresenter mView.disableUiElements(); mIsPrepared = false; + if (mContext != null && TelecomUtil.isInCall(mContext)) { + handleError(new IllegalStateException("Cannot play voicemail when call is in progress")); + return; + } + try { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnPreparedListener(this); diff --git a/java/com/android/dialer/binary/common/DialerApplication.java b/java/com/android/dialer/binary/common/DialerApplication.java index 08666a21c..0d38541e5 100644 --- a/java/com/android/dialer/binary/common/DialerApplication.java +++ b/java/com/android/dialer/binary/common/DialerApplication.java @@ -17,18 +17,17 @@ package com.android.dialer.binary.common; import android.app.Application; -import android.os.StrictMode; import android.os.Trace; import android.support.annotation.NonNull; import android.support.v4.os.BuildCompat; import com.android.dialer.blocking.BlockedNumbersAutoMigrator; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; -import com.android.dialer.buildtype.BuildType; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.common.concurrent.DefaultDialerExecutorFactory; import com.android.dialer.inject.HasRootComponent; import com.android.dialer.notification.NotificationChannelManager; import com.android.dialer.persistentlog.PersistentLogger; +import com.android.dialer.strictmode.DialerStrictMode; /** A common application subclass for all Dialer build variants. */ public abstract class DialerApplication extends Application implements HasRootComponent { @@ -38,9 +37,8 @@ public abstract class DialerApplication extends Application implements HasRootCo @Override public void onCreate() { Trace.beginSection("DialerApplication.onCreate"); - if (BuildType.get() == BuildType.BUGFOOD) { - enableStrictMode(); - } + DialerStrictMode.onApplicationCreate(); + super.onCreate(); new BlockedNumbersAutoMigrator( this.getApplicationContext(), @@ -56,13 +54,6 @@ public abstract class DialerApplication extends Application implements HasRootCo Trace.endSection(); } - private void enableStrictMode() { - StrictMode.setThreadPolicy( - new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build()); - StrictMode.setVmPolicy( - new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build()); - } - /** * Returns a new instance of the root component for the application. Sub classes should define a * root component that extends all the sub components "HasComponent" intefaces. The component diff --git a/java/com/android/dialer/callcomposer/CallComposerActivity.java b/java/com/android/dialer/callcomposer/CallComposerActivity.java index ddc1e87f8..e6e55134b 100644 --- a/java/com/android/dialer/callcomposer/CallComposerActivity.java +++ b/java/com/android/dialer/callcomposer/CallComposerActivity.java @@ -670,12 +670,20 @@ public class CallComposerActivity extends AppCompatActivity public void onAnimationStart(Animator animation) { isSendAndCallHidingOrHidden = shouldHide; sendAndCall.setVisibility(View.VISIBLE); + cameraIcon.setVisibility(View.VISIBLE); + galleryIcon.setVisibility(View.VISIBLE); + messageIcon.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { if (isSendAndCallHidingOrHidden) { sendAndCall.setVisibility(View.INVISIBLE); + } else { + // hide buttons to prevent overdrawing and talkback discoverability + cameraIcon.setVisibility(View.GONE); + galleryIcon.setVisibility(View.GONE); + messageIcon.setVisibility(View.GONE); } } diff --git a/java/com/android/dialer/callcomposer/GalleryCursorLoader.java b/java/com/android/dialer/callcomposer/GalleryCursorLoader.java index 39d6a4a6d..d33bfb398 100644 --- a/java/com/android/dialer/callcomposer/GalleryCursorLoader.java +++ b/java/com/android/dialer/callcomposer/GalleryCursorLoader.java @@ -16,7 +16,6 @@ package com.android.dialer.callcomposer; -import android.annotation.SuppressLint; import android.content.Context; import android.net.Uri; import android.provider.MediaStore.Files; @@ -44,11 +43,10 @@ public class GalleryCursorLoader extends CursorLoader { SORT_ORDER); } - @SuppressLint("DefaultLocale") private static String createSelection() { - return String.format( - "mime_type IN ('image/jpeg', 'image/jpg', 'image/png', 'image/webp')" - + " AND media_type in (%d)", - FileColumns.MEDIA_TYPE_IMAGE); + return "mime_type IN ('image/jpeg', 'image/jpg', 'image/png', 'image/webp')" + + " AND media_type in (" + + FileColumns.MEDIA_TYPE_IMAGE + + ")"; } } diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java index 7d5757bbd..410a3a012 100644 --- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java +++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java @@ -19,6 +19,7 @@ package com.android.dialer.calldetails; import android.content.Context; import android.net.Uri; import android.support.v7.widget.RecyclerView; +import android.telecom.PhoneAccount; import android.text.TextUtils; import android.view.View; import android.view.View.OnClickListener; @@ -93,7 +94,9 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder if (!TextUtils.isEmpty(contact.getSimDetails().getNetwork())) { networkView.setVisibility(View.VISIBLE); networkView.setText(contact.getSimDetails().getNetwork()); - networkView.setTextColor(context.getResources().getColor(contact.getSimDetails().getColor())); + if (contact.getSimDetails().getColor() != PhoneAccount.NO_HIGHLIGHT_COLOR) { + networkView.setTextColor(contact.getSimDetails().getColor()); + } } if (TextUtils.isEmpty(contact.getNumber())) { diff --git a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java index fad25a409..6ee469572 100644 --- a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java +++ b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java @@ -20,11 +20,11 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.os.StrictMode; import android.support.annotation.Nullable; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.strictmode.DialerStrictMode; import com.android.dialer.util.DialerUtils; import javax.inject.Inject; @@ -95,37 +95,26 @@ class SharedPrefConfigProvider implements ConfigProvider { @Override public String getString(String key, String defaultValue) { - return bypassStrictMode( + // Reading shared prefs on the main thread is generally safe since a single instance is cached. + return DialerStrictMode.bypass( () -> getSharedPrefs(appContext).getString(PREF_PREFIX + key, defaultValue)); } @Override public long getLong(String key, long defaultValue) { - return bypassStrictMode( + // Reading shared prefs on the main thread is generally safe since a single instance is cached. + return DialerStrictMode.bypass( () -> getSharedPrefs(appContext).getLong(PREF_PREFIX + key, defaultValue)); } @Override public boolean getBoolean(String key, boolean defaultValue) { - return bypassStrictMode( + // Reading shared prefs on the main thread is generally safe since a single instance is cached. + return DialerStrictMode.bypass( () -> getSharedPrefs(appContext).getBoolean(PREF_PREFIX + key, defaultValue)); } private static SharedPreferences getSharedPrefs(Context appContext) { return DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(appContext); } - - private interface Provider<T> { - T get(); - } - - // Reading shared prefs on the main thread is generally safe since a single instance is cached. - private static <T> T bypassStrictMode(Provider<T> provider) { - StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); - try { - return provider.get(); - } finally { - StrictMode.setThreadPolicy(oldPolicy); - } - } } diff --git a/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java b/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java index 4ad7ea465..5dbdf5e48 100644 --- a/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java +++ b/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java @@ -566,7 +566,7 @@ class ContactPhotoManagerImpl extends ContactPhotoManager implements Callback { if (request.mIsCircular) { final RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(resources, bitmap); drawable.setAntiAlias(true); - drawable.setCornerRadius(bitmap.getHeight() / 2); + drawable.setCornerRadius(drawable.getIntrinsicHeight() / 2); return drawable; } else { return new BitmapDrawable(resources, bitmap); diff --git a/java/com/android/dialer/lightbringer/Lightbringer.java b/java/com/android/dialer/lightbringer/Lightbringer.java index 9b8a18061..9120b24db 100644 --- a/java/com/android/dialer/lightbringer/Lightbringer.java +++ b/java/com/android/dialer/lightbringer/Lightbringer.java @@ -28,6 +28,8 @@ import android.telecom.PhoneAccountHandle; public interface Lightbringer { + boolean isEnabled(); + @MainThread boolean isReachable(@NonNull Context context, @Nullable String number); diff --git a/java/com/android/dialer/lightbringer/stub/LightbringerStub.java b/java/com/android/dialer/lightbringer/stub/LightbringerStub.java index 92230a49e..c98ae091b 100644 --- a/java/com/android/dialer/lightbringer/stub/LightbringerStub.java +++ b/java/com/android/dialer/lightbringer/stub/LightbringerStub.java @@ -35,6 +35,11 @@ public class LightbringerStub implements Lightbringer { @Inject public LightbringerStub() {} + @Override + public boolean isEnabled() { + return false; + } + @MainThread @Override public boolean isReachable(@NonNull Context context, @Nullable String number) { diff --git a/java/com/android/dialer/logging/LoggingBindings.java b/java/com/android/dialer/logging/LoggingBindings.java index 85ccfdfa1..ca9a0533e 100644 --- a/java/com/android/dialer/logging/LoggingBindings.java +++ b/java/com/android/dialer/logging/LoggingBindings.java @@ -80,4 +80,11 @@ public interface LoggingBindings { QuickContactBadge quickContact, InteractionEvent.Type interactionEvent, boolean shouldPerformClick); + + /** Logs People Api lookup result with error */ + void logPeopleApiLookupReportWithError( + long latency, int httpResponseCode, PeopleApiLookupError.Type errorType); + + /** Logs successful People Api lookup result */ + void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode); } diff --git a/java/com/android/dialer/logging/LoggingBindingsStub.java b/java/com/android/dialer/logging/LoggingBindingsStub.java index 38929969c..2dbcc3ffb 100644 --- a/java/com/android/dialer/logging/LoggingBindingsStub.java +++ b/java/com/android/dialer/logging/LoggingBindingsStub.java @@ -54,4 +54,11 @@ public class LoggingBindingsStub implements LoggingBindings { QuickContactBadge quickContact, InteractionEvent.Type interactionEvent, boolean shouldPerformClick) {} + + @Override + public void logPeopleApiLookupReportWithError( + long latency, int httpResponseCode, PeopleApiLookupError.Type errorType) {} + + @Override + public void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode) {} } diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto index 8ccaf2dea..92ff0a675 100644 --- a/java/com/android/dialer/logging/dialer_impression.proto +++ b/java/com/android/dialer/logging/dialer_impression.proto @@ -478,5 +478,10 @@ message DialerImpression { // In in call UI UPGRADE_TO_VIDEO_CALL_BUTTON_SHOWN = 1236; + + // Bubble primary button first click to expand bubble + BUBBLE_PRIMARY_BUTTON_EXPAND = 1237; + // Bubble prinary button second click to return to call + BUBBLE_PRIMARY_BUTTON_RETURN_TO_CALL = 1238; } } diff --git a/java/com/android/dialer/logging/people_api_lookup_error.proto b/java/com/android/dialer/logging/people_api_lookup_error.proto new file mode 100644 index 000000000..e37d10aee --- /dev/null +++ b/java/com/android/dialer/logging/people_api_lookup_error.proto @@ -0,0 +1,19 @@ +syntax = "proto2"; + +package com.android.dialer.logging; +option java_package = "com.android.dialer.logging"; +option java_multiple_files = true; +option optimize_for = LITE_RUNTIME; + + + + +message PeopleApiLookupError { + enum Type { + UNKNOWN = 0; + HTTP_RESPONSE_ERROR = 1; + WRONG_KIND_VALUE = 2; + NO_ITEM_FOUND = 3; + JSON_PARSING_ERROR = 4; + } +} diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java index ee6e61c2e..0d22a824c 100644 --- a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java +++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java @@ -60,6 +60,23 @@ public class QueryFilteringUtil { return queryIndex == query.length(); } + /** + * Returns true if the subparts of the name (split by white space) begin with the query. + * + * <p>Examples: + * + * <ul> + * <li>#nameContainsQuery("b", "Brandon") returns true + * <li>#nameContainsQuery("o", "Bob") returns false + * <li>#nameContainsQuery("o", "Bob Olive") returns true + * </ul> + */ + public static boolean nameContainsQuery(String query, String name) { + return Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase())) + .matcher(name.toLowerCase()) + .find(); + } + /** @return true if the number belongs to the query. */ public static boolean numberMatchesNumberQuery(String query, String number) { return PhoneNumberUtils.isGlobalPhoneNumber(query) diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java b/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java index 51992646a..05e98cc84 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java @@ -142,7 +142,7 @@ public final class SearchContactCursor implements Cursor { if (TextUtils.isEmpty(query) || QueryFilteringUtil.nameMatchesT9Query(query, previousName) || QueryFilteringUtil.numberMatchesNumberQuery(query, previousMostQualifiedNumber) - || previousName.contains(query)) { + || QueryFilteringUtil.nameContainsQuery(query, previousName)) { queryFilteredPositions.add(previousMostQualifiedPosition); } } diff --git a/java/com/android/dialer/strictmode/DialerStrictMode.java b/java/com/android/dialer/strictmode/DialerStrictMode.java new file mode 100644 index 000000000..c9bbeafbf --- /dev/null +++ b/java/com/android/dialer/strictmode/DialerStrictMode.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 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.dialer.strictmode; + +import android.os.StrictMode; +import android.os.StrictMode.ThreadPolicy; +import android.os.StrictMode.VmPolicy; +import com.android.dialer.buildtype.BuildType; + +/** + * Enables strict mode for the application, and provides means of temporarily disabling it. + * + * <p>NOTE: All methods in this class are stripped by proguard in release builds. + */ +public final class DialerStrictMode { + + /** Initializes strict mode on application start. */ + public static void onApplicationCreate() { + enableDeathPenalty(); + } + + /** + * Disables the strict mode death penalty. If strict mode is enabled for the build, warnings are + * printed instead of the application crashing. + * + * <p>You should typically do this only temporarily and restore the death penalty in a finally + * block using {@link #enableDeathPenalty()}. + */ + public static void disableDeathPenalty() { + if (isStrictModeAllowed()) { + StrictMode.setThreadPolicy(threadPolicyTemplate().build()); + StrictMode.setVmPolicy(vmPolicyTemplate().build()); + } + } + + /** + * Restore the death penalty. This should typically be called in a finally block after calling + * {@link #disableDeathPenalty()}. + */ + public static void enableDeathPenalty() { + if (isStrictModeAllowed()) { + StrictMode.setThreadPolicy(threadPolicyTemplate().penaltyDeath().build()); + StrictMode.setVmPolicy(vmPolicyTemplate().penaltyDeath().build()); + } + } + + private static ThreadPolicy.Builder threadPolicyTemplate() { + return new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog(); + } + + private static VmPolicy.Builder vmPolicyTemplate() { + return new StrictMode.VmPolicy.Builder().detectAll().penaltyLog(); + } + + private static boolean isStrictModeAllowed() { + return BuildType.get() == BuildType.BUGFOOD; + } + + /** Functional interface intended to be used with {@link #bypass(Provider)}. */ + public interface Provider<T> { + T get(); + } + + /** + * Convenience method for disabling and enabling the death penalty using lambdas. + * + * <p>For example: + * + * <p><code> + * DialerStrictMode.bypass(() -> doDiskAccessOnMainThread()); + * </code> + */ + public static <T> T bypass(Provider<T> provider) { + disableDeathPenalty(); + try { + return provider.get(); + } finally { + enableDeathPenalty(); + } + } +} diff --git a/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java b/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java index a1fc29edf..3f519ad82 100644 --- a/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java +++ b/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java @@ -45,7 +45,6 @@ public class VisualVoicemailEnabledChecker implements CallLogQueryHandler.Listen private SharedPreferences mPrefs; private boolean mHasActiveVoicemailProvider; private CallLogQueryHandler mCallLogQueryHandler; - private VoicemailStatusHelper mVoicemailStatusHelper; private Context mContext; private Callback mCallback; @@ -53,7 +52,6 @@ public class VisualVoicemailEnabledChecker implements CallLogQueryHandler.Listen mContext = context; mCallback = callback; mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); - mVoicemailStatusHelper = new VoicemailStatusHelper(); mHasActiveVoicemailProvider = mPrefs.getBoolean(PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false); } @@ -77,7 +75,7 @@ public class VisualVoicemailEnabledChecker implements CallLogQueryHandler.Listen @Override public void onVoicemailStatusFetched(Cursor statusCursor) { boolean hasActiveVoicemailProvider = - mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; + VoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; if (hasActiveVoicemailProvider != mHasActiveVoicemailProvider) { mHasActiveVoicemailProvider = hasActiveVoicemailProvider; mPrefs diff --git a/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java index 9df45c211..313fc1be1 100644 --- a/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java +++ b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java @@ -21,15 +21,17 @@ import android.provider.VoicemailContract.Status; import com.android.dialer.database.VoicemailStatusQuery; /** - * Interface used by the call log UI to determine what user message, if any, related to voicemail + * Utility used by the call log UI to determine what user message, if any, related to voicemail * source status needs to be shown. The messages are returned in the order of importance. * - * <p>The implementation of this interface interacts with the voicemail content provider to fetch - * statuses of all the registered voicemail sources and determines if any status message needs to be - * shown. The user of this interface must observe/listen to provider changes and invoke this class - * to check if any message needs to be shown. + * <p>This class interacts with the voicemail content provider to fetch statuses of all the + * registered voicemail sources and determines if any status message needs to be shown. The user of + * this class must observe/listen to provider changes and invoke this class to check if any message + * needs to be shown. */ -public class VoicemailStatusHelper { +public final class VoicemailStatusHelper { + + private VoicemailStatusHelper() {} /** * Returns the number of active voicemail sources installed. @@ -39,7 +41,7 @@ public class VoicemailStatusHelper { * @param cursor The caller is responsible for the life cycle of the cursor and resetting the * position */ - public int getNumberActivityVoicemailSources(Cursor cursor) { + public static int getNumberActivityVoicemailSources(Cursor cursor) { int count = 0; if (!cursor.moveToFirst()) { return 0; @@ -60,8 +62,10 @@ public class VoicemailStatusHelper { * activation is attempted, it will transition into CONFIGURING then into OK or other error state, * NOT_CONFIGURED is never set through an error. */ - private boolean isVoicemailSourceActive(Cursor cursor) { + private static boolean isVoicemailSourceActive(Cursor cursor) { return cursor.getString(VoicemailStatusQuery.SOURCE_PACKAGE_INDEX) != null + // getInt() returns 0 when null + && !cursor.isNull(VoicemailStatusQuery.CONFIGURATION_STATE_INDEX) && cursor.getInt(VoicemailStatusQuery.CONFIGURATION_STATE_INDEX) != Status.CONFIGURATION_STATE_NOT_CONFIGURED; } diff --git a/java/com/android/dialer/widget/MessageFragment.java b/java/com/android/dialer/widget/MessageFragment.java index 615ad3b77..7a0fcfd0f 100644 --- a/java/com/android/dialer/widget/MessageFragment.java +++ b/java/com/android/dialer/widget/MessageFragment.java @@ -134,10 +134,9 @@ public class MessageFragment extends Fragment @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (getMessage() == null) { - return false; + if (!TextUtils.isEmpty(getMessage())) { + getListener().onMessageFragmentSendMessage(getMessage()); } - getListener().onMessageFragmentSendMessage(getMessage()); return true; } diff --git a/java/com/android/dialershared/bubble/Bubble.java b/java/com/android/dialershared/bubble/Bubble.java index f2ba117d8..9606f5be6 100644 --- a/java/com/android/dialershared/bubble/Bubble.java +++ b/java/com/android/dialershared/bubble/Bubble.java @@ -61,6 +61,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.ViewAnimator; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; import com.android.dialershared.bubble.BubbleInfo.Action; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -87,6 +89,8 @@ public class Bubble { private final Context context; private final WindowManager windowManager; + private final Handler handler = new Handler(); + private LayoutParams windowParams; // Initialized in factory method @@ -100,9 +104,7 @@ public class Bubble { private boolean hideAfterText; private int collapseEndAction; - private final Handler handler = new Handler(); - - private ViewHolder viewHolder; + @VisibleForTesting ViewHolder viewHolder; private ViewPropertyAnimator collapseAnimation; private Integer overrideGravity; private ViewPropertyAnimator exitAnimator; @@ -238,47 +240,22 @@ public class Bubble { updatePrimaryIconAnimation(); } - /** - * Hide the button if visible. Will run a short exit animation before hiding. If the bubble is - * currently showing text, will hide after the text is done displaying. If the bubble is not - * visible this method does nothing. - */ + /** Hide the bubble. */ public void hide() { - if (visibility == Visibility.HIDDEN || visibility == Visibility.EXITING) { - return; - } - - if (textShowing) { - hideAfterText = true; - return; - } - - if (collapseAnimation != null) { - collapseEndAction = CollapseEnd.HIDE; - return; - } - - if (expanded) { - startCollapse(CollapseEnd.HIDE); + if (hideAfterText) { + // hideAndReset() will be called after showing text, do nothing here. return; } + hideHelper(this::defaultAfterHidingAnimation); + } - visibility = Visibility.EXITING; - exitAnimator = - viewHolder - .getPrimaryButton() - .animate() - .setInterpolator(new AnticipateInterpolator()) - .scaleX(0) - .scaleY(0) - .withEndAction( - () -> { - exitAnimator = null; - windowManager.removeView(viewHolder.getRoot()); - visibility = Visibility.HIDDEN; - updatePrimaryIconAnimation(); - }); - exitAnimator.start(); + /** Hide the bubble and reset {@viewHolder} to initial state */ + public void hideAndReset() { + hideHelper( + () -> { + defaultAfterHidingAnimation(); + reset(); + }); } /** Returns whether the bubble is currently visible */ @@ -350,8 +327,15 @@ public class Bubble { public boolean onPreDraw() { primaryButton.getViewTreeObserver().removeOnPreDrawListener(this); - // Prepare and capture end values + // Prepare and capture end values, always use the size of primaryText since + // its invisibility makes primaryButton smaller than expected TransitionValues endValues = new TransitionValues(); + endValues.values.put( + ChangeOnScreenBounds.PROPNAME_WIDTH, + viewHolder.getPrimaryText().getWidth()); + endValues.values.put( + ChangeOnScreenBounds.PROPNAME_HEIGHT, + viewHolder.getPrimaryText().getHeight()); endValues.view = primaryButton; transition.addTarget(endValues.view); transition.captureEndValues(endValues); @@ -377,7 +361,8 @@ public class Bubble { () -> { textShowing = false; if (hideAfterText) { - hide(); + // Always reset here since text shouldn't keep showing. + hideAndReset(); } else { doResize( () -> viewHolder.getPrimaryButton().setDisplayedChild(ViewHolder.CHILD_INDEX_ICON)); @@ -411,6 +396,7 @@ public class Bubble { void primaryButtonClick() { if (expanded || textShowing || currentInfo.getActions().isEmpty()) { + Logger.get(context).logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_RETURN_TO_CALL); try { currentInfo.getPrimaryIntent().send(); } catch (CanceledException e) { @@ -419,6 +405,7 @@ public class Bubble { return; } + Logger.get(context).logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_EXPAND); doResize( () -> { onLeftRightSwitch(isDrawingFromRight()); @@ -478,6 +465,48 @@ public class Bubble { return viewHolder.getRoot(); } + /** + * Hide the bubble if visible. Will run a short exit animation and before hiding, and {@code + * afterHiding} after hiding. If the bubble is currently showing text, will hide after the text is + * done displaying. If the bubble is not visible this method does nothing. + */ + private void hideHelper(Runnable afterHiding) { + if (visibility == Visibility.HIDDEN || visibility == Visibility.EXITING) { + return; + } + + if (textShowing) { + hideAfterText = true; + return; + } + + if (collapseAnimation != null) { + collapseEndAction = CollapseEnd.HIDE; + return; + } + + if (expanded) { + startCollapse(CollapseEnd.HIDE); + return; + } + + visibility = Visibility.EXITING; + exitAnimator = + viewHolder + .getPrimaryButton() + .animate() + .setInterpolator(new AnticipateInterpolator()) + .scaleX(0) + .scaleY(0) + .withEndAction(afterHiding); + exitAnimator.start(); + } + + private void reset() { + viewHolder = new ViewHolder(viewHolder.getRoot().getContext()); + update(); + } + private void update() { RippleDrawable backgroundRipple = (RippleDrawable) @@ -681,7 +710,16 @@ public class Bubble { windowManager.updateViewLayout(getRootView(), windowParams); } - private class ViewHolder { + private void defaultAfterHidingAnimation() { + exitAnimator = null; + windowManager.removeView(viewHolder.getRoot()); + visibility = Visibility.HIDDEN; + + updatePrimaryIconAnimation(); + } + + @VisibleForTesting + class ViewHolder { public static final int CHILD_INDEX_ICON = 0; public static final int CHILD_INDEX_TEXT = 1; diff --git a/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java b/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java index 37c820447..8cd61afce 100644 --- a/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java +++ b/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java @@ -41,6 +41,9 @@ public class ChangeOnScreenBounds extends Transition { @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) static final String PROPNAME_SCREEN_Y = "bubble:changeScreenBounds:screenY"; + static final String PROPNAME_WIDTH = "bubble:changeScreenBounds:width"; + static final String PROPNAME_HEIGHT = "bubble:changeScreenBounds:height"; + private static final Property<ViewBounds, PointF> TOP_LEFT_PROPERTY = new Property<ViewBounds, PointF>(PointF.class, "topLeft") { @Override @@ -70,21 +73,32 @@ public class ChangeOnScreenBounds extends Transition { @Override public void captureStartValues(TransitionValues transitionValues) { - captureValues(transitionValues); + captureValuesWithSize(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { - captureValues(transitionValues); + captureValuesWithSize(transitionValues); } - private void captureValues(TransitionValues values) { + /** + * Capture location (left and top) from {@code values.view} and size (width and height) from + * {@code values.values}. If size is not set, use the size of {@code values.view}. + */ + private void captureValuesWithSize(TransitionValues values) { View view = values.view; if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) { + Integer width = (Integer) values.values.get(PROPNAME_WIDTH); + Integer height = (Integer) values.values.get(PROPNAME_HEIGHT); + values.values.put( PROPNAME_BOUNDS, - new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); + new Rect( + view.getLeft(), + view.getTop(), + width == null ? view.getRight() : view.getLeft() + width, + height == null ? view.getBottom() : view.getTop() + height)); values.view.getLocationOnScreen(tempLocation); values.values.put(PROPNAME_SCREEN_X, tempLocation[0]); values.values.put(PROPNAME_SCREEN_Y, tempLocation[1]); diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java index d4b77ad3c..4da227c31 100644 --- a/java/com/android/incallui/CallButtonPresenter.java +++ b/java/com/android/incallui/CallButtonPresenter.java @@ -18,6 +18,7 @@ package com.android.incallui; import android.content.Context; import android.os.Bundle; +import android.os.Trace; import android.support.v4.app.Fragment; import android.support.v4.os.UserManagerCompat; import android.telecom.CallAudioState; @@ -101,6 +102,7 @@ public class CallButtonPresenter @Override public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { + Trace.beginSection("CallButtonPresenter.onStateChange"); if (newState == InCallState.OUTGOING) { mCall = callList.getOutgoingCall(); } else if (newState == InCallState.INCALL) { @@ -124,6 +126,7 @@ public class CallButtonPresenter mCall = null; } updateUi(newState, mCall); + Trace.endSection(); } /** diff --git a/java/com/android/incallui/CallCardPresenter.java b/java/com/android/incallui/CallCardPresenter.java index 390683233..222b5b81e 100644 --- a/java/com/android/incallui/CallCardPresenter.java +++ b/java/com/android/incallui/CallCardPresenter.java @@ -28,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.os.BatteryManager; import android.os.Handler; +import android.os.Trace; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -247,8 +248,10 @@ public class CallCardPresenter @Override public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { + Trace.beginSection("CallCardPresenter.onStateChange"); LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState); if (mInCallScreen == null) { + Trace.endSection(); return; } @@ -345,6 +348,7 @@ public class CallCardPresenter callState != DialerCall.State.INCOMING /* animate */); maybeSendAccessibilityEvent(oldState, newState, primaryChanged); + Trace.endSection(); } @Override @@ -583,8 +587,8 @@ public class CallCardPresenter if (call != null) { call.getLogState().contactLookupResult = entry.contactLookupResult; } - if (entry.contactUri != null) { - CallerInfoUtils.sendViewNotification(mContext, entry.contactUri); + if (entry.lookupUri != null) { + CallerInfoUtils.sendViewNotification(mContext, entry.lookupUri); } } diff --git a/java/com/android/incallui/ContactInfoCache.java b/java/com/android/incallui/ContactInfoCache.java index d50a5c26d..353015313 100644 --- a/java/com/android/incallui/ContactInfoCache.java +++ b/java/com/android/incallui/ContactInfoCache.java @@ -699,8 +699,6 @@ public class ContactInfoCache implements OnImageLoadCompleteListener { // Note in cache entry whether this is a pending async loading action to know whether to // wait for its callback or not. boolean hasPendingQuery; - /** This will be used for the "view" notification. */ - public Uri contactUri; /** Either a display photo or a thumbnail URI. */ Uri displayPhotoUri; @@ -741,8 +739,6 @@ public class ContactInfoCache implements OnImageLoadCompleteListener { + photo + ", isSipCall=" + isSipCall - + ", contactUri=" - + contactUri + ", displayPhotoUri=" + displayPhotoUri + ", contactLookupResult=" diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index c95086c1e..3ea2b176f 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable.Orientation; import android.os.Bundle; +import android.os.Trace; import android.support.annotation.ColorInt; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; @@ -112,6 +113,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity @Override protected void onCreate(Bundle icicle) { + Trace.beginSection("InCallActivity.onCreate"); LogUtil.i("InCallActivity.onCreate", ""); super.onCreate(icicle); @@ -129,6 +131,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay); + Trace.endSection(); } @Override @@ -144,6 +147,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity @Override protected void onStart() { + Trace.beginSection("InCallActivity.onStart"); LogUtil.i("InCallActivity.onStart", ""); super.onStart(); isVisible = true; @@ -154,40 +158,49 @@ public class InCallActivity extends TransactionSafeFragmentActivity // Hide the dialpad because there may not be enough room showDialpadFragment(false, false); } + Trace.endSection(); } @Override protected void onResume() { + Trace.beginSection("InCallActivity.onResume"); LogUtil.i("InCallActivity.onResume", ""); super.onResume(); common.onResume(); PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState(); pseudoScreenState.addListener(this); onPseudoScreenStateChanged(pseudoScreenState.isOn()); + Trace.endSection(); } /** onPause is guaranteed to be called when the InCallActivity goes in the background. */ @Override protected void onPause() { + Trace.beginSection("InCallActivity.onPause"); LogUtil.i("InCallActivity.onPause", ""); super.onPause(); common.onPause(); InCallPresenter.getInstance().getPseudoScreenState().removeListener(this); + Trace.endSection(); } @Override protected void onStop() { + Trace.beginSection("InCallActivity.onStop"); LogUtil.i("InCallActivity.onStop", ""); super.onStop(); common.onStop(); isVisible = false; + Trace.endSection(); } @Override protected void onDestroy() { + Trace.beginSection("InCallActivity.onDestroy"); LogUtil.i("InCallActivity.onDestroy", ""); super.onDestroy(); common.onDestroy(); + Trace.endSection(); } @Override @@ -476,8 +489,10 @@ public class InCallActivity extends TransactionSafeFragmentActivity } public void onPrimaryCallStateChanged() { + Trace.beginSection("InCallActivity.onPrimaryCallStateChanged"); LogUtil.i("InCallActivity.onPrimaryCallStateChanged", ""); showMainInCallFragment(); + Trace.endSection(); } public void onWiFiToLteHandover(DialerCall call) { @@ -514,15 +529,18 @@ public class InCallActivity extends TransactionSafeFragmentActivity } private void showMainInCallFragment() { + Trace.beginSection("InCallActivity.showMainInCallFragment"); // If the activity's onStart method hasn't been called yet then defer doing any work. if (!isVisible) { LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore"); + Trace.endSection(); return; } // Don't let this be reentrant. if (isInShowMainInCallFragment) { LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing"); + Trace.endSection(); return; } @@ -560,10 +578,13 @@ public class InCallActivity extends TransactionSafeFragmentActivity } if (didChangeInCall || didChangeVideo || didChangeAnswer) { + Trace.beginSection("InCallActivity.commitTransaction"); transaction.commitNow(); + Trace.endSection(); Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); } isInShowMainInCallFragment = false; + Trace.endSection(); } private ShouldShowUiResult getShouldShowAnswerUi() { diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java index 0dd654903..6c08c497b 100644 --- a/java/com/android/incallui/InCallPresenter.java +++ b/java/com/android/incallui/InCallPresenter.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.graphics.Point; import android.os.Bundle; import android.os.Handler; +import android.os.Trace; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -41,6 +42,7 @@ import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnCheckBlocke import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.blocking.FilteredNumbersUtil; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DefaultDialerExecutorFactory; import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.location.GeoUtil; import com.android.dialer.logging.InteractionEvent; @@ -189,6 +191,8 @@ public class InCallPresenter implements CallList.Listener { /** Determines if the InCall UI is in fullscreen mode or not. */ private boolean mIsFullScreen = false; + private boolean mScreenTimeoutEnabled = true; + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override @@ -321,11 +325,13 @@ public class InCallPresenter implements CallList.Listener { ContactInfoCache contactInfoCache, ProximitySensor proximitySensor, FilteredNumberAsyncQueryHandler filteredNumberQueryHandler) { + Trace.beginSection("InCallPresenter.setUp"); if (mServiceConnected) { LogUtil.i("InCallPresenter.setUp", "New service connection replacing existing one."); if (context != mContext || callList != mCallList) { throw new IllegalStateException(); } + Trace.endSection(); return; } @@ -360,7 +366,7 @@ public class InCallPresenter implements CallList.Listener { mCallList.addListener(this); // Create spam call list listener and add it to the list of listeners - mSpamCallListListener = new SpamCallListListener(context); + mSpamCallListListener = new SpamCallListListener(context, new DefaultDialerExecutorFactory()); mCallList.addListener(mSpamCallListListener); VideoPauseController.getInstance().setUp(this); @@ -371,6 +377,7 @@ public class InCallPresenter implements CallList.Listener { .listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); LogUtil.d("InCallPresenter.setUp", "Finished InCallPresenter.setUp"); + Trace.endSection(); } /** @@ -395,6 +402,7 @@ public class InCallPresenter implements CallList.Listener { } private void attemptFinishActivity() { + mScreenTimeoutEnabled = true; final boolean doFinish = (mInCallActivity != null && isActivityStarted()); LogUtil.i("InCallPresenter.attemptFinishActivity", "Hide in call UI: " + doFinish); if (doFinish) { @@ -510,6 +518,7 @@ public class InCallPresenter implements CallList.Listener { } public void onCallAdded(final android.telecom.Call call) { + Trace.beginSection("InCallPresenter.onCallAdded"); LatencyReport latencyReport = new LatencyReport(call); if (shouldAttemptBlocking(call)) { maybeBlockCall(call, latencyReport); @@ -525,6 +534,7 @@ public class InCallPresenter implements CallList.Listener { // Since a call has been added we are no longer waiting for Telecom to send us a call. setBoundAndWaitingForOutgoingCall(false, null); call.registerCallback(mCallCallback); + Trace.endSection(); } private boolean shouldAttemptBlocking(android.telecom.Call call) { @@ -687,11 +697,14 @@ public class InCallPresenter implements CallList.Listener { */ @Override public void onCallListChange(CallList callList) { + Trace.beginSection("InCallPresenter.onCallListChange"); if (mInCallActivity != null && mInCallActivity.isInCallScreenAnimating()) { mAwaitingCallListUpdate = true; + Trace.endSection(); return; } if (callList == null) { + Trace.endSection(); return; } @@ -741,11 +754,13 @@ public class InCallPresenter implements CallList.Listener { callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null; mInCallActivity.dismissKeyguard(hasCall); } + Trace.endSection(); } /** Called when there is a new incoming call. */ @Override public void onIncomingCall(DialerCall call) { + Trace.beginSection("InCallPresenter.onIncomingCall"); InCallState newState = startOrFinishUi(InCallState.INCOMING); InCallState oldState = mInCallState; @@ -761,6 +776,7 @@ public class InCallPresenter implements CallList.Listener { // Re-evaluate which fragment is being shown. mInCallActivity.onPrimaryCallStateChanged(); } + Trace.endSection(); } @Override @@ -1064,6 +1080,7 @@ public class InCallPresenter implements CallList.Listener { // TODO(maxwelb) - b/36649622: Investigate this redundant call mStatusBarNotifier.updateNotification(mCallList); } + applyScreenTimeout(); } /*package*/ @@ -1577,13 +1594,18 @@ public class InCallPresenter implements CallList.Listener { public void enableScreenTimeout(boolean enable) { LogUtil.v("InCallPresenter.enableScreenTimeout", "enableScreenTimeout: value=" + enable); + mScreenTimeoutEnabled = enable; + applyScreenTimeout(); + } + + private void applyScreenTimeout() { if (mInCallActivity == null) { - LogUtil.e("InCallPresenter.enableScreenTimeout", "InCallActivity is null."); + LogUtil.e("InCallPresenter.applyScreenTimeout", "InCallActivity is null."); return; } final Window window = mInCallActivity.getWindow(); - if (enable) { + if (mScreenTimeoutEnabled) { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); diff --git a/java/com/android/incallui/InCallServiceImpl.java b/java/com/android/incallui/InCallServiceImpl.java index d2b029741..a2e243202 100644 --- a/java/com/android/incallui/InCallServiceImpl.java +++ b/java/com/android/incallui/InCallServiceImpl.java @@ -19,6 +19,7 @@ package com.android.incallui; import android.content.Context; import android.content.Intent; import android.os.IBinder; +import android.os.Trace; import android.telecom.Call; import android.telecom.CallAudioState; import android.telecom.InCallService; @@ -45,26 +46,35 @@ public class InCallServiceImpl extends InCallService { @Override public void onBringToForeground(boolean showDialpad) { + Trace.beginSection("InCallServiceImpl.onBringToForeground"); InCallPresenter.getInstance().onBringToForeground(showDialpad); + Trace.endSection(); } @Override public void onCallAdded(Call call) { + Trace.beginSection("InCallServiceImpl.onCallAdded"); InCallPresenter.getInstance().onCallAdded(call); + Trace.endSection(); } @Override public void onCallRemoved(Call call) { + Trace.beginSection("InCallServiceImpl.onCallRemoved"); InCallPresenter.getInstance().onCallRemoved(call); + Trace.endSection(); } @Override public void onCanAddCallChanged(boolean canAddCall) { + Trace.beginSection("InCallServiceImpl.onCanAddCallChanged"); InCallPresenter.getInstance().onCanAddCallChanged(canAddCall); + Trace.endSection(); } @Override public IBinder onBind(Intent intent) { + Trace.beginSection("InCallServiceImpl.onBind"); final Context context = getApplicationContext(); final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context); InCallPresenter.getInstance() @@ -85,20 +95,25 @@ public class InCallServiceImpl extends InCallService { returnToCallController = new ReturnToCallController(this); } - return super.onBind(intent); + IBinder iBinder = super.onBind(intent); + Trace.endSection(); + return iBinder; } @Override public boolean onUnbind(Intent intent) { + Trace.beginSection("InCallServiceImpl.onUnbind"); super.onUnbind(intent); InCallPresenter.getInstance().onServiceUnbind(); tearDown(); + Trace.endSection(); return false; } private void tearDown() { + Trace.beginSection("InCallServiceImpl.tearDown"); Log.v(this, "tearDown"); // Tear down the InCall system TelecomAdapter.getInstance().clearInCallService(); @@ -107,5 +122,6 @@ public class InCallServiceImpl extends InCallService { returnToCallController.tearDown(); returnToCallController = null; } + Trace.endSection(); } } diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java index 978b14039..8e4b9cc65 100644 --- a/java/com/android/incallui/ReturnToCallController.java +++ b/java/com/android/incallui/ReturnToCallController.java @@ -102,6 +102,14 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio } } + private void hideAndReset() { + if (bubble != null) { + bubble.hideAndReset(); + } else { + LogUtil.i("ReturnToCallController.reset", "reset() called without calling show()"); + } + } + private void show() { if (bubble == null) { bubble = startNewBubble(); @@ -141,7 +149,7 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio bubble.showText(context.getText(R.string.incall_call_ended)); } if (!hasAnotherCall) { - hide(); + hideAndReset(); } } diff --git a/java/com/android/incallui/VideoCallPresenter.java b/java/com/android/incallui/VideoCallPresenter.java index 233b2b569..89cddb9c4 100644 --- a/java/com/android/incallui/VideoCallPresenter.java +++ b/java/com/android/incallui/VideoCallPresenter.java @@ -867,7 +867,7 @@ public class VideoCallPresenter false /* isRemotelyHeld */); enableCamera(mVideoCall, false); InCallPresenter.getInstance().setFullScreen(false); - + InCallPresenter.getInstance().enableScreenTimeout(false); mIsVideoMode = false; } diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java index 269fcd68b..d932c2488 100644 --- a/java/com/android/incallui/call/CallList.java +++ b/java/com/android/incallui/call/CallList.java @@ -114,7 +114,7 @@ public class CallList implements DialerCallDelegate { public void onCallAdded( final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) { - Trace.beginSection("onCallAdded"); + Trace.beginSection("CallList.onCallAdded"); final DialerCall call = new DialerCall(context, this, telecomCall, latencyReport, true /* registerCallback */); logSecondIncomingCall(context, call); @@ -123,6 +123,7 @@ public class CallList implements DialerCallDelegate { manager.registerCapabilitiesListener(call); manager.registerStateChangedListener(call); + Trace.beginSection("checkSpam"); final DialerCallListenerImpl dialerCallListener = new DialerCallListenerImpl(call); call.addListener(dialerCallListener); LogUtil.d("CallList.onCallAdded", "callState=" + call.getState()); @@ -169,7 +170,9 @@ public class CallList implements DialerCallDelegate { updateUserMarkedSpamStatus(call, context, number, dialerCallListener); } + Trace.endSection(); + Trace.beginSection("checkBlock"); FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(context); @@ -185,6 +188,7 @@ public class CallList implements DialerCallDelegate { }, call.getNumber(), GeoUtil.getCurrentCountryIso(context)); + Trace.endSection(); if (call.getState() == DialerCall.State.INCOMING || call.getState() == DialerCall.State.CALL_WAITING) { @@ -353,6 +357,7 @@ public class CallList implements DialerCallDelegate { /** Called when a single call has changed. */ private void onIncoming(DialerCall call) { + Trace.beginSection("CallList.onIncoming"); if (updateCallInMap(call)) { LogUtil.i("CallList.onIncoming", String.valueOf(call)); } @@ -360,6 +365,7 @@ public class CallList implements DialerCallDelegate { for (Listener listener : mListeners) { listener.onIncomingCall(call); } + Trace.endSection(); } public void addListener(@NonNull Listener listener) { @@ -570,6 +576,7 @@ public class CallList implements DialerCallDelegate { */ @VisibleForTesting void onUpdateCall(DialerCall call) { + Trace.beginSection("CallList.onUpdateCall"); LogUtil.d("CallList.onUpdateCall", String.valueOf(call)); if (!mCallById.containsKey(call.getId()) && call.isExternalCall()) { // When a regular call becomes external, it is removed from the call list, and there may be @@ -582,6 +589,7 @@ public class CallList implements DialerCallDelegate { if (updateCallInMap(call)) { LogUtil.i("CallList.onUpdateCall", String.valueOf(call)); } + Trace.endSection(); } /** @@ -606,6 +614,7 @@ public class CallList implements DialerCallDelegate { * @return false if no call previously existed and no call was added, otherwise true. */ private boolean updateCallInMap(DialerCall call) { + Trace.beginSection("CallList.updateCallInMap"); Objects.requireNonNull(call); boolean updated = false; @@ -635,6 +644,7 @@ public class CallList implements DialerCallDelegate { updated = true; } + Trace.endSection(); return updated; } @@ -764,7 +774,7 @@ public class CallList implements DialerCallDelegate { @Override public void onDialerCallUpdate() { - Trace.beginSection("onUpdate"); + Trace.beginSection("CallList.onDialerCallUpdate"); onUpdateCall(mCall); notifyGenericListeners(); Trace.endSection(); diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index 2e15264a5..6ba0c8121 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -430,7 +430,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } private void update() { - Trace.beginSection("Update"); + Trace.beginSection("DialerCall.update"); int oldState = getState(); // Clear any cache here that could potentially change on update. videoTech = null; @@ -455,6 +455,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } private void updateFromTelecomCall() { + Trace.beginSection("DialerCall.updateFromTelecomCall"); LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString()); mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState()); @@ -503,6 +504,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } } } + Trace.endSection(); } /** @@ -680,13 +682,23 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } public void setState(int state) { - mState = state; - if (mState == State.INCOMING) { + if (state == State.INCOMING) { mLogState.isIncoming = true; - } else if (mState == State.DISCONNECTED) { - mLogState.duration = + } else if (state == State.DISCONNECTED) { + long newDuration = getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis(); + if (mState != state) { + mLogState.duration = newDuration; + } else { + LogUtil.i( + "DialerCall.setState", + "ignoring state transition from DISCONNECTED to DISCONNECTED." + + " Duration would have changed from %s to %s", + mLogState.duration, + newDuration); + } } + mState = state; } public int getNumberPresentation() { diff --git a/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java b/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java index b093a1b15..035f5cdac 100644 --- a/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java +++ b/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java @@ -50,9 +50,9 @@ class DownloadMapImageTask extends AsyncTask<Location, Void, Drawable> { try { URL mapUrl = new URL(LocationUrlBuilder.getStaticMapUrl(ui.getContext(), locations[0])); + TrafficStats.setThreadStatsTag(TrafficStatsTags.DOWNLOAD_LOCATION_MAP_TAG); InputStream content = (InputStream) mapUrl.getContent(); - TrafficStats.setThreadStatsTag(TrafficStatsTags.DOWNLOAD_LOCATION_MAP_TAG); return Drawable.createFromStream(content, STATIC_MAP_SRC_NAME); } catch (Exception ex) { LogUtil.e("DownloadMapImageTask.doInBackground", "Exception!!!", ex); diff --git a/java/com/android/incallui/contactgrid/ContactGridManager.java b/java/com/android/incallui/contactgrid/ContactGridManager.java index a6d7d95f0..e4a8a1cc4 100644 --- a/java/com/android/incallui/contactgrid/ContactGridManager.java +++ b/java/com/android/incallui/contactgrid/ContactGridManager.java @@ -31,6 +31,7 @@ import android.widget.TextView; import android.widget.ViewAnimator; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; import com.android.dialer.lettertile.LetterTileDrawable; import com.android.dialer.util.DrawableConverter; import com.android.incallui.incall.protocol.ContactPhotoType; @@ -347,12 +348,16 @@ public class ContactGridManager { } if (info.isTimerVisible) { + bottomTextSwitcher.setDisplayedChild(1); + bottomTimerView.setBase( + primaryCallState.connectTimeMillis + - System.currentTimeMillis() + + SystemClock.elapsedRealtime()); if (!isTimerStarted) { - bottomTextSwitcher.setDisplayedChild(1); - bottomTimerView.setBase( - primaryCallState.connectTimeMillis - - System.currentTimeMillis() - + SystemClock.elapsedRealtime()); + LogUtil.i( + "ContactGridManager.updateBottomRow", + "starting timer with base: %d", + bottomTimerView.getBase()); bottomTimerView.start(); isTimerStarted = true; } diff --git a/java/com/android/incallui/incall/impl/ButtonChooser.java b/java/com/android/incallui/incall/impl/ButtonChooser.java index 55b82f015..095a8beba 100644 --- a/java/com/android/incallui/incall/impl/ButtonChooser.java +++ b/java/com/android/incallui/incall/impl/ButtonChooser.java @@ -18,6 +18,7 @@ package com.android.incallui.incall.impl; import android.support.annotation.NonNull; import com.android.dialer.common.Assert; +import com.android.incallui.incall.impl.MappedButtonConfig.MappingInfo; import com.android.incallui.incall.protocol.InCallButtonIds; import java.util.ArrayList; import java.util.Collections; @@ -103,12 +104,29 @@ final class ButtonChooser { if (placedButtons.size() >= numUiButtons) { return; } + // If the conflict button is allowed but disabled, don't place it since it probably will // move when it's enabled. if (!allowedButtons.contains(conflict) || disabledButtons.contains(conflict)) { continue; } + + if (isMutuallyExclusiveButtonAvailable( + config.lookupMappingInfo(conflict).getMutuallyExclusiveButton(), allowedButtons)) { + continue; + } placedButtons.add(conflict); } } + + private boolean isMutuallyExclusiveButtonAvailable( + int mutuallyExclusiveButton, @NonNull Set<Integer> allowedButtons) { + if (mutuallyExclusiveButton == MappingInfo.NO_MUTUALLY_EXCLUSIVE_BUTTON_SET) { + return false; + } + if (allowedButtons.contains(mutuallyExclusiveButton)) { + return true; + } + return false; + } } diff --git a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java index 99364e22c..0f4a95d38 100644 --- a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java +++ b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java @@ -74,6 +74,12 @@ class ButtonChooserFactory { mapping.put( InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, MappingInfo.builder(4).setSlotOrder(10).build()); mapping.put(InCallButtonIds.BUTTON_SWAP, MappingInfo.builder(5).setSlotOrder(0).build()); + mapping.put( + InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY, + MappingInfo.builder(5) + .setSlotOrder(Integer.MAX_VALUE) + .setMutuallyExclusiveButton(InCallButtonIds.BUTTON_SWAP) + .build()); return new ButtonChooser(new MappedButtonConfig(mapping)); } diff --git a/java/com/android/incallui/incall/impl/MappedButtonConfig.java b/java/com/android/incallui/incall/impl/MappedButtonConfig.java index 722983796..67c4137b9 100644 --- a/java/com/android/incallui/incall/impl/MappedButtonConfig.java +++ b/java/com/android/incallui/incall/impl/MappedButtonConfig.java @@ -141,7 +141,7 @@ final class MappedButtonConfig { } @NonNull - private MappingInfo lookupMappingInfo(@InCallButtonIds int button) { + public MappingInfo lookupMappingInfo(@InCallButtonIds int button) { MappingInfo info = mapping.get(button); if (info == null) { throw new IllegalArgumentException( @@ -154,6 +154,8 @@ final class MappedButtonConfig { @AutoValue abstract static class MappingInfo { + public static final int NO_MUTUALLY_EXCLUSIVE_BUTTON_SET = -1; + /** The Ui slot into which a given button desires to be placed. */ public abstract int getSlot(); @@ -171,11 +173,20 @@ final class MappedButtonConfig { */ public abstract int getConflictOrder(); + /** + * Returns an integer representing a button for which the given button conflicts. Defaults to + * {@link NO_MUTUALLY_EXCLUSIVE_BUTTON_SET}. + * + * <p>If the mutually exclusive button is chosen, the associated button should never be chosen. + */ + public abstract @InCallButtonIds int getMutuallyExclusiveButton(); + static Builder builder(int slot) { return new AutoValue_MappedButtonConfig_MappingInfo.Builder() .setSlot(slot) .setSlotOrder(Integer.MAX_VALUE) - .setConflictOrder(Integer.MAX_VALUE); + .setConflictOrder(Integer.MAX_VALUE) + .setMutuallyExclusiveButton(NO_MUTUALLY_EXCLUSIVE_BUTTON_SET); } /** Class used to build instances of {@link MappingInfo}. */ @@ -187,6 +198,8 @@ final class MappedButtonConfig { public abstract Builder setConflictOrder(int conflictOrder); + public abstract Builder setMutuallyExclusiveButton(@InCallButtonIds int button); + public abstract MappingInfo build(); } } diff --git a/java/com/android/incallui/spam/NumberInCallHistoryTask.java b/java/com/android/incallui/spam/NumberInCallHistoryTask.java deleted file mode 100644 index 886933fb5..000000000 --- a/java/com/android/incallui/spam/NumberInCallHistoryTask.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.spam; - -import android.annotation.TargetApi; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.os.AsyncTask; -import android.os.Build.VERSION_CODES; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.support.annotation.NonNull; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.AsyncTaskExecutor; -import com.android.dialer.common.concurrent.AsyncTaskExecutors; -import com.android.dialer.telecom.TelecomUtil; -import com.android.dialer.util.PermissionsUtil; -import com.android.incallui.call.DialerCall; -import com.android.incallui.call.DialerCall.CallHistoryStatus; -import java.util.Objects; - -/** Checks if the number is in the call history. */ -@TargetApi(VERSION_CODES.M) -public class NumberInCallHistoryTask extends AsyncTask<Void, Void, Integer> { - - public static final String TASK_ID = "number_in_call_history_status"; - - private final Context context; - private final Listener listener; - private final String number; - private final String countryIso; - - public NumberInCallHistoryTask( - @NonNull Context context, @NonNull Listener listener, String number, String countryIso) { - this.context = Objects.requireNonNull(context); - this.listener = Objects.requireNonNull(listener); - this.number = number; - this.countryIso = countryIso; - } - - public void submitTask() { - if (!PermissionsUtil.hasPhonePermissions(context)) { - return; - } - AsyncTaskExecutor asyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); - asyncTaskExecutor.submit(TASK_ID, this); - } - - @Override - @CallHistoryStatus - public Integer doInBackground(Void... params) { - String numberToQuery = number; - String fieldToQuery = Calls.NUMBER; - String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); - - // If we can normalize the number successfully, look in "normalized_number" - // field instead. Otherwise, look for number in "number" field. - if (!TextUtils.isEmpty(normalizedNumber)) { - numberToQuery = normalizedNumber; - fieldToQuery = Calls.CACHED_NORMALIZED_NUMBER; - } - try (Cursor cursor = - context - .getContentResolver() - .query( - TelecomUtil.getCallLogUri(context), - new String[] {CallLog.Calls._ID}, - fieldToQuery + " = ?", - new String[] {numberToQuery}, - null)) { - return cursor != null && cursor.getCount() > 0 - ? DialerCall.CALL_HISTORY_STATUS_PRESENT - : DialerCall.CALL_HISTORY_STATUS_NOT_PRESENT; - } catch (SQLiteException e) { - LogUtil.e("NumberInCallHistoryTask.doInBackground", "query call log error", e); - return DialerCall.CALL_HISTORY_STATUS_UNKNOWN; - } - } - - @Override - public void onPostExecute(@CallHistoryStatus Integer callHistoryStatus) { - listener.onComplete(callHistoryStatus); - } - - /** Callback for the async task. */ - public interface Listener { - - void onComplete(@CallHistoryStatus int callHistoryStatus); - } -} diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java index 56ae55823..7ca3fbba6 100644 --- a/java/com/android/incallui/spam/SpamCallListListener.java +++ b/java/com/android/incallui/spam/SpamCallListListener.java @@ -16,14 +16,21 @@ package com.android.incallui.spam; +import android.annotation.TargetApi; import android.app.Notification; import android.app.Notification.Builder; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; import android.graphics.drawable.Icon; +import android.os.Build.VERSION_CODES; +import android.provider.CallLog; +import android.provider.CallLog.Calls; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.os.BuildCompat; import android.telecom.DisconnectCause; import android.telephony.PhoneNumberUtils; @@ -31,17 +38,23 @@ import android.text.TextUtils; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.blocking.FilteredNumbersUtil; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.DialerExecutorFactory; import com.android.dialer.location.GeoUtil; import com.android.dialer.logging.ContactLookupResult; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.notification.NotificationChannelId; import com.android.dialer.spam.Spam; +import com.android.dialer.telecom.TelecomUtil; +import com.android.dialer.util.PermissionsUtil; import com.android.incallui.R; import com.android.incallui.call.CallList; import com.android.incallui.call.DialerCall; import com.android.incallui.call.DialerCall.CallHistoryStatus; +import java.util.Arrays; import java.util.Random; /** @@ -53,15 +66,67 @@ public class SpamCallListListener implements CallList.Listener { private final Context context; private final Random random; + private final DialerExecutorFactory dialerExecutorFactory; - public SpamCallListListener(Context context) { - this.context = context; - this.random = new Random(); + public SpamCallListListener(Context context, @NonNull DialerExecutorFactory factory) { + this(context, new Random(), factory); } - public SpamCallListListener(Context context, Random rand) { + public SpamCallListListener( + Context context, Random rand, @NonNull DialerExecutorFactory factory) { this.context = context; this.random = rand; + Assert.isNotNull(factory); + this.dialerExecutorFactory = factory; + } + + /** Checks if the number is in the call history. */ + @TargetApi(VERSION_CODES.M) + private final class NumberInCallHistoryWorker implements Worker<Void, Integer> { + + private final Context appContext; + private final String number; + private final String countryIso; + + public NumberInCallHistoryWorker( + @NonNull Context appContext, String number, String countryIso) { + this.appContext = Assert.isNotNull(appContext); + this.number = number; + this.countryIso = countryIso; + } + + @Override + @NonNull + @CallHistoryStatus + public Integer doInBackground(@Nullable Void input) throws Throwable { + String numberToQuery = number; + String fieldToQuery = Calls.NUMBER; + String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); + + // If we can normalize the number successfully, look in "normalized_number" + // field instead. Otherwise, look for number in "number" field. + if (!TextUtils.isEmpty(normalizedNumber)) { + numberToQuery = normalizedNumber; + fieldToQuery = Calls.CACHED_NORMALIZED_NUMBER; + } + + try (Cursor cursor = + appContext + .getContentResolver() + .query( + TelecomUtil.getCallLogUri(appContext), + new String[] {CallLog.Calls._ID}, + fieldToQuery + " = ?", + new String[] {numberToQuery}, + null)) { + return cursor != null && cursor.getCount() > 0 + ? DialerCall.CALL_HISTORY_STATUS_PRESENT + : DialerCall.CALL_HISTORY_STATUS_NOT_PRESENT; + } catch (SQLiteException e) { + LogUtil.e("NumberInCallHistoryWorker.doInBackground", "query call log error", e); + return DialerCall.CALL_HISTORY_STATUS_UNKNOWN; + } + } } @Override @@ -70,15 +135,24 @@ public class SpamCallListListener implements CallList.Listener { if (TextUtils.isEmpty(number)) { return; } - NumberInCallHistoryTask.Listener listener = - new NumberInCallHistoryTask.Listener() { - @Override - public void onComplete(@CallHistoryStatus int callHistoryStatus) { - call.setCallHistoryStatus(callHistoryStatus); - } - }; - new NumberInCallHistoryTask(context, listener, number, GeoUtil.getCurrentCountryIso(context)) - .submitTask(); + + String[] deniedPhonePermissions = + PermissionsUtil.getPermissionsCurrentlyDenied( + context, PermissionsUtil.allPhoneGroupPermissionsUsedInDialer); + if (deniedPhonePermissions.length > 0) { + LogUtil.i( + "NumberInCallHistoryWorker.submitTask", + "Need phone permissions: " + Arrays.toString(deniedPhonePermissions)); + return; + } + + NumberInCallHistoryWorker historyTask = + new NumberInCallHistoryWorker(context, number, GeoUtil.getCurrentCountryIso(context)); + dialerExecutorFactory + .createNonUiTaskBuilder(historyTask) + .onSuccess((result) -> call.setCallHistoryStatus(result)) + .build() + .executeParallel(null); } @Override diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java index 3e80a7f59..9a781e6c2 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java @@ -24,7 +24,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.StrictMode; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; import android.support.v4.os.BuildCompat; @@ -32,6 +31,7 @@ import android.text.TextUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.constants.ScheduledJobIds; +import com.android.dialer.strictmode.DialerStrictMode; import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -48,7 +48,6 @@ public class TranscriptionService extends JobService { private JobParameters jobParameters; private TranscriptionClientFactory clientFactory; private TranscriptionConfigProvider configProvider; - private StrictMode.VmPolicy originalPolicy; /** Callback used by a task to indicate it has finished processing its work item */ interface JobCallback { @@ -111,8 +110,7 @@ public class TranscriptionService extends JobService { LogUtil.i( "TranscriptionService.onStartJob", "transcription server address: " + configProvider.getServerAddress()); - originalPolicy = StrictMode.getVmPolicy(); - StrictMode.enableDefaults(); + DialerStrictMode.disableDeathPenalty(); // Re-enabled in cleanup. jobParameters = params; return checkForWork(); } @@ -142,10 +140,7 @@ public class TranscriptionService extends JobService { executorService.shutdownNow(); executorService = null; } - if (originalPolicy != null) { - StrictMode.setVmPolicy(originalPolicy); - originalPolicy = null; - } + DialerStrictMode.enableDeathPenalty(); } @MainThread |