From 47aa39c7ea423722254b5a70b50dc6c1513b9ee5 Mon Sep 17 00:00:00 2001 From: zachh Date: Mon, 4 Dec 2017 17:26:05 -0800 Subject: Added PhoneLookupSelector. This class is responsible for prioritizing and selecting data from a PhoneLookupInfo object, which contains information from many phone lookup sources. Bug: 34672501 Test: unit PiperOrigin-RevId: 177893924 Change-Id: Ib98a4656fe87141162a7ac53af4a0ad421196046 --- .../phonelookup/PhoneLookupDataSource.java | 7 +--- .../dialer/phonelookup/PhoneLookupSelector.java | 47 ++++++++++++++++++++++ .../composite/CompositePhoneLookup.java | 2 + 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 java/com/android/dialer/phonelookup/PhoneLookupSelector.java (limited to 'java/com') diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 7a7f2070b..010cb8541 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -31,6 +31,7 @@ import com.android.dialer.calllog.datasources.CallLogMutations; import com.android.dialer.common.LogUtil; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.PhoneLookupSelector; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.google.common.collect.ImmutableMap; @@ -376,11 +377,7 @@ public final class PhoneLookupDataSource implements CallLogDataSource { } } - // TODO(zachh): Extract this logic into a proper selection class or package. private static String selectName(PhoneLookupInfo phoneLookupInfo) { - if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) { - return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0).getName(); - } - return ""; + return PhoneLookupSelector.selectName(phoneLookupInfo); } } diff --git a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/PhoneLookupSelector.java new file mode 100644 index 000000000..a746ea44f --- /dev/null +++ b/java/com/android/dialer/phonelookup/PhoneLookupSelector.java @@ -0,0 +1,47 @@ +/* + * 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.phonelookup; + +import android.support.annotation.NonNull; + +/** + * Prioritizes information from a {@link PhoneLookupInfo}. + * + *

For example, a single {@link PhoneLookupInfo} may contain different name information from many + * different {@link PhoneLookup} sources. This class defines the rules for deciding which name + * should be selected for display to the user, by prioritizing the data from some {@link PhoneLookup + * PhoneLookups} over others. + * + *

Note that the logic in this class may be highly coupled with the logic in {@code + * CompositePhoneLookup}, because {@code CompositePhoneLookup} may also include prioritization logic + * for short-circuiting low-priority {@link PhoneLookup PhoneLookups}. + */ +public final class PhoneLookupSelector { + + /** + * Select the name associated with this number. Examples of this are a local contact's name or a + * business name received from caller ID. + */ + @NonNull + public static String selectName(PhoneLookupInfo phoneLookupInfo) { + if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) { + // Arbitrarily select the first contact's name. In the future, it may make sense to join the + // names such as "Mom, Dad" in the case that multiple contacts share the same number. + return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0).getName(); + } + return ""; + } +} diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java index 520c46f9e..f432e27ae 100644 --- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java @@ -53,6 +53,8 @@ public final class CompositePhoneLookup implements PhoneLookup { */ @Override public ListenableFuture lookup(@NonNull Call call) { + // TODO(zachh): Add short-circuiting logic so that this call is not blocked on low-priority + // lookups finishing when a higher-priority one has already finished. List> futures = new ArrayList<>(); for (PhoneLookup phoneLookup : phoneLookups) { futures.add(phoneLookup.lookup(call)); -- cgit v1.2.3 From b29f58262aef174c0b51c14c7b764e5ee934c67f Mon Sep 17 00:00:00 2001 From: zachh Date: Mon, 4 Dec 2017 17:53:27 -0800 Subject: Added PhoneLookupHistoryRecorder. When a call is added in InCallUi, it fetches the current PhoneLookupInfo for the call and writes it to PhoneLookupHistory. Required updating PhoneLookupHistoryContentProvider#update to use "replace" to (presumably) atomically insert when a row is missing. Bug: 34672501 Test: unit PiperOrigin-RevId: 177896892 Change-Id: I43f9ded240a81156722be816a9635d586de642a1 --- .../dialer/phonelookup/cp2/Cp2PhoneLookup.java | 3 +- .../PhoneLookupHistoryContentProvider.java | 30 ++++-- java/com/android/incallui/InCallPresenter.java | 2 + .../incallui/PhoneLookupHistoryRecorder.java | 116 +++++++++++++++++++++ 4 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 java/com/android/incallui/PhoneLookupHistoryRecorder.java (limited to 'java/com') diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index 307f998ea..cfb8fb7b8 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -82,7 +82,8 @@ public final class Cp2PhoneLookup implements PhoneLookup { @Override public ListenableFuture lookup(@NonNull Call call) { - throw new UnsupportedOperationException(); + // TODO(zachh): Implementation. + return MoreExecutors.newDirectExecutorService().submit(PhoneLookupInfo::getDefaultInstance); } @Override diff --git a/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java b/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java index 27041e746..e85654e99 100644 --- a/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java +++ b/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java @@ -201,6 +201,12 @@ public class PhoneLookupHistoryContentProvider extends ContentProvider { return rows; } + /** + * Note: If the normalized number is included as part of the URI (for example, + * "content://com.android.dialer.phonelookuphistory/PhoneLookupHistory/+123") then the update + * operation will actually be a "replace" operation, inserting a new row if one does not already + * exist. + */ @Override public int update( @NonNull Uri uri, @@ -214,7 +220,13 @@ public class PhoneLookupHistoryContentProvider extends ContentProvider { int match = uriMatcher.match(uri); switch (match) { case PHONE_LOOKUP_HISTORY_TABLE_CODE: - break; + int rows = database.update(PhoneLookupHistory.TABLE, values, selection, selectionArgs); + if (rows == 0) { + LogUtil.w("PhoneLookupHistoryContentProvider.update", "no rows updated"); + return rows; + } + notifyChange(uri); + return rows; case PHONE_LOOKUP_HISTORY_TABLE_ID_CODE: Assert.checkArgument( !values.containsKey(PhoneLookupHistory.NORMALIZED_NUMBER), @@ -222,19 +234,15 @@ public class PhoneLookupHistoryContentProvider extends ContentProvider { Assert.checkArgument(selection == null, "Do not specify selection when updating by ID"); Assert.checkArgument( selectionArgs == null, "Do not specify selection args when updating by ID"); - selection = PhoneLookupHistory.NORMALIZED_NUMBER + "= ?"; - selectionArgs = new String[] {uri.getLastPathSegment()}; - break; + + String normalizedNumber = uri.getLastPathSegment(); + values.put(PhoneLookupHistory.NORMALIZED_NUMBER, normalizedNumber); + database.replace(PhoneLookupHistory.TABLE, null, values); + notifyChange(uri); + return 1; default: throw new IllegalArgumentException("Unknown uri: " + uri); } - int rows = database.update(PhoneLookupHistory.TABLE, values, selection, selectionArgs); - if (rows == 0) { - LogUtil.w("PhoneLookupHistoryContentProvider.update", "no rows updated"); - return rows; - } - notifyChange(uri); - return rows; } private void notifyChange(Uri uri) { diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java index c5310b969..f8605ae7c 100644 --- a/java/com/android/incallui/InCallPresenter.java +++ b/java/com/android/incallui/InCallPresenter.java @@ -545,6 +545,8 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud // Since a call has been added we are no longer waiting for Telecom to send us a call. setBoundAndWaitingForOutgoingCall(false, null); call.registerCallback(mCallCallback); + // TODO(maxwelb): Return the future in recordPhoneLookupInfo and propagate. + PhoneLookupHistoryRecorder.recordPhoneLookupInfo(mContext.getApplicationContext(), call); Trace.endSection(); } diff --git a/java/com/android/incallui/PhoneLookupHistoryRecorder.java b/java/com/android/incallui/PhoneLookupHistoryRecorder.java new file mode 100644 index 000000000..2632e6515 --- /dev/null +++ b/java/com/android/incallui/PhoneLookupHistoryRecorder.java @@ -0,0 +1,116 @@ +/* + * 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.incallui; + +import android.content.ContentValues; +import android.content.Context; +import android.support.annotation.Nullable; +import android.telecom.Call; +import android.telecom.PhoneAccountHandle; +import android.telephony.PhoneNumberUtils; +import android.telephony.SubscriptionInfo; +import android.text.TextUtils; +import com.android.dialer.buildtype.BuildType; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutors; +import com.android.dialer.location.CountryDetector; +import com.android.dialer.phonelookup.PhoneLookupComponent; +import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; +import com.android.dialer.telecom.TelecomUtil; +import com.android.incallui.util.TelecomCallUtil; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Locale; + +/** + * Fetches the current {@link PhoneLookupInfo} for the provided call and writes it to the + * PhoneLookupHistory. + */ +final class PhoneLookupHistoryRecorder { + + /** + * If the new UI is enabled, fetches the current {@link PhoneLookupInfo} for the provided call and + * writes it to the PhoneLookupHistory. Otherwise does nothing. + */ + static void recordPhoneLookupInfo(Context appContext, Call call) { + if (!(BuildType.get() == BuildType.BUGFOOD || LogUtil.isDebugEnabled())) { + return; + } + ListenableFuture future = + PhoneLookupComponent.get(appContext).phoneLookup().lookup(call); + Futures.addCallback( + future, + new FutureCallback() { + @Override + public void onSuccess(@Nullable PhoneLookupInfo result) { + Assert.checkArgument(result != null); + Optional normalizedNumber = getNormalizedNumber(appContext, call); + if (!normalizedNumber.isPresent()) { + LogUtil.w("PhoneLookupHistoryRecorder.onSuccess", "couldn't get a number"); + return; + } + ContentValues contentValues = new ContentValues(); + contentValues.put(PhoneLookupHistory.PHONE_LOOKUP_INFO, result.toByteArray()); + contentValues.put(PhoneLookupHistory.LAST_MODIFIED, System.currentTimeMillis()); + appContext + .getContentResolver() + .update( + PhoneLookupHistory.CONTENT_URI + .buildUpon() + .appendEncodedPath(normalizedNumber.get()) + .build(), + contentValues, + null, + null); + } + + @Override + public void onFailure(Throwable t) { + // TODO(zachh): Consider how to best handle this; take measures to repair call log? + LogUtil.w( + "PhoneLookupHistoryRecorder.onFailure", "could not write PhoneLookupHistory", t); + } + }, + DialerExecutors.getLowPriorityThreadPool(appContext)); + } + + private static Optional getNormalizedNumber(Context appContext, Call call) { + PhoneAccountHandle phoneAccountHandle = call.getDetails().getAccountHandle(); + Optional subscriptionInfo = + TelecomUtil.getSubscriptionInfo(appContext, phoneAccountHandle); + String countryCode = + subscriptionInfo.isPresent() + ? subscriptionInfo.get().getCountryIso() + : CountryDetector.getInstance(appContext).getCurrentCountryIso(); + if (countryCode == null) { + LogUtil.w( + "PhoneLookupHistoryRecorder.getNormalizedNumber", + "couldn't find a country code for call"); + countryCode = "US"; + } + String rawNumber = TelecomCallUtil.getNumber(call); + if (TextUtils.isEmpty(rawNumber)) { + return Optional.absent(); + } + String normalizedNumber = + PhoneNumberUtils.formatNumberToE164(rawNumber, countryCode.toUpperCase(Locale.US)); + return normalizedNumber == null ? Optional.of(rawNumber) : Optional.of(normalizedNumber); + } +} -- cgit v1.2.3 From 5b77c7dfd420cb77cdb195fd441a8d57676a837f Mon Sep 17 00:00:00 2001 From: erfanian Date: Tue, 5 Dec 2017 08:43:58 -0800 Subject: Refactor Assisted Dialing Settings into standalone activity. This will enable easier porting back into the framework. Test: existing tests PiperOrigin-RevId: 177960274 Change-Id: I1aa18734758900a93b19f4bb87b7759f6f7ca370 --- .../app/settings/DialerSettingsActivity.java | 4 +- .../dialer/assisteddialing/ui/AndroidManifest.xml | 13 +++++++ .../ui/AssistedDialingSettingActivity.java | 44 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingActivity.java (limited to 'java/com') diff --git a/java/com/android/dialer/app/settings/DialerSettingsActivity.java b/java/com/android/dialer/app/settings/DialerSettingsActivity.java index fbd6f4808..641095512 100644 --- a/java/com/android/dialer/app/settings/DialerSettingsActivity.java +++ b/java/com/android/dialer/app/settings/DialerSettingsActivity.java @@ -35,7 +35,6 @@ import android.widget.Toast; import com.android.dialer.about.AboutPhoneFragment; import com.android.dialer.app.R; import com.android.dialer.assisteddialing.ConcreteCreator; -import com.android.dialer.assisteddialing.ui.AssistedDialingSettingFragment; import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.telephony.TelephonyManagerCompat; @@ -170,7 +169,8 @@ public class DialerSettingsActivity extends AppCompatPreferenceActivity { Header assistedDialingSettingsHeader = new Header(); assistedDialingSettingsHeader.titleRes = com.android.dialer.assisteddialing.ui.R.string.assisted_dialing_setting_title; - assistedDialingSettingsHeader.fragment = AssistedDialingSettingFragment.class.getName(); + assistedDialingSettingsHeader.intent = + new Intent("com.android.dialer.app.settings.SHOW_ASSISTED_DIALING_SETTINGS"); target.add(assistedDialingSettingsHeader); } diff --git a/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml index 724874750..5266b1376 100644 --- a/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml +++ b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml @@ -19,4 +19,17 @@ android:minSdkVersion="23" android:targetSdkVersion="24"/> + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingActivity.java b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingActivity.java new file mode 100644 index 000000000..ca36745b3 --- /dev/null +++ b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingActivity.java @@ -0,0 +1,44 @@ +/* + * 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.assisteddialing.ui; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; + +/** The Settings Activity for Assisted Dialing. */ +public class AssistedDialingSettingActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + + getFragmentManager() + .beginTransaction() + .replace(android.R.id.content, new AssistedDialingSettingFragment()) + .commit(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } +} -- cgit v1.2.3 From 81a77ffc4d36c6054a75acfe7b048e7c0d7a8744 Mon Sep 17 00:00:00 2001 From: yueg Date: Tue, 5 Dec 2017 10:29:03 -0800 Subject: Bubble v2 animation changes. Including: - expanded view expands/collapses from top of itself - small icon on avatar shows on left side when bubble is on right side - when expand on bottom, bubble move up a bit so that expanded view doesn't go off screen. It also go back to previous position when collapse. - remove animation for collapse when move expanded bubble This change should not enable bubble v2 for anyone. Bug: 67605985 Test: manual PiperOrigin-RevId: 177974562 Change-Id: Id83f3f744b717d51fbe58e58769ac2cd2810d2b5 --- .../incallui/NewReturnToCallController.java | 43 -- java/com/android/newbubble/NewBubble.java | 468 +++++++++++++-------- java/com/android/newbubble/NewMoveHandler.java | 40 +- .../newbubble/res/layout/new_bubble_base.xml | 129 +++--- java/com/android/newbubble/res/values/values.xml | 7 +- 5 files changed, 415 insertions(+), 272 deletions(-) (limited to 'java/com') diff --git a/java/com/android/incallui/NewReturnToCallController.java b/java/com/android/incallui/NewReturnToCallController.java index 399b18568..7a1abee51 100644 --- a/java/com/android/incallui/NewReturnToCallController.java +++ b/java/com/android/incallui/NewReturnToCallController.java @@ -29,8 +29,6 @@ import com.android.contacts.common.util.ContactDisplayUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.lettertile.LetterTileDrawable; -import com.android.dialer.logging.DialerImpression; -import com.android.dialer.logging.Logger; import com.android.dialer.telecom.TelecomUtil; import com.android.incallui.ContactInfoCache.ContactCacheEntry; import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; @@ -43,8 +41,6 @@ import com.android.incallui.call.DialerCall; import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo; import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo.IconSize; import com.android.newbubble.NewBubble; -import com.android.newbubble.NewBubble.BubbleExpansionStateListener; -import com.android.newbubble.NewBubble.ExpansionState; import com.android.newbubble.NewBubbleInfo; import com.android.newbubble.NewBubbleInfo.Action; import java.lang.ref.WeakReference; @@ -150,45 +146,6 @@ public class NewReturnToCallController implements InCallUiListener, Listener, Au return null; } NewBubble returnToCallBubble = NewBubble.createBubble(context, generateBubbleInfo()); - returnToCallBubble.setBubbleExpansionStateListener( - new BubbleExpansionStateListener() { - @Override - public void onBubbleExpansionStateChanged( - @ExpansionState int expansionState, boolean isUserAction) { - if (!isUserAction) { - return; - } - - DialerCall call = CallList.getInstance().getActiveOrBackgroundCall(); - switch (expansionState) { - case ExpansionState.START_EXPANDING: - if (call != null) { - Logger.get(context) - .logCallImpression( - DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_EXPAND, - call.getUniqueCallId(), - call.getTimeAddedMs()); - } else { - Logger.get(context) - .logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_EXPAND); - } - break; - case ExpansionState.START_COLLAPSING: - if (call != null) { - Logger.get(context) - .logCallImpression( - DialerImpression.Type.BUBBLE_COLLAPSE_BY_USER, - call.getUniqueCallId(), - call.getTimeAddedMs()); - } else { - Logger.get(context).logImpression(DialerImpression.Type.BUBBLE_COLLAPSE_BY_USER); - } - break; - default: - break; - } - } - }); returnToCallBubble.show(); return returnToCallBubble; } diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java index e690f4be4..ef3a971dd 100644 --- a/java/com/android/newbubble/NewBubble.java +++ b/java/com/android/newbubble/NewBubble.java @@ -17,12 +17,15 @@ package com.android.newbubble; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.PendingIntent.CanceledException; import android.content.Context; import android.content.Intent; +import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; @@ -51,10 +54,16 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.animation.AnticipateInterpolator; import android.view.animation.OvershootInterpolator; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.ViewAnimator; +import com.android.dialer.common.LogUtil; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; import com.android.dialer.util.DrawableConverter; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; import com.android.newbubble.NewBubbleInfo.Action; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -96,11 +105,14 @@ public class NewBubble { private CharSequence textAfterShow; private int collapseEndAction; - @VisibleForTesting ViewHolder viewHolder; + ViewHolder viewHolder; private ViewPropertyAnimator collapseAnimation; private Integer overrideGravity; private ViewPropertyAnimator exitAnimator; + private int leftBoundary; + private int savedYPosition = -1; + private final Runnable collapseRunnable = new Runnable() { @Override @@ -110,17 +122,11 @@ public class NewBubble { // Always reset here since text shouldn't keep showing. hideAndReset(); } else { - doResize( - () -> - viewHolder - .getPrimaryButton() - .setDisplayedChild(ViewHolder.CHILD_INDEX_AVATAR_AND_ICON)); + viewHolder.getPrimaryButton().setDisplayedChild(ViewHolder.CHILD_INDEX_AVATAR_AND_ICON); } } }; - private BubbleExpansionStateListener bubbleExpansionStateListener; - /** Type of action after bubble collapse */ @Retention(RetentionPolicy.SOURCE) @IntDef({CollapseEnd.NOTHING, CollapseEnd.HIDE}) @@ -206,15 +212,20 @@ public class NewBubble { windowManager = context.getSystemService(WindowManager.class); viewHolder = new ViewHolder(context); + + leftBoundary = + context.getResources().getDimensionPixelOffset(R.dimen.bubble_off_screen_size_horizontal) + - context + .getResources() + .getDimensionPixelSize(R.dimen.bubble_shadow_padding_size_horizontal); } /** Expands the main bubble menu. */ public void expand(boolean isUserAction) { - if (bubbleExpansionStateListener != null) { - bubbleExpansionStateListener.onBubbleExpansionStateChanged( - ExpansionState.START_EXPANDING, isUserAction); + if (isUserAction) { + logBasicOrCallImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_EXPAND); } - doResize(() -> viewHolder.setDrawerVisibility(View.VISIBLE)); + viewHolder.setDrawerVisibility(View.INVISIBLE); View expandedView = viewHolder.getExpandedView(); expandedView .getViewTreeObserver() @@ -222,13 +233,62 @@ public class NewBubble { new OnPreDrawListener() { @Override public boolean onPreDraw() { - // Animate expanded view to move from above primary button to its final position + // Move the whole bubble up so that expanded view is still in screen + int moveUpDistance = viewHolder.getMoveUpDistance(); + if (moveUpDistance != 0) { + savedYPosition = windowParams.y; + } + + // Calculate the move-to-middle distance + int deltaX = + (int) viewHolder.getRoot().findViewById(R.id.bubble_primary_container).getX(); + float k = (float) moveUpDistance / deltaX; + if (isDrawingFromRight()) { + deltaX = -deltaX; + } + + // Do X-move and Y-move together + + final int startX = windowParams.x - deltaX; + final int startY = windowParams.y; + ValueAnimator animator = ValueAnimator.ofFloat(startX, windowParams.x); + animator.setInterpolator(new LinearOutSlowInInterpolator()); + animator.addUpdateListener( + (valueAnimator) -> { + // Update windowParams and the root layout. + // We can't do ViewPropertyAnimation since it clips children. + float newX = (float) valueAnimator.getAnimatedValue(); + if (moveUpDistance != 0) { + windowParams.y = startY - (int) (Math.abs(newX - (float) startX) * k); + } + windowParams.x = (int) newX; + windowManager.updateViewLayout(viewHolder.getRoot(), windowParams); + }); + animator.addListener( + new AnimatorListener() { + @Override + public void onAnimationEnd(Animator animation) { + // Show expanded view + expandedView.setVisibility(View.VISIBLE); + expandedView.setTranslationY(-expandedView.getHeight()); + expandedView + .animate() + .setInterpolator(new LinearOutSlowInInterpolator()) + .translationY(0); + } + + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + animator.start(); + expandedView.getViewTreeObserver().removeOnPreDrawListener(this); - expandedView.setTranslationY(-viewHolder.getRoot().getHeight()); - expandedView - .animate() - .setInterpolator(new LinearOutSlowInInterpolator()) - .translationY(0); return false; } }); @@ -236,6 +296,115 @@ public class NewBubble { expanded = true; } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public void startCollapse( + @CollapseEnd int endAction, boolean isUserAction, boolean shouldRecoverYPosition) { + View expandedView = viewHolder.getExpandedView(); + if (expandedView.getVisibility() != View.VISIBLE || collapseAnimation != null) { + // Drawer is already collapsed or animation is running. + return; + } + + overrideGravity = isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT; + setFocused(false); + + if (collapseEndAction == CollapseEnd.NOTHING) { + collapseEndAction = endAction; + } + if (isUserAction && collapseEndAction == CollapseEnd.NOTHING) { + logBasicOrCallImpression(DialerImpression.Type.BUBBLE_COLLAPSE_BY_USER); + } + // Animate expanded view to move from its position to above primary button and hide + collapseAnimation = + expandedView + .animate() + .translationY(-expandedView.getHeight()) + .setInterpolator(new FastOutLinearInInterpolator()) + .withEndAction( + () -> { + collapseAnimation = null; + expanded = false; + + if (textShowing) { + // Will do resize once the text is done. + return; + } + + // Set drawer visibility to INVISIBLE instead of GONE to keep primary button fixed + viewHolder.setDrawerVisibility(View.INVISIBLE); + + // Do X-move and Y-move together + int deltaX = + (int) viewHolder.getRoot().findViewById(R.id.bubble_primary_container).getX(); + int startX = windowParams.x; + int startY = windowParams.y; + float k = + (savedYPosition != -1 && shouldRecoverYPosition) + ? (savedYPosition - startY) / (float) deltaX + : 0; + Path path = new Path(); + path.moveTo(windowParams.x, windowParams.y); + path.lineTo( + windowParams.x - deltaX, + (savedYPosition != -1 && shouldRecoverYPosition) + ? savedYPosition + : windowParams.y); + // The position is not useful after collapse + savedYPosition = -1; + + ValueAnimator animator = ValueAnimator.ofFloat(startX, startX - deltaX); + animator.setInterpolator(new LinearOutSlowInInterpolator()); + animator.addUpdateListener( + (valueAnimator) -> { + // Update windowParams and the root layout. + // We can't do ViewPropertyAnimation since it clips children. + float newX = (float) valueAnimator.getAnimatedValue(); + if (k != 0) { + windowParams.y = startY + (int) (Math.abs(newX - (float) startX) * k); + } + windowParams.x = (int) newX; + windowManager.updateViewLayout(viewHolder.getRoot(), windowParams); + }); + animator.addListener( + new AnimatorListener() { + @Override + public void onAnimationEnd(Animator animation) { + // If collapse on the right side, the primary button move left a bit after + // drawer + // visibility becoming GONE. To avoid it, we create a new ViewHolder. + replaceViewHolder(); + } + + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + animator.start(); + + // If this collapse was to come before a hide, do it now. + if (collapseEndAction == CollapseEnd.HIDE) { + hide(); + } + collapseEndAction = CollapseEnd.NOTHING; + + // Resume normal gravity after any resizing is done. + handler.postDelayed( + () -> { + overrideGravity = null; + if (!viewHolder.isMoving()) { + viewHolder.undoGravityOverride(); + } + }, + // Need to wait twice as long for resize and layout + WINDOW_REDRAW_DELAY_MILLIS * 2); + }); + } + /** * Make the bubble visible. Will show a short entrance animation as it enters. If the bubble is * already showing this method does nothing. @@ -269,8 +438,7 @@ public class NewBubble { | LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.TRANSLUCENT); windowParams.gravity = Gravity.TOP | Gravity.LEFT; - windowParams.x = - context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_horizontal); + windowParams.x = leftBoundary; windowParams.y = currentInfo.getStartingYPosition(); windowParams.height = LayoutParams.WRAP_CONTENT; windowParams.width = LayoutParams.WRAP_CONTENT; @@ -392,7 +560,8 @@ public class NewBubble { public void showText(@NonNull CharSequence text) { textShowing = true; if (expanded) { - startCollapse(CollapseEnd.NOTHING, false); + startCollapse( + CollapseEnd.NOTHING, false /* isUserAction */, false /* shouldRecoverYPosition */); doShowText(text); } else { // Need to transition from old bounds to new bounds manually @@ -409,68 +578,65 @@ public class NewBubble { return; } - doResize( - () -> { - doShowText(text); - // Hide the text so we can animate it in - viewHolder.getPrimaryText().setAlpha(0); - - ViewAnimator primaryButton = viewHolder.getPrimaryButton(); - // Cancel the automatic transition scheduled in doShowText - TransitionManager.endTransitions((ViewGroup) primaryButton.getParent()); - primaryButton - .getViewTreeObserver() - .addOnPreDrawListener( - new OnPreDrawListener() { - @Override - public boolean onPreDraw() { - primaryButton.getViewTreeObserver().removeOnPreDrawListener(this); - - // 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( - NewChangeOnScreenBounds.PROPNAME_WIDTH, - viewHolder.getPrimaryText().getWidth()); - endValues.values.put( - NewChangeOnScreenBounds.PROPNAME_HEIGHT, - viewHolder.getPrimaryText().getHeight()); - endValues.view = primaryButton; - transition.addTarget(endValues.view); - transition.captureEndValues(endValues); - - // animate the primary button bounds change - Animator bounds = - transition.createAnimator(primaryButton, startValues, endValues); - - // Animate the text in - Animator alpha = - ObjectAnimator.ofFloat(viewHolder.getPrimaryText(), View.ALPHA, 1f); - - AnimatorSet set = new AnimatorSet(); - set.play(bounds).before(alpha); - set.start(); - return false; - } - }); - }); + doShowText(text); + // Hide the text so we can animate it in + viewHolder.getPrimaryText().setAlpha(0); + + ViewAnimator primaryButton = viewHolder.getPrimaryButton(); + // Cancel the automatic transition scheduled in doShowText + TransitionManager.endTransitions((ViewGroup) primaryButton.getParent()); + primaryButton + .getViewTreeObserver() + .addOnPreDrawListener( + new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + primaryButton.getViewTreeObserver().removeOnPreDrawListener(this); + + // 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( + NewChangeOnScreenBounds.PROPNAME_WIDTH, + viewHolder.getPrimaryText().getWidth()); + endValues.values.put( + NewChangeOnScreenBounds.PROPNAME_HEIGHT, + viewHolder.getPrimaryText().getHeight()); + endValues.view = primaryButton; + transition.addTarget(endValues.view); + transition.captureEndValues(endValues); + + // animate the primary button bounds change + Animator bounds = + transition.createAnimator(primaryButton, startValues, endValues); + + // Animate the text in + Animator alpha = + ObjectAnimator.ofFloat(viewHolder.getPrimaryText(), View.ALPHA, 1f); + + AnimatorSet set = new AnimatorSet(); + set.play(bounds).before(alpha); + set.start(); + return false; + } + }); } handler.removeCallbacks(collapseRunnable); handler.postDelayed(collapseRunnable, SHOW_TEXT_DURATION_MILLIS); } - public void setBubbleExpansionStateListener( - BubbleExpansionStateListener bubbleExpansionStateListener) { - this.bubbleExpansionStateListener = bubbleExpansionStateListener; - } - @Nullable Integer getGravityOverride() { return overrideGravity; } void onMoveStart() { - startCollapse(CollapseEnd.NOTHING, true); + if (viewHolder.getExpandedView().getVisibility() == View.VISIBLE) { + viewHolder.setDrawerVisibility(View.INVISIBLE); + } + expanded = false; + savedYPosition = -1; + viewHolder .getPrimaryButton() .animate() @@ -482,11 +648,6 @@ public class NewBubble { void onMoveFinish() { viewHolder.getPrimaryButton().animate().translationZ(0); - // If it's GONE, no resize is necessary. If it's VISIBLE, it will get cleaned up when the - // collapse animation finishes - if (viewHolder.getExpandedView().getVisibility() == View.INVISIBLE) { - doResize(null); - } } void primaryButtonClick() { @@ -494,12 +655,22 @@ public class NewBubble { return; } if (expanded) { - startCollapse(CollapseEnd.NOTHING, true); + startCollapse( + CollapseEnd.NOTHING, true /* isUserAction */, true /* shouldRecoverYPosition */); } else { expand(true); } } + void onLeftRightSwitch(boolean onRight) { + // Set layout direction so the small icon is not partially hidden. + View primaryIcon = viewHolder.getPrimaryIcon(); + int newGravity = (onRight ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM; + FrameLayout.LayoutParams layoutParams = + new FrameLayout.LayoutParams(primaryIcon.getWidth(), primaryIcon.getHeight(), newGravity); + primaryIcon.setLayoutParams(layoutParams); + } + LayoutParams getWindowParams() { return windowParams; } @@ -532,7 +703,7 @@ public class NewBubble { } if (expanded) { - startCollapse(CollapseEnd.HIDE, false); + startCollapse(CollapseEnd.HIDE, false /* isUserAction */, false /* shouldRecoverYPosition */); return; } @@ -618,34 +789,39 @@ public class NewBubble { } } - private void doResize(@Nullable Runnable operation) { - // If we're resizing on the right side of the screen, there is an implicit move operation - // necessary. The WindowManager does not sync the move and resize operations, so serious jank - // would occur. To fix this, instead of resizing the window, we create a new one and destroy - // the old one. There is a short delay before destroying the old view to ensure the new one has - // had time to draw. + /** + * Create a new ViewHolder object to replace the old one.It only happens when not moving and + * collapsed. + */ + void replaceViewHolder() { + LogUtil.enterBlock("NewBubble.replaceViewHolder"); ViewHolder oldViewHolder = viewHolder; - if (isDrawingFromRight()) { - viewHolder = new ViewHolder(oldViewHolder.getRoot().getContext()); - update(); - viewHolder - .getPrimaryButton() - .setDisplayedChild(oldViewHolder.getPrimaryButton().getDisplayedChild()); - viewHolder.getPrimaryText().setText(oldViewHolder.getPrimaryText().getText()); - } - if (operation != null) { - operation.run(); - } + // Create a new ViewHolder and copy needed info. + viewHolder = new ViewHolder(oldViewHolder.getRoot().getContext()); + viewHolder + .getPrimaryButton() + .setDisplayedChild(oldViewHolder.getPrimaryButton().getDisplayedChild()); + viewHolder.getPrimaryText().setText(oldViewHolder.getPrimaryText().getText()); - if (isDrawingFromRight()) { - swapViewHolders(oldViewHolder); - } - } + int size = context.getResources().getDimensionPixelSize(R.dimen.bubble_small_icon_size); + viewHolder + .getPrimaryIcon() + .setLayoutParams( + new FrameLayout.LayoutParams( + size, + size, + Gravity.BOTTOM | (isDrawingFromRight() ? Gravity.LEFT : Gravity.RIGHT))); - private void swapViewHolders(ViewHolder oldViewHolder) { + update(); + + // Add new view at its horizontal boundary ViewGroup root = viewHolder.getRoot(); + windowParams.x = leftBoundary; + windowParams.gravity = Gravity.TOP | (isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT); windowManager.addView(root, windowParams); + + // Remove the old view after delay root.getViewTreeObserver() .addOnPreDrawListener( new OnPreDrawListener() { @@ -661,63 +837,8 @@ public class NewBubble { }); } - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public void startCollapse(@CollapseEnd int endAction, boolean isUserAction) { - View expandedView = viewHolder.getExpandedView(); - if (expandedView.getVisibility() != View.VISIBLE || collapseAnimation != null) { - // Drawer is already collapsed or animation is running. - return; - } - - overrideGravity = isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT; - setFocused(false); - - if (collapseEndAction == CollapseEnd.NOTHING) { - collapseEndAction = endAction; - } - if (bubbleExpansionStateListener != null && collapseEndAction == CollapseEnd.NOTHING) { - bubbleExpansionStateListener.onBubbleExpansionStateChanged( - ExpansionState.START_COLLAPSING, isUserAction); - } - // Animate expanded view to move from its position to above primary button and hide - collapseAnimation = - expandedView - .animate() - .translationY(-viewHolder.getRoot().getHeight()) - .setInterpolator(new FastOutLinearInInterpolator()) - .withEndAction( - () -> { - collapseAnimation = null; - expanded = false; - - if (textShowing) { - // Will do resize once the text is done. - return; - } - - // Hide the drawer and resize if possible. - viewHolder.setDrawerVisibility(View.INVISIBLE); - if (!viewHolder.isMoving() || !isDrawingFromRight()) { - doResize(() -> viewHolder.setDrawerVisibility(View.GONE)); - } - - // If this collapse was to come before a hide, do it now. - if (collapseEndAction == CollapseEnd.HIDE) { - hide(); - } - collapseEndAction = CollapseEnd.NOTHING; - - // Resume normal gravity after any resizing is done. - handler.postDelayed( - () -> { - overrideGravity = null; - if (!viewHolder.isMoving()) { - viewHolder.undoGravityOverride(); - } - }, - // Need to wait twice as long for resize and layout - WINDOW_REDRAW_DELAY_MILLIS * 2); - }); + int getDrawerVisibility() { + return viewHolder.getExpandedView().getVisibility(); } private boolean isDrawingFromRight() { @@ -741,6 +862,16 @@ public class NewBubble { updatePrimaryIconAnimation(); } + private void logBasicOrCallImpression(DialerImpression.Type impressionType) { + DialerCall call = CallList.getInstance().getActiveOrBackgroundCall(); + if (call != null) { + Logger.get(context) + .logCallImpression(impressionType, call.getUniqueCallId(), call.getTimeAddedMs()); + } else { + Logger.get(context).logImpression(impressionType); + } + } + @VisibleForTesting class ViewHolder { @@ -779,7 +910,8 @@ public class NewBubble { root.setOnBackPressedListener( () -> { if (visibility == Visibility.SHOWING && expanded) { - startCollapse(CollapseEnd.NOTHING, true); + startCollapse( + CollapseEnd.NOTHING, true /* isUserAction */, true /* shouldRecoverYPosition */); return true; } return false; @@ -794,7 +926,8 @@ public class NewBubble { root.setOnTouchListener( (v, event) -> { if (expanded && event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { - startCollapse(CollapseEnd.NOTHING, true); + startCollapse( + CollapseEnd.NOTHING, true /* isUserAction */, true /* shouldRecoverYPosition */); return true; } return false; @@ -812,6 +945,16 @@ public class NewBubble { moveHandler.setClickable(clickable); } + public int getMoveUpDistance() { + int deltaAllowed = + expandedView.getHeight() + - context + .getResources() + .getDimensionPixelOffset(R.dimen.bubble_button_padding_vertical) + * 2; + return moveHandler.getMoveUpDistance(deltaAllowed); + } + public ViewGroup getRoot() { return root; } @@ -864,9 +1007,4 @@ public class NewBubble { moveHandler.undoGravityOverride(); } } - - /** Listener for bubble expansion state change. */ - public interface BubbleExpansionStateListener { - void onBubbleExpansionStateChanged(@ExpansionState int expansionState, boolean isUserAction); - } } diff --git a/java/com/android/newbubble/NewMoveHandler.java b/java/com/android/newbubble/NewMoveHandler.java index 189ad8472..9cb1f1eca 100644 --- a/java/com/android/newbubble/NewMoveHandler.java +++ b/java/com/android/newbubble/NewMoveHandler.java @@ -48,6 +48,8 @@ class NewMoveHandler implements OnTouchListener { private final int maxX; private final int maxY; private final int bubbleSize; + private final int bubbleShadowPaddingHorizontal; + private final int bubbleExpandedViewWidth; private final float touchSlopSquared; private boolean clickable = true; @@ -70,8 +72,14 @@ class NewMoveHandler implements OnTouchListener { @Override public float getValue(LayoutParams windowParams) { int realX = windowParams.x; - realX = realX + bubbleSize / 2; + // Get bubble center position from real position + if (bubble.getDrawerVisibility() == View.INVISIBLE) { + realX += bubbleExpandedViewWidth / 2 + bubbleShadowPaddingHorizontal * 2; + } else { + realX += bubbleSize / 2 + bubbleShadowPaddingHorizontal; + } if (relativeToRight(windowParams)) { + // If gravity is right, get distant from bubble center position to screen right edge int displayWidth = context.getResources().getDisplayMetrics().widthPixels; realX = displayWidth - realX; } @@ -88,12 +96,19 @@ class NewMoveHandler implements OnTouchListener { } else { onRight = (gravityOverride & Gravity.RIGHT) == Gravity.RIGHT; } - int centeringOffset = bubbleSize / 2; + // Get real position from bubble center position + int centeringOffset; + if (bubble.getDrawerVisibility() == View.INVISIBLE) { + centeringOffset = bubbleExpandedViewWidth / 2 + bubbleShadowPaddingHorizontal * 2; + } else { + centeringOffset = bubbleSize / 2 + bubbleShadowPaddingHorizontal; + } windowParams.x = (int) (onRight ? (displayWidth - value - centeringOffset) : value - centeringOffset); windowParams.gravity = Gravity.TOP | (onRight ? Gravity.RIGHT : Gravity.LEFT); if (bubble.isVisible()) { windowManager.updateViewLayout(bubble.getRootView(), windowParams); + bubble.onLeftRightSwitch(onRight); } } }; @@ -120,8 +135,13 @@ class NewMoveHandler implements OnTouchListener { windowManager = context.getSystemService(WindowManager.class); bubbleSize = context.getResources().getDimensionPixelSize(R.dimen.bubble_size); + bubbleShadowPaddingHorizontal = + context.getResources().getDimensionPixelSize(R.dimen.bubble_shadow_padding_size_horizontal); + bubbleExpandedViewWidth = + context.getResources().getDimensionPixelSize(R.dimen.bubble_expanded_width); + // The following value is based on bubble center minX = - context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_horizontal) + context.getResources().getDimensionPixelOffset(R.dimen.bubble_off_screen_size_horizontal) + bubbleSize / 2; minY = context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_vertical) @@ -156,6 +176,12 @@ class NewMoveHandler implements OnTouchListener { moveYAnimation.animateToFinalPosition(yProperty.getValue(bubble.getWindowParams())); } + public int getMoveUpDistance(int deltaAllowed) { + int currentY = (int) yProperty.getValue(bubble.getWindowParams()); + int currentDelta = maxY - currentY; + return currentDelta >= deltaAllowed ? 0 : deltaAllowed - currentDelta; + } + @Override public boolean onTouch(View v, MotionEvent event) { float eventX = event.getRawX(); @@ -222,6 +248,14 @@ class NewMoveHandler implements OnTouchListener { moveXAnimation = new SpringAnimation(bubble.getWindowParams(), xProperty); moveXAnimation.setSpring(new SpringForce()); moveXAnimation.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY); + // Moving when expanded makes expanded view INVISIBLE, and the whole view is not at the + // boundary. It's time to create a viewHolder. + moveXAnimation.addEndListener( + (animation, canceled, value, velocity) -> { + if (!isMoving && bubble.getDrawerVisibility() == View.INVISIBLE) { + bubble.replaceViewHolder(); + } + }); } if (moveYAnimation == null) { diff --git a/java/com/android/newbubble/res/layout/new_bubble_base.xml b/java/com/android/newbubble/res/layout/new_bubble_base.xml index 8cac982f4..8d4771631 100644 --- a/java/com/android/newbubble/res/layout/new_bubble_base.xml +++ b/java/com/android/newbubble/res/layout/new_bubble_base.xml @@ -19,7 +19,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:clipChildren="false" + android:clipChildren="true" + android:clipToPadding="false" tools:theme="@style/Theme.AppCompat"> - - + + + android:clipChildren="true" + android:clipToPadding="false" + android:layout_below="@id/bubble_primary_container"> - - - - - + - + android:elevation="@dimen/bubble_expanded_elevation" + android:rotation="45"> + + + + + + + diff --git a/java/com/android/newbubble/res/values/values.xml b/java/com/android/newbubble/res/values/values.xml index 6dda61d6c..71f813ac6 100644 --- a/java/com/android/newbubble/res/values/values.xml +++ b/java/com/android/newbubble/res/values/values.xml @@ -24,8 +24,11 @@ 16dp 12dp 16dp - -16dp - 64dp + + -4dp + + 48dp + 16dp -16dp 12dp -- cgit v1.2.3 From 625cdb00c265205a28e3167aae5976e27cbab5c7 Mon Sep 17 00:00:00 2001 From: zachh Date: Tue, 5 Dec 2017 11:53:57 -0800 Subject: Temporarily disable PhoneHistoryRecorder. PhoneLookupHistory provider isn't correct overridden in GoogleDialer manifest. I'l fix that in a later CL. Test: manual PiperOrigin-RevId: 177988816 Change-Id: I42af03d22db50bd70219d52826294529462bae43 --- java/com/android/incallui/InCallPresenter.java | 2 -- 1 file changed, 2 deletions(-) (limited to 'java/com') diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java index f8605ae7c..c5310b969 100644 --- a/java/com/android/incallui/InCallPresenter.java +++ b/java/com/android/incallui/InCallPresenter.java @@ -545,8 +545,6 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud // Since a call has been added we are no longer waiting for Telecom to send us a call. setBoundAndWaitingForOutgoingCall(false, null); call.registerCallback(mCallCallback); - // TODO(maxwelb): Return the future in recordPhoneLookupInfo and propagate. - PhoneLookupHistoryRecorder.recordPhoneLookupInfo(mContext.getApplicationContext(), call); Trace.endSection(); } -- cgit v1.2.3 From d524f75074f4b04fee80719ea56d27d56f2bec16 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 5 Dec 2017 13:08:09 -0800 Subject: Add DATA_USAGE column to the annotated call log. Data in this column are copied from the DATA_USAGE column in the system call log (android.provider.CallLog.Calls#DATA_USAGE). Bug: 70218437 Test: AnnotatedCallLogDatabaseHelperTest, SystemCallLogDataSourceTest PiperOrigin-RevId: 177999609 Change-Id: I64d70734e8e85767f1e93a4a22ce2aae6c2168a1 --- .../dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java | 1 + .../dialer/calllog/database/contract/AnnotatedCallLogContract.java | 7 +++++++ .../calllog/datasources/systemcalllog/SystemCallLogDataSource.java | 4 ++++ 3 files changed, 12 insertions(+) (limited to 'java/com') diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java index 0d8e8ceeb..8c6d58634 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java @@ -45,6 +45,7 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { + (AnnotatedCallLog.PHOTO_ID + " integer, ") + (AnnotatedCallLog.LOOKUP_URI + " text, ") + (AnnotatedCallLog.DURATION + " integer, ") + + (AnnotatedCallLog.DATA_USAGE + " integer, ") + (AnnotatedCallLog.NUMBER_TYPE_LABEL + " text, ") + (AnnotatedCallLog.IS_READ + " integer, ") + (AnnotatedCallLog.NEW + " integer, ") diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java index c9b463e74..9efe21487 100644 --- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java +++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java @@ -224,6 +224,13 @@ public class AnnotatedCallLogContract { /** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/annotated_call_log"; + /** + * See {@link android.provider.CallLog.Calls#DATA_USAGE}. + * + *

Type: INTEGER (long) + */ + public static final String DATA_USAGE = "data_usage"; + /** * See {@link android.provider.CallLog.Calls#DURATION}. * diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index 5ca160778..ef40c308e 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -220,6 +220,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_LABEL, Calls.DURATION, + Calls.DATA_USAGE, Calls.TRANSCRIPTION, Calls.VOICEMAIL_URI, Calls.IS_READ, @@ -259,6 +260,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { int cachedNumberTypeColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_TYPE); int cachedNumberLabelColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_LABEL); int durationsColumn = cursor.getColumnIndexOrThrow(Calls.DURATION); + int dataUsageColumn = cursor.getColumnIndexOrThrow(Calls.DATA_USAGE); int transcriptionColumn = cursor.getColumnIndexOrThrow(Calls.TRANSCRIPTION); int voicemailUriColumn = cursor.getColumnIndexOrThrow(Calls.VOICEMAIL_URI); int isReadColumn = cursor.getColumnIndexOrThrow(Calls.IS_READ); @@ -286,6 +288,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { int cachedNumberType = cursor.getInt(cachedNumberTypeColumn); String cachedNumberLabel = cursor.getString(cachedNumberLabelColumn); int duration = cursor.getInt(durationsColumn); + int dataUsage = cursor.getInt(dataUsageColumn); String transcription = cursor.getString(transcriptionColumn); String voicemailUri = cursor.getString(voicemailUriColumn); int isRead = cursor.getInt(isReadColumn); @@ -334,6 +337,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { appContext, contentValues, phoneAccountComponentName, phoneAccountId); contentValues.put(AnnotatedCallLog.FEATURES, features); contentValues.put(AnnotatedCallLog.DURATION, duration); + contentValues.put(AnnotatedCallLog.DATA_USAGE, dataUsage); contentValues.put(AnnotatedCallLog.TRANSCRIPTION, transcription); contentValues.put(AnnotatedCallLog.VOICEMAIL_URI, voicemailUri); -- cgit v1.2.3 From 9a50cda3294e4e0830c0e99984563fe0275b41fc Mon Sep 17 00:00:00 2001 From: erfanian Date: Tue, 5 Dec 2017 14:04:02 -0800 Subject: Display the automatically detected home country in assisted dialing settings. Some degenerative cases are considered, like detected home countries that are currently unavailable. Bug: 68775522 Test: unit tests PiperOrigin-RevId: 178007559 Change-Id: Id85d7a2af1aa67757450bbbc80fd3ee67790b21c --- .../ui/AssistedDialingSettingFragment.java | 41 +++++++++++++++++++--- .../assisteddialing/ui/res/values/strings.xml | 7 ++-- .../ui/res/xml/assisted_dialing_setting.xml | 2 +- 3 files changed, 43 insertions(+), 7 deletions(-) (limited to 'java/com') diff --git a/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java index 03418940a..d4fb3f64b 100644 --- a/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java +++ b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java @@ -22,15 +22,18 @@ import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.SwitchPreference; -import android.text.TextUtils; +import android.telephony.TelephonyManager; +import com.android.dialer.assisteddialing.AssistedDialingMediator; import com.android.dialer.assisteddialing.ConcreteCreator; import com.android.dialer.assisteddialing.CountryCodeProvider; +import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.google.auto.value.AutoValue; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** The setting for Assisted Dialing */ @TargetApi(VERSION_CODES.N) @@ -38,6 +41,7 @@ import java.util.List; public class AssistedDialingSettingFragment extends PreferenceFragment { private CountryCodeProvider countryCodeProvider; + private AssistedDialingMediator assistedDialingMediator; @AutoValue abstract static class DisplayNameAndCountryCodeTuple { @@ -59,6 +63,10 @@ public class AssistedDialingSettingFragment extends PreferenceFragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + assistedDialingMediator = + ConcreteCreator.createNewAssistedDialingMediator( + getContext().getSystemService(TelephonyManager.class), getContext()); + countryCodeProvider = ConcreteCreator.getCountryCodeProvider(ConfigProviderBindings.get(getContext())); @@ -73,14 +81,39 @@ public class AssistedDialingSettingFragment extends PreferenceFragment { findPreference(getContext().getString(R.string.assisted_dialing_setting_cc_key)); updateCountryChoices(countryChooserPref); + updateCountryChooserSummary(countryChooserPref); - if (!TextUtils.isEmpty(countryChooserPref.getEntry())) { - countryChooserPref.setSummary(countryChooserPref.getEntry()); - } countryChooserPref.setOnPreferenceChangeListener(this::updateListSummary); switchPref.setOnPreferenceChangeListener(this::logIfUserDisabledFeature); } + private void updateCountryChooserSummary(ListPreference countryChooserPref) { + String defaultSummaryText = countryChooserPref.getEntries()[0].toString(); + + if (countryChooserPref.getEntry().equals(defaultSummaryText)) { + Optional userHomeCountryCode = assistedDialingMediator.userHomeCountryCode(); + if (userHomeCountryCode.isPresent()) { + CharSequence[] entries = countryChooserPref.getEntries(); + try { + CharSequence regionalDisplayName = + entries[countryChooserPref.findIndexOfValue(userHomeCountryCode.get())]; + countryChooserPref.setSummary( + getContext() + .getString( + R.string.assisted_dialing_setting_cc_default_summary, regionalDisplayName)); + } catch (ArrayIndexOutOfBoundsException e) { + // This might happen if there is a mismatch between the automatically + // detected home country, and the countries currently eligible to select in the settings. + LogUtil.i( + "AssistedDialingSettingFragment.onCreate", + "Failed to find human readable mapping for country code, using default."); + } + } + } else { + countryChooserPref.setSummary(countryChooserPref.getEntry()); + } + } + /** * Filters the default entries in the country chooser by only showing those countries in which the * feature in enabled. diff --git a/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml b/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml index 3a81780a9..35aa2f147 100644 --- a/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml +++ b/java/com/android/dialer/assisteddialing/ui/res/values/strings.xml @@ -23,7 +23,10 @@ Predict and add a country code when you call while traveling abroad - Automatically detected + Automatically detected • %1$s + + + Automatically detected Home country @@ -36,7 +39,7 @@ - @string/assisted_dialing_setting_cc_default_summary + @string/assisted_dialing_setting_cc_default_summary_fallback Afghanistan (+93) Åland Islands (+358) Albania (+355) diff --git a/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml b/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml index c1706b2ed..9fb61a159 100644 --- a/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml +++ b/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml @@ -31,7 +31,7 @@ android:entries="@array/assisted_dialing_cc_entries" android:entryValues="@array/assisted_dialing_cc_values" android:key="@string/assisted_dialing_setting_cc_key" - android:summary="@string/assisted_dialing_setting_cc_default_summary" + android:summary="@string/assisted_dialing_setting_cc_default_summary_fallback" android:title="@string/assisted_dialing_setting_cc_title"/> -- cgit v1.2.3 From c7ec1530095dd90c041d6b5d340e9e66056c3b3b Mon Sep 17 00:00:00 2001 From: twyen Date: Tue, 5 Dec 2017 15:00:23 -0800 Subject: Ignore "new" voicemails that are too old for notifications Bug: 68049435 Test: CallLogNotificationQueryHelperTest PiperOrigin-RevId: 178016400 Change-Id: I6ddbebc9edce6830966072d42cf05eb21f26c8ac --- .../calllog/CallLogNotificationsQueryHelper.java | 56 +++++++++++++++++++--- 1 file changed, 50 insertions(+), 6 deletions(-) (limited to 'java/com') diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java index f962e17ac..47457360d 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java @@ -28,6 +28,7 @@ import android.os.Build; import android.provider.CallLog.Calls; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.support.v4.os.UserManagerCompat; import android.telephony.PhoneNumberUtils; @@ -35,7 +36,9 @@ import android.text.TextUtils; import com.android.dialer.app.R; import com.android.dialer.calllogutils.PhoneNumberDisplayUtil; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.database.Selection; import com.android.dialer.compat.android.provider.VoicemailCompat; +import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.location.GeoUtil; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumbercache.ContactInfoHelper; @@ -43,11 +46,16 @@ import com.android.dialer.util.PermissionsUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeUnit; /** Helper class operating on call log notifications. */ @TargetApi(Build.VERSION_CODES.M) public class CallLogNotificationsQueryHelper { + @VisibleForTesting + static final String CONFIG_NEW_VOICEMAIL_NOTIFICATION_THRESHOLD_OFFSET = + "new_voicemail_notification_threshold"; + private final Context mContext; private final NewCallsQuery mNewCallsQuery; private final ContactInfoHelper mContactInfoHelper; @@ -147,7 +155,12 @@ public class CallLogNotificationsQueryHelper { */ @Nullable public List getNewVoicemails() { - return mNewCallsQuery.query(Calls.VOICEMAIL_TYPE); + return mNewCallsQuery.query( + Calls.VOICEMAIL_TYPE, + System.currentTimeMillis() + - ConfigProviderBindings.get(mContext) + .getLong( + CONFIG_NEW_VOICEMAIL_NOTIFICATION_THRESHOLD_OFFSET, TimeUnit.DAYS.toMillis(7))); } /** @@ -220,10 +233,21 @@ public class CallLogNotificationsQueryHelper { /** Allows determining the new calls for which a notification should be generated. */ public interface NewCallsQuery { + long NO_THRESHOLD = Long.MAX_VALUE; + /** Returns the new calls of a certain type for which a notification should be generated. */ @Nullable List query(int type); + /** + * Returns the new calls of a certain type for which a notification should be generated. + * + * @param thresholdMillis New calls added before this timestamp will be considered old, or + * {@link #NO_THRESHOLD} if threshold is not checked. + */ + @Nullable + List query(int type, long thresholdMillis); + /** Returns a {@link NewCall} pointed by the {@code callsUri} */ @Nullable NewCall query(Uri callsUri); @@ -317,6 +341,14 @@ public class CallLogNotificationsQueryHelper { @Nullable @TargetApi(Build.VERSION_CODES.M) public List query(int type) { + return query(type, NO_THRESHOLD); + } + + @Override + @Nullable + @TargetApi(Build.VERSION_CODES.M) + @SuppressWarnings("MissingPermission") + public List query(int type, long thresholdMillis) { if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) { LogUtil.w( "CallLogNotificationsQueryHelper.DefaultNewCallsQuery.query", @@ -328,15 +360,27 @@ public class CallLogNotificationsQueryHelper { // TYPE matches the query type. // IS_READ is not 1. A call might be backed up and restored, so it will be "new" to the // call log, but the user has already read it on another device. - final String selection = - String.format("%s = 1 AND %s = ? AND %s IS NOT 1", Calls.NEW, Calls.TYPE, Calls.IS_READ); - final String[] selectionArgs = new String[] {Integer.toString(type)}; + Selection.Builder selectionBuilder = + Selection.builder() + .and(Selection.column(Calls.NEW).is("= 1")) + .and(Selection.column(Calls.TYPE).is("=", type)) + .and(Selection.column(Calls.IS_READ).is("IS NOT 1")); + if (thresholdMillis != NO_THRESHOLD) { + selectionBuilder = + selectionBuilder.and( + Selection.column(Calls.DATE) + .is("IS NULL") + .buildUpon() + .or(Selection.column(Calls.DATE).is(">=", thresholdMillis)) + .build()); + } + Selection selection = selectionBuilder.build(); try (Cursor cursor = mContentResolver.query( Calls.CONTENT_URI_WITH_VOICEMAIL, (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? PROJECTION_O : PROJECTION, - selection, - selectionArgs, + selection.getSelection(), + selection.getSelectionArgs(), Calls.DEFAULT_SORT_ORDER)) { if (cursor == null) { return null; -- cgit v1.2.3 From 6357ef788c564a0a3f92fc5761d7e0ecbf62c181 Mon Sep 17 00:00:00 2001 From: zachh Date: Tue, 5 Dec 2017 15:27:56 -0800 Subject: Check for null subscription info list in TelecomUtil#getSubscriptionInfo. The javadoc for the method states that it can return null if the current state is unknown. Bug: 70224613 Test: unit PiperOrigin-RevId: 178020566 Change-Id: I4cd6f76b7c0572d1c9797eba4bf9c3981846468e --- java/com/android/dialer/telecom/TelecomUtil.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'java/com') diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java index c79d9013d..6f424de4a 100644 --- a/java/com/android/dialer/telecom/TelecomUtil.java +++ b/java/com/android/dialer/telecom/TelecomUtil.java @@ -158,7 +158,11 @@ public abstract class TelecomUtil { return Optional.absent(); } SubscriptionManager subscriptionManager = context.getSystemService(SubscriptionManager.class); - for (SubscriptionInfo info : subscriptionManager.getActiveSubscriptionInfoList()) { + List subscriptionInfos = subscriptionManager.getActiveSubscriptionInfoList(); + if (subscriptionInfos == null) { + return Optional.absent(); + } + for (SubscriptionInfo info : subscriptionInfos) { if (phoneAccountHandle.getId().startsWith(info.getIccId())) { return Optional.of(info); } -- cgit v1.2.3 From 73107131025b0bbfa20a6a8be782456494e1043d Mon Sep 17 00:00:00 2001 From: twyen Date: Tue, 5 Dec 2017 16:24:23 -0800 Subject: Allow VVM activation to be rerun if carrier sent STATUS SMS Carrier might send STATUS SMS actively if the account configuration has changed. Previously it will be discarded if the account is already activated. In this CL activation will be rerun if the messageData already exist since it is initiated by an STATUS SMS. Bug: 69857261 Test: ActivationTaskTest PiperOrigin-RevId: 178028542 Change-Id: I6d103569c2be4eeadc3a7877d160fff03ad8b40e --- java/com/android/voicemail/impl/ActivationTask.java | 5 +++-- java/com/android/voicemail/impl/PreOMigrationHandler.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'java/com') diff --git a/java/com/android/voicemail/impl/ActivationTask.java b/java/com/android/voicemail/impl/ActivationTask.java index 29c91e01e..83f2fd836 100644 --- a/java/com/android/voicemail/impl/ActivationTask.java +++ b/java/com/android/voicemail/impl/ActivationTask.java @@ -61,7 +61,7 @@ public class ActivationTask extends BaseTask { private static final int RETRY_TIMES = 4; private static final int RETRY_INTERVAL_MILLIS = 5_000; - private static final String EXTRA_MESSAGE_DATA_BUNDLE = "extra_message_data_bundle"; + @VisibleForTesting static final String EXTRA_MESSAGE_DATA_BUNDLE = "extra_message_data_bundle"; private final RetryPolicy mRetryPolicy; @@ -168,7 +168,8 @@ public class ActivationTask extends BaseTask { } VvmLog.i(TAG, "VVM content provider configured - " + helper.getVvmType()); - if (VvmAccountManager.isAccountActivated(getContext(), phoneAccountHandle)) { + if (mMessageData == null + && VvmAccountManager.isAccountActivated(getContext(), phoneAccountHandle)) { VvmLog.i(TAG, "Account is already activated"); // The activated state might come from restored data, the filter still needs to be set up. helper.activateSmsFilter(); diff --git a/java/com/android/voicemail/impl/PreOMigrationHandler.java b/java/com/android/voicemail/impl/PreOMigrationHandler.java index 3ec5e0826..2c454715c 100644 --- a/java/com/android/voicemail/impl/PreOMigrationHandler.java +++ b/java/com/android/voicemail/impl/PreOMigrationHandler.java @@ -18,6 +18,7 @@ package com.android.voicemail.impl; import android.content.Context; import android.os.Bundle; +import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; @@ -49,7 +50,7 @@ public final class PreOMigrationHandler { private static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; - private static final String PRE_O_MIGRATION_FINISHED = "pre_o_migration_finished"; + @VisibleForTesting static final String PRE_O_MIGRATION_FINISHED = "pre_o_migration_finished"; @WorkerThread public static void migrate(Context context, PhoneAccountHandle phoneAccountHandle) { -- cgit v1.2.3 From fbf55bf0055717be081df885833b2a0e9045ae03 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 5 Dec 2017 16:34:03 -0800 Subject: Grouping each listener's logic in an inner class in CallDetailsActivity. Bug: 70218437 Test: Existing tests PiperOrigin-RevId: 178029820 Change-Id: I60afe7c6d61a61ce4aa2fd2e30fbd5d869072930 --- .../dialer/calldetails/CallDetailsActivity.java | 310 +++++++++++++-------- 1 file changed, 190 insertions(+), 120 deletions(-) (limited to 'java/com') diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java index 06b6a1040..b51d833dc 100644 --- a/java/com/android/dialer/calldetails/CallDetailsActivity.java +++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java @@ -35,7 +35,6 @@ import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.widget.Toast; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; -import com.android.dialer.calldetails.CallDetailsFooterViewHolder.DeleteCallDetailsListener; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.Assert; @@ -46,7 +45,7 @@ import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.duo.Duo; import com.android.dialer.duo.DuoComponent; import com.android.dialer.enrichedcall.EnrichedCallComponent; -import com.android.dialer.enrichedcall.EnrichedCallManager.HistoricalDataChangedListener; +import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; @@ -55,17 +54,14 @@ import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.postcall.PostCall; import com.android.dialer.precall.PreCall; import com.android.dialer.protos.ProtoParsers; +import com.google.common.base.Preconditions; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.List; import java.util.Map; /** Displays the details of a specific call log entry. */ -public class CallDetailsActivity extends AppCompatActivity - implements CallDetailsHeaderViewHolder.CallbackActionListener, - CallDetailsFooterViewHolder.ReportCallIdListener, - DeleteCallDetailsListener, - HistoricalDataChangedListener { +public class CallDetailsActivity extends AppCompatActivity { public static final String EXTRA_PHONE_NUMBER = "phone_number"; public static final String EXTRA_HAS_ENRICHED_CALL_DATA = "has_enriched_call_data"; @@ -73,7 +69,16 @@ public class CallDetailsActivity extends AppCompatActivity public static final String EXTRA_CONTACT = "contact"; public static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id"; private static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing"; - private static final String TASK_DELETE = "task_delete"; + + private final CallDetailsHeaderViewHolder.CallbackActionListener callbackActionListener = + new CallbackActionListener(this); + private final CallDetailsFooterViewHolder.DeleteCallDetailsListener deleteCallDetailsListener = + new DeleteCallDetailsListener(this); + private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener = + new ReportCallIdListener(this); + private final EnrichedCallManager.HistoricalDataChangedListener + enrichedCallHistoricalDataChangedListener = + new EnrichedCallHistoricalDataChangedListener(this); private CallDetailsEntries entries; private DialerContact contact; @@ -130,7 +135,7 @@ public class CallDetailsActivity extends AppCompatActivity EnrichedCallComponent.get(this) .getEnrichedCallManager() - .registerHistoricalDataChangedListener(this); + .registerHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener); EnrichedCallComponent.get(this) .getEnrichedCallManager() .requestAllHistoricalData(contact.getNumber(), entries); @@ -142,7 +147,7 @@ public class CallDetailsActivity extends AppCompatActivity EnrichedCallComponent.get(this) .getEnrichedCallManager() - .unregisterHistoricalDataChangedListener(this); + .unregisterHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener); } @Override @@ -161,9 +166,9 @@ public class CallDetailsActivity extends AppCompatActivity this /* context */, contact, entries.getEntriesList(), - this /* callbackListener */, - this /* reportCallIdListener */, - this /* callDetailDeletionListener */); + callbackActionListener, + reportCallIdListener, + deleteCallDetailsListener); RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); @@ -177,113 +182,6 @@ public class CallDetailsActivity extends AppCompatActivity super.onBackPressed(); } - @Override - public void reportCallId(String number) { - ReportDialogFragment.newInstance(number).show(getFragmentManager(), null); - } - - @Override - public boolean canReportCallerId(String number) { - return getIntent().getExtras().getBoolean(EXTRA_CAN_REPORT_CALLER_ID, false); - } - - @Override - public void onHistoricalDataChanged() { - Map> mappedResults = - getAllHistoricalData(contact.getNumber(), entries); - - adapter.updateCallDetailsEntries( - generateAndMapNewCallDetailsEntriesHistoryResults( - contact.getNumber(), entries, mappedResults) - .getEntriesList()); - } - - @Override - public void placeImsVideoCall(String phoneNumber) { - Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_IMS_VIDEO_CALL_BACK); - PreCall.start( - this, - new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS) - .setIsVideoCall(true)); - } - - @Override - public void placeDuoVideoCall(String phoneNumber) { - Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK); - Duo duo = DuoComponent.get(this).getDuo(); - if (!duo.isReachable(this, phoneNumber)) { - placeImsVideoCall(phoneNumber); - return; - } - - try { - startActivityForResult(duo.getIntent(this, phoneNumber), ActivityRequestCodes.DIALTACTS_DUO); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.activity_not_available, Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void placeVoiceCall(String phoneNumber, String postDialDigits) { - Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_VOICE_CALL_BACK); - - boolean canSupportedAssistedDialing = - getIntent().getExtras().getBoolean(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, false); - CallIntentBuilder callIntentBuilder = - new CallIntentBuilder(phoneNumber + postDialDigits, CallInitiationType.Type.CALL_DETAILS); - if (canSupportedAssistedDialing) { - callIntentBuilder.setAllowAssistedDial(true); - } - - PreCall.start(this, callIntentBuilder); - } - - @Override - public void delete() { - AsyncTaskExecutors.createAsyncTaskExecutor() - .submit(TASK_DELETE, new DeleteCallsTask(this, contact, entries)); - } - - @NonNull - private Map> getAllHistoricalData( - @Nullable String number, @NonNull CallDetailsEntries entries) { - if (number == null) { - return Collections.emptyMap(); - } - - Map> historicalData = - EnrichedCallComponent.get(this) - .getEnrichedCallManager() - .getAllHistoricalData(number, entries); - if (historicalData == null) { - return Collections.emptyMap(); - } - return historicalData; - } - - private static CallDetailsEntries generateAndMapNewCallDetailsEntriesHistoryResults( - @Nullable String number, - @NonNull CallDetailsEntries callDetailsEntries, - @NonNull Map> mappedResults) { - if (number == null) { - return callDetailsEntries; - } - CallDetailsEntries.Builder mutableCallDetailsEntries = CallDetailsEntries.newBuilder(); - for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) { - CallDetailsEntry.Builder newEntry = CallDetailsEntry.newBuilder().mergeFrom(entry); - List results = mappedResults.get(entry); - if (results != null) { - newEntry.addAllHistoryResults(mappedResults.get(entry)); - LogUtil.v( - "CallLogAdapter.generateAndMapNewCallDetailsEntriesHistoryResults", - "mapped %d results", - newEntry.getHistoryResultsList().size()); - } - mutableCallDetailsEntries.addEntries(newEntry.build()); - } - return mutableCallDetailsEntries.build(); - } - /** Delete specified calls from the call log. */ private static class DeleteCallsTask extends AsyncTask { // Use a weak reference to hold the Activity so that there is no memory leak. @@ -349,4 +247,176 @@ public class CallDetailsActivity extends AppCompatActivity activity.finish(); } } + + private static final class CallbackActionListener + implements CallDetailsHeaderViewHolder.CallbackActionListener { + private final WeakReference activityWeakReference; + + CallbackActionListener(Activity activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void placeImsVideoCall(String phoneNumber) { + Logger.get(getActivity()) + .logImpression(DialerImpression.Type.CALL_DETAILS_IMS_VIDEO_CALL_BACK); + PreCall.start( + getActivity(), + new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS) + .setIsVideoCall(true)); + } + + @Override + public void placeDuoVideoCall(String phoneNumber) { + Logger.get(getActivity()) + .logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK); + Duo duo = DuoComponent.get(getActivity()).getDuo(); + if (!duo.isReachable(getActivity(), phoneNumber)) { + placeImsVideoCall(phoneNumber); + return; + } + + try { + getActivity() + .startActivityForResult( + duo.getIntent(getActivity(), phoneNumber), ActivityRequestCodes.DIALTACTS_DUO); + } catch (ActivityNotFoundException e) { + Toast.makeText(getActivity(), R.string.activity_not_available, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void placeVoiceCall(String phoneNumber, String postDialDigits) { + Logger.get(getActivity()).logImpression(DialerImpression.Type.CALL_DETAILS_VOICE_CALL_BACK); + + boolean canSupportedAssistedDialing = + getActivity() + .getIntent() + .getExtras() + .getBoolean(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, false); + CallIntentBuilder callIntentBuilder = + new CallIntentBuilder(phoneNumber + postDialDigits, CallInitiationType.Type.CALL_DETAILS); + if (canSupportedAssistedDialing) { + callIntentBuilder.setAllowAssistedDial(true); + } + + PreCall.start(getActivity(), callIntentBuilder); + } + + private Activity getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + } + + private static final class DeleteCallDetailsListener + implements CallDetailsFooterViewHolder.DeleteCallDetailsListener { + private static final String ASYNC_TASK_ID = "task_delete"; + + private final WeakReference activityWeakReference; + + DeleteCallDetailsListener(CallDetailsActivity activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void delete() { + AsyncTaskExecutors.createAsyncTaskExecutor() + .submit( + ASYNC_TASK_ID, + new DeleteCallsTask(getActivity(), getActivity().contact, getActivity().entries)); + } + + private CallDetailsActivity getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + } + + private static final class ReportCallIdListener + implements CallDetailsFooterViewHolder.ReportCallIdListener { + private final WeakReference activityWeakReference; + + ReportCallIdListener(Activity activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void reportCallId(String number) { + ReportDialogFragment.newInstance(number) + .show(getActivity().getFragmentManager(), null /* tag */); + } + + @Override + public boolean canReportCallerId(String number) { + return getActivity().getIntent().getExtras().getBoolean(EXTRA_CAN_REPORT_CALLER_ID, false); + } + + private Activity getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + } + + private static final class EnrichedCallHistoricalDataChangedListener + implements EnrichedCallManager.HistoricalDataChangedListener { + private final WeakReference activityWeakReference; + + EnrichedCallHistoricalDataChangedListener(CallDetailsActivity activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void onHistoricalDataChanged() { + CallDetailsActivity activity = getActivity(); + Map> mappedResults = + getAllHistoricalData(activity.contact.getNumber(), activity.entries); + + activity.adapter.updateCallDetailsEntries( + generateAndMapNewCallDetailsEntriesHistoryResults( + activity.contact.getNumber(), activity.entries, mappedResults) + .getEntriesList()); + } + + private CallDetailsActivity getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + + @NonNull + private Map> getAllHistoricalData( + @Nullable String number, @NonNull CallDetailsEntries entries) { + if (number == null) { + return Collections.emptyMap(); + } + + Map> historicalData = + EnrichedCallComponent.get(getActivity()) + .getEnrichedCallManager() + .getAllHistoricalData(number, entries); + if (historicalData == null) { + return Collections.emptyMap(); + } + return historicalData; + } + + private static CallDetailsEntries generateAndMapNewCallDetailsEntriesHistoryResults( + @Nullable String number, + @NonNull CallDetailsEntries callDetailsEntries, + @NonNull Map> mappedResults) { + if (number == null) { + return callDetailsEntries; + } + CallDetailsEntries.Builder mutableCallDetailsEntries = CallDetailsEntries.newBuilder(); + for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) { + CallDetailsEntry.Builder newEntry = CallDetailsEntry.newBuilder().mergeFrom(entry); + List results = mappedResults.get(entry); + if (results != null) { + newEntry.addAllHistoryResults(mappedResults.get(entry)); + LogUtil.v( + "CallDetailsActivity.generateAndMapNewCallDetailsEntriesHistoryResults", + "mapped %d results", + newEntry.getHistoryResultsList().size()); + } + mutableCallDetailsEntries.addEntries(newEntry.build()); + } + return mutableCallDetailsEntries.build(); + } + } } -- cgit v1.2.3 From 952fc68f330177b05c29275155b3f473b7ac4dbe Mon Sep 17 00:00:00 2001 From: yueg Date: Tue, 5 Dec 2017 16:34:23 -0800 Subject: Hide bubble in (New)ReturnToCallController.tearDown(). There is the case that onDisconnect() is not called but InCallService is unbind. Currently we only hide bubble when call disconnected. We should also do it when InCallService is unbind. Test: NewReturnToCallControllerTest, ReturnToCallControllerTest PiperOrigin-RevId: 178029862 Change-Id: I6806c5c80adb06317f09019bdf3420b462225945 --- java/com/android/incallui/NewReturnToCallController.java | 1 + java/com/android/incallui/ReturnToCallController.java | 1 + 2 files changed, 2 insertions(+) (limited to 'java/com') diff --git a/java/com/android/incallui/NewReturnToCallController.java b/java/com/android/incallui/NewReturnToCallController.java index 7a1abee51..abff000fd 100644 --- a/java/com/android/incallui/NewReturnToCallController.java +++ b/java/com/android/incallui/NewReturnToCallController.java @@ -98,6 +98,7 @@ public class NewReturnToCallController implements InCallUiListener, Listener, Au } public void tearDown() { + hide(); InCallPresenter.getInstance().removeInCallUiListener(this); CallList.getInstance().removeListener(this); AudioModeProvider.getInstance().removeListener(this); diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java index 5f4cc5f84..58d868818 100644 --- a/java/com/android/incallui/ReturnToCallController.java +++ b/java/com/android/incallui/ReturnToCallController.java @@ -84,6 +84,7 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio } public void tearDown() { + hide(); InCallPresenter.getInstance().removeInCallUiListener(this); CallList.getInstance().removeListener(this); AudioModeProvider.getInstance().removeListener(this); -- cgit v1.2.3