diff options
45 files changed, 572 insertions, 256 deletions
diff --git a/java/com/android/bubble/res/values-car/values.xml b/java/com/android/bubble/res/values-car/values.xml index cf8839a84..47f20a1bf 100644 --- a/java/com/android/bubble/res/values-car/values.xml +++ b/java/com/android/bubble/res/values-car/values.xml @@ -18,10 +18,8 @@ <resources> <dimen name="bubble_size">64dp</dimen> - <dimen name="bubble_drawer_margin">54dp</dimen> + <dimen name="bubble_drawer_margin">50dp</dimen> <dimen name="bubble_drawer_padding_start">38dp</dimen> - <dimen name="bubble_drawer_padding_end">10dp</dimen> - <dimen name="bubble_icon_padding">18dp</dimen> - <dimen name="bubble_shadow_padding_size">18dp</dimen> + <dimen name="bubble_drawer_padding_end">12dp</dimen> </resources> diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index eb95a4ee4..bbb45943a 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -133,8 +133,8 @@ import com.android.dialer.searchfragment.list.NewSearchFragment; import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListener; import com.android.dialer.simulator.Simulator; import com.android.dialer.simulator.SimulatorComponent; -import com.android.dialer.smartdial.SmartDialNameMatcher; -import com.android.dialer.smartdial.SmartDialPrefix; +import com.android.dialer.smartdial.util.SmartDialNameMatcher; +import com.android.dialer.smartdial.util.SmartDialPrefix; import com.android.dialer.storage.StorageComponent; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.DialerUtils; diff --git a/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java b/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java index 5b48ccfd0..1d2cda3ea 100644 --- a/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java +++ b/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java @@ -22,9 +22,9 @@ import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import com.android.contacts.common.list.ContactListItemView; import com.android.dialer.common.LogUtil; -import com.android.dialer.dialpadview.SmartDialCursorLoader; -import com.android.dialer.smartdial.SmartDialMatchPosition; -import com.android.dialer.smartdial.SmartDialNameMatcher; +import com.android.dialer.smartdial.SmartDialCursorLoader; +import com.android.dialer.smartdial.util.SmartDialMatchPosition; +import com.android.dialer.smartdial.util.SmartDialNameMatcher; import com.android.dialer.util.CallUtil; import java.util.ArrayList; diff --git a/java/com/android/dialer/app/list/SmartDialSearchFragment.java b/java/com/android/dialer/app/list/SmartDialSearchFragment.java index e97a16c19..1a7f19515 100644 --- a/java/com/android/dialer/app/list/SmartDialSearchFragment.java +++ b/java/com/android/dialer/app/list/SmartDialSearchFragment.java @@ -31,7 +31,7 @@ import com.android.dialer.app.R; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.common.LogUtil; import com.android.dialer.database.DialerDatabaseHelper; -import com.android.dialer.dialpadview.SmartDialCursorLoader; +import com.android.dialer.smartdial.SmartDialCursorLoader; import com.android.dialer.util.PermissionsUtil; import com.android.dialer.widget.EmptyContentView; import java.util.Arrays; diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index fbb58312a..042ff30a2 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -110,7 +110,9 @@ public final class PhoneLookupDataSource implements CallLogDataSource { * <p>This method uses the following algorithm: * * <ul> - * <li>Selects the distinct DialerPhoneNumbers from the AnnotatedCallLog + * <li>Finds the phone numbers of interest by taking the union of the distinct + * DialerPhoneNumbers from the AnnotatedCallLog and the pending inserts provided in {@code + * mutations} * <li>Uses them to fetch the current information from PhoneLookupHistory, in order to construct * a map from DialerPhoneNumber to PhoneLookupInfo * <ul> @@ -137,9 +139,10 @@ public final class PhoneLookupDataSource implements CallLogDataSource { phoneLookupHistoryRowsToUpdate.clear(); phoneLookupHistoryRowsToDelete.clear(); - // First query information from annotated call log. + // First query information from annotated call log (and include pending inserts). ListenableFuture<Map<DialerPhoneNumber, Set<Long>>> annotatedCallLogIdsByNumberFuture = - backgroundExecutorService.submit(() -> queryIdAndNumberFromAnnotatedCallLog(appContext)); + backgroundExecutorService.submit( + () -> collectIdAndNumberFromAnnotatedCallLogAndPendingInserts(appContext, mutations)); // Use it to create the original info map. ListenableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> originalInfoMapFuture = @@ -317,9 +320,28 @@ public final class PhoneLookupDataSource implements CallLogDataSource { return numbers.build(); } - private Map<DialerPhoneNumber, Set<Long>> queryIdAndNumberFromAnnotatedCallLog( - Context appContext) { + private Map<DialerPhoneNumber, Set<Long>> collectIdAndNumberFromAnnotatedCallLogAndPendingInserts( + Context appContext, CallLogMutations mutations) { Map<DialerPhoneNumber, Set<Long>> idsByNumber = new ArrayMap<>(); + // First add any pending inserts to the map. + for (Entry<Long, ContentValues> entry : mutations.getInserts().entrySet()) { + long id = entry.getKey(); + ContentValues insertedContentValues = entry.getValue(); + DialerPhoneNumber dialerPhoneNumber; + try { + dialerPhoneNumber = + DialerPhoneNumber.parseFrom( + insertedContentValues.getAsByteArray(AnnotatedCallLog.NUMBER)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException(e); + } + Set<Long> ids = idsByNumber.get(dialerPhoneNumber); + if (ids == null) { + ids = new ArraySet<>(); + idsByNumber.put(dialerPhoneNumber, ids); + } + ids.add(id); + } try (Cursor cursor = appContext @@ -332,7 +354,9 @@ public final class PhoneLookupDataSource implements CallLogDataSource { null)) { if (cursor == null) { - LogUtil.e("PhoneLookupDataSource.queryIdAndNumberFromAnnotatedCallLog", "null cursor"); + LogUtil.e( + "PhoneLookupDataSource.collectIdAndNumberFromAnnotatedCallLogAndPendingInserts", + "null cursor"); return ImmutableMap.of(); } diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index 0ed185966..95fbf9d04 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -27,6 +27,7 @@ import android.os.Build; import android.os.Handler; import android.provider.CallLog; import android.provider.CallLog.Calls; +import android.provider.VoicemailContract; import android.support.annotation.ColorInt; import android.support.annotation.MainThread; import android.support.annotation.Nullable; @@ -92,13 +93,21 @@ public class SystemCallLogDataSource implements CallLogDataSource { } // TODO(zachh): Need to somehow register observers if user enables permission after launch? + CallLogObserver callLogObserver = + new CallLogObserver(ThreadUtil.getUiThreadHandler(), appContext, contentObserverCallbacks); + appContext .getContentResolver() - .registerContentObserver( - CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, - true, - new CallLogObserver( - ThreadUtil.getUiThreadHandler(), appContext, contentObserverCallbacks)); + .registerContentObserver(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, true, callLogObserver); + + if (!PermissionsUtil.hasAddVoicemailPermissions(appContext)) { + LogUtil.i("SystemCallLogDataSource.registerContentObservers", "no add voicemail permissions"); + return; + } + // TODO(uabdullah): Need to somehow register observers if user enables permission after launch? + appContext + .getContentResolver() + .registerContentObserver(VoicemailContract.Status.CONTENT_URI, true, callLogObserver); } @Override @@ -462,7 +471,11 @@ public class SystemCallLogDataSource implements CallLogDataSource { @Override public void onChange(boolean selfChange, Uri uri) { Assert.isMainThread(); - LogUtil.enterBlock("SystemCallLogDataSource.CallLogObserver.onChange"); + LogUtil.i( + "SystemCallLogDataSource.CallLogObserver.onChange", + "Uri:%s, SelfChange:%b", + String.valueOf(uri), + selfChange); super.onChange(selfChange, uri); /* diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index c10b52123..719878cec 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -58,10 +58,10 @@ public final class NewCallLogFragment extends Fragment } @Override - public void onCreate(Bundle state) { - super.onCreate(state); + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); - LogUtil.enterBlock("NewCallLogFragment.onCreate"); + LogUtil.enterBlock("NewCallLogFragment.onActivityCreated"); CallLogComponent component = CallLogComponent.get(getContext()); CallLogFramework callLogFramework = component.callLogFramework(); diff --git a/java/com/android/dialer/common/concurrent/UiListener.java b/java/com/android/dialer/common/concurrent/UiListener.java index 9541dbc0c..df791301f 100644 --- a/java/com/android/dialer/common/concurrent/UiListener.java +++ b/java/com/android/dialer/common/concurrent/UiListener.java @@ -71,7 +71,10 @@ public class UiListener<OutputT> extends Fragment { if (uiListener == null) { LogUtil.i("UiListener.create", "creating new UiListener for " + taskId); uiListener = new UiListener<>(); - fragmentManager.beginTransaction().add(uiListener, taskId).commit(); + // When launching an activity with the screen off, its onSaveInstanceState() is called before + // its fragments are created, which means we can't use commit() and need to use + // commitAllowingStateLoss(). This is not a problem for UiListener which saves no state. + fragmentManager.beginTransaction().add(uiListener, taskId).commitAllowingStateLoss(); } return uiListener; } @@ -130,6 +133,9 @@ public class UiListener<OutputT> extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); + // Note: We use commitAllowingStateLoss when attaching the fragment so it may not be safe to + // read savedInstanceState in all situations. (But it's not anticipated that this fragment + // should need to rely on saved state.) } @Override diff --git a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java index 6bed818da..b01689da4 100644 --- a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java +++ b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java @@ -57,22 +57,16 @@ public class TelephonyManagerCompat { * * <p>This signals to the telephony platform that an outgoing call qualifies for assisted dialing. */ - public static final String ALLOW_ASSISTED_DIAL = "android.telecom.extra.ALLOW_ASSISTED_DIAL"; - - // TODO(erfanian): a bug Replace with the platform/telecom constant when available. - /** - * Indicates that an outgoing call has undergone assisted dialing. - * - * <p>Unlike {@link ALLOW_ASSISTED_DIAL}, the presence of this key further indicates that a call - * has undergone Assisted Dialing -- not just that it qualified for Assisted Dialing. - */ - public static final String IS_ASSISTED_DIALED = "android.telecom.extra.IS_ASSISTED_DIALED"; + public static final String USE_ASSISTED_DIALING = "android.telecom.extra.USE_ASSISTED_DIALING"; // TODO(erfanian): a bug Replace with the platform/telecom API when available. /** Additional information relating to the assisted dialing transformation. */ public static final String ASSISTED_DIALING_EXTRAS = "android.telecom.extra.ASSISTED_DIALING_EXTRAS"; + /** Indicates the Connection/Call used assisted dialing. */ + public static final int PROPERTY_ASSISTED_DIALING_USED = 0x00000200; + public static final String EXTRA_IS_REFRESH = BuildCompat.isAtLeastOMR1() ? "android.telephony.extra.IS_REFRESH" : "is_refresh"; diff --git a/java/com/android/dialer/database/DialerDatabaseHelper.java b/java/com/android/dialer/database/DialerDatabaseHelper.java index b0bd62a34..3fb87304b 100644 --- a/java/com/android/dialer/database/DialerDatabaseHelper.java +++ b/java/com/android/dialer/database/DialerDatabaseHelper.java @@ -42,8 +42,8 @@ import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; -import com.android.dialer.smartdial.SmartDialNameMatcher; -import com.android.dialer.smartdial.SmartDialPrefix; +import com.android.dialer.smartdial.util.SmartDialNameMatcher; +import com.android.dialer.smartdial.util.SmartDialPrefix; import com.android.dialer.util.PermissionsUtil; import java.util.ArrayList; import java.util.HashSet; diff --git a/java/com/android/dialer/dialpadview/DialpadCharMappings.java b/java/com/android/dialer/dialpadview/DialpadCharMappings.java index 03bc2e728..0bb28ae0d 100644 --- a/java/com/android/dialer/dialpadview/DialpadCharMappings.java +++ b/java/com/android/dialer/dialpadview/DialpadCharMappings.java @@ -147,6 +147,23 @@ public class DialpadCharMappings { : null; } + /** + * Returns the character-key map of the provided ISO 639-2 language code. + * + * <p>Note: this method is for implementations of {@link + * com.android.dialer.smartdial.map.SmartDialMap} only. {@link #getCharToKeyMap(Context)} should + * be used for all other purposes. + * + * <p>It is the caller's responsibility to ensure the language code is valid and a character + * mapping is defined for that language. Otherwise, an exception will be thrown. + */ + public static SimpleArrayMap<Character, Character> getCharToKeyMap(String languageCode) { + SimpleArrayMap<Character, Character> charToKeyMap = CHAR_TO_KEY_MAPS.get(languageCode); + + return Assert.isNotNull( + charToKeyMap, "No character mappings can be found for language code '%s'", languageCode); + } + /** Returns the default character-key map (the one that uses the Latin alphabet). */ public static SimpleArrayMap<Character, Character> getDefaultCharToKeyMap() { return Latin.CHAR_TO_KEY; diff --git a/java/com/android/dialer/dialpadview/DialpadView.java b/java/com/android/dialer/dialpadview/DialpadView.java index 2f494e49d..7b95ba7ea 100644 --- a/java/com/android/dialer/dialpadview/DialpadView.java +++ b/java/com/android/dialer/dialpadview/DialpadView.java @@ -87,6 +87,7 @@ public class DialpadView extends LinearLayout { private ViewGroup mRateContainer; private TextView mIldCountry; private TextView mIldRate; + private boolean mIsLandscapeMode; public DialpadView(Context context) { this(context, null); @@ -125,6 +126,12 @@ public class DialpadView extends LinearLayout { protected void onFinishInflate() { super.onFinishInflate(); + // The orientation obtained at this point should be used as the only truth for DialpadView as we + // observed inconsistency between configurations obtained here and in + // OnPreDrawListenerForKeyLayoutAdjust under rare circumstances. + mIsLandscapeMode = + (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); + setupKeypad(); mDigits = (EditText) findViewById(R.id.digits); mDelete = (ImageButton) findViewById(R.id.deleteButton); @@ -281,7 +288,7 @@ public class DialpadView extends LinearLayout { final DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[i]); ViewPropertyAnimator animator = dialpadKey.animate(); - if (isLandscapeMode()) { + if (mIsLandscapeMode) { // Landscape orientation requires translation along the X axis. // For RTL locales, ensure we translate negative on the X axis. dialpadKey.setTranslationX((mIsRtl ? -1 : 1) * mTranslateDistance); @@ -320,7 +327,7 @@ public class DialpadView extends LinearLayout { * @return The animation delay. */ private int getKeyButtonAnimationDelay(int buttonId) { - if (isLandscapeMode()) { + if (mIsLandscapeMode) { if (mIsRtl) { if (buttonId == R.id.three) { return KEY_FRAME_DURATION * 1; @@ -408,7 +415,7 @@ public class DialpadView extends LinearLayout { * @return The animation duration. */ private int getKeyButtonAnimationDuration(int buttonId) { - if (isLandscapeMode()) { + if (mIsLandscapeMode) { if (mIsRtl) { if (buttonId == R.id.one || buttonId == R.id.four @@ -463,10 +470,6 @@ public class DialpadView extends LinearLayout { return 0; } - private boolean isLandscapeMode() { - return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; - } - /** * An {@link OnPreDrawListener} that adjusts the height/width of each key layout so that they can * be properly aligned. @@ -525,7 +528,7 @@ public class DialpadView extends LinearLayout { } private boolean shouldAdjustKeySizes() { - return isLandscapeMode() ? shouldAdjustKeyWidths() : shouldAdjustDigitKeyHeights(); + return mIsLandscapeMode ? shouldAdjustKeyWidths() : shouldAdjustDigitKeyHeights(); } /** @@ -533,7 +536,7 @@ public class DialpadView extends LinearLayout { * device is in landscape mode. */ private boolean shouldAdjustKeyWidths() { - Assert.checkState(isLandscapeMode()); + Assert.checkState(mIsLandscapeMode); DialpadKeyButton dialpadKeyButton = (DialpadKeyButton) findViewById(BUTTON_IDS[0]); LinearLayout keyLayout = @@ -556,7 +559,7 @@ public class DialpadView extends LinearLayout { * called when the device is in portrait mode. */ private boolean shouldAdjustDigitKeyHeights() { - Assert.checkState(!isLandscapeMode()); + Assert.checkState(!mIsLandscapeMode); DialpadKeyButton dialpadKey = (DialpadKeyButton) findViewById(BUTTON_IDS[0]); LinearLayout keyLayout = (LinearLayout) dialpadKey.findViewById(R.id.dialpad_key_layout); @@ -576,7 +579,7 @@ public class DialpadView extends LinearLayout { } private void adjustKeySizes() { - if (isLandscapeMode()) { + if (mIsLandscapeMode) { adjustKeyWidths(); } else { adjustDigitKeyHeights(); @@ -594,7 +597,7 @@ public class DialpadView extends LinearLayout { * LinearLayout#setLayoutParams(ViewGroup.LayoutParams)}. */ private void adjustDigitKeyHeights() { - Assert.checkState(!isLandscapeMode()); + Assert.checkState(!mIsLandscapeMode); int maxHeight = 0; @@ -638,7 +641,7 @@ public class DialpadView extends LinearLayout { * View#setLayoutParams(ViewGroup.LayoutParams)}. */ private void adjustKeyWidths() { - Assert.checkState(isLandscapeMode()); + Assert.checkState(mIsLandscapeMode); int maxWidth = 0; for (int buttonId : BUTTON_IDS) { diff --git a/java/com/android/dialer/function/BiConsumer.java b/java/com/android/dialer/function/BiConsumer.java new file mode 100644 index 000000000..50052210e --- /dev/null +++ b/java/com/android/dialer/function/BiConsumer.java @@ -0,0 +1,24 @@ +/* + * 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.function; + +/** Functional interface for consuming two generic values. */ +public interface BiConsumer<T, U> { + + /** Consumes a value. */ + void accept(T t, U u); +} diff --git a/java/com/android/dialer/main/impl/MainPagerAdapter.java b/java/com/android/dialer/main/impl/MainPagerAdapter.java index 2d224f69c..d294640ee 100644 --- a/java/com/android/dialer/main/impl/MainPagerAdapter.java +++ b/java/com/android/dialer/main/impl/MainPagerAdapter.java @@ -20,7 +20,7 @@ import android.content.Context; import android.support.annotation.IntDef; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.app.FragmentPagerAdapter; import com.android.dialer.calllog.ui.NewCallLogFragment; import com.android.dialer.common.Assert; import com.android.dialer.voicemail.listui.NewVoicemailFragment; @@ -28,7 +28,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** Adapter for {@link MainActivity} ViewPager. */ -final class MainPagerAdapter extends FragmentStatePagerAdapter { +final class MainPagerAdapter extends FragmentPagerAdapter { @Retention(RetentionPolicy.SOURCE) @IntDef({ diff --git a/java/com/android/dialer/phonelookup/PhoneLookup.java b/java/com/android/dialer/phonelookup/PhoneLookup.java index eeab4dadd..bb14c1ff6 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/PhoneLookup.java @@ -62,6 +62,12 @@ public interface PhoneLookup { ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap); /** + * Populates the sub-message that this {@link PhoneLookup} is responsible for by copying the + * sub-message value from {@code source} to {@code destination} + */ + void copySubMessage(PhoneLookupInfo.Builder destination, PhoneLookupInfo source); + + /** * Called when the results of the {@link #getMostRecentPhoneLookupInfo(ImmutableMap)} have been * applied by the caller. * diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java index ee2244615..bb7856fff 100644 --- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java @@ -112,14 +112,15 @@ public final class CompositePhoneLookup implements PhoneLookup { ImmutableMap.builder(); for (DialerPhoneNumber dialerPhoneNumber : existingInfoMap.keySet()) { PhoneLookupInfo.Builder combinedInfo = PhoneLookupInfo.newBuilder(); - for (ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> map : allMaps) { + for (int i = 0; i < allMaps.size(); i++) { + ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> map = allMaps.get(i); PhoneLookupInfo subInfo = map.get(dialerPhoneNumber); if (subInfo == null) { throw new IllegalStateException( "A sublookup didn't return an info for number: " + LogUtil.sanitizePhoneNumber(dialerPhoneNumber.getRawInput().getNumber())); } - combinedInfo.mergeFrom(subInfo); + phoneLookups.get(i).copySubMessage(combinedInfo, subInfo); } combinedMap.put(dialerPhoneNumber, combinedInfo.build()); } @@ -129,6 +130,11 @@ public final class CompositePhoneLookup implements PhoneLookup { } @Override + public void copySubMessage(PhoneLookupInfo.Builder destination, PhoneLookupInfo source) { + throw new UnsupportedOperationException(); + } + + @Override public ListenableFuture<Void> onSuccessfulBulkUpdate() { List<ListenableFuture<Void>> futures = new ArrayList<>(); for (PhoneLookup phoneLookup : phoneLookups) { diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index b31d0e72e..a477e035c 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -36,6 +36,7 @@ import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info; import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo; +import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.android.dialer.storage.Unencrypted; import com.android.dialer.telecom.TelecomCallUtil; @@ -45,6 +46,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.protobuf.InvalidProtocolBufferException; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -131,8 +133,25 @@ public final class Cp2PhoneLookup implements PhoneLookup { private boolean isDirtyInternal(ImmutableSet<DialerPhoneNumber> phoneNumbers) { long lastModified = sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L); - return contactsUpdated(queryPhoneTableForContactIds(phoneNumbers), lastModified) - || contactsDeleted(lastModified); + // We are always going to need to do this check and it is pretty cheap so do it first. + if (anyContactsDeletedSince(lastModified)) { + return true; + } + // Hopefully the most common case is there are no contacts updated; we can detect this cheaply. + if (noContactsModifiedSince(lastModified)) { + return false; + } + // This method is more expensive but is probably the most likely scenario; we are looking for + // changes to contacts which have been called. + if (contactsUpdated(queryPhoneTableForContactIds(phoneNumbers), lastModified)) { + return true; + } + // This is the most expensive method so do it last; the scenario is that a contact which has + // been called got disassociated with a number and we need to clear their information. + if (contactsUpdated(queryPhoneLookupHistoryForContactIds(), lastModified)) { + return true; + } + return false; } /** @@ -155,6 +174,46 @@ public final class Cp2PhoneLookup implements PhoneLookup { return contactIds; } + /** Gets all of the contact ids from PhoneLookupHistory. */ + private Set<Long> queryPhoneLookupHistoryForContactIds() { + Set<Long> contactIds = new ArraySet<>(); + try (Cursor cursor = + appContext + .getContentResolver() + .query( + PhoneLookupHistory.CONTENT_URI, + new String[] { + PhoneLookupHistory.PHONE_LOOKUP_INFO, + }, + null, + null, + null)) { + + if (cursor == null) { + LogUtil.w("Cp2PhoneLookup.queryPhoneLookupHistoryForContactIds", "null cursor"); + return contactIds; + } + + if (cursor.moveToFirst()) { + int phoneLookupInfoColumn = + cursor.getColumnIndexOrThrow(PhoneLookupHistory.PHONE_LOOKUP_INFO); + do { + PhoneLookupInfo phoneLookupInfo; + try { + phoneLookupInfo = PhoneLookupInfo.parseFrom(cursor.getBlob(phoneLookupInfoColumn)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException(e); + } + for (Cp2ContactInfo info : phoneLookupInfo.getCp2Info().getCp2ContactInfoList()) { + contactIds.add(info.getContactId()); + } + } while (cursor.moveToNext()); + } + } + + return contactIds; + } + private Set<Long> queryPhoneTableForContactIdsBasedOnE164(Set<String> validE164Numbers) { Set<Long> contactIds = new ArraySet<>(); if (validE164Numbers.isEmpty()) { @@ -226,8 +285,26 @@ public final class Cp2PhoneLookup implements PhoneLookup { null); } + private boolean noContactsModifiedSince(long lastModified) { + try (Cursor cursor = + appContext + .getContentResolver() + .query( + Contacts.CONTENT_URI, + new String[] {Contacts._ID}, + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?", + new String[] {Long.toString(lastModified)}, + Contacts._ID + " limit 1")) { + if (cursor == null) { + LogUtil.w("Cp2PhoneLookup.noContactsModifiedSince", "null cursor"); + return false; + } + return cursor.getCount() == 0; + } + } + /** Returns true if any contacts were deleted after {@code lastModified}. */ - private boolean contactsDeleted(long lastModified) { + private boolean anyContactsDeletedSince(long lastModified) { try (Cursor cursor = appContext .getContentResolver() @@ -236,9 +313,9 @@ public final class Cp2PhoneLookup implements PhoneLookup { new String[] {DeletedContacts.CONTACT_DELETED_TIMESTAMP}, DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?", new String[] {Long.toString(lastModified)}, - null)) { + DeletedContacts.CONTACT_DELETED_TIMESTAMP + " limit 1")) { if (cursor == null) { - LogUtil.w("Cp2PhoneLookup.contactsDeleted", "null cursor"); + LogUtil.w("Cp2PhoneLookup.anyContactsDeletedSince", "null cursor"); return false; } return cursor.getCount() > 0; @@ -253,6 +330,11 @@ public final class Cp2PhoneLookup implements PhoneLookup { () -> getMostRecentPhoneLookupInfoInternal(existingInfoMap)); } + @Override + public void copySubMessage(PhoneLookupInfo.Builder destination, PhoneLookupInfo source) { + destination.setCp2Info(source.getCp2Info()); + } + private ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> getMostRecentPhoneLookupInfoInternal( ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap) { currentLastTimestampProcessed = null; @@ -407,6 +489,11 @@ public final class Cp2PhoneLookup implements PhoneLookup { partitionedNumbers.dialerPhoneNumbersForE164(e164Number); Cp2ContactInfo info = buildCp2ContactInfoFromPhoneCursor(appContext, cursor); addInfo(map, dialerPhoneNumbers, info); + + // We are going to remove the numbers that we've handled so that we later can detect + // numbers that weren't handled and therefore need to have their contact information + // removed. + updatedNumbers.removeAll(dialerPhoneNumbers); } } } @@ -424,10 +511,20 @@ public final class Cp2PhoneLookup implements PhoneLookup { partitionedNumbers.dialerPhoneNumbersForUnformattable(unformattableNumber); Cp2ContactInfo info = buildCp2ContactInfoFromPhoneCursor(appContext, cursor); addInfo(map, dialerPhoneNumbers, info); + + // We are going to remove the numbers that we've handled so that we later can detect + // numbers that weren't handled and therefore need to have their contact information + // removed. + updatedNumbers.removeAll(dialerPhoneNumbers); } } } } + // The leftovers in updatedNumbers that weren't removed are numbers that were previously + // associated with contacts, but are no longer. Remove the contact information for them. + for (DialerPhoneNumber dialerPhoneNumber : updatedNumbers) { + map.put(dialerPhoneNumber, ImmutableSet.of()); + } return map; } diff --git a/java/com/android/dialer/precall/impl/AssistedDialAction.java b/java/com/android/dialer/precall/impl/AssistedDialAction.java index c4f61d2dd..dc2510960 100644 --- a/java/com/android/dialer/precall/impl/AssistedDialAction.java +++ b/java/com/android/dialer/precall/impl/AssistedDialAction.java @@ -51,10 +51,12 @@ public class AssistedDialAction implements PreCallAction { AssistedDialingMediator assistedDialingMediator = ConcreteCreator.createNewAssistedDialingMediator( context.getSystemService(TelephonyManager.class), context); + if (Build.VERSION.SDK_INT > ConcreteCreator.BUILD_CODE_CEILING) { + builder.getOutgoingCallExtras().putBoolean(TelephonyManagerCompat.USE_ASSISTED_DIALING, true); + } if (!assistedDialingMediator.isPlatformEligible()) { return; } - builder.getOutgoingCallExtras().putBoolean(TelephonyManagerCompat.ALLOW_ASSISTED_DIAL, true); String phoneNumber = builder.getUri().getScheme().equals(PhoneAccount.SCHEME_TEL) ? builder.getUri().getSchemeSpecificPart() @@ -62,8 +64,8 @@ public class AssistedDialAction implements PreCallAction { Optional<TransformationInfo> transformedNumber = assistedDialingMediator.attemptAssistedDial(phoneNumber); if (transformedNumber.isPresent()) { + builder.getOutgoingCallExtras().putBoolean(TelephonyManagerCompat.USE_ASSISTED_DIALING, true); Bundle assistedDialingExtras = transformedNumber.get().toBundle(); - builder.getOutgoingCallExtras().putBoolean(TelephonyManagerCompat.IS_ASSISTED_DIALED, true); builder .getOutgoingCallExtras() .putBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS, assistedDialingExtras); diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java index 23e3f9d88..23f368f54 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java @@ -28,9 +28,9 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.contacts.common.preference.ContactsPreferences; -import com.android.dialer.dialpadview.SmartDialCursorLoader; import com.android.dialer.searchfragment.common.Projections; import com.android.dialer.searchfragment.common.SearchCursor; +import com.android.dialer.smartdial.SmartDialCursorLoader; /** Cursor Loader for CP2 contacts. */ public final class SearchContactsCursorLoader extends CursorLoader { diff --git a/java/com/android/dialer/shortcuts/PinnedShortcuts.java b/java/com/android/dialer/shortcuts/PinnedShortcuts.java index bfcc3df81..6e23a5c21 100644 --- a/java/com/android/dialer/shortcuts/PinnedShortcuts.java +++ b/java/com/android/dialer/shortcuts/PinnedShortcuts.java @@ -46,7 +46,6 @@ import java.util.Map; * <p>When refreshing pinned shortcuts, we check to make sure that pinned contact information is * still up to date (e.g. photo and name). We also check to see if the contact has been deleted from * the user's contacts, and if so, we disable the pinned shortcut. - * */ @TargetApi(VERSION_CODES.N_MR1) // Shortcuts introduced in N MR1 final class PinnedShortcuts { @@ -107,6 +106,14 @@ final class PinnedShortcuts { // setRank is nonsensical for pinned shortcuts and therefore could not be calculated. continue; } + // Exclude shortcuts like the "Phone NUI" shortcut. + String action = null; + if (shortcutInfo.getIntent() != null) { + action = shortcutInfo.getIntent().getAction(); + } + if (action == null || !action.equals("com.android.dialer.shortcuts.CALL_CONTACT")) { + continue; + } String lookupKey = DialerShortcut.getLookupKeyFromShortcutInfo(shortcutInfo); Uri lookupUri = DialerShortcut.getLookupUriFromShortcutInfo(shortcutInfo); diff --git a/java/com/android/dialer/simulator/impl/SimulatorDialogFragment.java b/java/com/android/dialer/simulator/impl/SimulatorDialogFragment.java index f8403c7fe..96ea62739 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorDialogFragment.java +++ b/java/com/android/dialer/simulator/impl/SimulatorDialogFragment.java @@ -29,7 +29,7 @@ public final class SimulatorDialogFragment extends DialogFragment { private final String[] callerIdPresentationItems = { "ALLOWED", "PAYPHONE", "RESTRICTED", "UNKNOWN" }; - private int callerIdPresentationChoice; + private int callerIdPresentationChoice = 1; private DialogCallback dialogCallback; @@ -47,6 +47,7 @@ public final class SimulatorDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle bundle) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); final EditText input = new EditText(getActivity()); + input.setHint("Please input phone number"); builder .setTitle("Phone Number:") .setView(input) diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java index 89c5d2f14..ff00dd87e 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -120,7 +120,8 @@ final class SimulatorVoiceCall private void addSpamIncomingCall() { String callerId = "+1-661-778-3020"; /* Blacklisted custom spam number */ - SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */); + connectionTag = + SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */); } private void addNewEmergencyCallBack() { diff --git a/java/com/android/dialer/smartdial/RussianSmartDialMap.java b/java/com/android/dialer/smartdial/RussianSmartDialMap.java deleted file mode 100644 index ada9182e1..000000000 --- a/java/com/android/dialer/smartdial/RussianSmartDialMap.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.smartdial; - -import android.support.v4.util.SimpleArrayMap; -import com.google.common.base.Optional; - -/** A {@link SmartDialMap} for the Russian alphabet. */ -@SuppressWarnings("Guava") -final class RussianSmartDialMap extends SmartDialMap { - private static final SimpleArrayMap<Character, Character> CHAR_TO_KEY_MAP = - new SimpleArrayMap<>(); - - // Reference: https://en.wikipedia.org/wiki/Russian_alphabet - static { - CHAR_TO_KEY_MAP.put('а', '2'); - CHAR_TO_KEY_MAP.put('б', '2'); - CHAR_TO_KEY_MAP.put('в', '2'); - CHAR_TO_KEY_MAP.put('г', '2'); - - CHAR_TO_KEY_MAP.put('д', '3'); - CHAR_TO_KEY_MAP.put('е', '3'); - CHAR_TO_KEY_MAP.put('ё', '3'); - CHAR_TO_KEY_MAP.put('ж', '3'); - CHAR_TO_KEY_MAP.put('з', '3'); - - CHAR_TO_KEY_MAP.put('и', '4'); - CHAR_TO_KEY_MAP.put('й', '4'); - CHAR_TO_KEY_MAP.put('к', '4'); - CHAR_TO_KEY_MAP.put('л', '4'); - - CHAR_TO_KEY_MAP.put('м', '5'); - CHAR_TO_KEY_MAP.put('н', '5'); - CHAR_TO_KEY_MAP.put('о', '5'); - CHAR_TO_KEY_MAP.put('п', '5'); - - CHAR_TO_KEY_MAP.put('р', '6'); - CHAR_TO_KEY_MAP.put('с', '6'); - CHAR_TO_KEY_MAP.put('т', '6'); - CHAR_TO_KEY_MAP.put('у', '6'); - - CHAR_TO_KEY_MAP.put('ф', '7'); - CHAR_TO_KEY_MAP.put('х', '7'); - CHAR_TO_KEY_MAP.put('ц', '7'); - CHAR_TO_KEY_MAP.put('ч', '7'); - - CHAR_TO_KEY_MAP.put('ш', '8'); - CHAR_TO_KEY_MAP.put('щ', '8'); - CHAR_TO_KEY_MAP.put('ъ', '8'); - CHAR_TO_KEY_MAP.put('ы', '8'); - - CHAR_TO_KEY_MAP.put('ь', '9'); - CHAR_TO_KEY_MAP.put('э', '9'); - CHAR_TO_KEY_MAP.put('ю', '9'); - CHAR_TO_KEY_MAP.put('я', '9'); - } - - private static RussianSmartDialMap instance; - - static RussianSmartDialMap getInstance() { - if (instance == null) { - instance = new RussianSmartDialMap(); - } - - return instance; - } - - private RussianSmartDialMap() {} - - @Override - Optional<Character> normalizeCharacter(char ch) { - ch = Character.toLowerCase(ch); - return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent(); - } - - @Override - SimpleArrayMap<Character, Character> getCharToKeyMap() { - return CHAR_TO_KEY_MAP; - } -} diff --git a/java/com/android/dialer/dialpadview/SmartDialCursorLoader.java b/java/com/android/dialer/smartdial/SmartDialCursorLoader.java index d085b55bd..f6bc9325a 100644 --- a/java/com/android/dialer/dialpadview/SmartDialCursorLoader.java +++ b/java/com/android/dialer/smartdial/SmartDialCursorLoader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.dialer.dialpadview; +package com.android.dialer.smartdial; import android.content.AsyncTaskLoader; import android.content.Context; @@ -25,7 +25,7 @@ import com.android.dialer.common.LogUtil; import com.android.dialer.database.Database; import com.android.dialer.database.DialerDatabaseHelper; import com.android.dialer.database.DialerDatabaseHelper.ContactNumber; -import com.android.dialer.smartdial.SmartDialNameMatcher; +import com.android.dialer.smartdial.util.SmartDialNameMatcher; import com.android.dialer.util.PermissionsUtil; import java.util.ArrayList; diff --git a/java/com/android/dialer/smartdial/map/BulgarianSmartDialMap.java b/java/com/android/dialer/smartdial/map/BulgarianSmartDialMap.java new file mode 100644 index 000000000..5be9761a1 --- /dev/null +++ b/java/com/android/dialer/smartdial/map/BulgarianSmartDialMap.java @@ -0,0 +1,49 @@ +/* + * 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.smartdial.map; + +import android.support.v4.util.SimpleArrayMap; +import com.android.dialer.dialpadview.DialpadCharMappings; +import com.google.common.base.Optional; + +/** A {@link SmartDialMap} for the Bulgarian alphabet. */ +@SuppressWarnings("Guava") +final class BulgarianSmartDialMap extends SmartDialMap { + + private static BulgarianSmartDialMap instance; + + static BulgarianSmartDialMap getInstance() { + if (instance == null) { + instance = new BulgarianSmartDialMap(); + } + + return instance; + } + + private BulgarianSmartDialMap() {} + + @Override + Optional<Character> normalizeCharacter(char ch) { + ch = Character.toLowerCase(ch); + return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent(); + } + + @Override + SimpleArrayMap<Character, Character> getCharToKeyMap() { + return DialpadCharMappings.getCharToKeyMap("bul"); + } +} diff --git a/java/com/android/dialer/smartdial/CompositeSmartDialMap.java b/java/com/android/dialer/smartdial/map/CompositeSmartDialMap.java index d51e46f76..df32d4ce7 100644 --- a/java/com/android/dialer/smartdial/CompositeSmartDialMap.java +++ b/java/com/android/dialer/smartdial/map/CompositeSmartDialMap.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.dialer.smartdial; +package com.android.dialer.smartdial.map; import android.content.Context; import android.support.annotation.VisibleForTesting; @@ -45,7 +45,9 @@ public class CompositeSmartDialMap { private static final SimpleArrayMap<String, SmartDialMap> EXTRA_MAPS = new SimpleArrayMap<>(); static { + EXTRA_MAPS.put("bul", BulgarianSmartDialMap.getInstance()); EXTRA_MAPS.put("rus", RussianSmartDialMap.getInstance()); + EXTRA_MAPS.put("ukr", UkrainianSmartDialMap.getInstance()); } private CompositeSmartDialMap() {} @@ -56,7 +58,7 @@ public class CompositeSmartDialMap { * <p>The provided character is expected to be a normalized character. See {@link * SmartDialMap#normalizeCharacter(char)} for details. */ - static boolean isValidDialpadCharacter(Context context, char ch) { + public static boolean isValidDialpadCharacter(Context context, char ch) { if (DEFAULT_MAP.isValidDialpadCharacter(ch)) { return true; } @@ -71,7 +73,7 @@ public class CompositeSmartDialMap { * <p>The provided character is expected to be a normalized character. See {@link * SmartDialMap#normalizeCharacter(char)} for details. */ - static boolean isValidDialpadAlphabeticChar(Context context, char ch) { + public static boolean isValidDialpadAlphabeticChar(Context context, char ch) { if (DEFAULT_MAP.isValidDialpadAlphabeticChar(ch)) { return true; } @@ -83,7 +85,7 @@ public class CompositeSmartDialMap { /** * Returns true if the provided character is a digit, and can be mapped to a key on the dialpad. */ - static boolean isValidDialpadNumericChar(Context context, char ch) { + public static boolean isValidDialpadNumericChar(Context context, char ch) { if (DEFAULT_MAP.isValidDialpadNumericChar(ch)) { return true; } @@ -100,7 +102,7 @@ public class CompositeSmartDialMap { * * <p>If the provided character can't be mapped to a key on the dialpad, return -1. */ - static byte getDialpadIndex(Context context, char ch) { + public static byte getDialpadIndex(Context context, char ch) { Optional<Byte> dialpadIndex = DEFAULT_MAP.getDialpadIndex(ch); if (dialpadIndex.isPresent()) { return dialpadIndex.get(); @@ -122,7 +124,7 @@ public class CompositeSmartDialMap { * * <p>If the provided character can't be mapped to a key on the dialpad, return the character. */ - static char getDialpadNumericCharacter(Context context, char ch) { + public static char getDialpadNumericCharacter(Context context, char ch) { Optional<Character> dialpadNumericChar = DEFAULT_MAP.getDialpadNumericCharacter(ch); if (dialpadNumericChar.isPresent()) { return dialpadNumericChar.get(); @@ -142,7 +144,7 @@ public class CompositeSmartDialMap { * * <p>If the provided character can't be mapped to a key on the dialpad, return the character. */ - static char normalizeCharacter(Context context, char ch) { + public static char normalizeCharacter(Context context, char ch) { Optional<Character> normalizedChar = DEFAULT_MAP.normalizeCharacter(ch); if (normalizedChar.isPresent()) { return normalizedChar.get(); diff --git a/java/com/android/dialer/smartdial/LatinSmartDialMap.java b/java/com/android/dialer/smartdial/map/LatinSmartDialMap.java index b67901bbe..b8ef951c5 100644 --- a/java/com/android/dialer/smartdial/LatinSmartDialMap.java +++ b/java/com/android/dialer/smartdial/map/LatinSmartDialMap.java @@ -14,52 +14,15 @@ * limitations under the License. */ -package com.android.dialer.smartdial; +package com.android.dialer.smartdial.map; import android.support.v4.util.SimpleArrayMap; +import com.android.dialer.dialpadview.DialpadCharMappings; import com.google.common.base.Optional; /** A {@link SmartDialMap} for the Latin alphabet, which is for T9 dialpad searching. */ @SuppressWarnings("Guava") final class LatinSmartDialMap extends SmartDialMap { - private static final SimpleArrayMap<Character, Character> CHAR_TO_KEY_MAP = - new SimpleArrayMap<>(); - - static { - CHAR_TO_KEY_MAP.put('a', '2'); - CHAR_TO_KEY_MAP.put('b', '2'); - CHAR_TO_KEY_MAP.put('c', '2'); - - CHAR_TO_KEY_MAP.put('d', '3'); - CHAR_TO_KEY_MAP.put('e', '3'); - CHAR_TO_KEY_MAP.put('f', '3'); - - CHAR_TO_KEY_MAP.put('g', '4'); - CHAR_TO_KEY_MAP.put('h', '4'); - CHAR_TO_KEY_MAP.put('i', '4'); - - CHAR_TO_KEY_MAP.put('j', '5'); - CHAR_TO_KEY_MAP.put('k', '5'); - CHAR_TO_KEY_MAP.put('l', '5'); - - CHAR_TO_KEY_MAP.put('m', '6'); - CHAR_TO_KEY_MAP.put('n', '6'); - CHAR_TO_KEY_MAP.put('o', '6'); - - CHAR_TO_KEY_MAP.put('p', '7'); - CHAR_TO_KEY_MAP.put('q', '7'); - CHAR_TO_KEY_MAP.put('r', '7'); - CHAR_TO_KEY_MAP.put('s', '7'); - - CHAR_TO_KEY_MAP.put('t', '8'); - CHAR_TO_KEY_MAP.put('u', '8'); - CHAR_TO_KEY_MAP.put('v', '8'); - - CHAR_TO_KEY_MAP.put('w', '9'); - CHAR_TO_KEY_MAP.put('x', '9'); - CHAR_TO_KEY_MAP.put('y', '9'); - CHAR_TO_KEY_MAP.put('z', '9'); - } private static LatinSmartDialMap instance; @@ -780,6 +743,6 @@ final class LatinSmartDialMap extends SmartDialMap { @Override SimpleArrayMap<Character, Character> getCharToKeyMap() { - return CHAR_TO_KEY_MAP; + return DialpadCharMappings.getDefaultCharToKeyMap(); } } diff --git a/java/com/android/dialer/smartdial/map/RussianSmartDialMap.java b/java/com/android/dialer/smartdial/map/RussianSmartDialMap.java new file mode 100644 index 000000000..c10bbb0ce --- /dev/null +++ b/java/com/android/dialer/smartdial/map/RussianSmartDialMap.java @@ -0,0 +1,49 @@ +/* + * 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.smartdial.map; + +import android.support.v4.util.SimpleArrayMap; +import com.android.dialer.dialpadview.DialpadCharMappings; +import com.google.common.base.Optional; + +/** A {@link SmartDialMap} for the Russian alphabet. */ +@SuppressWarnings("Guava") +final class RussianSmartDialMap extends SmartDialMap { + + private static RussianSmartDialMap instance; + + static RussianSmartDialMap getInstance() { + if (instance == null) { + instance = new RussianSmartDialMap(); + } + + return instance; + } + + private RussianSmartDialMap() {} + + @Override + Optional<Character> normalizeCharacter(char ch) { + ch = Character.toLowerCase(ch); + return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent(); + } + + @Override + SimpleArrayMap<Character, Character> getCharToKeyMap() { + return DialpadCharMappings.getCharToKeyMap("rus"); + } +} diff --git a/java/com/android/dialer/smartdial/SmartDialMap.java b/java/com/android/dialer/smartdial/map/SmartDialMap.java index bc5c9ea72..c74dd2893 100644 --- a/java/com/android/dialer/smartdial/SmartDialMap.java +++ b/java/com/android/dialer/smartdial/map/SmartDialMap.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.dialer.smartdial; +package com.android.dialer.smartdial.map; import android.support.v4.util.SimpleArrayMap; import com.google.common.base.Optional; diff --git a/java/com/android/dialer/smartdial/map/UkrainianSmartDialMap.java b/java/com/android/dialer/smartdial/map/UkrainianSmartDialMap.java new file mode 100644 index 000000000..844732c32 --- /dev/null +++ b/java/com/android/dialer/smartdial/map/UkrainianSmartDialMap.java @@ -0,0 +1,48 @@ +/* + * 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.smartdial.map; + +import android.support.v4.util.SimpleArrayMap; +import com.android.dialer.dialpadview.DialpadCharMappings; +import com.google.common.base.Optional; + +/** A {@link SmartDialMap} for the Ukrainian alphabet. */ +final class UkrainianSmartDialMap extends SmartDialMap { + + private static UkrainianSmartDialMap instance; + + static UkrainianSmartDialMap getInstance() { + if (instance == null) { + instance = new UkrainianSmartDialMap(); + } + + return instance; + } + + private UkrainianSmartDialMap() {} + + @Override + Optional<Character> normalizeCharacter(char ch) { + ch = Character.toLowerCase(ch); + return isValidDialpadAlphabeticChar(ch) ? Optional.of(ch) : Optional.absent(); + } + + @Override + SimpleArrayMap<Character, Character> getCharToKeyMap() { + return DialpadCharMappings.getCharToKeyMap("ukr"); + } +} diff --git a/java/com/android/dialer/smartdial/SmartDialMatchPosition.java b/java/com/android/dialer/smartdial/util/SmartDialMatchPosition.java index 8056ad723..db317ae6b 100644 --- a/java/com/android/dialer/smartdial/SmartDialMatchPosition.java +++ b/java/com/android/dialer/smartdial/util/SmartDialMatchPosition.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.dialer.smartdial; +package com.android.dialer.smartdial.util; -import android.util.Log; +import com.android.dialer.common.LogUtil; import java.util.ArrayList; /** @@ -59,7 +59,7 @@ public class SmartDialMatchPosition { public static void print(ArrayList<SmartDialMatchPosition> list) { for (int i = 0; i < list.size(); i++) { SmartDialMatchPosition m = list.get(i); - Log.d(TAG, "[" + m.start + "," + m.end + "]"); + LogUtil.d(TAG, "[" + m.start + "," + m.end + "]"); } } diff --git a/java/com/android/dialer/smartdial/SmartDialNameMatcher.java b/java/com/android/dialer/smartdial/util/SmartDialNameMatcher.java index 4e3e0cc3f..725c88c57 100644 --- a/java/com/android/dialer/smartdial/SmartDialNameMatcher.java +++ b/java/com/android/dialer/smartdial/util/SmartDialNameMatcher.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package com.android.dialer.smartdial; +package com.android.dialer.smartdial.util; import android.content.Context; import android.support.annotation.Nullable; import android.text.TextUtils; -import com.android.dialer.smartdial.SmartDialPrefix.PhoneNumberTokens; +import com.android.dialer.smartdial.map.CompositeSmartDialMap; +import com.android.dialer.smartdial.util.SmartDialPrefix.PhoneNumberTokens; import java.util.ArrayList; /** diff --git a/java/com/android/dialer/smartdial/SmartDialPrefix.java b/java/com/android/dialer/smartdial/util/SmartDialPrefix.java index b9c1f8c11..9af411913 100644 --- a/java/com/android/dialer/smartdial/SmartDialPrefix.java +++ b/java/com/android/dialer/smartdial/util/SmartDialPrefix.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.dialer.smartdial; +package com.android.dialer.smartdial.util; import android.content.Context; import android.content.SharedPreferences; @@ -22,6 +22,7 @@ import android.preference.PreferenceManager; import android.support.annotation.VisibleForTesting; import android.telephony.TelephonyManager; import android.text.TextUtils; +import com.android.dialer.smartdial.map.CompositeSmartDialMap; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java index 22f3727e6..c64a50231 100644 --- a/java/com/android/dialer/telecom/TelecomUtil.java +++ b/java/com/android/dialer/telecom/TelecomUtil.java @@ -178,6 +178,10 @@ public abstract class TelecomUtil { * are not included. */ public static boolean isInManagedCall(Context context) { + return instance.isInManagedCall(context); + } + + public static boolean isInCall(Context context) { return instance.isInCall(context); } @@ -289,7 +293,7 @@ public abstract class TelecomUtil { @VisibleForTesting() public static class TelecomUtilImpl { - public boolean isInCall(Context context) { + public boolean isInManagedCall(Context context) { if (hasReadPhoneStatePermission(context)) { // The TelecomManager#isInCall method returns true anytime the user is in a call. // Starting in O, the APIs include support for self-managed ConnectionServices so that other @@ -308,6 +312,10 @@ public abstract class TelecomUtil { return false; } + public boolean isInCall(Context context) { + return hasReadPhoneStatePermission(context) && getTelecomManager(context).isInCall(); + } + public boolean hasPermission(Context context, String permission) { return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java index 315bf1cf0..93d5cda3e 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java @@ -67,9 +67,9 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> private final Clock clock; /** {@link Integer#MAX_VALUE} when the "Today" header should not be displayed. */ - private final int todayHeaderPosition; + private int todayHeaderPosition = Integer.MAX_VALUE; /** {@link Integer#MAX_VALUE} when the "Older" header should not be displayed. */ - private final int olderHeaderPosition; + private int olderHeaderPosition = Integer.MAX_VALUE; private final FragmentManager fragmentManager; /** A valid id for {@link VoicemailEntry} is greater than 0 */ @@ -107,7 +107,15 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> this.clock = clock; this.fragmentManager = fragmentManager; initializeMediaPlayerListeners(); + updateHeaderPositions(); + } + private void updateHeaderPositions() { + LogUtil.i( + "NewVoicemailAdapter.updateHeaderPositions", + "before updating todayPos:%d, olderPos:%d", + todayHeaderPosition, + olderHeaderPosition); // Calculate header adapter positions by reading cursor. long currentTimeMillis = clock.currentTimeMillis(); if (cursor.moveToNext()) { @@ -134,6 +142,11 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> this.todayHeaderPosition = Integer.MAX_VALUE; this.olderHeaderPosition = Integer.MAX_VALUE; } + LogUtil.i( + "NewVoicemailAdapter.updateHeaderPositions", + "after updating todayPos:%d, olderPos:%d", + todayHeaderPosition, + olderHeaderPosition); } private void initializeMediaPlayerListeners() { @@ -143,8 +156,10 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> } public void updateCursor(Cursor updatedCursor) { + LogUtil.enterBlock("NewVoicemailAdapter.updateCursor"); deletedVoicemailPosition.clear(); this.cursor = updatedCursor; + updateHeaderPositions(); notifyDataSetChanged(); } @@ -509,8 +524,6 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> Assert.checkArgument(expandedViewHolder.getViewHolderVoicemailUri().equals(voicemailUri)); - notifyItemRemoved(expandedViewHolder.getAdapterPosition()); - Assert.checkArgument(currentlyExpandedViewHolderId == expandedViewHolder.getViewHolderId()); collapseExpandedViewHolder(expandedViewHolder); @@ -524,6 +537,8 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> .onSuccess(deleteVoicemailCallBack) .build() .executeSerial(new Pair<>(context, voicemailUri)); + + notifyItemRemoved(expandedViewHolder.getAdapterPosition()); } private void onVoicemailDeleted(Integer integer) { diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java index 24bed0f04..dac4ebafc 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java @@ -21,6 +21,7 @@ import static android.view.View.VISIBLE; import android.app.FragmentManager; import android.content.Context; import android.database.Cursor; +import android.graphics.Typeface; import android.net.Uri; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; @@ -124,6 +125,9 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On transcriptionTextView.setText(voicemailTranscription); } + // Bold if voicemail is unread + boldViewHolderIfUnread(); + itemView.setOnClickListener(this); menuButton.setOnClickListener( NewVoicemailMenu.createOnClickListener(context, voicemailEntryOfViewHolder)); @@ -173,6 +177,20 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On mediaPlayerView.getVisibility() == VISIBLE); } + private void boldViewHolderIfUnread() { + LogUtil.v( + "NewVoicemailViewHolder.boldViewHolderIfUnread", + "id:%d, isRead:%d", + voicemailEntryOfViewHolder.id(), + voicemailEntryOfViewHolder.isRead()); + + if (voicemailEntryOfViewHolder.isRead() == 0) { + primaryTextView.setTypeface(null, Typeface.BOLD); + secondaryTextView.setTypeface(null, Typeface.BOLD); + transcriptionTextView.setTypeface(null, Typeface.BOLD); + } + } + // TODO(uabdullah): Consider/Implement TYPE (e.g Spam, TYPE_VOICEMAIL) private void setPhoto(VoicemailEntry voicemailEntry) { ContactPhotoManager.getInstance(context) @@ -214,6 +232,10 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On isViewHolderExpanded = false; viewHolderVoicemailUri = null; + primaryTextView.setTypeface(null, Typeface.NORMAL); + secondaryTextView.setTypeface(null, Typeface.NORMAL); + transcriptionTextView.setTypeface(null, Typeface.NORMAL); + mediaPlayerView.reset(); LogUtil.i( diff --git a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java index 6a55483a4..55d36b364 100644 --- a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java +++ b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java @@ -43,7 +43,8 @@ final class VoicemailCursorLoader extends CursorLoader { AnnotatedCallLog.GEOCODED_LOCATION, AnnotatedCallLog.CALL_TYPE, AnnotatedCallLog.TRANSCRIPTION, - AnnotatedCallLog.VOICEMAIL_URI + AnnotatedCallLog.VOICEMAIL_URI, + AnnotatedCallLog.IS_READ }; // Indexes for VOICEMAIL_COLUMNS @@ -60,6 +61,7 @@ final class VoicemailCursorLoader extends CursorLoader { private static final int CALL_TYPE = 10; private static final int TRANSCRIPTION = 11; private static final int VOICEMAIL_URI = 12; + private static final int IS_READ = 13; // TODO(zachh): Optimize indexes VoicemailCursorLoader(Context context) { @@ -95,6 +97,7 @@ final class VoicemailCursorLoader extends CursorLoader { .setVoicemailUri(cursor.getString(VOICEMAIL_URI)) .setGeocodedLocation(cursor.getString(GEOCODED_LOCATION)) .setCallType(cursor.getInt(CALL_TYPE)) + .setIsRead(cursor.getInt(IS_READ)) .build(); } diff --git a/java/com/android/dialer/voicemail/model/VoicemailEntry.java b/java/com/android/dialer/voicemail/model/VoicemailEntry.java index df30dee9c..702f52d17 100644 --- a/java/com/android/dialer/voicemail/model/VoicemailEntry.java +++ b/java/com/android/dialer/voicemail/model/VoicemailEntry.java @@ -32,7 +32,8 @@ public abstract class VoicemailEntry { .setNumber(DialerPhoneNumber.getDefaultInstance()) .setPhotoId(0) .setDuration(0) - .setCallType(0); + .setCallType(0) + .setIsRead(0); } public abstract int id(); @@ -69,6 +70,8 @@ public abstract class VoicemailEntry { public abstract int callType(); + public abstract int isRead(); + /** Builder for {@link VoicemailEntry}. */ @AutoValue.Builder public abstract static class Builder { @@ -99,6 +102,8 @@ public abstract class VoicemailEntry { public abstract Builder setCallType(int callType); + public abstract Builder setIsRead(int isRead); + public abstract VoicemailEntry build(); } } diff --git a/java/com/android/incallui/AndroidManifest.xml b/java/com/android/incallui/AndroidManifest.xml index b9d481b31..a98cc91d2 100644 --- a/java/com/android/incallui/AndroidManifest.xml +++ b/java/com/android/incallui/AndroidManifest.xml @@ -47,10 +47,13 @@ android:name="android.telephony.hide_voicemail_settings_menu" android:value="true"/> + <!-- Go variants need hardware acceleration for IMS video calls even though it is disabled at + the application level --> <activity android:directBootAware="true" android:excludeFromRecents="true" android:exported="false" + android:hardwareAccelerated="true" android:label="@string/phoneAppLabel" android:launchMode="singleInstance" android:name="com.android.incallui.InCallActivity" diff --git a/java/com/android/incallui/NewReturnToCallController.java b/java/com/android/incallui/NewReturnToCallController.java index ad49d6828..ca60a52c1 100644 --- a/java/com/android/incallui/NewReturnToCallController.java +++ b/java/com/android/incallui/NewReturnToCallController.java @@ -101,7 +101,7 @@ public class NewReturnToCallController implements InCallUiListener, Listener, Au if (showing) { hide(); } else { - if (TelecomUtil.isInManagedCall(context)) { + if (getCall() != null) { show(); } } @@ -157,22 +157,15 @@ public class NewReturnToCallController implements InCallUiListener, Listener, Au @Override public void onDisconnect(DialerCall call) { - if (call.wasParentCall()) { - // It's disconnected after the last child call is disconnected, and we already did everything - // for the last child. - LogUtil.i( - "ReturnToCallController.onDisconnect", "being called for a parent call and do nothing"); - return; - } - if (bubble != null - && bubble.isVisible() - && (!TelecomUtil.isInManagedCall(context) - || CallList.getInstance().getActiveOrBackgroundCall() != null)) { - bubble.showText(context.getText(R.string.incall_call_ended)); - } - // For conference call, we should hideAndReset for the last disconnected child call while the - // parent call is still there. - if (!CallList.getInstance().hasNonParentActiveOrBackgroundCall()) { + LogUtil.enterBlock("ReturnToCallController.onDisconnect"); + if (bubble != null && bubble.isVisible() && (getCall() == null)) { + // Show "Call ended" and hide bubble when there is no outgoing, active or background call + LogUtil.i("ReturnToCallController.onDisconnect", "show call ended and hide bubble"); + // Don't show text if it's Duo upgrade + // It doesn't work for Duo fallback upgrade since we're not considered in call + if (!TelecomUtil.isInCall(context) || CallList.getInstance().getIncomingCall() != null) { + bubble.showText(context.getText(R.string.incall_call_ended)); + } hideAndReset(); } else { startContactInfoSearch(); @@ -197,19 +190,21 @@ public class NewReturnToCallController implements InCallUiListener, Listener, Au } private void startContactInfoSearch() { - DialerCall dialerCall = CallList.getInstance().getIncomingCall(); - if (dialerCall == null) { - dialerCall = CallList.getInstance().getOutgoingCall(); - } - if (dialerCall == null) { - dialerCall = CallList.getInstance().getActiveOrBackgroundCall(); - } + DialerCall dialerCall = getCall(); if (dialerCall != null) { contactInfoCache.findInfo( dialerCall, false /* isIncoming */, new ReturnToCallContactInfoCacheCallback(this)); } } + private DialerCall getCall() { + DialerCall dialerCall = CallList.getInstance().getOutgoingCall(); + if (dialerCall == null) { + dialerCall = CallList.getInstance().getActiveOrBackgroundCall(); + } + return dialerCall; + } + private void onPhotoAvatarReceived(@NonNull Drawable photo) { if (bubble != null) { bubble.updatePhotoAvatar(photo); diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index 812024904..94c79e904 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -20,6 +20,7 @@ import android.Manifest.permission; import android.content.Context; import android.hardware.camera2.CameraCharacteristics; import android.net.Uri; +import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -43,6 +44,7 @@ import android.telecom.VideoProfile; import android.text.TextUtils; import com.android.contacts.common.compat.CallCompat; import com.android.contacts.common.compat.telecom.TelecomManagerCompat; +import com.android.dialer.assisteddialing.ConcreteCreator; import com.android.dialer.assisteddialing.TransformationInfo; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentParser; @@ -1073,19 +1075,50 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa return mLogState.isIncoming; } + /** + * Try and determine if the call used assisted dialing. + * + * <p>We will not be able to verify a call underwent assisted dialing until the Platform + * implmentation is complete in P+. + * + * @return a boolean indicating assisted dialing may have been performed + */ public boolean isAssistedDialed() { if (getIntentExtras() != null) { - return getIntentExtras().getBoolean(TelephonyManagerCompat.IS_ASSISTED_DIALED, false); + // O_MR1 and below uses the existence of USE_ASSISTED_DIALING to indicate assisted dialing + // was used. The Dialer client is responsible for performing assisted dialing before + // placing the outgoing call. + // + // The existence of the assisted dialing extras indicates that assisted dialing took place. + if (getIntentExtras().getBoolean(TelephonyManagerCompat.USE_ASSISTED_DIALING, false) + && getAssistedDialingExtras() != null + && Build.VERSION.SDK_INT <= ConcreteCreator.BUILD_CODE_CEILING) { + return true; + } + } + + // Starting in P+ USE_ASSISTED_DIALING indicates that the client requested the platform + // perform assisted dialing. PROPERTY_ASSISTED_DIALING_USED indicates assisted dialing took + // place. + if (hasProperty(TelephonyManagerCompat.PROPERTY_ASSISTED_DIALING_USED) + && Build.VERSION.SDK_INT > ConcreteCreator.BUILD_CODE_CEILING) { + return true; } return false; } + @Nullable public TransformationInfo getAssistedDialingExtras() { - if (isAssistedDialed()) { - return TransformationInfo.newInstanceFromBundle( - getIntentExtras().getBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS)); + if (getIntentExtras() == null) { + return null; } - return null; + + if (getIntentExtras().getBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS) == null) { + return null; + } + + return TransformationInfo.newInstanceFromBundle( + getIntentExtras().getBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS)); } public LatencyReport getLatencyReport() { diff --git a/java/com/android/incallui/contactgrid/TopRow.java b/java/com/android/incallui/contactgrid/TopRow.java index f8a485519..556b11ba0 100644 --- a/java/com/android/incallui/contactgrid/TopRow.java +++ b/java/com/android/incallui/contactgrid/TopRow.java @@ -175,7 +175,7 @@ public class TopRow { } } - if (state.isAssistedDialed) { + if (state.isAssistedDialed && state.assistedDialingExtras != null) { LogUtil.i("TopRow.getLabelForDialing", "using assisted dialing label."); String countryCode = String.valueOf(state.assistedDialingExtras.transformedNumberCountryCallingCode()); diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java index fa3dd6e01..e7603f041 100644 --- a/java/com/android/incallui/spam/SpamCallListListener.java +++ b/java/com/android/incallui/spam/SpamCallListListener.java @@ -455,6 +455,6 @@ public class SpamCallListListener implements CallList.Listener { } static String getNotificationTagForCall(@NonNull DialerCall call) { - return NOTIFICATION_TAG_PREFIX + call.getNumber(); + return NOTIFICATION_TAG_PREFIX + call.getUniqueCallId(); } } diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java index 3378ad81a..469c15d71 100644 --- a/java/com/android/newbubble/NewBubble.java +++ b/java/com/android/newbubble/NewBubble.java @@ -39,6 +39,7 @@ import android.support.annotation.VisibleForTesting; import android.support.v4.graphics.ColorUtils; import android.support.v4.os.BuildCompat; import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import android.text.TextUtils; import android.transition.TransitionManager; import android.transition.TransitionValues; import android.view.ContextThemeWrapper; @@ -70,6 +71,7 @@ import com.android.newbubble.NewBubbleInfo.Action; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Locale; /** * Creates and manages a bubble window from information in a {@link NewBubbleInfo}. Before creating, @@ -406,6 +408,8 @@ public class NewBubble { hideAfterText = false; + boolean isRtl = + TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; if (windowParams == null) { // Apps targeting O+ must use TYPE_APPLICATION_OVERLAY, which is not available prior to O. @SuppressWarnings("deprecation") @@ -423,7 +427,7 @@ public class NewBubble { | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.TRANSLUCENT); - windowParams.gravity = Gravity.TOP | Gravity.LEFT; + windowParams.gravity = Gravity.TOP | (isRtl ? Gravity.RIGHT : Gravity.LEFT); windowParams.x = leftBoundary; windowParams.y = currentInfo.getStartingYPosition(); windowParams.height = LayoutParams.WRAP_CONTENT; @@ -441,6 +445,9 @@ public class NewBubble { viewHolder.getPrimaryButton().setScaleY(0); viewHolder.getPrimaryAvatar().setAlpha(0f); viewHolder.getPrimaryIcon().setAlpha(0f); + if (isRtl) { + onLeftRightSwitch(true); + } } viewHolder.setChildClickable(true); @@ -795,7 +802,13 @@ public class NewBubble { } private void configureButton(Action action, NewCheckableButton button) { - button.setCompoundDrawablesWithIntrinsicBounds(action.getIconDrawable(), null, null, null); + boolean isRtl = + TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + if (isRtl) { + button.setCompoundDrawablesWithIntrinsicBounds(null, null, action.getIconDrawable(), null); + } else { + button.setCompoundDrawablesWithIntrinsicBounds(action.getIconDrawable(), null, null, null); + } button.setChecked(action.isChecked()); button.setEnabled(action.isEnabled()); button.setText(action.getName()); 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 f83b75395..f6ce26dd1 100644 --- a/java/com/android/newbubble/res/layout/new_bubble_base.xml +++ b/java/com/android/newbubble/res/layout/new_bubble_base.xml @@ -21,6 +21,7 @@ android:layout_height="wrap_content" android:clipChildren="true" android:clipToPadding="false" + android:layoutDirection="ltr" tools:theme="@style/Theme.AppCompat"> <RelativeLayout android:id="@+id/bubble_primary_container" |