diff options
56 files changed, 1083 insertions, 395 deletions
diff --git a/assets/quantum/res/drawable/quantum_ic_delete_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_delete_vd_theme_24.xml new file mode 100644 index 000000000..900b559e3 --- /dev/null +++ b/assets/quantum/res/drawable/quantum_ic_delete_vd_theme_24.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="@android:color/white" + android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> +</vector>
\ No newline at end of file diff --git a/assets/quantum/res/drawable/quantum_ic_play_arrow_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_play_arrow_vd_theme_24.xml new file mode 100644 index 000000000..e17e625a5 --- /dev/null +++ b/assets/quantum/res/drawable/quantum_ic_play_arrow_vd_theme_24.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M8,5v14l11,-7z"/> +</vector>
\ No newline at end of file diff --git a/assets/quantum/res/drawable/quantum_ic_volume_up_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_volume_up_vd_theme_24.xml new file mode 100644 index 000000000..ac14beced --- /dev/null +++ b/assets/quantum/res/drawable/quantum_ic_volume_up_vd_theme_24.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="@android:color/white" + android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/> +</vector>
\ No newline at end of file diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml index ad771496d..12abaa6a9 100644 --- a/java/com/android/dialer/app/AndroidManifest.xml +++ b/java/com/android/dialer/app/AndroidManifest.xml @@ -140,5 +140,6 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/> </provider> + <meta-data android:name="supports_per_number_preferred_account" android:value="true" /> </application> </manifest> diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 269e598e1..d9a63fab2 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -96,6 +96,7 @@ import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.UiUtil; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.constants.ActivityRequestCodes; @@ -178,6 +179,7 @@ public class DialtactsActivity extends TransactionSafeActivity private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; private static final String KEY_IN_NEW_SEARCH_UI = "in_new_search_ui"; private static final String KEY_SEARCH_QUERY = "search_query"; + private static final String KEY_DIALPAD_QUERY = "dialpad_query"; private static final String KEY_FIRST_LAUNCH = "first_launch"; private static final String KEY_WAS_CONFIGURATION_CHANGE = "was_configuration_change"; private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown"; @@ -439,6 +441,7 @@ public class DialtactsActivity extends TransactionSafeActivity .commit(); } else { mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); + mDialpadQuery = savedInstanceState.getString(KEY_DIALPAD_QUERY); mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); mInNewSearch = savedInstanceState.getBoolean(KEY_IN_NEW_SEARCH_UI); @@ -654,6 +657,7 @@ public class DialtactsActivity extends TransactionSafeActivity LogUtil.enterBlock("DialtactsActivity.onSaveInstanceState"); super.onSaveInstanceState(outState); outState.putString(KEY_SEARCH_QUERY, mSearchQuery); + outState.putString(KEY_DIALPAD_QUERY, mDialpadQuery); outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); outState.putBoolean(KEY_IN_NEW_SEARCH_UI, mInNewSearch); @@ -1425,6 +1429,21 @@ public class DialtactsActivity extends TransactionSafeActivity } @Override + public boolean onSearchListTouch(MotionEvent event) { + if (mIsDialpadShown) { + PerformanceReport.recordClick(UiAction.Type.CLOSE_DIALPAD); + hideDialpadFragment(true, false); + if (TextUtils.isEmpty(mDialpadQuery)) { + exitSearchUi(); + } + return true; + } else { + UiUtil.hideKeyboardFrom(this, mSearchEditTextLayout); + } + return false; + } + + @Override public void onListFragmentScrollStateChange(int scrollState) { PerformanceReport.recordScrollStateChange(scrollState); if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { @@ -1659,17 +1678,6 @@ public class DialtactsActivity extends TransactionSafeActivity } @Override - public boolean onSearchListTouch(MotionEvent event) { - if (mIsDialpadShown) { - hideDialpadFragment(true, false); - if (TextUtils.isEmpty(mDialpadQuery)) { - exitSearchUi(); - } - } - return false; - } - - @Override public void onCallPlaced() { if (mIsDialpadShown) { hideDialpadFragment(false, true); diff --git a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java index 584f07fe3..a0bbfa0f1 100644 --- a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java @@ -40,6 +40,7 @@ import com.android.dialer.notification.NotificationChannelManager; /** Shows a notification in the status bar for legacy vociemail. */ @TargetApi(VERSION_CODES.O) public final class LegacyVoicemailNotifier { + private static final String NOTIFICATION_TAG_PREFIX = "LegacyVoicemail_"; private static final String NOTIFICATION_TAG = "LegacyVoicemail"; private static final int NOTIFICATION_ID = 1; @@ -77,7 +78,8 @@ public final class LegacyVoicemailNotifier { callVoicemailIntent, voicemailSettingsIntent, isRefresh); - DialerNotificationManager.notify(context, NOTIFICATION_TAG, NOTIFICATION_ID, notification); + DialerNotificationManager.notify( + context, getNotificationTag(context, handle), NOTIFICATION_ID, notification); } @NonNull @@ -146,10 +148,22 @@ public final class LegacyVoicemailNotifier { } } - public static void cancelNotification(@NonNull Context context) { + public static void cancelNotification( + @NonNull Context context, @NonNull PhoneAccountHandle phoneAccountHandle) { LogUtil.enterBlock("LegacyVoicemailNotifier.cancelNotification"); Assert.checkArgument(BuildCompat.isAtLeastO()); - DialerNotificationManager.cancel(context, NOTIFICATION_TAG, NOTIFICATION_ID); + Assert.isNotNull(phoneAccountHandle); + DialerNotificationManager.cancel( + context, getNotificationTag(context, phoneAccountHandle), NOTIFICATION_ID); + } + + @NonNull + private static String getNotificationTag( + @NonNull Context context, @NonNull PhoneAccountHandle phoneAccountHandle) { + if (context.getSystemService(TelephonyManager.class).getPhoneCount() <= 1) { + return NOTIFICATION_TAG; + } + return NOTIFICATION_TAG_PREFIX + phoneAccountHandle.getId(); } private LegacyVoicemailNotifier() {} diff --git a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java index 3ce837b8c..fee845469 100644 --- a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java +++ b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java @@ -96,7 +96,7 @@ public class LegacyVoicemailNotificationReceiver extends BroadcastReceiver { if (count == 0) { LogUtil.i("LegacyVoicemailNotificationReceiver.onReceive", "clearing notification"); - LegacyVoicemailNotifier.cancelNotification(context); + LegacyVoicemailNotifier.cancelNotification(context, phoneAccountHandle); return; } diff --git a/java/com/android/dialer/calllog/CallLogModule.java b/java/com/android/dialer/calllog/CallLogModule.java index 2f2f16d5b..9926cebb9 100644 --- a/java/com/android/dialer/calllog/CallLogModule.java +++ b/java/com/android/dialer/calllog/CallLogModule.java @@ -19,12 +19,11 @@ package com.android.dialer.calllog; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.DataSources; import com.android.dialer.calllog.datasources.contacts.ContactsDataSource; +import com.android.dialer.calllog.datasources.phonelookup.PhoneLookupDataSource; import com.android.dialer.calllog.datasources.systemcalllog.SystemCallLogDataSource; +import com.google.common.collect.ImmutableList; import dagger.Module; import dagger.Provides; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; /** Dagger module which satisfies call log dependencies. */ @Module @@ -32,10 +31,12 @@ public abstract class CallLogModule { @Provides static DataSources provideCallLogDataSources( - SystemCallLogDataSource systemCallLogDataSource, ContactsDataSource contactsDataSource) { + SystemCallLogDataSource systemCallLogDataSource, + ContactsDataSource contactsDataSource, + PhoneLookupDataSource phoneLookupDataSource) { // System call log must be first, see getDataSourcesExcludingSystemCallLog below. - List<CallLogDataSource> allDataSources = - Collections.unmodifiableList(Arrays.asList(systemCallLogDataSource, contactsDataSource)); + ImmutableList<CallLogDataSource> allDataSources = + ImmutableList.of(systemCallLogDataSource, contactsDataSource, phoneLookupDataSource); return new DataSources() { @Override public SystemCallLogDataSource getSystemCallLogDataSource() { @@ -43,12 +44,12 @@ public abstract class CallLogModule { } @Override - public List<CallLogDataSource> getDataSourcesIncludingSystemCallLog() { + public ImmutableList<CallLogDataSource> getDataSourcesIncludingSystemCallLog() { return allDataSources; } @Override - public List<CallLogDataSource> getDataSourcesExcludingSystemCallLog() { + public ImmutableList<CallLogDataSource> getDataSourcesExcludingSystemCallLog() { return allDataSources.subList(1, allDataSources.size()); } }; diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java index 3062710d4..0d8e8ceeb 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java @@ -89,6 +89,13 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { + AnnotatedCallLog.CALL_TYPE + ");"; + private static final String CREATE_INDEX_ON_NUMBER_SQL = + "create index number_index on " + + AnnotatedCallLog.TABLE + + " (" + + AnnotatedCallLog.NUMBER + + ");"; + @Override public void onCreate(SQLiteDatabase db) { LogUtil.enterBlock("AnnotatedCallLogDatabaseHelper.onCreate"); @@ -96,6 +103,7 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { db.execSQL(CREATE_TABLE_SQL); db.execSQL(String.format(Locale.US, CREATE_TRIGGER_SQL, maxRows, maxRows)); db.execSQL(CREATE_INDEX_ON_CALL_TYPE_SQL); + db.execSQL(CREATE_INDEX_ON_NUMBER_SQL); // TODO(zachh): Consider logging impression. LogUtil.i( "AnnotatedCallLogDatabaseHelper.onCreate", diff --git a/java/com/android/dialer/calllog/datasources/DataSources.java b/java/com/android/dialer/calllog/datasources/DataSources.java index 911ca3fa3..113a9f7b1 100644 --- a/java/com/android/dialer/calllog/datasources/DataSources.java +++ b/java/com/android/dialer/calllog/datasources/DataSources.java @@ -17,14 +17,14 @@ package com.android.dialer.calllog.datasources; import com.android.dialer.calllog.datasources.systemcalllog.SystemCallLogDataSource; -import java.util.List; +import com.google.common.collect.ImmutableList; /** Immutable lists of data sources used to populate the annotated call log. */ public interface DataSources { SystemCallLogDataSource getSystemCallLogDataSource(); - List<CallLogDataSource> getDataSourcesIncludingSystemCallLog(); + ImmutableList<CallLogDataSource> getDataSourcesIncludingSystemCallLog(); - List<CallLogDataSource> getDataSourcesExcludingSystemCallLog(); + ImmutableList<CallLogDataSource> getDataSourcesExcludingSystemCallLog(); } diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java new file mode 100644 index 000000000..90298a104 --- /dev/null +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -0,0 +1,139 @@ +/* + * 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.calllog.datasources.phonelookup; + +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.support.annotation.MainThread; +import android.support.annotation.WorkerThread; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; +import com.android.dialer.calllog.datasources.CallLogDataSource; +import com.android.dialer.calllog.datasources.CallLogMutations; +import com.android.dialer.common.LogUtil; +import com.android.dialer.phonelookup.PhoneLookup; +import com.android.dialer.storage.Unencrypted; +import com.google.common.collect.ImmutableSet; +import com.google.protobuf.InvalidProtocolBufferException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import javax.inject.Inject; + +/** + * Responsible for maintaining the columns in the annotated call log which are derived from phone + * numbers. + */ +public final class PhoneLookupDataSource implements CallLogDataSource { + private static final String PREF_LAST_TIMESTAMP_PROCESSED = "phoneLookupLastTimestampProcessed"; + + private final PhoneLookup phoneLookup; + private final SharedPreferences sharedPreferences; + + @Inject + PhoneLookupDataSource(PhoneLookup phoneLookup, @Unencrypted SharedPreferences sharedPreferences) { + this.phoneLookup = phoneLookup; + this.sharedPreferences = sharedPreferences; + } + + @WorkerThread + @Override + public boolean isDirty(Context appContext) { + ImmutableSet<DialerPhoneNumber> uniqueDialerPhoneNumbers = + queryDistinctDialerPhoneNumbersFromAnnotatedCallLog(appContext); + + long lastTimestampProcessedSharedPrefValue = + sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L); + try { + // TODO(zachh): Would be good to rework call log architecture to properly use futures. + // TODO(zachh): Consider how individual lookups should behave wrt timeouts/exceptions and + // handle appropriately here. + return phoneLookup + .isDirty(uniqueDialerPhoneNumbers, lastTimestampProcessedSharedPrefValue) + .get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + @WorkerThread + @Override + public void fill(Context appContext, CallLogMutations mutations) { + // TODO(zachh): Implementation. + } + + @WorkerThread + @Override + public void onSuccessfulFill(Context appContext) { + // TODO(zachh): Implementation. + } + + @WorkerThread + @Override + public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) { + // TODO(zachh): Implementation. + return new ContentValues(); + } + + @MainThread + @Override + public void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + // No content observers required for this data source. + } + + private static ImmutableSet<DialerPhoneNumber> + queryDistinctDialerPhoneNumbersFromAnnotatedCallLog(Context appContext) { + ImmutableSet.Builder<DialerPhoneNumber> numbers = ImmutableSet.builder(); + + try (Cursor cursor = + appContext + .getContentResolver() + .query( + AnnotatedCallLog.DISTINCT_NUMBERS_CONTENT_URI, + new String[] {AnnotatedCallLog.NUMBER}, + null, + null, + null)) { + + if (cursor == null) { + LogUtil.e( + "PhoneLookupDataSource.queryDistinctDialerPhoneNumbersFromAnnotatedCallLog", + "null cursor"); + return numbers.build(); + } + + if (cursor.moveToFirst()) { + int numberColumn = cursor.getColumnIndexOrThrow(AnnotatedCallLog.NUMBER); + do { + byte[] blob = cursor.getBlob(numberColumn); + if (blob == null) { + // Not all [incoming] calls have associated phone numbers. + continue; + } + try { + numbers.add(DialerPhoneNumber.parseFrom(blob)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException(e); + } + } while (cursor.moveToNext()); + } + } + return numbers.build(); + } +} diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java index e85b57e33..8d11bcbe3 100644 --- a/java/com/android/dialer/dialpadview/DialpadFragment.java +++ b/java/com/android/dialer/dialpadview/DialpadFragment.java @@ -643,7 +643,7 @@ public class DialpadFragment extends Fragment iconId = R.drawable.ic_wifi_calling; } mFloatingActionButtonController.changeIcon( - res.getDrawable(iconId, null), res.getString(R.string.description_dial_button)); + iconId, res.getString(R.string.description_dial_button)); mDialpadQueryListener = FragmentUtils.getParentUnsafe(this, OnDialpadQueryChangedListener.class); diff --git a/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml b/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml index 2f62e1407..2e6b6eca0 100644 --- a/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml +++ b/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml @@ -66,6 +66,7 @@ android:layout_centerHorizontal="true" android:contentDescription="@string/description_dial_button" android:src="@drawable/quantum_ic_call_vd_theme_24" + android:tint="#ffffff" app:backgroundTint="@color/dialpad_fab_green" app:colorControlNormal="#ffffff" app:elevation="@dimen/floating_action_button_translation_z"/> diff --git a/java/com/android/dialer/phonelookup/testing/FakePhoneLookup.java b/java/com/android/dialer/phonelookup/testing/FakePhoneLookup.java new file mode 100644 index 000000000..853116f9a --- /dev/null +++ b/java/com/android/dialer/phonelookup/testing/FakePhoneLookup.java @@ -0,0 +1,83 @@ +/* + * 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.testing; + +import android.support.annotation.NonNull; +import android.telecom.Call; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.phonelookup.PhoneLookup; +import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +/** Fake implementation of {@link PhoneLookup} used for unit tests. */ +@AutoValue +public abstract class FakePhoneLookup implements PhoneLookup { + + abstract PhoneLookupInfo lookupResult(); + + abstract ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> bulkUpdateResult(); + + abstract boolean isDirtyResult(); + + public static Builder builder() { + return new AutoValue_FakePhoneLookup.Builder() + .setLookupResult(PhoneLookupInfo.getDefaultInstance()) + .setBulkUpdateResult(ImmutableMap.of()) + .setIsDirtyResult(false); + } + + /** Builder. */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setLookupResult(PhoneLookupInfo phoneLookupInfo); + + public abstract Builder setBulkUpdateResult( + ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> map); + + public abstract Builder setIsDirtyResult(boolean isDirty); + + public abstract FakePhoneLookup build(); + } + + @Override + public ListenableFuture<PhoneLookupInfo> lookup(@NonNull Call call) { + SettableFuture<PhoneLookupInfo> future = SettableFuture.create(); + future.set(lookupResult()); + return future; + } + + @Override + public ListenableFuture<Boolean> isDirty( + ImmutableSet<DialerPhoneNumber> phoneNumbers, long lastModified) { + SettableFuture<Boolean> future = SettableFuture.create(); + future.set(isDirtyResult()); + return future; + } + + @Override + public ListenableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> bulkUpdate( + ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap, long lastModified) { + SettableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> future = + SettableFuture.create(); + future.set(bulkUpdateResult()); + return future; + } +} diff --git a/java/com/android/dialer/precall/PreCallAction.java b/java/com/android/dialer/precall/PreCallAction.java index 9434694a4..9ddc6f205 100644 --- a/java/com/android/dialer/precall/PreCallAction.java +++ b/java/com/android/dialer/precall/PreCallAction.java @@ -16,6 +16,7 @@ package com.android.dialer.precall; +import android.content.Context; import android.support.annotation.MainThread; import com.android.dialer.callintent.CallIntentBuilder; @@ -28,12 +29,29 @@ import com.android.dialer.callintent.CallIntentBuilder; public interface PreCallAction { /** + * Whether the action requires an activity to operate. This method is called on all actions before + * {@link #runWithUi(PreCallCoordinator)} is called. If {@link true} is returned, {@link + * #runWithUi(PreCallCoordinator)} will be guaranteed to be called on the execution phase. + * Otherwise {@link #runWithoutUi(Context, CallIntentBuilder)} may be called instead and the + * action will not be able to show UI, perform async task, or abort the call. This method should + * not make any state changes. + */ + @MainThread + boolean requiresUi(Context context, CallIntentBuilder builder); + + /** + * Called when all actions returned {@code false} for {@link #requiresUi(Context, + * CallIntentBuilder)}. + */ + void runWithoutUi(Context context, CallIntentBuilder builder); + + /** * Runs the action. Should block on the main thread until the action is finished. If the action is * not instantaneous, {@link PreCallCoordinator#startPendingAction()} should be called to release * the thread and continue later. */ @MainThread - void run(PreCallCoordinator coordinator); + void runWithUi(PreCallCoordinator coordinator); /** * Called when the UI is being paused when a {@link PreCallCoordinator.PendingAction} is started, diff --git a/java/com/android/dialer/precall/PreCallCoordinator.java b/java/com/android/dialer/precall/PreCallCoordinator.java index 40b909a51..cb3221afd 100644 --- a/java/com/android/dialer/precall/PreCallCoordinator.java +++ b/java/com/android/dialer/precall/PreCallCoordinator.java @@ -33,10 +33,7 @@ public interface PreCallCoordinator { @NonNull CallIntentBuilder getBuilder(); - /** - * @return the activity to attach the UI to. Returns {@link null} if the coordinator is running on - * headless mode. TODO(twyen): implement headless mode. - */ + /** @return the activity to attach the UI to. */ @NonNull Activity getActivity(); @@ -60,10 +57,10 @@ public interface PreCallCoordinator { * Called by the current running {@link PreCallAction} to release the main thread and resume * pre-call later. * - * @return a {@link PendingAction} which {@link PendingAction#finish(boolean)} should be called to - * resume pre-call. For example the action shows a dialog to the user, startPendingAction() - * should be called as the action will not be finished immediately. When the dialog is - * completed, {@code finish()} is then called to continue the next step. + * @return a {@link PendingAction} which {@link PendingAction#finish()} should be called to resume + * pre-call. For example the action shows a dialog to the user, startPendingAction() should be + * called as the action will not be finished immediately. When the dialog is completed, {@code + * finish()} is then called to continue the next step. */ @MainThread @NonNull diff --git a/java/com/android/dialer/precall/impl/AssistedDialAction.java b/java/com/android/dialer/precall/impl/AssistedDialAction.java index edf97cce3..c4f61d2dd 100644 --- a/java/com/android/dialer/precall/impl/AssistedDialAction.java +++ b/java/com/android/dialer/precall/impl/AssistedDialAction.java @@ -17,6 +17,7 @@ package com.android.dialer.precall.impl; import android.annotation.TargetApi; +import android.content.Context; import android.os.Build; import android.os.Bundle; import android.telecom.PhoneAccount; @@ -35,18 +36,21 @@ import java.util.Optional; /** Rewrites the call URI with country code. TODO(erfanian): use phone account for multi SIM */ public class AssistedDialAction implements PreCallAction { + @Override + public boolean requiresUi(Context context, CallIntentBuilder builder) { + return false; + } + @SuppressWarnings("AndroidApiChecker") // Use of optional @TargetApi(Build.VERSION_CODES.N) @Override - public void run(PreCallCoordinator coordinator) { - CallIntentBuilder builder = coordinator.getBuilder(); + public void runWithoutUi(Context context, CallIntentBuilder builder) { if (!builder.isAssistedDialAllowed()) { return; } AssistedDialingMediator assistedDialingMediator = ConcreteCreator.createNewAssistedDialingMediator( - coordinator.getActivity().getSystemService(TelephonyManager.class), - coordinator.getActivity()); + context.getSystemService(TelephonyManager.class), context); if (!assistedDialingMediator.isPlatformEligible()) { return; } @@ -69,5 +73,10 @@ public class AssistedDialAction implements PreCallAction { } @Override + public void runWithUi(PreCallCoordinator coordinator) { + runWithoutUi(coordinator.getActivity(), coordinator.getBuilder()); + } + + @Override public void onDiscard() {} } diff --git a/java/com/android/dialer/precall/impl/CallingAccountSelector.java b/java/com/android/dialer/precall/impl/CallingAccountSelector.java index ca74bef08..d763c7a5f 100644 --- a/java/com/android/dialer/precall/impl/CallingAccountSelector.java +++ b/java/com/android/dialer/precall/impl/CallingAccountSelector.java @@ -59,16 +59,27 @@ public class CallingAccountSelector implements PreCallAction { private boolean isDiscarding; @Override - @MainThread - public void run(PreCallCoordinator coordinator) { - CallIntentBuilder builder = coordinator.getBuilder(); + public boolean requiresUi(Context context, CallIntentBuilder builder) { if (builder.getPhoneAccountHandle() != null) { - return; + return false; } - Activity activity = coordinator.getActivity(); - TelecomManager telecomManager = activity.getSystemService(TelecomManager.class); + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); List<PhoneAccountHandle> accounts = telecomManager.getCallCapablePhoneAccounts(); if (accounts.size() <= 1) { + return false; + } + return true; + } + + @Override + public void runWithoutUi(Context context, CallIntentBuilder builder) { + // do nothing. + } + + @Override + public void runWithUi(PreCallCoordinator coordinator) { + CallIntentBuilder builder = coordinator.getBuilder(); + if (!requiresUi(coordinator.getActivity(), builder)) { return; } switch (builder.getUri().getScheme()) { diff --git a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java index 6302a2395..485823e9a 100644 --- a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java +++ b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java @@ -93,7 +93,7 @@ public class PreCallCoordinatorImpl implements PreCallCoordinator { } LogUtil.i("PreCallCoordinatorImpl.runNextAction", "running " + actions.get(currentActionIndex)); currentAction = actions.get(currentActionIndex); - actions.get(currentActionIndex).run(this); + actions.get(currentActionIndex).runWithUi(this); if (pendingAction == null) { onActionFinished(); } diff --git a/java/com/android/dialer/precall/impl/PreCallImpl.java b/java/com/android/dialer/precall/impl/PreCallImpl.java index 21c5dc9e2..1c78bb8b0 100644 --- a/java/com/android/dialer/precall/impl/PreCallImpl.java +++ b/java/com/android/dialer/precall/impl/PreCallImpl.java @@ -20,8 +20,10 @@ import android.content.Context; import android.content.Intent; import android.support.annotation.NonNull; import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.common.LogUtil; import com.android.dialer.precall.PreCall; import com.android.dialer.precall.PreCallAction; +import com.android.dialer.precall.PreCallComponent; import com.android.dialer.precall.PreCallCoordinator; import com.google.common.collect.ImmutableList; import javax.inject.Inject; @@ -40,8 +42,26 @@ public class PreCallImpl implements PreCall { @NonNull @Override public Intent buildIntent(Context context, CallIntentBuilder builder) { + if (!requiresUi(context, builder)) { + LogUtil.i("PreCallImpl.buildIntent", "No UI requested, running pre-call directly"); + for (PreCallAction action : PreCallComponent.get(context).getPreCall().getActions()) { + action.runWithoutUi(context, builder); + } + return builder.build(); + } + LogUtil.i("PreCallImpl.buildIntent", "building intent to start activity"); Intent intent = new Intent(context, PreCallActivity.class); intent.putExtra(PreCallCoordinator.EXTRA_CALL_INTENT_BUILDER, builder); return intent; } + + private boolean requiresUi(Context context, CallIntentBuilder builder) { + for (PreCallAction action : PreCallComponent.get(context).getPreCall().getActions()) { + if (action.requiresUi(context, builder)) { + LogUtil.i("PreCallImpl.requiresUi", action + " requested UI"); + return true; + } + } + return false; + } } diff --git a/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java b/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java index 322baa2ef..1b10765a5 100644 --- a/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java +++ b/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java @@ -131,6 +131,7 @@ public class PreferredSimFallbackProvider extends ContentProvider { == -1) { throw new IllegalStateException("update failed"); } + getContext().getContentResolver().notifyChange(PreferredSimFallbackContract.CONTENT_URI, null); return 1; } diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java index 1ecb486d2..c4ff27545 100644 --- a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java +++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java @@ -137,7 +137,7 @@ public class QueryFilteringUtil { * @param context The context * @return The original string with characters replaced with T9 representations. */ - static String getT9Representation(String s, Context context) { + public static String getT9Representation(String s, Context context) { StringBuilder builder = new StringBuilder(s.length()); for (char c : s.toLowerCase().toCharArray()) { builder.append(getDigit(c, context)); diff --git a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java index 166902b2b..dc16f9dd0 100644 --- a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java +++ b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java @@ -39,6 +39,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Set; /** @@ -52,6 +53,7 @@ final class ContactFilterCursor implements Cursor { private final Cursor cursor; // List of cursor ids that are valid for displaying after filtering. private final List<Integer> queryFilteredPositions = new ArrayList<>(); + private final ContactTernarySearchTree contactTree; private int currentPosition = 0; @@ -77,6 +79,7 @@ final class ContactFilterCursor implements Cursor { */ ContactFilterCursor(Cursor cursor, @Nullable String query, Context context) { this.cursor = createCursor(cursor); + contactTree = buildContactSearchTree(context, this.cursor); filter(query, context); } @@ -225,6 +228,69 @@ final class ContactFilterCursor implements Cursor { } /** + * Returns a ternary search trie based on the contact at the cursor's current position with the + * following terms inserted: + * + * <ul> + * <li>Contact's whole display name, company name and nickname. + * <li>The T9 representations of those values + * <li>The T9 initials of those values + * <li>All possible substrings a contact's phone number + * </ul> + */ + private static ContactTernarySearchTree buildContactSearchTree(Context context, Cursor cursor) { + ContactTernarySearchTree tree = new ContactTernarySearchTree(); + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + int position = cursor.getPosition(); + Set<String> queryMatches = new ArraySet<>(); + addMatches(context, queryMatches, cursor.getString(Projections.DISPLAY_NAME)); + addMatches(context, queryMatches, cursor.getString(Projections.COMPANY_NAME)); + addMatches(context, queryMatches, cursor.getString(Projections.NICKNAME)); + for (String query : queryMatches) { + tree.put(query, position); + } + String number = QueryFilteringUtil.digitsOnly(cursor.getString(Projections.PHONE_NUMBER)); + Set<String> numberSubstrings = new ArraySet<>(); + numberSubstrings.add(number); + for (int start = 0; start < number.length(); start++) { + numberSubstrings.add(number.substring(start, number.length())); + } + for (String substring : numberSubstrings) { + tree.put(substring, position); + } + } + return tree; + } + + /** + * Returns a set containing: + * + * <ul> + * <li>The white space divided parts of phrase + * <li>The T9 representation of the white space divided parts of phrase + * <li>The T9 representation of the initials (i.e. first character of each part) of phrase + * </ul> + */ + private static void addMatches(Context context, Set<String> existingMatches, String phrase) { + if (TextUtils.isEmpty(phrase)) { + return; + } + String initials = ""; + phrase = phrase.toLowerCase(Locale.getDefault()); + existingMatches.add(phrase); + for (String name : phrase.split("\\s")) { + if (TextUtils.isEmpty(name)) { + continue; + } + existingMatches.add(name); + existingMatches.add(QueryFilteringUtil.getT9Representation(name, context)); + initials += name.charAt(0); + } + existingMatches.add(QueryFilteringUtil.getT9Representation(initials, context)); + } + + /** * Filters out contacts that do not match the query. * * <p>The query can have at least 1 of 3 forms: @@ -249,24 +315,14 @@ final class ContactFilterCursor implements Cursor { query = ""; } queryFilteredPositions.clear(); - query = query.toLowerCase(); - cursor.moveToPosition(-1); - - while (cursor.moveToNext()) { - int position = cursor.getPosition(); - String number = cursor.getString(Projections.PHONE_NUMBER); - String name = cursor.getString(Projections.DISPLAY_NAME); - String companyName = cursor.getString(Projections.COMPANY_NAME); - String nickName = cursor.getString(Projections.NICKNAME); - if (TextUtils.isEmpty(query) - || QueryFilteringUtil.nameMatchesT9Query(query, name, context) - || QueryFilteringUtil.numberMatchesNumberQuery(query, number) - || QueryFilteringUtil.nameContainsQuery(query, name) - || QueryFilteringUtil.nameContainsQuery(query, companyName) - || QueryFilteringUtil.nameContainsQuery(query, nickName)) { - queryFilteredPositions.add(position); + if (TextUtils.isEmpty(query)) { + for (int i = 0; i < cursor.getCount(); i++) { + queryFilteredPositions.add(i); } + } else { + queryFilteredPositions.addAll(contactTree.get(query.toLowerCase(Locale.getDefault()))); } + Collections.sort(queryFilteredPositions); currentPosition = 0; cursor.moveToFirst(); } diff --git a/java/com/android/dialer/searchfragment/cp2/ContactTernarySearchTree.java b/java/com/android/dialer/searchfragment/cp2/ContactTernarySearchTree.java new file mode 100644 index 000000000..88738e281 --- /dev/null +++ b/java/com/android/dialer/searchfragment/cp2/ContactTernarySearchTree.java @@ -0,0 +1,97 @@ +/* + * 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.searchfragment.cp2; + +import android.support.v4.util.ArraySet; +import android.text.TextUtils; +import java.util.Set; + +/** Ternary Search Tree for searching a list of contacts. */ +public class ContactTernarySearchTree { + + private Node root; + + /** + * Add {@code value} to all middle and end {@link Node#values} that correspond to {@code key}. + * + * <p>For example, if {@code key} were "FOO", {@code value} would be added to nodes "F", "O" and + * "O". But if the traversal required visiting {@link Node#left} or {@link Node#right}, {@code + * value} wouldn't be added to those nodes. + */ + public void put(String key, int value) { + if (TextUtils.isEmpty(key)) { + return; + } + root = put(root, key, value, 0); + } + + private Node put(Node node, String key, int value, int position) { + char c = key.charAt(position); + if (node == null) { + node = new Node(); + node.key = c; + } + if (c < node.key) { + node.left = put(node.left, key, value, position); + } else if (c > node.key) { + node.right = put(node.right, key, value, position); + } else if (position < key.length() - 1) { + node.values.add(value); + node.mid = put(node.mid, key, value, position + 1); + } else { + node.values.add(value); + } + return node; + } + + /** Returns true if {@code key} is contained in the trie. */ + public boolean contains(String key) { + return !get(key).isEmpty(); + } + + /** Return value stored at Node (in this case, a set of integers). */ + public Set<Integer> get(String key) { + Node x = get(root, key, 0); + return x == null ? new ArraySet<>() : x.values; + } + + private Node get(Node node, String key, int position) { + if (node == null) { + return null; + } + char c = key.charAt(position); + if (c < node.key) { + return get(node.left, key, position); + } else if (c > node.key) { + return get(node.right, key, position); + } else if (position < key.length() - 1) { + return get(node.mid, key, position + 1); + } else { + return node; + } + } + + /** Node in ternary search trie. Children are denoted as left, middle and right nodes. */ + private static class Node { + private char key; + private final Set<Integer> values = new ArraySet<>(); + + private Node left; + private Node mid; + private Node right; + } +} diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index 8306d37a6..93263ceb2 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -32,6 +32,7 @@ import android.support.annotation.VisibleForTesting; import android.support.v13.app.FragmentCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -444,7 +445,9 @@ public final class NewSearchFragment extends Fragment * the list of supported actions, see {@link SearchActionViewHolder.Action}. */ private List<Integer> getActions() { - if (TextUtils.isEmpty(query) || query.length() == 1 || isRegularSearch()) { + boolean nonDialableQueryInRegularSearch = + isRegularSearch() && !PhoneNumberUtils.isGlobalPhoneNumber(query); + if (TextUtils.isEmpty(query) || query.length() == 1 || nonDialableQueryInRegularSearch) { return Collections.emptyList(); } diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java index 3bf9b4666..c79d9013d 100644 --- a/java/com/android/dialer/telecom/TelecomUtil.java +++ b/java/com/android/dialer/telecom/TelecomUtil.java @@ -33,9 +33,12 @@ import android.support.v4.content.ContextCompat; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.text.TextUtils; import android.util.Pair; import com.android.dialer.common.LogUtil; +import com.google.common.base.Optional; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -146,6 +149,24 @@ public abstract class TelecomUtil { } /** + * @return the {@link SubscriptionInfo} of the SIM if {@code phoneAccountHandle} corresponds to a + * valid SIM. Absent otherwise. + */ + public static Optional<SubscriptionInfo> getSubscriptionInfo( + @NonNull Context context, @NonNull PhoneAccountHandle phoneAccountHandle) { + if (TextUtils.isEmpty(phoneAccountHandle.getId())) { + return Optional.absent(); + } + SubscriptionManager subscriptionManager = context.getSystemService(SubscriptionManager.class); + for (SubscriptionInfo info : subscriptionManager.getActiveSubscriptionInfoList()) { + if (phoneAccountHandle.getId().startsWith(info.getIccId())) { + return Optional.of(info); + } + } + return Optional.absent(); + } + + /** * Returns true if there is a dialer managed call in progress. Self managed calls starting from O * are not included. */ diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java index 4629ce277..d5db60846 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java @@ -30,7 +30,7 @@ import android.support.v4.util.Pair; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; -import android.widget.Button; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import com.android.dialer.common.Assert; @@ -45,9 +45,10 @@ import com.android.dialer.voicemail.model.VoicemailEntry; */ public class NewVoicemailMediaPlayerView extends LinearLayout { - private Button playButton; - private Button speakerButton; - private Button deleteButton; + private ImageButton playButton; + private ImageButton speakerButton; + private ImageButton phoneButton; + private ImageButton deleteButton; private TextView totalDurationView; private Uri voicemailUri; private FragmentManager fragmentManager; @@ -72,6 +73,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { private void initializeMediaPlayerButtonsAndViews() { playButton = findViewById(R.id.playButton); speakerButton = findViewById(R.id.speakerButton); + phoneButton = findViewById(R.id.phoneButton); deleteButton = findViewById(R.id.deleteButton); totalDurationView = findViewById(R.id.playback_seek_total_duration); } @@ -79,6 +81,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { private void setupListenersForMediaPlayerButtons() { playButton.setOnClickListener(playButtonListener); speakerButton.setOnClickListener(speakerButtonListener); + phoneButton.setOnClickListener(phoneButtonListener); deleteButton.setOnClickListener(deleteButtonListener); } @@ -164,6 +167,17 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { } }; + private final View.OnClickListener phoneButtonListener = + new View.OnClickListener() { + @Override + public void onClick(View view) { + LogUtil.i( + "NewVoicemailMediaPlayer.phoneButtonListener", + "speaker request for voicemailUri: %s", + voicemailUri.toString()); + } + }; + private final View.OnClickListener deleteButtonListener = new View.OnClickListener() { @Override diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml index e8e560059..07ce86a1d 100644 --- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml +++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml @@ -67,22 +67,36 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="10dp" - android:orientation="horizontal"> - <!-- TODO(a bug): Remove these buttons as this is a place holder for the Media Player --> - <Button + android:gravity="center" + android:orientation="horizontal" + android:weightSum="4"> + + + <ImageButton android:id="@+id/playButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Play"/> - <Button + style="@style/voicemail_media_player_buttons" + android:layout_weight="1" + android:src="@drawable/quantum_ic_play_arrow_vd_theme_24"/> + + + <ImageButton android:id="@+id/speakerButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Speaker"/> - <Button + style="@style/voicemail_media_player_buttons" + android:layout_weight="1" + android:src="@drawable/quantum_ic_volume_up_vd_theme_24"/> + + + <ImageButton + android:id="@+id/phoneButton" + style="@style/voicemail_media_player_buttons" + android:layout_weight="1" + android:src="@drawable/quantum_ic_phone_vd_theme_24"/> + + <ImageButton android:id="@+id/deleteButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Delete"/> + style="@style/voicemail_media_player_buttons" + android:layout_weight="1" + android:src="@drawable/quantum_ic_delete_vd_theme_24"/> + </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/java/com/android/dialer/voicemail/listui/res/values/dimens.xml b/java/com/android/dialer/voicemail/listui/res/values/dimens.xml index 6c062ae28..e37bc65fe 100644 --- a/java/com/android/dialer/voicemail/listui/res/values/dimens.xml +++ b/java/com/android/dialer/voicemail/listui/res/values/dimens.xml @@ -29,6 +29,11 @@ <dimen name="voicemail_icon_size">16dp</dimen> <dimen name="voicemail_playback_state_text_size">14sp</dimen> + <!-- TODO(uabdullah): Work with UX on this value to ensure proper spacing between + the seekbar and transcription --> <dimen name="voicemail_media_player_padding_top">20dp</dimen> <dimen name="voicemail_duration_size">14sp</dimen> + <!-- TODO(uabdullah): Work with UX on these values so that the touch target is not too small --> + <dimen name="voicemail_media_player_height">56dp</dimen> + <dimen name="voicemail_media_player_width">0dp</dimen> </resources> diff --git a/java/com/android/dialer/voicemail/listui/res/values/styles.xml b/java/com/android/dialer/voicemail/listui/res/values/styles.xml new file mode 100644 index 000000000..aec46090a --- /dev/null +++ b/java/com/android/dialer/voicemail/listui/res/values/styles.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<resources> + + <style name="voicemail_media_player_buttons"> + <item name="android:layout_width">@dimen/voicemail_media_player_width</item> + <item name="android:layout_height">@dimen/voicemail_media_player_height</item> + <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item> + </style> +</resources>
\ No newline at end of file diff --git a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java index 2b496c0ca..aaa1e150d 100644 --- a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java +++ b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java @@ -24,16 +24,22 @@ import android.preference.PreferenceScreen; import android.preference.SwitchPreference; import android.provider.Settings; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.notification.NotificationChannelManager; +import com.android.dialer.telecom.TelecomUtil; import com.android.voicemail.VoicemailClient; import com.android.voicemail.VoicemailClient.ActivationStateListener; import com.android.voicemail.VoicemailComponent; +import com.google.common.base.Optional; /** * Fragment for voicemail settings. Requires {@link VoicemailClient#PARAM_PHONE_ACCOUNT_HANDLE} set @@ -45,6 +51,16 @@ public class VoicemailSettingsFragment extends PreferenceFragment private static final String TAG = "VmSettingsActivity"; + // Extras copied from com.android.phone.settings.VoicemailSettingsActivity, + // it does not recognize EXTRA_PHONE_ACCOUNT_HANDLE in O. + @VisibleForTesting + static final String SUB_ID_EXTRA = + "com.android.phone.settings.SubscriptionInfoHelper.SubscriptionId"; + // Extra on intent containing the label of a subscription. + @VisibleForTesting + static final String SUB_LABEL_EXTRA = + "com.android.phone.settings.SubscriptionInfoHelper.SubscriptionLabel"; + @Nullable private PhoneAccountHandle phoneAccountHandle; private VoicemailClient voicemailClient; @@ -167,6 +183,19 @@ public class VoicemailSettingsFragment extends PreferenceFragment advancedSettingsIntent.putExtra(TelephonyManager.EXTRA_HIDE_PUBLIC_SETTINGS, true); advancedSettingsIntent.putExtra( TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle); + + // (a bug): EXTRA_PHONE_ACCOUNT_HANDLE not implemented in telephony in O. + Optional<SubscriptionInfo> subscriptionInfo = + TelecomUtil.getSubscriptionInfo(getContext(), phoneAccountHandle); + if (subscriptionInfo.isPresent()) { + advancedSettingsIntent.putExtra(SUB_ID_EXTRA, subscriptionInfo.get().getSubscriptionId()); + PhoneAccount phoneAccount = + getContext().getSystemService(TelecomManager.class).getPhoneAccount(phoneAccountHandle); + if (phoneAccount != null) { + advancedSettingsIntent.putExtra(SUB_LABEL_EXTRA, phoneAccount.getLabel()); + } + } + advancedSettings.setIntent(advancedSettingsIntent); voicemailChangePinPreference.setOnPreferenceClickListener( new OnPreferenceClickListener() { diff --git a/java/com/android/dialer/widget/FloatingActionButtonController.java b/java/com/android/dialer/widget/FloatingActionButtonController.java index a0c4e6ddd..dde4d44ce 100644 --- a/java/com/android/dialer/widget/FloatingActionButtonController.java +++ b/java/com/android/dialer/widget/FloatingActionButtonController.java @@ -18,7 +18,7 @@ package com.android.dialer.widget; import android.app.Activity; import android.content.res.Resources; -import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener; import android.view.View; @@ -39,6 +39,7 @@ public class FloatingActionButtonController { private final int mFloatingActionButtonMarginRight; private final FloatingActionButton mFab; private final Interpolator mFabInterpolator; + private int mFabIconId = -1; private int mScreenWidth; public FloatingActionButtonController(Activity activity, FloatingActionButton fab) { @@ -82,9 +83,12 @@ public class FloatingActionButtonController { } } - public void changeIcon(Drawable icon, String description) { - if (mFab.getDrawable() != icon || !mFab.getContentDescription().equals(description)) { - mFab.setImageDrawable(icon); + public void changeIcon(@DrawableRes int iconId, String description) { + if (this.mFabIconId != iconId) { + mFab.setImageResource(iconId); + this.mFabIconId = iconId; + } + if (!mFab.getContentDescription().equals(description)) { mFab.setContentDescription(description); } } diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index cdab6b4f5..ed10ed0bb 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -16,6 +16,8 @@ package com.android.incallui; +import android.app.AlertDialog; +import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.graphics.drawable.GradientDrawable; @@ -34,6 +36,10 @@ import android.view.KeyEvent; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.Toast; +import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; @@ -59,6 +65,8 @@ import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory; import com.android.incallui.incall.protocol.InCallScreen; import com.android.incallui.incall.protocol.InCallScreenDelegate; import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; +import com.android.incallui.incalluilock.InCallUiLock; +import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment; import com.android.incallui.video.bindings.VideoBindings; import com.android.incallui.video.protocol.VideoCallScreen; import com.android.incallui.video.protocol.VideoCallScreenDelegate; @@ -76,8 +84,10 @@ public class InCallActivity extends TransactionSafeFragmentActivity public static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1; public static final int PENDING_INTENT_REQUEST_CODE_BUBBLE = 2; - private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen"; private static final String TAG_ANSWER_SCREEN = "tag_answer_screen"; + private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment"; + private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi"; + private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen"; private static final String TAG_VIDEO_CALL_SCREEN = "tag_video_call_screen"; private static final String DID_SHOW_ANSWER_SCREEN_KEY = "did_show_answer_screen"; @@ -87,9 +97,11 @@ public class InCallActivity extends TransactionSafeFragmentActivity private static final String CONFIG_ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled"; private final InCallActivityCommon common; + private InCallOrientationEventListener inCallOrientationEventListener; private boolean didShowAnswerScreen; private boolean didShowInCallScreen; private boolean didShowVideoCallScreen; + private boolean dismissKeyguard; private int[] backgroundDrawableColors; private GradientDrawable backgroundDrawable; private boolean isVisible; @@ -138,6 +150,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity } common.onCreate(icicle); + inCallOrientationEventListener = new InCallOrientationEventListener(this); getWindow() .getDecorView() @@ -340,7 +353,21 @@ public class InCallActivity extends TransactionSafeFragmentActivity } public boolean isDialpadVisible() { - return common.isDialpadVisible(); + DialpadFragment dialpadFragment = getDialpadFragment(); + return dialpadFragment != null && dialpadFragment.isVisible(); + } + + /** + * Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} + * TODO(a bug): Make this method private after InCallActivityCommon is deleted. + */ + @Nullable + DialpadFragment getDialpadFragment() { + FragmentManager fragmentManager = getDialpadFragmentManager(); + if (fragmentManager == null) { + return null; + } + return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT); } public void onForegroundCallChanged(DialerCall newForegroundCall) { @@ -419,37 +446,109 @@ public class InCallActivity extends TransactionSafeFragmentActivity } public void dismissKeyguard(boolean dismiss) { - common.dismissKeyguard(dismiss); + if (dismissKeyguard == dismiss) { + return; + } + + dismissKeyguard = dismiss; + if (dismiss) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + } } public void showPostCharWaitDialog(String callId, String chars) { common.showPostCharWaitDialog(callId, chars); } - public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) { - common.maybeShowErrorDialogOnDisconnect(disconnectMessage); + public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) { + LogUtil.i( + "InCallActivity.showDialogOrToastForDisconnectedCall", + "disconnect cause: %s", + disconnectMessage); + + if (disconnectMessage.dialog == null || isFinishing()) { + return; + } + + dismissPendingDialogs(); + + // Show a toast if the app is in background when a dialog can't be visible. + if (!isVisible()) { + Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG) + .show(); + return; + } + + // Show the dialog. + common.setErrorDialog(disconnectMessage.dialog); + InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog"); + disconnectMessage.dialog.setOnDismissListener( + dialogInterface -> { + lock.release(); + onDialogDismissed(); + }); + disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + disconnectMessage.dialog.show(); + } + + private void onDialogDismissed() { + common.setErrorDialog(null); + CallList.getInstance().onErrorDialogDismissed(); } public void dismissPendingDialogs() { - if (isVisible) { - LogUtil.i("InCallActivity.dismissPendingDialogs", ""); - common.dismissPendingDialogs(); - AnswerScreen answerScreen = getAnswerScreen(); - if (answerScreen != null) { - answerScreen.dismissPendingDialogs(); - } - needDismissPendingDialogs = false; - } else { - // The activity is not visible and onSaveInstanceState may have been called so defer the - // dismissing action. + LogUtil.i("InCallActivity.dismissPendingDialogs", ""); + + if (!isVisible) { + // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have + // been called. LogUtil.i( "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible"); needDismissPendingDialogs = true; + return; + } + + // Dismiss the error dialog + Dialog errorDialog = common.getErrorDialog(); + if (errorDialog != null) { + errorDialog.dismiss(); + common.setErrorDialog(null); + } + + // Dismiss the phone account selection dialog + SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment = + common.getSelectPhoneAccountDialogFragment(); + if (selectPhoneAccountDialogFragment != null) { + selectPhoneAccountDialogFragment.dismiss(); + common.setSelectPhoneAccountDialogFragment(null); + } + + // Dismiss the dialog for international call on WiFi + InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment = + (InternationalCallOnWifiDialogFragment) + getSupportFragmentManager().findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI); + if (internationalCallOnWifiFragment != null) { + internationalCallOnWifiFragment.dismiss(); + } + + // Dismiss the answer screen + AnswerScreen answerScreen = getAnswerScreen(); + if (answerScreen != null) { + answerScreen.dismissPendingDialogs(); } + + needDismissPendingDialogs = false; } - private void enableInCallOrientationEventListener(boolean enable) { - common.enableInCallOrientationEventListener(enable); + // TODO(a bug): Make this method private after InCallActivityCommon is deleted. + void enableInCallOrientationEventListener(boolean enable) { + if (enable) { + inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */); + } else { + inCallOrientationEventListener.disable(); + } } public void setExcludeFromRecents(boolean exclude) { @@ -515,17 +614,67 @@ public class InCallActivity extends TransactionSafeFragmentActivity Trace.endSection(); } - public void onWiFiToLteHandover(DialerCall call) { - common.showWifiToLteHandoverToast(call); - } + public void showToastForWiFiToLteHandover(DialerCall call) { + if (call.hasShownWiFiToLteHandoverToast()) { + return; + } - public void onHandoverToWifiFailed(DialerCall call) { - common.showWifiFailedDialog(call); + Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show(); + call.setHasShownWiFiToLteHandoverToast(); } - public void onInternationalCallOnWifi(@NonNull DialerCall call) { - LogUtil.enterBlock("InCallActivity.onInternationalCallOnWifi"); - common.showInternationalCallOnWifiDialog(call); + public void showDialogOrToastForWifiHandoverFailure(DialerCall call) { + if (call.showWifiHandoverAlertAsToast()) { + Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT) + .show(); + return; + } + + dismissPendingDialogs(); + + AlertDialog.Builder builder = + new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title); + + // This allows us to use the theme of the dialog instead of the activity + View dialogCheckBoxView = + View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */); + CheckBox wifiHandoverFailureCheckbox = + (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox); + wifiHandoverFailureCheckbox.setChecked(false); + + InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog"); + Dialog errorDialog = + builder + .setView(dialogCheckBoxView) + .setMessage(R.string.video_call_lte_to_wifi_failed_message) + .setOnCancelListener(dialogInterface -> onDialogDismissed()) + .setPositiveButton( + android.R.string.ok, + (dialogInterface, id) -> { + call.setDoNotShowDialogForHandoffToWifiFailure( + wifiHandoverFailureCheckbox.isChecked()); + dialogInterface.cancel(); + onDialogDismissed(); + }) + .setOnDismissListener(dialogInterface -> lock.release()) + .create(); + + common.setErrorDialog(errorDialog); + errorDialog.show(); + } + + public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) { + if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) { + LogUtil.i( + "InCallActivity.showDialogForInternationalCallOnWifi", + "InternationalCallOnWifiDialogFragment.shouldShow returned false"); + return; + } + + InternationalCallOnWifiDialogFragment fragment = + InternationalCallOnWifiDialogFragment.newInstance( + call.getId(), common.getCallbackForInternationalCallOnWifiDialog()); + fragment.show(getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI); } @Override diff --git a/java/com/android/incallui/InCallActivityCommon.java b/java/com/android/incallui/InCallActivityCommon.java index 5a5d770d0..8f82295ed 100644 --- a/java/com/android/incallui/InCallActivityCommon.java +++ b/java/com/android/incallui/InCallActivityCommon.java @@ -19,12 +19,8 @@ package com.android.incallui; import android.app.ActivityManager; import android.app.ActivityManager.AppTask; import android.app.ActivityManager.TaskDescription; -import android.app.AlertDialog; import android.app.Dialog; import android.app.KeyguardManager; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; @@ -46,8 +42,6 @@ import android.view.Window; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.CheckBox; -import android.widget.Toast; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; import com.android.dialer.animation.AnimUtils; @@ -63,8 +57,6 @@ import com.android.incallui.call.CallList; import com.android.incallui.call.DialerCall; import com.android.incallui.call.DialerCall.State; import com.android.incallui.call.TelecomAdapter; -import com.android.incallui.disconnectdialog.DisconnectMessage; -import com.android.incallui.incalluilock.InCallUiLock; import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment; import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback; import com.google.common.base.Optional; @@ -102,13 +94,11 @@ public class InCallActivityCommon { private static Optional<Integer> audioRouteForTesting = Optional.absent(); private final InCallActivity inCallActivity; - private boolean dismissKeyguard; private boolean showPostCharWaitDialogOnResume; private String showPostCharWaitDialogCallId; private String showPostCharWaitDialogChars; - private Dialog dialog; + private Dialog errorDialog; private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment; - private InCallOrientationEventListener inCallOrientationEventListener; private Animation dialpadSlideInAnimation; private Animation dialpadSlideOutAnimation; private boolean animateDialpadOnShow; @@ -243,14 +233,12 @@ public class InCallActivityCommon { "InCallActivityCommon.onCreate", "international fragment exists attaching callback"); existingInternationalFragment.setCallback(internationalCallOnWifiCallback); } - - inCallOrientationEventListener = new InCallOrientationEventListener(inCallActivity); } public void onSaveInstanceState(Bundle out) { // TODO: The dialpad fragment should handle this as part of its own state - out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible()); - DialpadFragment dialpadFragment = getDialpadFragment(); + out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, inCallActivity.isDialpadVisible()); + DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment(); if (dialpadFragment != null) { out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText()); } @@ -260,14 +248,11 @@ public class InCallActivityCommon { Trace.beginSection("InCallActivityCommon.onStart"); // setting activity should be last thing in setup process InCallPresenter.getInstance().setActivity(inCallActivity); - enableInCallOrientationEventListener( + inCallActivity.enableInCallOrientationEventListener( inCallActivity.getRequestedOrientation() == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION); InCallPresenter.getInstance().onActivityStarted(); - if (!isRecreating) { - InCallPresenter.getInstance().onUiShowing(true); - } Trace.endSection(); } @@ -279,6 +264,7 @@ public class InCallActivityCommon { "InCallPresenter is ready for tear down, not sending updates"); } else { updateTaskDescription(); + InCallPresenter.getInstance().onUiShowing(true); } // If there is a pending request to show or hide the dialpad, handle that now. @@ -291,20 +277,20 @@ public class InCallActivityCommon { inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */); animateDialpadOnShow = false; - DialpadFragment dialpadFragment = getDialpadFragment(); + DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment(); if (dialpadFragment != null) { dialpadFragment.setDtmfText(dtmfTextToPreopulate); dtmfTextToPreopulate = null; } } else { LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad"); - if (getDialpadFragment() != null) { + if (inCallActivity.getDialpadFragment() != null) { inCallActivity.showDialpadFragment(false /* show */, false /* animate */); } } showDialpadRequest = DIALPAD_REQUEST_NONE; } - updateNavigationBar(isDialpadVisible()); + updateNavigationBar(inCallActivity.isDialpadVisible()); if (showPostCharWaitDialogOnResume) { showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars); @@ -319,10 +305,15 @@ public class InCallActivityCommon { // onPause is guaranteed to be called when the InCallActivity goes // in the background. public void onPause() { - DialpadFragment dialpadFragment = getDialpadFragment(); + DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment(); if (dialpadFragment != null) { dialpadFragment.onDialerKeyUp(null); } + + InCallPresenter.getInstance().onUiShowing(false); + if (inCallActivity.isFinishing()) { + InCallPresenter.getInstance().unsetActivity(inCallActivity); + } } public void onStop() { @@ -338,18 +329,14 @@ public class InCallActivityCommon { } } - enableInCallOrientationEventListener(false); + inCallActivity.enableInCallOrientationEventListener(false); InCallPresenter.getInstance().updateIsChangingConfigurations(); InCallPresenter.getInstance().onActivityStopped(); if (!isRecreating) { - InCallPresenter.getInstance().onUiShowing(false); - if (dialog != null) { - dialog.dismiss(); + if (errorDialog != null) { + errorDialog.dismiss(); } } - if (inCallActivity.isFinishing()) { - InCallPresenter.getInstance().unsetActivity(inCallActivity); - } } public void onDestroy() { @@ -394,7 +381,7 @@ public class InCallActivityCommon { return true; } - DialpadFragment dialpadFragment = getDialpadFragment(); + DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment(); if (dialpadFragment != null && dialpadFragment.isVisible()) { inCallActivity.showDialpadFragment(false /* show */, true /* animate */); return true; @@ -412,7 +399,7 @@ public class InCallActivityCommon { } public boolean onKeyUp(int keyCode, KeyEvent event) { - DialpadFragment dialpadFragment = getDialpadFragment(); + DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment(); // push input to the dialer. if (dialpadFragment != null && (dialpadFragment.isVisible()) @@ -517,7 +504,7 @@ public class InCallActivityCommon { // As soon as the user starts typing valid dialable keys on the // keyboard (presumably to type DTMF tones) we start passing the // key events to the DTMFDialer's onDialerKeyDown. - DialpadFragment dialpadFragment = getDialpadFragment(); + DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment(); if (dialpadFragment != null && dialpadFragment.isVisible()) { return dialpadFragment.onDialerKeyDown(event); } @@ -525,18 +512,6 @@ public class InCallActivityCommon { return false; } - public void dismissKeyguard(boolean dismiss) { - if (dismissKeyguard == dismiss) { - return; - } - dismissKeyguard = dismiss; - if (dismiss) { - inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); - } else { - inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); - } - } - public void showPostCharWaitDialog(String callId, String chars) { if (inCallActivity.isVisible()) { PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars); @@ -552,19 +527,6 @@ public class InCallActivityCommon { } } - public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) { - LogUtil.i( - "InCallActivityCommon.maybeShowErrorDialogOnDisconnect", - "disconnect cause: %s", - disconnectMessage); - - if (!inCallActivity.isFinishing()) { - if (disconnectMessage.dialog != null) { - showErrorDialog(disconnectMessage.dialog, disconnectMessage.toastMessage); - } - } - } - /** * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should * be shown on launch. @@ -586,67 +548,6 @@ public class InCallActivityCommon { } } - void dismissPendingDialogs() { - if (dialog != null) { - dialog.dismiss(); - dialog = null; - } - if (selectPhoneAccountDialogFragment != null) { - selectPhoneAccountDialogFragment.dismiss(); - selectPhoneAccountDialogFragment = null; - } - - InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment = - (InternationalCallOnWifiDialogFragment) - inCallActivity - .getSupportFragmentManager() - .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI); - if (internationalCallOnWifiFragment != null) { - LogUtil.i( - "InCallActivityCommon.dismissPendingDialogs", - "dismissing InternationalCallOnWifiDialogFragment"); - internationalCallOnWifiFragment.dismiss(); - } - } - - private void showErrorDialog(Dialog dialog, CharSequence message) { - LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message); - inCallActivity.dismissPendingDialogs(); - - // Show toast if apps is in background when dialog won't be visible. - if (!inCallActivity.isVisible()) { - Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show(); - return; - } - - this.dialog = dialog; - InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog"); - dialog.setOnDismissListener( - new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed"); - lock.release(); - onDialogDismissed(); - } - }); - dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - dialog.show(); - } - - private void onDialogDismissed() { - dialog = null; - CallList.getInstance().onErrorDialogDismissed(); - } - - public void enableInCallOrientationEventListener(boolean enable) { - if (enable) { - inCallOrientationEventListener.enable(true); - } else { - inCallOrientationEventListener.disable(); - } - } - public void setExcludeFromRecents(boolean exclude) { List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks(); int taskId = inCallActivity.getTaskId(); @@ -665,83 +566,6 @@ public class InCallActivityCommon { } } - void showInternationalCallOnWifiDialog(@NonNull DialerCall call) { - LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog"); - if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) { - LogUtil.i( - "InCallActivityCommon.showInternationalCallOnWifiDialog", - "InternationalCallOnWifiDialogFragment.shouldShow returned false"); - return; - } - - InternationalCallOnWifiDialogFragment fragment = - InternationalCallOnWifiDialogFragment.newInstance( - call.getId(), internationalCallOnWifiCallback); - fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI); - } - - public void showWifiToLteHandoverToast(DialerCall call) { - if (call.hasShownWiFiToLteHandoverToast()) { - return; - } - Toast.makeText( - inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG) - .show(); - call.setHasShownWiFiToLteHandoverToast(); - } - - public void showWifiFailedDialog(final DialerCall call) { - if (call.showWifiHandoverAlertAsToast()) { - LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast"); - Toast.makeText( - inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT) - .show(); - return; - } - - dismissPendingDialogs(); - - AlertDialog.Builder builder = - new AlertDialog.Builder(inCallActivity) - .setTitle(R.string.video_call_lte_to_wifi_failed_title); - - // This allows us to use the theme of the dialog instead of the activity - View dialogCheckBoxView = - View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null); - final CheckBox wifiHandoverFailureCheckbox = - (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox); - wifiHandoverFailureCheckbox.setChecked(false); - - InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog"); - dialog = - builder - .setView(dialogCheckBoxView) - .setMessage(R.string.video_call_lte_to_wifi_failed_message) - .setOnCancelListener( - new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - onDialogDismissed(); - } - }) - .setPositiveButton( - android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - call.setDoNotShowDialogForHandoffToWifiFailure( - wifiHandoverFailureCheckbox.isChecked()); - dialog.cancel(); - onDialogDismissed(); - } - }) - .setOnDismissListener((dialog) -> lock.release()) - .create(); - - LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog"); - dialog.show(); - } - void updateNavigationBar(boolean isDialpadVisible) { if (!ActivityCompat.isInMultiWindowMode(inCallActivity)) { View navigationBarBackground = @@ -754,7 +578,7 @@ public class InCallActivityCommon { public boolean showDialpadFragment(boolean show, boolean animate) { // If the dialpad is already visible, don't animate in. If it's gone, don't animate out. - boolean isDialpadVisible = isDialpadVisible(); + boolean isDialpadVisible = inCallActivity.isDialpadVisible(); LogUtil.i( "InCallActivityCommon.showDialpadFragment", "show: %b, animate: %b, " + "isDialpadVisible: %b", @@ -783,9 +607,10 @@ public class InCallActivityCommon { } else { if (show) { performShowDialpadFragment(dialpadFragmentManager); - getDialpadFragment().animateShowDialpad(); + inCallActivity.getDialpadFragment().animateShowDialpad(); } - getDialpadFragment() + inCallActivity + .getDialpadFragment() .getView() .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation); } @@ -800,7 +625,7 @@ public class InCallActivityCommon { private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) { FragmentTransaction transaction = dialpadFragmentManager.beginTransaction(); - DialpadFragment dialpadFragment = getDialpadFragment(); + DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment(); if (dialpadFragment == null) { transaction.add( inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT); @@ -833,21 +658,6 @@ public class InCallActivityCommon { updateNavigationBar(false /* isDialpadVisible */); } - public boolean isDialpadVisible() { - DialpadFragment dialpadFragment = getDialpadFragment(); - return dialpadFragment != null && dialpadFragment.isVisible(); - } - - /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */ - @Nullable - private DialpadFragment getDialpadFragment() { - FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager(); - if (fragmentManager == null) { - return null; - } - return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT); - } - public void updateTaskDescription() { Resources resources = inCallActivity.getResources(); int color; @@ -864,10 +674,6 @@ public class InCallActivityCommon { inCallActivity.setTaskDescription(td); } - public boolean hasPendingDialogs() { - return dialog != null; - } - private void internalResolveIntent(Intent intent) { if (!intent.getAction().equals(Intent.ACTION_MAIN)) { return; @@ -902,7 +708,7 @@ public class InCallActivityCommon { outgoingCall.disconnect(); } - dismissKeyguard(true); + inCallActivity.dismissKeyguard(true); } boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog(); @@ -937,4 +743,37 @@ public class InCallActivityCommon { inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT); return true; } + + /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */ + @Deprecated + @Nullable + Dialog getErrorDialog() { + return errorDialog; + } + + /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */ + @Deprecated + void setErrorDialog(@Nullable Dialog errorDialog) { + this.errorDialog = errorDialog; + } + + /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */ + @Deprecated + @Nullable + SelectPhoneAccountDialogFragment getSelectPhoneAccountDialogFragment() { + return selectPhoneAccountDialogFragment; + } + + /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */ + @Deprecated + void setSelectPhoneAccountDialogFragment( + @Nullable SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment) { + this.selectPhoneAccountDialogFragment = selectPhoneAccountDialogFragment; + } + + /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */ + @Deprecated + InternationalCallOnWifiDialogFragment.Callback getCallbackForInternationalCallOnWifiDialog() { + return internationalCallOnWifiCallback; + } } diff --git a/java/com/android/incallui/InCallOrientationEventListener.java b/java/com/android/incallui/InCallOrientationEventListener.java index e6b0bc027..8aae6fb37 100644 --- a/java/com/android/incallui/InCallOrientationEventListener.java +++ b/java/com/android/incallui/InCallOrientationEventListener.java @@ -126,12 +126,13 @@ public class InCallOrientationEventListener extends OrientationEventListener { } /** - * Enables the OrientationEventListener and notifies listeners of current orientation if notify - * flag is true + * Enables the OrientationEventListener and optionally notifies listeners of the current + * orientation. * - * @param notify true or false. Notify device orientation changed if true. + * @param notifyDeviceOrientationChange Whether to notify listeners that the device orientation is + * changed. */ - public void enable(boolean notify) { + public void enable(boolean notifyDeviceOrientationChange) { if (mEnabled) { Log.v(this, "enable: Orientation listener is already enabled. Ignoring..."); return; @@ -139,15 +140,15 @@ public class InCallOrientationEventListener extends OrientationEventListener { super.enable(); mEnabled = true; - if (notify) { + if (notifyDeviceOrientationChange) { InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation); } } - /** Enables the OrientationEventListener with notify flag defaulting to false. */ + /** Enables the OrientationEventListener. */ @Override public void enable() { - enable(false); + enable(false /* notifyDeviceOrientationChange */); } /** Disables the OrientationEventListener. */ diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java index 6c1c13027..3ba2ccff6 100644 --- a/java/com/android/incallui/InCallPresenter.java +++ b/java/com/android/incallui/InCallPresenter.java @@ -88,8 +88,6 @@ import java.util.concurrent.atomic.AtomicBoolean; public class InCallPresenter implements CallList.Listener, AudioModeProvider.AudioModeListener { private static final String PIXEL2017_SYSTEM_FEATURE = "com.google.android.feature.PIXEL_2017_EXPERIENCE"; - private static final String EXTRA_FIRST_TIME_SHOWN = - "com.android.incallui.intent.extra.FIRST_TIME_SHOWN"; private static final long BLOCK_QUERY_TIMEOUT_MS = 1000; @@ -215,14 +213,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud } } }; - /** - * Is true when the activity has been previously started. Some code needs to know not just if the - * activity is currently up, but if it had been previously shown in foreground for this in-call - * session (e.g., StatusBarNotifier). This gets reset when the session ends in the tear-down - * method. - */ - private boolean mIsActivityPreviouslyStarted = false; - + /** Whether or not InCallService is bound to Telecom. */ private boolean mServiceBound = false; @@ -476,7 +467,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud // By the time the UI finally comes up, the call may already be disconnected. // If that's the case, we may need to show an error dialog. if (mCallList != null && mCallList.getDisconnectedCall() != null) { - maybeShowErrorDialogOnDisconnect(mCallList.getDisconnectedCall()); + showDialogOrToastForDisconnectedCall(mCallList.getDisconnectedCall()); } // When the UI comes up, we need to first check the in-call state. @@ -690,14 +681,14 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud @Override public void onWiFiToLteHandover(DialerCall call) { if (mInCallActivity != null) { - mInCallActivity.onWiFiToLteHandover(call); + mInCallActivity.showToastForWiFiToLteHandover(call); } } @Override public void onHandoverToWifiFailed(DialerCall call) { if (mInCallActivity != null) { - mInCallActivity.onHandoverToWifiFailed(call); + mInCallActivity.showDialogOrToastForWifiHandoverFailure(call); } } @@ -705,7 +696,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud public void onInternationalCallOnWifi(@NonNull DialerCall call) { LogUtil.enterBlock("InCallPresenter.onInternationalCallOnWifi"); if (mInCallActivity != null) { - mInCallActivity.onInternationalCallOnWifi(call); + mInCallActivity.showDialogForInternationalCallOnWifi(call); } } @@ -841,7 +832,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud */ @Override public void onDisconnect(DialerCall call) { - maybeShowErrorDialogOnDisconnect(call); + showDialogOrToastForDisconnectedCall(call); // We need to do the run the same code as onCallListChange. onCallListChange(mCallList); @@ -1052,22 +1043,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud mProximitySensor.onInCallShowing(showing); } - Intent broadcastIntent = Bindings.get(mContext).getUiReadyBroadcastIntent(mContext); - if (broadcastIntent != null) { - broadcastIntent.putExtra(EXTRA_FIRST_TIME_SHOWN, !mIsActivityPreviouslyStarted); - - if (showing) { - LogUtil.d("InCallPresenter.onUiShowing", "Sending sticky broadcast: ", broadcastIntent); - mContext.sendStickyBroadcast(broadcastIntent); - } else { - LogUtil.d("InCallPresenter.onUiShowing", "Removing sticky broadcast: ", broadcastIntent); - mContext.removeStickyBroadcast(broadcastIntent); - } - } - - if (showing) { - mIsActivityPreviouslyStarted = true; - } else { + if (!showing) { updateIsChangingConfigurations(); } @@ -1265,19 +1241,19 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud } } - /** - * For some disconnected causes, we show a dialog. This calls into the activity to show the dialog - * if appropriate for the call. - */ - private void maybeShowErrorDialogOnDisconnect(DialerCall call) { + /** Instruct the in-call activity to show an error dialog or toast for a disconnected call. */ + private void showDialogOrToastForDisconnectedCall(DialerCall call) { + if (!isActivityStarted() || call.getState() != DialerCall.State.DISCONNECTED) { + return; + } + // For newly disconnected calls, we may want to show a dialog on specific error conditions - if (isActivityStarted() && call.getState() == DialerCall.State.DISCONNECTED) { - if (call.getAccountHandle() == null && !call.isConferenceCall()) { - setDisconnectCauseForMissingAccounts(call); - } - mInCallActivity.maybeShowErrorDialogOnDisconnect( - new DisconnectMessage(mInCallActivity, call)); + if (call.getAccountHandle() == null && !call.isConferenceCall()) { + setDisconnectCauseForMissingAccounts(call); } + + mInCallActivity.showDialogOrToastForDisconnectedCall( + new DisconnectMessage(mInCallActivity, call)); } /** @@ -1449,7 +1425,6 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud cleanupSurfaces(); - mIsActivityPreviouslyStarted = false; mIsChangingConfigurations = false; // blow away stale contact info so that we get fresh data on diff --git a/java/com/android/incallui/bindings/InCallUiBindings.java b/java/com/android/incallui/bindings/InCallUiBindings.java index 5c6aef4be..c15b68de9 100644 --- a/java/com/android/incallui/bindings/InCallUiBindings.java +++ b/java/com/android/incallui/bindings/InCallUiBindings.java @@ -26,10 +26,6 @@ public interface InCallUiBindings { @Nullable PhoneNumberService newPhoneNumberService(Context context); - /** @return An {@link Intent} to be broadcast when the InCallUI is visible. */ - @Nullable - Intent getUiReadyBroadcastIntent(Context context); - /** * @return An {@link Intent} to be broadcast when the call state button in the InCallUI is touched * while in a call. diff --git a/java/com/android/incallui/bindings/InCallUiBindingsStub.java b/java/com/android/incallui/bindings/InCallUiBindingsStub.java index 3a005b0fb..3a9e1dc52 100644 --- a/java/com/android/incallui/bindings/InCallUiBindingsStub.java +++ b/java/com/android/incallui/bindings/InCallUiBindingsStub.java @@ -31,12 +31,6 @@ public class InCallUiBindingsStub implements InCallUiBindings { @Override @Nullable - public Intent getUiReadyBroadcastIntent(Context context) { - return null; - } - - @Override - @Nullable public Intent getCallStateButtonBroadcastIntent(Context context) { return null; } diff --git a/java/com/android/incallui/incall/impl/ButtonController.java b/java/com/android/incallui/incall/impl/ButtonController.java index cefbd723b..5e37a492b 100644 --- a/java/com/android/incallui/incall/impl/ButtonController.java +++ b/java/com/android/incallui/incall/impl/ButtonController.java @@ -16,6 +16,7 @@ package com.android.incallui.incall.impl; +import android.graphics.drawable.AnimationDrawable; import android.support.annotation.CallSuper; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; @@ -569,11 +570,14 @@ interface ButtonController { InCallButtonIds.BUTTON_SWAP_SIM, R.string.incall_content_description_swap_sim, R.string.incall_label_swap_sim, - R.drawable.quantum_ic_swap_calls_white_36); + R.drawable.ic_sim_change_white); } @Override public void onClick(View view) { + AnimationDrawable drawable = (AnimationDrawable) button.getIconDrawable(); + drawable.stop(); // animation is one shot, stop it so it can be started again. + drawable.start(); delegate.swapSimClicked(); } } diff --git a/java/com/android/incallui/incall/impl/CheckableLabeledButton.java b/java/com/android/incallui/incall/impl/CheckableLabeledButton.java index 325c3a92a..ca018acc2 100644 --- a/java/com/android/incallui/incall/impl/CheckableLabeledButton.java +++ b/java/com/android/incallui/incall/impl/CheckableLabeledButton.java @@ -47,6 +47,7 @@ public class CheckableLabeledButton extends LinearLayout implements Checkable { private boolean isChecked; private OnCheckedChangeListener onCheckedChangeListener; private ImageView iconView; + @DrawableRes private int iconResource = 0; private TextView labelView; private Drawable background; private Drawable backgroundMore; @@ -135,8 +136,15 @@ public class CheckableLabeledButton extends LinearLayout implements Checkable { new int[] {color, Color.WHITE})); } + public Drawable getIconDrawable() { + return iconView.getDrawable(); + } + public void setIconDrawable(@DrawableRes int drawableRes) { - iconView.setImageResource(drawableRes); + if (iconResource != drawableRes) { + iconView.setImageResource(drawableRes); + iconResource = drawableRes; + } } public void setLabelText(@StringRes int stringRes) { diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_00.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_00.png Binary files differnew file mode 100644 index 000000000..4c8b33f95 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_00.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_01.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_01.png Binary files differnew file mode 100644 index 000000000..910177920 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_01.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_02.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_02.png Binary files differnew file mode 100644 index 000000000..92a27eed2 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_02.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_03.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_03.png Binary files differnew file mode 100644 index 000000000..484058f22 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_03.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_04.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_04.png Binary files differnew file mode 100644 index 000000000..348ae927a --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_04.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_05.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_05.png Binary files differnew file mode 100644 index 000000000..011915a93 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_05.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_06.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_06.png Binary files differnew file mode 100644 index 000000000..c1cc0d6d7 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_06.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_07.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_07.png Binary files differnew file mode 100644 index 000000000..75233db73 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_07.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_08.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_08.png Binary files differnew file mode 100644 index 000000000..2918e1a4b --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_08.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_09.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_09.png Binary files differnew file mode 100644 index 000000000..7e927674c --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_09.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_10.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_10.png Binary files differnew file mode 100644 index 000000000..008931431 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_10.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_11.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_11.png Binary files differnew file mode 100644 index 000000000..f66a6b669 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_11.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_12.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_12.png Binary files differnew file mode 100644 index 000000000..9303d9596 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_12.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_13.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_13.png Binary files differnew file mode 100644 index 000000000..d2bef6e77 --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_13.png diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_14.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_14.png Binary files differnew file mode 100644 index 000000000..a5434ecdc --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_14.png diff --git a/java/com/android/incallui/incall/impl/res/drawable/ic_sim_change_white.xml b/java/com/android/incallui/incall/impl/res/drawable/ic_sim_change_white.xml new file mode 100644 index 000000000..00b1b7ace --- /dev/null +++ b/java/com/android/incallui/incall/impl/res/drawable/ic_sim_change_white.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<animation-list xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/ic_sim_change_white" + android:oneshot="true"> + <item + android:drawable="@drawable/ic_sim_change_white_00" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_01" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_02" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_03" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_04" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_05" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_06" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_07" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_08" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_09" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_10" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_11" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_12" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_13" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_14" + android:duration="33"/> + <item + android:drawable="@drawable/ic_sim_change_white_00" + android:duration="33"/> +</animation-list>
\ No newline at end of file |