diff options
Diffstat (limited to 'java')
107 files changed, 2175 insertions, 910 deletions
diff --git a/java/com/android/contacts/common/model/ContactLoader.java b/java/com/android/contacts/common/model/ContactLoader.java index d2c757709..51b8e3efc 100644 --- a/java/com/android/contacts/common/model/ContactLoader.java +++ b/java/com/android/contacts/common/model/ContactLoader.java @@ -218,7 +218,7 @@ public class ContactLoader extends AsyncTaskLoader<Contact> { @Override public Contact loadInBackground() { - LogUtil.e(TAG, "loadInBackground=" + mLookupUri); + LogUtil.v(TAG, "loadInBackground=" + mLookupUri); try { final ContentResolver resolver = getContext().getContentResolver(); final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(resolver, mLookupUri); diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 293ebed87..ff64ba168 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -116,9 +116,10 @@ import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCo import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; -import com.android.dialer.logging.LoggingBindings; import com.android.dialer.logging.ScreenEvent; import com.android.dialer.logging.UiAction; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.p13n.inference.P13nRanking; import com.android.dialer.p13n.inference.protocol.P13nRanker; import com.android.dialer.p13n.inference.protocol.P13nRanker.P13nRefreshCompleteListener; @@ -612,8 +613,9 @@ public class DialtactsActivity extends TransactionSafeActivity // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. ThreadUtil.postDelayedOnUiThread( () -> - Logger.get(this) - .logRecordMemory(LoggingBindings.ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME), + MetricsComponent.get(this) + .metrics() + .recordMemory(Metrics.DIALTACTS_ON_RESUME_MEMORY_EVENT_NAME), 1000); } diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java index 7f635dbca..dd1d98c83 100644 --- a/java/com/android/dialer/app/calllog/CallLogFragment.java +++ b/java/com/android/dialer/app/calllog/CallLogFragment.java @@ -58,11 +58,15 @@ import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; import com.android.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; +import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.database.CallLogQueryHandler.Listener; import com.android.dialer.location.GeoUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; +import com.android.dialer.metrics.jank.RecyclerViewJankLogger; import com.android.dialer.oem.CequintCallerIdManager; import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.phonenumbercache.ContactInfoHelper; @@ -305,7 +309,13 @@ public class CallLogFragment extends Fragment protected void setupView(View view) { recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); + if (ConfigProviderBindings.get(getContext()).getBoolean("is_call_log_item_anim_null", false)) { + recyclerView.setItemAnimator(null); + } recyclerView.setHasFixedSize(true); + recyclerView.addOnScrollListener( + new RecyclerViewJankLogger( + MetricsComponent.get(getContext()).metrics(), Metrics.OLD_CALL_LOG_JANK_EVENT_NAME)); layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); PerformanceReport.logOnScrollStateChange(recyclerView); @@ -661,8 +671,19 @@ public class CallLogFragment extends Fragment multiSelectUnSelectAllViewContent.setVisibility(show ? View.VISIBLE : View.GONE); multiSelectUnSelectAllViewContent.setAlpha(show ? 0 : 1); multiSelectUnSelectAllViewContent.animate().alpha(show ? 1 : 0).start(); - FragmentUtils.getParentUnsafe(this, CallLogFragmentListener.class) - .showMultiSelectRemoveView(show); + if (show) { + FragmentUtils.getParentUnsafe(this, CallLogFragmentListener.class) + .showMultiSelectRemoveView(true); + } else { + // This method is called after onDestroy. In DialtactsActivity, ListsFragment implements this + // interface and never goes away with configuration changes so this is safe. MainActivity + // removes that extra layer though, so we need to check if the parent is still there. + CallLogFragmentListener listener = + FragmentUtils.getParent(this, CallLogFragmentListener.class); + if (listener != null) { + listener.showMultiSelectRemoveView(false); + } + } } @Override diff --git a/java/com/android/dialer/app/res/values/strings.xml b/java/com/android/dialer/app/res/values/strings.xml index 2d5542bbf..34ec611c3 100644 --- a/java/com/android/dialer/app/res/values/strings.xml +++ b/java/com/android/dialer/app/res/values/strings.xml @@ -686,8 +686,8 @@ <!-- Content of voicemail donation promo dialog [CHAR LIMIT=NONE] --> <string name="voicemail_donation_promo_content"> - Let Google review your voicemail messages to improve transcription quality. Your - voicemail messages will not be tied to your Google Account. + Let Google review your voicemail messages to improve transcription quality. + For voicemail transcription analysis, your voicemail messages are stored anonymously. </string> <!-- Text for a 'learn more' link at the end of the voicemail donation promo dialog content --> diff --git a/java/com/android/dialer/binary/common/DialerApplication.java b/java/com/android/dialer/binary/common/DialerApplication.java index c0e6ae660..c23926021 100644 --- a/java/com/android/dialer/binary/common/DialerApplication.java +++ b/java/com/android/dialer/binary/common/DialerApplication.java @@ -23,11 +23,14 @@ import android.support.v4.os.BuildCompat; import com.android.dialer.blocking.BlockedNumbersAutoMigrator; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; import com.android.dialer.calllog.CallLogComponent; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.inject.HasRootComponent; import com.android.dialer.notification.NotificationChannelManager; import com.android.dialer.persistentlog.PersistentLogger; import com.android.dialer.strictmode.StrictModeComponent; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; /** A common application subclass for all Dialer build variants. */ public abstract class DialerApplication extends Application implements HasRootComponent { @@ -46,6 +49,10 @@ public abstract class DialerApplication extends Application implements HasRootCo DialerExecutorComponent.get(this).dialerExecutorFactory()) .asyncAutoMigrate(); CallLogComponent.get(this).callLogFramework().registerContentObservers(getApplicationContext()); + Futures.addCallback( + CallLogComponent.get(this).getAnnotatedCallLogMigrator().migrate(), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); PersistentLogger.initialize(this); if (BuildCompat.isAtLeastO()) { diff --git a/java/com/android/dialer/buildtype/BuildType.java b/java/com/android/dialer/buildtype/BuildType.java index 6b6bc2906..c5c41d247 100644 --- a/java/com/android/dialer/buildtype/BuildType.java +++ b/java/com/android/dialer/buildtype/BuildType.java @@ -28,15 +28,19 @@ public class BuildType { /** The type of build. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ - BUGFOOD, FISHFOOD, DOGFOOD, RELEASE, TEST, + Type.BUGFOOD, + Type.FISHFOOD, + Type.DOGFOOD, + Type.RELEASE, + Type.TEST, }) - public @interface Type {} - - public static final int BUGFOOD = 1; - public static final int FISHFOOD = 2; - public static final int DOGFOOD = 3; - public static final int RELEASE = 4; - public static final int TEST = 5; + public @interface Type { + int BUGFOOD = 1; + int FISHFOOD = 2; + int DOGFOOD = 3; + int RELEASE = 4; + int TEST = 5; + } private static int cachedBuildType; private static boolean didInitializeBuildType; diff --git a/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java b/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java index 70b9f9e37..4019dd011 100644 --- a/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java +++ b/java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java @@ -16,6 +16,7 @@ package com.android.dialer.buildtype; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.proguard.UsedByReflection; /** Gets the build type. */ @@ -25,6 +26,6 @@ public class BuildTypeAccessorImpl implements BuildTypeAccessor { @Override @BuildType.Type public int getBuildType() { - return BuildType.RELEASE; + return Type.RELEASE; } } diff --git a/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java b/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java new file mode 100644 index 000000000..f8c6fcef1 --- /dev/null +++ b/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 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; + +import android.content.Context; +import android.content.SharedPreferences; +import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.configprovider.ConfigProviderBindings; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.storage.Unencrypted; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import javax.inject.Inject; + +/** + * Builds the annotated call log on application create once after the feature is enabled to reduce + * the latency the first time call log is shown. + */ +public final class AnnotatedCallLogMigrator { + + private static final String PREF_MIGRATED = "annotatedCallLogMigratorMigrated"; + + private final Context appContext; + private final SharedPreferences sharedPreferences; + private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; + private final ListeningExecutorService backgorundExecutor; + + @Inject + AnnotatedCallLogMigrator( + @ApplicationContext Context appContext, + @Unencrypted SharedPreferences sharedPreferences, + @BackgroundExecutor ListeningExecutorService backgroundExecutor, + RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker) { + this.appContext = appContext; + this.sharedPreferences = sharedPreferences; + this.backgorundExecutor = backgroundExecutor; + this.refreshAnnotatedCallLogWorker = refreshAnnotatedCallLogWorker; + } + + /** + * Builds the annotated call log on application create once after the feature is enabled to reduce + * the latency the first time call log is shown. + */ + public ListenableFuture<Void> migrate() { + + return Futures.transformAsync( + shouldMigrate(), + (shouldMigrate) -> { + if (!shouldMigrate) { + return Futures.immediateFuture(null); + } + return Futures.transform( + refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(), + (unused) -> { + sharedPreferences.edit().putBoolean(PREF_MIGRATED, true).apply(); + return null; + }, + MoreExecutors.directExecutor()); + }, + MoreExecutors.directExecutor()); + } + + private ListenableFuture<Boolean> shouldMigrate() { + return backgorundExecutor.submit( + () -> { + if (!(ConfigProviderBindings.get(appContext) + .getBoolean("is_nui_shortcut_enabled", false))) { + return false; + } + if (sharedPreferences.getBoolean(PREF_MIGRATED, false)) { + return false; + } + return true; + }); + } +} diff --git a/java/com/android/dialer/calllog/CallLogComponent.java b/java/com/android/dialer/calllog/CallLogComponent.java index bb5bfee2a..a2a5084fc 100644 --- a/java/com/android/dialer/calllog/CallLogComponent.java +++ b/java/com/android/dialer/calllog/CallLogComponent.java @@ -16,6 +16,7 @@ package com.android.dialer.calllog; import android.content.Context; +import com.android.dialer.calllog.notifier.RefreshAnnotatedCallLogNotifier; import com.android.dialer.inject.HasRootComponent; import dagger.Subcomponent; @@ -25,8 +26,12 @@ public abstract class CallLogComponent { public abstract CallLogFramework callLogFramework(); + public abstract RefreshAnnotatedCallLogNotifier getRefreshAnnotatedCallLogNotifier(); + public abstract RefreshAnnotatedCallLogWorker getRefreshAnnotatedCallLogWorker(); + public abstract AnnotatedCallLogMigrator getAnnotatedCallLogMigrator(); + public abstract ClearMissedCalls getClearMissedCalls(); public static CallLogComponent get(Context context) { diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java index 440055de6..7da8d9c0c 100644 --- a/java/com/android/dialer/calllog/CallLogFramework.java +++ b/java/com/android/dialer/calllog/CallLogFramework.java @@ -17,40 +17,26 @@ package com.android.dialer.calllog; import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.DataSources; -import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.storage.Unencrypted; import javax.inject.Inject; import javax.inject.Singleton; /** - * Coordinates work across CallLog data sources to detect if the annotated call log is out of date - * ("dirty") and update it if necessary. + * Coordinates work across {@link DataSources}. * * <p>All methods should be called on the main thread. */ @Singleton -public final class CallLogFramework implements CallLogDataSource.ContentObserverCallbacks { - - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String PREF_FORCE_REBUILD = "callLogFrameworkForceRebuild"; +public final class CallLogFramework { private final DataSources dataSources; - private final SharedPreferences sharedPreferences; - - @Nullable private CallLogUi ui; @Inject - CallLogFramework(DataSources dataSources, @Unencrypted SharedPreferences sharedPreferences) { + CallLogFramework(DataSources dataSources) { this.dataSources = dataSources; - this.sharedPreferences = sharedPreferences; } /** Registers the content observers for all data sources. */ @@ -63,58 +49,10 @@ public final class CallLogFramework implements CallLogDataSource.ContentObserver // TODO(zachh): Find a way to access Main#isNewUiEnabled without creating a circular dependency. if (ConfigProviderBindings.get(appContext).getBoolean("is_nui_shortcut_enabled", false)) { for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { - dataSource.registerContentObservers(appContext, this); + dataSource.registerContentObservers(appContext); } } else { LogUtil.i("CallLogFramework.registerContentObservers", "not registering content observers"); } } - - /** - * Attach a UI component to the framework so that it may be notified of changes to the annotated - * call log. - */ - public void attachUi(CallLogUi ui) { - LogUtil.enterBlock("CallLogFramework.attachUi"); - this.ui = ui; - } - - /** - * Detaches the UI from the framework. This should be called when the UI is hidden or destroyed - * and no longer needs to be notified of changes to the annotated call log. - */ - public void detachUi() { - LogUtil.enterBlock("CallLogFramework.detachUi"); - this.ui = null; - } - - /** - * Marks the call log as dirty and notifies any attached UI components. If there are no UI - * components currently attached, this is an efficient operation since it is just writing a shared - * pref. - * - * <p>We don't want to actually force a rebuild when there is no UI running because we don't want - * to be constantly rebuilding the database when the device is sitting on a desk and receiving a - * lot of calls, for example. - */ - @Override - @MainThread - public void markDirtyAndNotify(Context appContext) { - Assert.isMainThread(); - LogUtil.enterBlock("CallLogFramework.markDirtyAndNotify"); - - sharedPreferences.edit().putBoolean(PREF_FORCE_REBUILD, true).apply(); - - if (ui != null) { - ui.invalidateUi(); - } - } - - /** Callbacks invoked on listening UI components. */ - public interface CallLogUi { - - /** Notifies the call log UI that the annotated call log is out of date. */ - @MainThread - void invalidateUi(); - } } diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java new file mode 100644 index 000000000..e0bfcd8a3 --- /dev/null +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogReceiver.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 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; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.support.annotation.Nullable; +import com.android.dialer.calllog.constants.IntentNames; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DefaultFutureCallback; +import com.android.dialer.common.concurrent.ThreadUtil; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * A {@link BroadcastReceiver} that starts/cancels refreshing the annotated call log when notified. + */ +public final class RefreshAnnotatedCallLogReceiver extends BroadcastReceiver { + + /** + * This is a reasonable time that it might take between related call log writes, that also + * shouldn't slow down single-writes too much. For example, when populating the database using the + * simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 call + * log entries. + */ + private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L; + + private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; + + @Nullable private Runnable refreshAnnotatedCallLogRunnable; + + /** Returns an {@link IntentFilter} containing all actions accepted by this broadcast receiver. */ + public static IntentFilter getIntentFilter() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG); + intentFilter.addAction(IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG); + return intentFilter; + } + + public RefreshAnnotatedCallLogReceiver(Context context) { + refreshAnnotatedCallLogWorker = + CallLogComponent.get(context).getRefreshAnnotatedCallLogWorker(); + } + + @Override + public void onReceive(Context context, Intent intent) { + LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.onReceive"); + + String action = intent.getAction(); + + if (IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG.equals(action)) { + boolean checkDirty = intent.getBooleanExtra(IntentNames.EXTRA_CHECK_DIRTY, false); + refreshAnnotatedCallLog(checkDirty); + } else if (IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG.equals(action)) { + cancelRefreshingAnnotatedCallLog(); + } + } + + /** + * Request a refresh of the annotated call log. + * + * <p>Note that the execution will be delayed by {@link #REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS}. + * Once the work begins, it can't be cancelled. + * + * @see #cancelRefreshingAnnotatedCallLog() + */ + private void refreshAnnotatedCallLog(boolean checkDirty) { + LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.refreshAnnotatedCallLog"); + + // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests + // in quick succession don't result in too much work. For example, if we get 10 requests in + // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait + // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests + // are serialized in RefreshAnnotatedCallLogWorker. + // + // We might get many requests in quick succession, for example, when the simulator inserts + // hundreds of rows into the system call log, or when the data for a new call is incrementally + // written to different columns as it becomes available. + ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); + + refreshAnnotatedCallLogRunnable = + () -> { + ListenableFuture<Void> future = + checkDirty + ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() + : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); + Futures.addCallback( + future, new DefaultFutureCallback<>(), MoreExecutors.directExecutor()); + }; + + ThreadUtil.getUiThreadHandler() + .postDelayed(refreshAnnotatedCallLogRunnable, REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS); + } + + /** + * When a refresh is requested, its execution is delayed (see {@link + * #refreshAnnotatedCallLog(boolean)}). This method only cancels the refresh if it hasn't started. + */ + private void cancelRefreshingAnnotatedCallLog() { + LogUtil.enterBlock("RefreshAnnotatedCallLogReceiver.cancelRefreshingAnnotatedCallLog"); + + ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); + } +} diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java index 4c5904ef1..a430d14a8 100644 --- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java @@ -18,6 +18,7 @@ package com.android.dialer.calllog; import android.content.Context; import android.content.SharedPreferences; +import com.android.dialer.calllog.constants.SharedPrefKeys; import com.android.dialer.calllog.database.CallLogDatabaseComponent; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.CallLogMutations; @@ -95,7 +96,7 @@ public class RefreshAnnotatedCallLogWorker { // Default to true. If the pref doesn't exist, the annotated call log hasn't been // created and we just skip isDirty checks and force a rebuild. boolean forceRebuildPrefValue = - sharedPreferences.getBoolean(CallLogFramework.PREF_FORCE_REBUILD, true); + sharedPreferences.getBoolean(SharedPrefKeys.FORCE_REBUILD, true); if (forceRebuildPrefValue) { LogUtil.i( "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", @@ -183,7 +184,7 @@ public class RefreshAnnotatedCallLogWorker { return Futures.transform( onSuccessfulFillFuture, unused -> { - sharedPreferences.edit().putBoolean(CallLogFramework.PREF_FORCE_REBUILD, false).apply(); + sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, false).apply(); return null; }, backgroundExecutorService); diff --git a/java/com/android/dialer/calllog/constants/IntentNames.java b/java/com/android/dialer/calllog/constants/IntentNames.java new file mode 100644 index 000000000..3912450a1 --- /dev/null +++ b/java/com/android/dialer/calllog/constants/IntentNames.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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.constants; + +/** A class containing names for call log intents. */ +public final class IntentNames { + + public static final String ACTION_REFRESH_ANNOTATED_CALL_LOG = "refresh_annotated_call_log"; + + public static final String ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG = + "cancel_refreshing_annotated_call_log"; + + public static final String EXTRA_CHECK_DIRTY = "check_dirty"; + + private IntentNames() {} +} diff --git a/java/com/android/dialer/calllog/constants/SharedPrefKeys.java b/java/com/android/dialer/calllog/constants/SharedPrefKeys.java new file mode 100644 index 000000000..41e65bb53 --- /dev/null +++ b/java/com/android/dialer/calllog/constants/SharedPrefKeys.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 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.constants; + +/** Keys for shared preferences in the call log package. */ +public final class SharedPrefKeys { + + public static final String FORCE_REBUILD = "force_rebuild"; + + private SharedPrefKeys() {} +} diff --git a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java index 60654a81a..dbed1d81c 100644 --- a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java @@ -104,14 +104,5 @@ public interface CallLogDataSource { ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc); @MainThread - void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks); - - /** - * Methods which may optionally be called as a result of a data source's content observer firing. - */ - interface ContentObserverCallbacks { - @MainThread - void markDirtyAndNotify(Context appContext); - } + void registerContentObservers(Context appContext); } diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 8dec43759..40788f42a 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -61,8 +61,7 @@ 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, PhoneLookup.ContentObserverCallbacks { +public final class PhoneLookupDataSource implements CallLogDataSource { private final PhoneLookup<PhoneLookupInfo> phoneLookup; private final ListeningExecutorService backgroundExecutorService; @@ -85,8 +84,6 @@ public final class PhoneLookupDataSource */ private final Set<String> phoneLookupHistoryRowsToDelete = new ArraySet<>(); - private CallLogDataSource.ContentObserverCallbacks dataSourceContentObserverCallbacks; - @Inject PhoneLookupDataSource( PhoneLookup<PhoneLookupInfo> phoneLookup, @@ -288,17 +285,8 @@ public final class PhoneLookupDataSource @MainThread @Override - public void registerContentObservers( - Context appContext, CallLogDataSource.ContentObserverCallbacks contentObserverCallbacks) { - dataSourceContentObserverCallbacks = contentObserverCallbacks; - phoneLookup.registerContentObservers(appContext, this); - } - - @MainThread - @Override - public void markDirtyAndNotify(Context appContext) { - Assert.isMainThread(); - dataSourceContentObserverCallbacks.markDirtyAndNotify(appContext); + public void registerContentObservers(Context appContext) { + phoneLookup.registerContentObservers(appContext); } private static ImmutableSet<DialerPhoneNumber> diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index ee484d95e..6daa5e757 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -20,13 +20,10 @@ import android.Manifest.permission; import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; -import android.database.ContentObserver; import android.database.Cursor; -import android.net.Uri; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.os.Handler; import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.VoicemailContract; @@ -47,11 +44,11 @@ import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.Ann import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.CallLogMutations; import com.android.dialer.calllog.datasources.util.RowCombiner; +import com.android.dialer.calllog.observer.MarkDirtyObserver; import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; -import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.compat.android.provider.VoicemailCompat; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.android.dialer.storage.StorageComponent; @@ -79,18 +76,21 @@ public class SystemCallLogDataSource implements CallLogDataSource { static final String PREF_LAST_TIMESTAMP_PROCESSED = "systemCallLogLastTimestampProcessed"; private final ListeningExecutorService backgroundExecutorService; + private final MarkDirtyObserver markDirtyObserver; @Nullable private Long lastTimestampProcessed; @Inject - SystemCallLogDataSource(@BackgroundExecutor ListeningExecutorService backgroundExecutorService) { + SystemCallLogDataSource( + @BackgroundExecutor ListeningExecutorService backgroundExecutorService, + MarkDirtyObserver markDirtyObserver) { this.backgroundExecutorService = backgroundExecutorService; + this.markDirtyObserver = markDirtyObserver; } @MainThread @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { Assert.isMainThread(); LogUtil.enterBlock("SystemCallLogDataSource.registerContentObservers"); @@ -101,12 +101,13 @@ public class SystemCallLogDataSource implements CallLogDataSource { } // TODO(zachh): Need to somehow register observers if user enables permission after launch? - CallLogObserver callLogObserver = - new CallLogObserver(ThreadUtil.getUiThreadHandler(), appContext, contentObserverCallbacks); - + // The system call log has a last updated timestamp, but deletes are physical (the "deleted" + // column is unused). This means that we can't detect deletes without scanning the entire table, + // which would be too slow. So, we just rely on content observers to trigger rebuilds when any + // change is made to the system call log. appContext .getContentResolver() - .registerContentObserver(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, true, callLogObserver); + .registerContentObserver(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, true, markDirtyObserver); if (!PermissionsUtil.hasAddVoicemailPermissions(appContext)) { LogUtil.i("SystemCallLogDataSource.registerContentObservers", "no add voicemail permissions"); @@ -115,7 +116,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { // TODO(uabdullah): Need to somehow register observers if user enables permission after launch? appContext .getContentResolver() - .registerContentObserver(VoicemailContract.Status.CONTENT_URI, true, callLogObserver); + .registerContentObserver(VoicemailContract.Status.CONTENT_URI, true, markDirtyObserver); } @Override @@ -523,36 +524,4 @@ public class SystemCallLogDataSource implements CallLogDataSource { } return ids; } - - private static class CallLogObserver extends ContentObserver { - private final Context appContext; - private final ContentObserverCallbacks contentObserverCallbacks; - - CallLogObserver( - Handler handler, Context appContext, ContentObserverCallbacks contentObserverCallbacks) { - super(handler); - this.appContext = appContext; - this.contentObserverCallbacks = contentObserverCallbacks; - } - - @MainThread - @Override - public void onChange(boolean selfChange, Uri uri) { - Assert.isMainThread(); - LogUtil.i( - "SystemCallLogDataSource.CallLogObserver.onChange", - "Uri:%s, SelfChange:%b", - String.valueOf(uri), - selfChange); - super.onChange(selfChange, uri); - - /* - * The system call log has a last updated timestamp, but deletes are physical (the "deleted" - * column is unused). This means that we can't detect deletes without scanning the entire - * table, which would be too slow. So, we just rely on content observers to trigger rebuilds - * when any change is made to the system call log. - */ - contentObserverCallbacks.markDirtyAndNotify(appContext); - } - } } diff --git a/java/com/android/dialer/calllog/notifier/RefreshAnnotatedCallLogNotifier.java b/java/com/android/dialer/calllog/notifier/RefreshAnnotatedCallLogNotifier.java new file mode 100644 index 000000000..5b73ad778 --- /dev/null +++ b/java/com/android/dialer/calllog/notifier/RefreshAnnotatedCallLogNotifier.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.notifier; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.support.v4.content.LocalBroadcastManager; +import com.android.dialer.calllog.constants.IntentNames; +import com.android.dialer.calllog.constants.SharedPrefKeys; +import com.android.dialer.common.LogUtil; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.storage.Unencrypted; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Notifies that a refresh of the annotated call log needs to be started/cancelled. + * + * <p>Methods in this class are usually invoked when the underlying data backing the annotated call + * log change. + * + * <p>For example, a {@link android.database.ContentObserver} for the system call log can use {@link + * #markDirtyAndNotify()} to force the annotated call log to be rebuilt. + */ +@Singleton +public class RefreshAnnotatedCallLogNotifier { + + private final Context appContext; + private final SharedPreferences sharedPreferences; + + @Inject + RefreshAnnotatedCallLogNotifier( + @ApplicationContext Context appContext, @Unencrypted SharedPreferences sharedPreferences) { + this.appContext = appContext; + this.sharedPreferences = sharedPreferences; + } + + /** + * Mark the annotated call log as "dirty" and notify that it needs to be refreshed. + * + * <p>This will force a rebuild by skip checking whether the annotated call log is "dirty". + */ + public void markDirtyAndNotify() { + LogUtil.enterBlock("RefreshAnnotatedCallLogNotifier.markDirtyAndNotify"); + + sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, true).apply(); + notify(/* checkDirty = */ false); + } + + /** + * Notifies that the annotated call log needs to be refreshed. + * + * <p>Note that the notification is sent as a broadcast, which means the annotated call log might + * not be refreshed if there is no corresponding receiver registered. + * + * @param checkDirty Whether to check if the annotated call log is "dirty" before proceeding to + * rebuild it. + */ + public void notify(boolean checkDirty) { + LogUtil.i("RefreshAnnotatedCallLogNotifier.notify", "checkDirty = %s", checkDirty); + + Intent intent = new Intent(); + intent.setAction(IntentNames.ACTION_REFRESH_ANNOTATED_CALL_LOG); + intent.putExtra(IntentNames.EXTRA_CHECK_DIRTY, checkDirty); + + LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent); + } + + /** + * Notifies to cancel refreshing the annotated call log. + * + * <p>Note that this method does not guarantee the job to be cancelled. As the notification is + * sent as a broadcast, please see the corresponding receiver for details about cancelling the + * job. + */ + public void cancel() { + LogUtil.enterBlock("RefreshAnnotatedCallLogNotifier.cancel"); + + Intent intent = new Intent(); + intent.setAction(IntentNames.ACTION_CANCEL_REFRESHING_ANNOTATED_CALL_LOG); + + LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent); + } +} diff --git a/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java b/java/com/android/dialer/calllog/observer/MarkDirtyObserver.java index 1c41d8f7f..8ab1974bc 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/MarkDirtyObserver.java +++ b/java/com/android/dialer/calllog/observer/MarkDirtyObserver.java @@ -14,34 +14,40 @@ * limitations under the License */ -package com.android.dialer.phonelookup.blockednumber; +package com.android.dialer.calllog.observer; -import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.support.annotation.MainThread; +import android.support.annotation.VisibleForTesting; +import com.android.dialer.calllog.notifier.RefreshAnnotatedCallLogNotifier; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; -import com.android.dialer.phonelookup.PhoneLookup.ContentObserverCallbacks; +import javax.inject.Inject; -/** Calls {@link ContentObserverCallbacks#markDirtyAndNotify(Context)} when the content changed */ -class MarkDirtyObserver extends ContentObserver { +/** + * Mark the annotated call log as dirty and notify that a refresh is in order when the content + * changes. + */ +public final class MarkDirtyObserver extends ContentObserver { - private final Context appContext; - private final ContentObserverCallbacks contentObserverCallbacks; + private final RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier; - MarkDirtyObserver(Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + @Inject + public MarkDirtyObserver(RefreshAnnotatedCallLogNotifier refreshAnnotatedCallLogNotifier) { super(ThreadUtil.getUiThreadHandler()); - this.appContext = appContext; - this.contentObserverCallbacks = contentObserverCallbacks; + this.refreshAnnotatedCallLogNotifier = refreshAnnotatedCallLogNotifier; } @MainThread @Override public void onChange(boolean selfChange, Uri uri) { Assert.isMainThread(); - LogUtil.enterBlock("SystemBlockedNumberPhoneLookup.FilteredNumberObserver.onChange"); - contentObserverCallbacks.markDirtyAndNotify(appContext); + LogUtil.i( + "MarkDirtyObserver.onChange", "Uri:%s, SelfChange:%b", String.valueOf(uri), selfChange); + + refreshAnnotatedCallLogNotifier.markDirtyAndNotify(); } } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 5e676f072..bb1a7303e 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -22,44 +22,32 @@ import android.support.annotation.VisibleForTesting; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; +import android.support.v4.content.LocalBroadcastManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.dialer.calllog.CallLogComponent; -import com.android.dialer.calllog.CallLogFramework; -import com.android.dialer.calllog.CallLogFramework.CallLogUi; -import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker; +import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DefaultFutureCallback; -import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; -import com.android.dialer.common.concurrent.UiListener; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; +import com.android.dialer.metrics.jank.RecyclerViewJankLogger; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.TimeUnit; /** The "new" call log fragment implementation, which is built on top of the annotated call log. */ -public final class NewCallLogFragment extends Fragment - implements CallLogUi, LoaderCallbacks<Cursor> { - - /* - * This is a reasonable time that it might take between related call log writes, that also - * shouldn't slow down single-writes too much. For example, when populating the database using - * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 - * call log entries. - */ - private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L; +public final class NewCallLogFragment extends Fragment implements LoaderCallbacks<Cursor> { @VisibleForTesting static final long MARK_ALL_CALLS_READ_WAIT_MILLIS = TimeUnit.SECONDS.toMillis(3); - private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; - private UiListener<Void> refreshAnnotatedCallLogListener; + private RefreshAnnotatedCallLogReceiver refreshAnnotatedCallLogReceiver; private RecyclerView recyclerView; - @Nullable private Runnable refreshAnnotatedCallLogRunnable; private boolean shouldMarkCallsRead = false; private final Runnable setShouldMarkCallsReadTrue = () -> shouldMarkCallsRead = true; @@ -74,16 +62,7 @@ public final class NewCallLogFragment extends Fragment LogUtil.enterBlock("NewCallLogFragment.onActivityCreated"); - CallLogComponent component = CallLogComponent.get(getContext()); - CallLogFramework callLogFramework = component.callLogFramework(); - callLogFramework.attachUi(this); - - // TODO(zachh): Use support fragment manager and add support for them in executors library. - refreshAnnotatedCallLogListener = - DialerExecutorComponent.get(getContext()) - .createUiListener( - getActivity().getFragmentManager(), "NewCallLogFragment.refreshAnnotatedCallLog"); - refreshAnnotatedCallLogWorker = component.getRefreshAnnotatedCallLogWorker(); + refreshAnnotatedCallLogReceiver = new RefreshAnnotatedCallLogReceiver(getContext()); } @Override @@ -97,13 +76,52 @@ public final class NewCallLogFragment extends Fragment public void onResume() { super.onResume(); - LogUtil.enterBlock("NewCallLogFragment.onResume"); + boolean isHidden = isHidden(); + LogUtil.i("NewCallLogFragment.onResume", "isHidden = %s", isHidden); + + // As a fragment's onResume() is tied to the containing Activity's onResume(), being resumed is + // not equivalent to becoming visible. + // For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + // will be called but it is not visible. + if (!isHidden) { + onFragmentShown(); + } + } + + @Override + public void onPause() { + super.onPause(); + LogUtil.enterBlock("NewCallLogFragment.onPause"); + + onFragmentHidden(); + } - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.attachUi(this); + @Override + public void onHiddenChanged(boolean hidden) { + super.onHiddenChanged(hidden); + LogUtil.i("NewCallLogFragment.onHiddenChanged", "hidden = %s", hidden); - // TODO(zachh): Consider doing this when fragment becomes visible. - refreshAnnotatedCallLog(true /* checkDirty */); + if (hidden) { + onFragmentHidden(); + } else { + onFragmentShown(); + } + } + + /** + * To be called when the fragment becomes visible. + * + * <p>Note that for a fragment, being resumed is not equivalent to becoming visible. + * + * <p>For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + * will be called but it is not visible. + */ + private void onFragmentShown() { + registerRefreshAnnotatedCallLogReceiver(); + + CallLogComponent.get(getContext()) + .getRefreshAnnotatedCallLogNotifier() + .notify(/* checkDirty = */ true); // There are some types of data that we show in the call log that are not represented in the // AnnotatedCallLog. For example, CP2 information for invalid numbers can sometimes only be @@ -123,18 +141,22 @@ public final class NewCallLogFragment extends Fragment .postDelayed(setShouldMarkCallsReadTrue, MARK_ALL_CALLS_READ_WAIT_MILLIS); } - @Override - public void onPause() { - super.onPause(); - - LogUtil.enterBlock("NewCallLogFragment.onPause"); - + /** + * To be called when the fragment becomes hidden. + * + * <p>This can happen in the following two cases: + * + * <ul> + * <li>hide the fragment but keep the parent activity visible (e.g., calling {@link + * android.support.v4.app.FragmentTransaction#hide(Fragment)} in an activity, or + * <li>the parent activity is paused. + * </ul> + */ + private void onFragmentHidden() { // This is pending work that we don't actually need to follow through with. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(setShouldMarkCallsReadTrue); - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.detachUi(); + unregisterRefreshAnnotatedCallLogReceiver(); if (shouldMarkCallsRead) { Futures.addCallback( @@ -151,48 +173,31 @@ public final class NewCallLogFragment extends Fragment View view = inflater.inflate(R.layout.new_call_log_fragment, container, false); recyclerView = view.findViewById(R.id.new_call_log_recycler_view); + recyclerView.addOnScrollListener( + new RecyclerViewJankLogger( + MetricsComponent.get(getContext()).metrics(), Metrics.NEW_CALL_LOG_JANK_EVENT_NAME)); getLoaderManager().restartLoader(0, null, this); return view; } - private void refreshAnnotatedCallLog(boolean checkDirty) { - LogUtil.enterBlock("NewCallLogFragment.refreshAnnotatedCallLog"); - - // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests - // in quick succession don't result in too much work. For example, if we get 10 requests in - // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait - // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests - // are serialized in RefreshAnnotatedCallLogWorker. - // - // We might get many requests in quick succession, for example, when the simulator inserts - // hundreds of rows into the system call log, or when the data for a new call is incrementally - // written to different columns as it becomes available. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); - - refreshAnnotatedCallLogRunnable = - () -> { - ListenableFuture<Void> future = - checkDirty - ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() - : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); - refreshAnnotatedCallLogListener.listen( - getContext(), - future, - unused -> {}, - throwable -> { - throw new RuntimeException(throwable); - }); - }; - ThreadUtil.getUiThreadHandler() - .postDelayed(refreshAnnotatedCallLogRunnable, REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS); + private void registerRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewCallLogFragment.registerRefreshAnnotatedCallLogReceiver"); + + LocalBroadcastManager.getInstance(getContext()) + .registerReceiver( + refreshAnnotatedCallLogReceiver, RefreshAnnotatedCallLogReceiver.getIntentFilter()); } - @Override - public void invalidateUi() { - LogUtil.enterBlock("NewCallLogFragment.invalidateUi"); - refreshAnnotatedCallLog(false /* checkDirty */); + private void unregisterRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewCallLogFragment.unregisterRefreshAnnotatedCallLogReceiver"); + + // Cancel pending work as we don't need it any more. + CallLogComponent.get(getContext()).getRefreshAnnotatedCallLogNotifier().cancel(); + + LocalBroadcastManager.getInstance(getContext()) + .unregisterReceiver(refreshAnnotatedCallLogReceiver); } @Override diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java index c85a9fddd..a13f2e333 100644 --- a/java/com/android/dialer/calllog/ui/menu/Modules.java +++ b/java/com/android/dialer/calllog/ui/menu/Modules.java @@ -51,7 +51,7 @@ final class Modules { PhoneNumberHelper.canPlaceCallsTo(normalizedNumber, row.numberPresentation()); if (canPlaceCalls) { - addModuleForVideoOrAudioCall(context, modules, row, normalizedNumber); + addModuleForCalls(context, modules, row, normalizedNumber); SharedModules.maybeAddModuleForSendingTextMessage( context, modules, normalizedNumber, row.numberAttributes().getIsBlocked()); } @@ -90,12 +90,12 @@ final class Modules { return modules; } - private static void addModuleForVideoOrAudioCall( + private static void addModuleForCalls( Context context, List<ContactActionModule> modules, CoalescedRow row, String normalizedNumber) { - // If a number is blocked, skip this menu item. + // Don't add call options if a number is blocked. if (row.numberAttributes().getIsBlocked()) { return; } @@ -104,28 +104,21 @@ final class Modules { TelecomUtil.composePhoneAccountHandle( row.phoneAccountComponentName(), row.phoneAccountId()); - // For a spam number, only audio calls are allowed. - if (row.numberAttributes().getIsSpam()) { - modules.add( - IntentModule.newCallModule( - context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); - return; - } + // Add an audio call item + modules.add( + IntentModule.newCallModule( + context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); - if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) { - // Add an audio call item for video calls. Clicking the top entry on the bottom sheet will - // trigger a video call. - modules.add( - IntentModule.newCallModule( - context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); - } else { - // Add a video call item for audio calls. Click the top entry on the bottom sheet will - // trigger an audio call. - // TODO(zachh): Only show video option if video capabilities present? + // Add a video item if (1) the call log entry is for a video call, and (2) the call is not spam. + if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO + && !row.numberAttributes().getIsSpam()) { modules.add( IntentModule.newVideoCallModule( context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); } + + // TODO(zachh): Also show video option if the call log entry is for an audio call but video + // capabilities are present? } private static void addModuleForAccessingCallDetails( diff --git a/java/com/android/dialer/commandline/CommandLineReceiver.java b/java/com/android/dialer/commandline/CommandLineReceiver.java index e5e78c46a..effca2e92 100644 --- a/java/com/android/dialer/commandline/CommandLineReceiver.java +++ b/java/com/android/dialer/commandline/CommandLineReceiver.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.text.TextUtils; import com.android.dialer.buildtype.BuildType; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.commandline.Command.IllegalCommandLineArgumentException; import com.android.dialer.common.LogUtil; import com.google.common.util.concurrent.FutureCallback; @@ -44,7 +45,7 @@ public class CommandLineReceiver extends BroadcastReceiver { LogUtil.e("CommandLineReceiver", "missing tag"); return; } - if (!LogUtil.isDebugEnabled() && BuildType.get() != BuildType.BUGFOOD) { + if (!LogUtil.isDebugEnabled() && BuildType.get() != Type.BUGFOOD) { LogUtil.i(outputTag, "DISABLED"); return; } diff --git a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java index c349d2235..3321d93f2 100644 --- a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java +++ b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java @@ -341,11 +341,22 @@ public class SpecialCharSequenceMgr { for (int slot = 0; slot < telephonyManager.getPhoneCount(); slot++) { String deviceId = telephonyManager.getDeviceId(slot); if (!TextUtils.isEmpty(deviceId)) { - addDeviceIdRow(holder, deviceId, /* showBarcode */ false); + addDeviceIdRow( + holder, + deviceId, + /* showDecimal */ + context.getResources().getBoolean(R.bool.show_device_id_in_hex_and_decimal), + /* showBarcode */ false); } } } else { - addDeviceIdRow(holder, telephonyManager.getDeviceId(), /* showBarcode */ true); + addDeviceIdRow( + holder, + telephonyManager.getDeviceId(), + /* showDecimal */ + context.getResources().getBoolean(R.bool.show_device_id_in_hex_and_decimal), + /* showBarcode */ + context.getResources().getBoolean(R.bool.show_device_id_as_barcode)); } new AlertDialog.Builder(context) @@ -361,7 +372,8 @@ public class SpecialCharSequenceMgr { return false; } - private static void addDeviceIdRow(ViewGroup holder, String deviceId, boolean showBarcode) { + private static void addDeviceIdRow( + ViewGroup holder, String deviceId, boolean showDecimal, boolean showBarcode) { if (TextUtils.isEmpty(deviceId)) { return; } @@ -378,11 +390,12 @@ public class SpecialCharSequenceMgr { // If this is the valid length IMEI or MEID (14 digits), show it in all formats, otherwise fall // back to just showing the raw hex - if (hex.length() == 14) { + if (hex.length() == 14 && showDecimal) { ((TextView) row.findViewById(R.id.deviceid_hex)).setText(hex); ((TextView) row.findViewById(R.id.deviceid_dec)).setText(getDecimalFromHex(hex)); row.findViewById(R.id.deviceid_dec_label).setVisibility(View.VISIBLE); } else { + row.findViewById(R.id.deviceid_hex_label).setVisibility(View.GONE); ((TextView) row.findViewById(R.id.deviceid_hex)).setText(deviceId); } diff --git a/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml b/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml index 53f3e5d0d..efeeb5f1e 100644 --- a/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml +++ b/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml @@ -19,6 +19,7 @@ android:layout_marginTop="?dialogPreferredPadding" android:orientation="vertical"> <TextView + android:id="@+id/deviceid_hex_label" style="@style/DeviceIdBody" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc000/bools.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_as_barcode">true</bool> +</resources> diff --git a/java/com/android/dialer/dialpadview/res/values-mcc310-mnc120/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc120/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc310-mnc120/bools.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_as_barcode">true</bool> +</resources> diff --git a/java/com/android/dialer/dialpadview/res/values-mcc311-mnc480/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc480/bools.xml new file mode 100644 index 000000000..7e549e825 --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc480/bools.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_in_hex_and_decimal">true</bool> +</resources> diff --git a/java/com/android/dialer/dialpadview/res/values-mcc311-mnc490/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc490/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc490/bools.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_as_barcode">true</bool> +</resources> diff --git a/java/com/android/dialer/dialpadview/res/values-mcc311-mnc870/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc870/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc311-mnc870/bools.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_as_barcode">true</bool> +</resources> diff --git a/java/com/android/dialer/dialpadview/res/values-mcc312-mnc530/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc312-mnc530/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc312-mnc530/bools.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_as_barcode">true</bool> +</resources> diff --git a/java/com/android/dialer/dialpadview/res/values-mcc316-mnc010/bools.xml b/java/com/android/dialer/dialpadview/res/values-mcc316-mnc010/bools.xml new file mode 100644 index 000000000..99fa7da7f --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values-mcc316-mnc010/bools.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_as_barcode">true</bool> +</resources> diff --git a/java/com/android/dialer/dialpadview/res/values/bools.xml b/java/com/android/dialer/dialpadview/res/values/bools.xml new file mode 100644 index 000000000..b7fed07e7 --- /dev/null +++ b/java/com/android/dialer/dialpadview/res/values/bools.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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> + <bool name="show_device_id_in_hex_and_decimal">false</bool> + <bool name="show_device_id_as_barcode">false</bool> +</resources> diff --git a/java/com/android/dialer/glidephotomanager/PhotoInfo.java b/java/com/android/dialer/glidephotomanager/PhotoInfo.java index e4dd87152..016221f40 100644 --- a/java/com/android/dialer/glidephotomanager/PhotoInfo.java +++ b/java/com/android/dialer/glidephotomanager/PhotoInfo.java @@ -61,13 +61,6 @@ public abstract class PhotoInfo { */ public abstract boolean isVideo(); - /** - * Should the result be circularized. - * - * <p>Defaults to true. - */ - public abstract boolean isCircular(); - /** Builder for {@link PhotoInfo} */ @AutoValue.Builder public abstract static class Builder { @@ -92,8 +85,6 @@ public abstract class PhotoInfo { public abstract Builder setIsVideo(boolean isVideo); - public abstract Builder setIsCircular(boolean isCircular); - public abstract PhotoInfo build(); } @@ -104,7 +95,6 @@ public abstract class PhotoInfo { .setIsVoicemail(false) .setIsBlocked(false) .setIsSpam(false) - .setIsCircular(true) .setIsVideo(false); } } diff --git a/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java index 10c4dfb4c..e14e604a1 100644 --- a/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java +++ b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java @@ -51,16 +51,7 @@ public class GlidePhotoManagerImpl implements GlidePhotoManager { Assert.isMainThread(); badge.assignContactUri(parseUri(photoInfo.lookupUri())); badge.setOverlay(null); - LetterTileDrawable defaultDrawable = getDefaultDrawable(photoInfo); - GlideRequest<Drawable> request = - buildRequest(GlideApp.with(badge), photoInfo) - .placeholder(defaultDrawable) // when the photo is still loading. - .fallback(defaultDrawable); // when there's nothing to load. - - if (photoInfo.isCircular()) { - request.circleCrop(); - } - + GlideRequest<Drawable> request = buildRequest(GlideApp.with(badge), photoInfo); request.into(badge); } @@ -68,21 +59,39 @@ public class GlidePhotoManagerImpl implements GlidePhotoManager { // Warning: Glide ignores extra attributes on BitmapDrawable such as tint and draw the bitmap // directly so be sure not to set tint in the XML of any drawable referenced below. - // Whether the number is blocked takes precedence over the spam status. + GlideRequest<Drawable> request; + boolean circleCrop = true; // Photos are cropped to a circle by default. + if (photoInfo.isBlocked()) { - return requestManager.load(R.drawable.ic_block_grey_48dp); - } - if (photoInfo.isSpam()) { - return requestManager.load(R.drawable.ic_report_red_48dp); - } - if (!TextUtils.isEmpty(photoInfo.photoUri())) { - return requestManager.load(parseUri(photoInfo.photoUri())); + // Whether the number is blocked takes precedence over the spam status. + request = requestManager.load(R.drawable.ic_block_grey_48dp); + + } else if (photoInfo.isSpam()) { + request = requestManager.load(R.drawable.quantum_ic_report_vd_red_24); + circleCrop = false; // The spam icon is an octagon so we don't crop it. + + } else if (!TextUtils.isEmpty(photoInfo.photoUri())) { + request = requestManager.load(parseUri(photoInfo.photoUri())); + + } else if (photoInfo.photoId() != 0) { + request = + requestManager.load(ContentUris.withAppendedId(Data.CONTENT_URI, photoInfo.photoId())); + + } else { + // load null to indicate fallback should be used. + request = requestManager.load((Object) null); } - if (photoInfo.photoId() != 0) { - return requestManager.load(ContentUris.withAppendedId(Data.CONTENT_URI, photoInfo.photoId())); + + LetterTileDrawable defaultDrawable = getDefaultDrawable(photoInfo); + request + .placeholder(defaultDrawable) // when the photo is still loading. + .fallback(defaultDrawable); // when there's nothing to load. + + if (circleCrop) { + request.circleCrop(); } - // load null to indicate fallback should be used. - return requestManager.load((Object) null); + + return request; } /** @@ -108,7 +117,7 @@ public class GlidePhotoManagerImpl implements GlidePhotoManager { LetterTileDrawable.SHAPE_CIRCLE, LetterTileDrawable.getContactTypeFromPrimitives( photoInfo.isVoicemail(), - false, // TODO(twyen):implement + photoInfo.isSpam(), photoInfo.isBusiness(), TelecomManager.PRESENTATION_ALLOWED, // TODO(twyen):implement false)); // TODO(twyen):implement diff --git a/java/com/android/dialer/logging/LoggingBindings.java b/java/com/android/dialer/logging/LoggingBindings.java index ec98ed124..ca9a0533e 100644 --- a/java/com/android/dialer/logging/LoggingBindings.java +++ b/java/com/android/dialer/logging/LoggingBindings.java @@ -20,15 +20,6 @@ import android.widget.QuickContactBadge; /** Allows the container application to gather analytics. */ public interface LoggingBindings { - String ON_CREATE_PRIMES_EVENT_NAME = "Application.onCreate"; - String ACTIVITY_ON_CREATE_PRIMES_EVENT_NAME = "GoogleDialtactsActivity.onCreate"; - String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING = - "CallList.onCallAdded_To_InCallActivity.onCreate_Incoming"; - String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING = - "CallList.onCallAdded_To_InCallActivity.onCreate_Outgoing"; - String ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "GoogleDialtactsActivity.onResume"; - String INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "IncallActivity.OnResume"; - String INCALL_ACTIVITY_ON_STOP_MEMORY_EVENT_NAME = "IncallActivity.OnStop"; /** * Logs an DialerImpression event that's not associated with a specific call. * @@ -96,13 +87,4 @@ public interface LoggingBindings { /** Logs successful People Api lookup result */ void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode); - - /** Log start a latency timer */ - void logStartLatencyTimer(String timerEventName); - - /** Log end a latency timer */ - void logStopLatencyTimer(String timerEventName); - - /** Log get a memory snapshot */ - void logRecordMemory(String memoryEventName); } diff --git a/java/com/android/dialer/logging/LoggingBindingsStub.java b/java/com/android/dialer/logging/LoggingBindingsStub.java index 74ac294fc..2dbcc3ffb 100644 --- a/java/com/android/dialer/logging/LoggingBindingsStub.java +++ b/java/com/android/dialer/logging/LoggingBindingsStub.java @@ -61,13 +61,4 @@ public class LoggingBindingsStub implements LoggingBindings { @Override public void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode) {} - - @Override - public void logStartLatencyTimer(String timerEventName) {} - - @Override - public void logStopLatencyTimer(String timerEventName) {} - - @Override - public void logRecordMemory(String memoryEventName) {} } diff --git a/java/com/android/dialer/main/MainActivityPeer.java b/java/com/android/dialer/main/MainActivityPeer.java index c1a328a65..9c5627be8 100644 --- a/java/com/android/dialer/main/MainActivityPeer.java +++ b/java/com/android/dialer/main/MainActivityPeer.java @@ -26,6 +26,8 @@ public interface MainActivityPeer { void onActivityResume(); + void onUserLeaveHint(); + void onActivityStop(); void onActivityDestroyed(); diff --git a/java/com/android/dialer/main/impl/AndroidManifest.xml b/java/com/android/dialer/main/impl/AndroidManifest.xml index 6b7475f97..972c9d929 100644 --- a/java/com/android/dialer/main/impl/AndroidManifest.xml +++ b/java/com/android/dialer/main/impl/AndroidManifest.xml @@ -29,7 +29,7 @@ android:launchMode="singleTask" android:name="com.android.dialer.main.impl.MainActivity" android:resizeableActivity="true" - android:theme="@style/NuiMainActivityTheme" + android:theme="@style/NuiActivityTheme" android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"> diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java index ac2cb389e..1646becf4 100644 --- a/java/com/android/dialer/main/impl/MainActivity.java +++ b/java/com/android/dialer/main/impl/MainActivity.java @@ -82,6 +82,7 @@ public class MainActivity extends TransactionSafeActivity @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); + setIntent(intent); activePeer.onNewIntent(intent); } @@ -92,6 +93,12 @@ public class MainActivity extends TransactionSafeActivity } @Override + protected void onUserLeaveHint() { + super.onUserLeaveHint(); + activePeer.onUserLeaveHint(); + } + + @Override protected void onStop() { super.onStop(); activePeer.onActivityStop(); diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java index 9b734f40c..ccd7a4b49 100644 --- a/java/com/android/dialer/main/impl/MainSearchController.java +++ b/java/com/android/dialer/main/impl/MainSearchController.java @@ -68,8 +68,7 @@ import java.util.ArrayList; public class MainSearchController implements SearchBarListener { private static final String KEY_IS_FAB_HIDDEN = "is_fab_hidden"; - private static final String KEY_CURRENT_TAB = "current_tab"; - private static final String KEY_BOTTOM_NAV_VISIBILITY = "bottom_nav_visibility"; + private static final String KEY_TOOLBAR_SHADOW_VISIBILITY = "toolbar_shadow_visibility"; private static final String KEY_IS_TOOLBAR_EXPANDED = "is_toolbar_expanded"; private static final String KEY_IS_TOOLBAR_SLIDE_UP = "is_toolbar_slide_up"; @@ -80,25 +79,30 @@ public class MainSearchController implements SearchBarListener { private final BottomNavBar bottomNav; private final FloatingActionButton fab; private final MainToolbar toolbar; + private final View toolbarShadow; public MainSearchController( MainActivity mainActivity, BottomNavBar bottomNav, FloatingActionButton fab, - MainToolbar toolbar) { + MainToolbar toolbar, + View toolbarShadow) { this.mainActivity = mainActivity; this.bottomNav = bottomNav; this.fab = fab; this.toolbar = toolbar; + this.toolbarShadow = toolbarShadow; } /** Should be called if we're showing the dialpad because of a new ACTION_DIAL intent. */ - public void showDialpadFromNewIntent(boolean animate) { - showDialpad(animate, true); + public void showDialpadFromNewIntent() { + LogUtil.enterBlock("MainSearchController.showDialpadFromNewIntent"); + showDialpad(/* animate=*/ false, /* fromNewIntent=*/ true); } /** Shows the dialpad, hides the FAB and slides the toolbar off screen. */ public void showDialpad(boolean animate) { + LogUtil.enterBlock("MainSearchController.showDialpad"); showDialpad(animate, false); } @@ -108,17 +112,22 @@ public class MainSearchController implements SearchBarListener { fab.hide(); toolbar.slideUp(animate); toolbar.expand(animate, Optional.absent()); + toolbarShadow.setVisibility(View.VISIBLE); mainActivity.setTitle(R.string.dialpad_activity_title); FragmentTransaction transaction = mainActivity.getFragmentManager().beginTransaction(); + NewSearchFragment searchFragment = getSearchFragment(); // Show Search - if (getSearchFragment() == null) { - NewSearchFragment searchFragment = NewSearchFragment.newInstance(false); + if (searchFragment == null) { + // TODO(a bug): zero suggest results aren't actually shown but this enabled the nearby + // places promo to be shown. + searchFragment = NewSearchFragment.newInstance(/* showZeroSuggest=*/ true); transaction.add(R.id.fragment_container, searchFragment, SEARCH_FRAGMENT_TAG); } else if (!isSearchVisible()) { - transaction.show(getSearchFragment()); + transaction.show(searchFragment); } + searchFragment.setQuery("", CallInitiationType.Type.DIALPAD); // Show Dialpad if (getDialpadFragment() == null) { @@ -143,6 +152,7 @@ public class MainSearchController implements SearchBarListener { * @see {@link #closeSearch(boolean)} to "remove" the dialpad. */ private void hideDialpad(boolean animate, boolean bottomNavVisible) { + LogUtil.enterBlock("MainSearchController.hideDialpad"); Assert.checkArgument(isDialpadVisible()); fab.show(); @@ -188,6 +198,7 @@ public class MainSearchController implements SearchBarListener { /** Should be called when {@link DialpadListener#onDialpadShown()} is called. */ public void onDialpadShown() { + LogUtil.enterBlock("MainSearchController.onDialpadShown"); getDialpadFragment().slideUp(true); hideBottomNav(); } @@ -203,6 +214,7 @@ public class MainSearchController implements SearchBarListener { * </ol> */ public void onSearchListTouch() { + LogUtil.enterBlock("MainSearchController.onSearchListTouched"); if (isDialpadVisible()) { if (TextUtils.isEmpty(getDialpadFragment().getQuery())) { Logger.get(mainActivity) @@ -234,13 +246,13 @@ public class MainSearchController implements SearchBarListener { */ public boolean onBackPressed() { if (isDialpadVisible() && !TextUtils.isEmpty(getDialpadFragment().getQuery())) { - LogUtil.i("MainSearchController#onBackPressed", "Dialpad visible with query"); + LogUtil.i("MainSearchController.onBackPressed", "Dialpad visible with query"); Logger.get(mainActivity) .logImpression(DialerImpression.Type.NUI_PRESS_BACK_BUTTON_TO_HIDE_DIALPAD); hideDialpad(/* animate=*/ true, /* bottomNavVisible=*/ false); return true; } else if (isSearchVisible()) { - LogUtil.i("MainSearchController#onBackPressed", "Search is visible"); + LogUtil.i("MainSearchController.onBackPressed", "Search is visible"); Logger.get(mainActivity) .logImpression( isDialpadVisible() @@ -258,6 +270,7 @@ public class MainSearchController implements SearchBarListener { * dialpad. */ private void closeSearch(boolean animate) { + LogUtil.enterBlock("MainSearchController.closeSearch"); Assert.checkArgument(isSearchVisible()); if (isDialpadVisible()) { hideDialpad(animate, /* bottomNavVisible=*/ true); @@ -266,6 +279,7 @@ public class MainSearchController implements SearchBarListener { } showBottomNav(); toolbar.collapse(animate); + toolbarShadow.setVisibility(View.GONE); mainActivity.getFragmentManager().beginTransaction().remove(getSearchFragment()).commit(); // Clear the dialpad so the phone number isn't persisted between search sessions. @@ -311,31 +325,40 @@ public class MainSearchController implements SearchBarListener { */ @Override public void onSearchBarClicked() { + LogUtil.enterBlock("MainSearchController.onSearchBarClicked"); Logger.get(mainActivity).logImpression(DialerImpression.Type.NUI_CLICK_SEARCH_BAR); openSearch(Optional.absent()); } private void openSearch(Optional<String> query) { + LogUtil.enterBlock("MainSearchController.openSearch"); fab.hide(); toolbar.expand(/* animate=*/ true, query); toolbar.showKeyboard(); + toolbarShadow.setVisibility(View.VISIBLE); hideBottomNav(); FragmentTransaction transaction = mainActivity.getFragmentManager().beginTransaction(); + NewSearchFragment searchFragment = getSearchFragment(); // Show Search - if (getSearchFragment() == null) { - NewSearchFragment searchFragment = NewSearchFragment.newInstance(false); + if (searchFragment == null) { + // TODO(a bug): zero suggest results aren't actually shown but this enabled the nearby + // places promo to be shown. + searchFragment = NewSearchFragment.newInstance(true); transaction.add(R.id.fragment_container, searchFragment, SEARCH_FRAGMENT_TAG); } else if (!isSearchVisible()) { transaction.show(getSearchFragment()); } + searchFragment.setQuery( + query.isPresent() ? query.get() : "", CallInitiationType.Type.REGULAR_SEARCH); transaction.commit(); } @Override public void onSearchBackButtonClicked() { + LogUtil.enterBlock("MainSearchController.onSearchBackButtonClicked"); closeSearch(true); } @@ -380,6 +403,18 @@ public class MainSearchController implements SearchBarListener { return false; } + @Override + public void onUserLeaveHint() { + if (isInSearch()) { + closeSearch(false); + } + } + + @Override + public void onCallPlacedFromSearch() { + closeSearch(false); + } + public void onVoiceResults(int resultCode, Intent data) { if (resultCode == AppCompatActivity.RESULT_OK) { ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); @@ -396,15 +431,13 @@ public class MainSearchController implements SearchBarListener { public void onSaveInstanceState(Bundle bundle) { bundle.putBoolean(KEY_IS_FAB_HIDDEN, !fab.isShown()); - bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab()); - bundle.putInt(KEY_BOTTOM_NAV_VISIBILITY, bottomNav.getVisibility()); + bundle.putInt(KEY_TOOLBAR_SHADOW_VISIBILITY, toolbarShadow.getVisibility()); bundle.putBoolean(KEY_IS_TOOLBAR_EXPANDED, toolbar.isExpanded()); bundle.putBoolean(KEY_IS_TOOLBAR_SLIDE_UP, toolbar.isSlideUp()); } public void onRestoreInstanceState(Bundle savedInstanceState) { - bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); - bottomNav.setVisibility(savedInstanceState.getInt(KEY_BOTTOM_NAV_VISIBILITY)); + toolbarShadow.setVisibility(savedInstanceState.getInt(KEY_TOOLBAR_SHADOW_VISIBILITY)); if (savedInstanceState.getBoolean(KEY_IS_FAB_HIDDEN, false)) { fab.hide(); } diff --git a/java/com/android/dialer/main/impl/NewMainActivityPeer.java b/java/com/android/dialer/main/impl/NewMainActivityPeer.java index ed67df936..6f5c18623 100644 --- a/java/com/android/dialer/main/impl/NewMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/NewMainActivityPeer.java @@ -57,6 +57,9 @@ public class NewMainActivityPeer implements MainActivityPeer { public void onActivityResume() {} @Override + public void onUserLeaveHint() {} + + @Override public void onActivityStop() {} @Override diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 07c7185ae..8762f5889 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -30,16 +30,24 @@ import android.provider.CallLog.Calls; import android.provider.ContactsContract.QuickContact; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; import com.android.dialer.animation.AnimUtils; +import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.CallLogFragment; import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener; import com.android.dialer.app.calllog.CallLogNotificationsService; +import com.android.dialer.app.calllog.IntentProvider; import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; import com.android.dialer.app.list.DragDropController; import com.android.dialer.app.list.OldSpeedDialFragment; @@ -47,6 +55,8 @@ import com.android.dialer.app.list.OnDragDropListener; import com.android.dialer.app.list.OnListFragmentScrolledListener; import com.android.dialer.app.list.PhoneFavoriteSquareTileView; import com.android.dialer.app.list.RemoveView; +import com.android.dialer.callcomposer.CallComposerActivity; +import com.android.dialer.calldetails.CallDetailsActivity; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.FragmentUtils.FragmentUtilListener; @@ -65,6 +75,7 @@ import com.android.dialer.dialpadview.DialpadFragment; import com.android.dialer.dialpadview.DialpadFragment.DialpadListener; import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback; import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener; +import com.android.dialer.duo.DuoComponent; import com.android.dialer.interactions.PhoneNumberInteraction; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; @@ -81,6 +92,7 @@ import com.android.dialer.storage.StorageComponent; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.TransactionSafeActivity; +import com.android.voicemail.VoicemailComponent; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.TimeUnit; @@ -190,13 +202,18 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen MainBottomNavBarBottomNavTabListener bottomNavTabListener = new MainBottomNavBarBottomNavTabListener(mainActivity, mainActivity.getFragmentManager()); bottomNav.addOnTabSelectedListener(bottomNavTabListener); + // TODO(uabdullah): Handle case of when a sim is inserted/removed while the activity is open. + boolean showVoicemailTab = canVoicemailTabBeShown(mainActivity); + bottomNav.showVoicemail(showVoicemailTab); callLogFragmentListener = new MainCallLogFragmentListener( mainActivity, mainActivity.getContentResolver(), bottomNav, toolbar); bottomNav.addOnTabSelectedListener(callLogFragmentListener); - searchController = getNewMainSearchController(bottomNav, fab, toolbar); + searchController = + getNewMainSearchController( + bottomNav, fab, toolbar, mainActivity.findViewById(R.id.toolbar_shadow)); toolbar.setSearchBarListener(searchController); onDialpadQueryChangedListener = getNewOnDialpadQueryChangedListener(searchController); @@ -216,7 +233,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen mainActivity.findViewById(R.id.remove_view), mainActivity.findViewById(R.id.search_view_container)); - lastTabController = new LastTabController(mainActivity, bottomNav); + lastTabController = new LastTabController(mainActivity, bottomNav, showVoicemailTab); // Restore our view state if needed, else initialize as if the app opened for the first time if (savedInstanceState != null) { @@ -224,39 +241,119 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen searchController.onRestoreInstanceState(savedInstanceState); bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); } else { - showTabOnIntent(mainActivity.getIntent()); + onHandleIntent(mainActivity.getIntent()); } } + /** + * Check and return whether the voicemail tab should be shown or not. This includes the following + * criteria under which we show the voicemail tab: + * <li>The voicemail number exists (e.g we are able to dial into listen to voicemail or press and + * hold 1) + * <li>Visual voicemail is enabled from the settings tab + * <li>Visual voicemail carrier is supported by dialer + * <li>There is no voicemail carrier app installed. + * + * @param context + * @return return if voicemail tab should be shown or not depending on what the voicemail state is + * for the carrier. + */ + private static boolean canVoicemailTabBeShown(Context context) { + PhoneAccountHandle defaultUserSelectedAccount = + TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_VOICEMAIL); + + if (isVoicemailAvailable(context, defaultUserSelectedAccount)) { + return true; + } + if (VoicemailComponent.get(context) + .getVoicemailClient() + .isVoicemailEnabled(context, defaultUserSelectedAccount)) { + return true; + } + return false; + } + + /** + * Check if voicemail is enabled/accessible. + * + * @return true if voicemail is enabled and accessible. Note that this can be false "temporarily" + * after the app boot e.g if the sim isn't fully recognized. TODO(uabdullah): Possibly add a + * listener of some kind to detect when a sim is recognized. TODO(uabdullah): Move this to a + * utility class or wrap it all in a static inner class. + */ + private static boolean isVoicemailAvailable( + Context context, PhoneAccountHandle defaultUserSelectedAccount) { + + if (!TelecomUtil.hasReadPhoneStatePermission(context)) { + LogUtil.i( + "OldMainActivityPeer.isVoicemailAvailable", + "No read phone permisison or not the default dialer."); + return false; + } + + if (defaultUserSelectedAccount == null) { + // In a single-SIM phone, there is no default outgoing phone account selected by + // the user, so just call TelephonyManager#getVoicemailNumber directly. + return !TextUtils.isEmpty(getTelephonyManager(context).getVoiceMailNumber()); + } else { + return !TextUtils.isEmpty( + TelecomUtil.getVoicemailNumber(context, defaultUserSelectedAccount)); + } + } + + private static TelephonyManager getTelephonyManager(Context context) { + return context.getSystemService(TelephonyManager.class); + } + @Override public void onNewIntent(Intent intent) { LogUtil.enterBlock("OldMainActivityPeer.onNewIntent"); - showTabOnIntent(intent); + onHandleIntent(intent); } - private void showTabOnIntent(Intent intent) { - if (isShowTabIntent(intent)) { + private void onHandleIntent(Intent intent) { + // Some important implementation notes: + // 1) If the intent contains extra data to open to a specific screen (e.g. DIAL intent), when + // the user leaves that screen, they will return here and add see a blank screen unless we + // select a tab here. + // 2) Don't return early here in case the intent does contain extra data. + // 3) External intents should take priority over other intents (like Calls.CONTENT_TYPE). + if (Calls.CONTENT_TYPE.equals(intent.getType())) { + Bundle extras = intent.getExtras(); + if (extras != null && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) { + LogUtil.i("OldMainActivityPeer.onHandleIntent", "Voicemail content type intent"); + bottomNav.selectTab(TabIndex.VOICEMAIL); + Logger.get(mainActivity).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CLICKED); + } else { + LogUtil.i("OldMainActivityPeer.onHandleIntent", "Call log content type intent"); + bottomNav.selectTab(TabIndex.CALL_LOG); + } + + } else if (isShowTabIntent(intent)) { + LogUtil.i("OldMainActivityPeer.onHandleIntent", "Show tab intent"); bottomNav.selectTab(getTabFromIntent(intent)); - return; + } else if (lastTabController.isEnabled) { + LogUtil.i("OldMainActivityPeer.onHandleIntent", "Show last tab"); + lastTabController.selectLastTab(); + } else { + bottomNav.selectTab(TabIndex.SPEED_DIAL); } - if (isDialIntent(intent)) { - searchController.showDialpadFromNewIntent(false); + if (isDialOrAddCallIntent(intent)) { + LogUtil.i("OldMainActivityPeer.onHandleIntent", "Dial or add call intent"); // Dialpad will grab the intent and populate the number - return; + searchController.showDialpadFromNewIntent(); } - if (lastTabController.isEnabled) { - lastTabController.selectLastTab(); - return; + if (intent.getBooleanExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, false)) { + LogUtil.i("OldMainActivityPeer.onHandleIntent", "clearing all new voicemails"); + CallLogNotificationsService.markAllNewVoicemailsAsOld(mainActivity); } - - bottomNav.selectTab(TabIndex.SPEED_DIAL); } - /** Returns true if the given intent contains a phone number to populate the dialer with */ - private boolean isDialIntent(Intent intent) { - if (intent == null || intent.getData() == null) { + /** Returns true if the given intent is a Dial intent with data or an Add Call intent. */ + private boolean isDialOrAddCallIntent(Intent intent) { + if (intent == null) { return false; } @@ -270,7 +367,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen return true; } } - return false; + return DialpadFragment.isAddCallMode(intent); } @Override @@ -284,6 +381,18 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen .getDatabaseHelper(mainActivity) .startSmartDialUpdateThread(forceUpdate); showPostCallPrompt(); + + if (searchController.isInSearch() + || callLogAdapterOnActionModeStateChangedListener.isActionModeStateEnabled()) { + bottomNav.setVisibility(View.GONE); + } else { + bottomNav.setVisibility(View.VISIBLE); + } + } + + @Override + public void onUserLeaveHint() { + searchController.onUserLeaveHint(); } @Override @@ -328,6 +437,44 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen resultCode); if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) { searchController.onVoiceResults(resultCode, data); + } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_COMPOSER) { + if (resultCode == AppCompatActivity.RESULT_FIRST_USER) { + LogUtil.i( + "OldMainActivityPeer.onActivityResult", "returned from call composer, error occurred"); + String message = + mainActivity.getString( + R.string.call_composer_connection_failed, + data.getStringExtra(CallComposerActivity.KEY_CONTACT_NAME)); + Snackbar.make(snackbarContainer, message, Snackbar.LENGTH_LONG).show(); + } else { + LogUtil.i("OldMainActivityPeer.onActivityResult", "returned from call composer, no error"); + } + + } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) { + if (resultCode == AppCompatActivity.RESULT_OK + && data != null + && data.getBooleanExtra(CallDetailsActivity.EXTRA_HAS_ENRICHED_CALL_DATA, false)) { + String number = data.getStringExtra(CallDetailsActivity.EXTRA_PHONE_NUMBER); + int snackbarDurationMillis = 5_000; + Snackbar.make( + snackbarContainer, + mainActivity.getString(R.string.ec_data_deleted), + snackbarDurationMillis) + .setAction( + R.string.view_conversation, + v -> + mainActivity.startActivity( + IntentProvider.getSendSmsIntentProvider(number).getIntent(mainActivity))) + .setActionTextColor( + ContextCompat.getColor(mainActivity, R.color.dialer_snackbar_action_text_color)) + .show(); + } + + } else if (requestCode == ActivityRequestCodes.DIALTACTS_DUO) { + // We just returned from starting Duo for a task. Reload our reachability data since it + // may have changed after a user finished activating Duo. + DuoComponent.get(mainActivity).getDuo().reloadReachability(mainActivity); + } else { LogUtil.e("OldMainActivityPeer.onActivityResult", "Unknown request code: " + requestCode); } @@ -335,6 +482,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @Override public boolean onBackPressed() { + LogUtil.enterBlock("OldMainActivityPeer.onBackPressed"); if (searchController.onBackPressed()) { return true; } @@ -375,8 +523,11 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } public MainSearchController getNewMainSearchController( - BottomNavBar bottomNavBar, FloatingActionButton fab, MainToolbar mainToolbar) { - return new MainSearchController(mainActivity, bottomNavBar, fab, mainToolbar); + BottomNavBar bottomNavBar, + FloatingActionButton fab, + MainToolbar mainToolbar, + View toolbarShadow) { + return new MainSearchController(mainActivity, bottomNavBar, fab, mainToolbar, toolbarShadow); } public MainOnDialpadQueryChangedListener getNewOnDialpadQueryChangedListener( @@ -448,6 +599,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @Override public void onCallPlacedFromDialpad() { // TODO(calderwoodra): logging + searchController.onCallPlacedFromSearch(); } } @@ -468,6 +620,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @Override public void onCallPlacedFromSearch() { // TODO(calderwoodra): logging + searchController.onCallPlacedFromSearch(); } } @@ -482,7 +635,6 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } /** @see CallLogAdapter.OnActionModeStateChangedListener */ - // TODO(calderwoodra): What is the purpose of this listener? private static final class MainCallLogAdapterOnActionModeStateChangedListener implements CallLogAdapter.OnActionModeStateChangedListener { @@ -928,11 +1080,13 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private final Context context; private final BottomNavBar bottomNavBar; private final boolean isEnabled; + private final boolean canShowVoicemailTab; - LastTabController(Context context, BottomNavBar bottomNavBar) { + LastTabController(Context context, BottomNavBar bottomNavBar, boolean canShowVoicemailTab) { this.context = context; this.bottomNavBar = bottomNavBar; isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false); + this.canShowVoicemailTab = canShowVoicemailTab; } /** Sets the last tab if the feature is enabled, otherwise defaults to speed dial. */ @@ -944,6 +1098,12 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen .unencryptedSharedPrefs() .getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL); } + + // If the voicemail tab cannot be shown, default to showing speed dial + if (tabIndex == TabIndex.VOICEMAIL && !canShowVoicemailTab) { + tabIndex = TabIndex.SPEED_DIAL; + } + bottomNavBar.selectTab(tabIndex); } diff --git a/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java b/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java index 2945e39a9..d9a446f84 100644 --- a/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java +++ b/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java @@ -123,6 +123,10 @@ public final class BottomNavBar extends LinearLayout { } } + public void showVoicemail(boolean showTab) { + voicemail.setVisibility(showTab ? View.VISIBLE : View.GONE); + } + public void setNotificationCount(@TabIndex int tab, int count) { if (tab == TabIndex.SPEED_DIAL) { speedDial.setNotificationCount(count); diff --git a/java/com/android/dialer/main/impl/res/layout/main_activity.xml b/java/com/android/dialer/main/impl/res/layout/main_activity.xml index d8b13a6c2..4f0284126 100644 --- a/java/com/android/dialer/main/impl/res/layout/main_activity.xml +++ b/java/com/android/dialer/main/impl/res/layout/main_activity.xml @@ -68,11 +68,13 @@ layout="@layout/toolbar_layout"/> <ImageView + android:id="@+id/toolbar_shadow" android:layout_width="match_parent" android:layout_height="2dp" android:scaleType="fitXY" android:src="@drawable/search_shadow" - android:layout_below="@+id/toolbar"/> + android:layout_below="@+id/toolbar" + android:visibility="gone"/> <!-- TODO(calderwoodra): investigate what this is for and why we want it. --> <!-- Host container for the contact tile drag shadow --> diff --git a/java/com/android/dialer/glidephotomanager/impl/res/drawable/ic_report_red_48dp.xml b/java/com/android/dialer/main/impl/res/values-v27/styles.xml index 8a1ddcd99..c91cba245 100644 --- a/java/com/android/dialer/glidephotomanager/impl/res/drawable/ic_report_red_48dp.xml +++ b/java/com/android/dialer/main/impl/res/values-v27/styles.xml @@ -1,5 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> - <!-- ~ Copyright (C) 2018 The Android Open Source Project ~ @@ -15,22 +14,13 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> +<resources> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - - <item> - <shape android:shape="oval"> - <solid android:color="#A52714"/> - <size - android:height="48dp" - android:width="48dp"/> - </shape> - </item> - - <item - android:drawable="@drawable/quantum_ic_report_vd_theme_24" - android:gravity="center" - android:height="36dp" - android:width="36dp"/> - -</layer-list>
\ No newline at end of file + <!-- Used on sdk 27 and above --> + <style name="NuiActivityTheme" parent="NuiActivityThemeBase"> + <!-- Used to change the navigation bar color --> + <item name="android:windowLightNavigationBar">true</item> + <item name="android:navigationBarColor">?android:windowBackground</item> + <item name="android:navigationBarDividerColor">#E0E0E0</item> + </style> +</resources>
\ No newline at end of file diff --git a/java/com/android/dialer/main/impl/res/values/styles.xml b/java/com/android/dialer/main/impl/res/values/styles.xml index 2865f2587..47fdbac93 100644 --- a/java/com/android/dialer/main/impl/res/values/styles.xml +++ b/java/com/android/dialer/main/impl/res/values/styles.xml @@ -15,14 +15,35 @@ ~ limitations under the License --> <resources> - <style name="NuiMainActivityTheme" parent="Theme.AppCompat.Light.NoActionBar"> + + <!-- Activities should use this theme as their style --> + <style name="NuiActivityTheme" parent="NuiActivityThemeBase"/> + + <!-- Used as a procy for values-v27/styles.xml --> + <style name="NuiActivityThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> + <!-- App colors --> <item name="android:colorPrimary">@color/dialtacts_theme_color</item> <item name="android:colorPrimaryDark">@color/dialer_theme_color_dark</item> <item name="android:colorAccent">@color/dialer_secondary_color</item> + <!-- TODO(calderwoodra): figure out what this is used for, but I think it's for checkboxes --> + <item name="android:colorControlActivated">@color/dialer_theme_color</item> + + <!-- Text colors --> + <item name="android:textColorPrimary">@color/dialer_primary_text_color</item> + <item name="android:textColorSecondary">@color/dialer_secondary_text_color</item> + <item name="android:textColorLink">@color/dialer_theme_color</item> + + <!-- Themeing for material buttons and widgets --> + <item name="android:colorButtonNormal">@color/dialer_theme_color</item> + <item name="android:textAppearanceButton">@style/DialerButtonTextStyle</item> + <!-- Theme needed for DialpadFragment --> <item name="dialpad_style">@style/Dialpad.Light</item> + <!-- Custom theme for Alert Dialogs--> + <item name="android:alertDialogTheme">@style/AlertDialogTheme</item> + <!-- Required for actionmode/multiselect to render properly. --> <!-- TODO(calderwoodra): Check to see if we can remove this after NewVoicemailFragment launches --> <item name="actionModeStyle">@style/ActionModeStyle</item> diff --git a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java index 604422978..a129fca8b 100644 --- a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java +++ b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java @@ -16,13 +16,17 @@ package com.android.dialer.main.impl.toolbar; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; +import android.support.annotation.StringRes; import android.support.v7.widget.PopupMenu.OnMenuItemClickListener; import android.support.v7.widget.Toolbar; import android.util.AttributeSet; import android.view.MenuItem; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageButton; +import android.widget.RelativeLayout; import com.android.dialer.common.Assert; import com.android.dialer.util.ViewUtil; import com.google.common.base.Optional; @@ -74,22 +78,40 @@ public final class MainToolbar extends Toolbar implements OnMenuItemClickListene return; } isSlideUp = true; - animate() - .translationY(-getHeight()) - .setDuration(animate ? SLIDE_DURATION : 0) - .setInterpolator(SLIDE_INTERPOLATOR) - .start(); + ValueAnimator animator = ValueAnimator.ofFloat(0, -getHeight()); + animator.setDuration(animate ? SLIDE_DURATION : 0); + animator.setInterpolator(SLIDE_INTERPOLATOR); + animator.addUpdateListener( + new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int val = ((Float) animation.getAnimatedValue()).intValue(); + RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams(); + params.topMargin = val; + requestLayout(); + } + }); + animator.start(); } /** Slides the toolbar down and back onto the screen. */ public void slideDown(boolean animate) { Assert.checkArgument(isSlideUp); isSlideUp = false; - animate() - .translationY(0) - .setDuration(animate ? SLIDE_DURATION : 0) - .setInterpolator(SLIDE_INTERPOLATOR) - .start(); + ValueAnimator animator = ValueAnimator.ofFloat(-getHeight(), 0); + animator.setDuration(animate ? SLIDE_DURATION : 0); + animator.setInterpolator(SLIDE_INTERPOLATOR); + animator.addUpdateListener( + new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int val = ((Float) animation.getAnimatedValue()).intValue(); + RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams(); + params.topMargin = val; + requestLayout(); + } + }); + animator.start(); } /** @see SearchBarView#collapse(boolean) */ @@ -129,4 +151,8 @@ public final class MainToolbar extends Toolbar implements OnMenuItemClickListene public MainToolbarMenu getOverflowMenu() { return overflowMenu; } + + public void setHint(@StringRes int hint) { + searchBar.setHint(hint); + } } diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java index a074b5131..857c4b9c9 100644 --- a/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java +++ b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java @@ -16,6 +16,7 @@ package com.android.dialer.main.impl.toolbar; +import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; /** Useful callback for {@link SearchBarView} listeners. */ @@ -36,6 +37,12 @@ public interface SearchBarListener { /** Called when a toolbar menu item is clicked. */ boolean onMenuItemClicked(MenuItem menuItem); + /** Called when {@link AppCompatActivity#onUserLeaveHint()} is called. */ + void onUserLeaveHint(); + + /** Called when the user places a call from search (regular or dialpad). */ + void onCallPlacedFromSearch(); + /** Interface for returning voice results to the search bar. */ interface VoiceSearchResultCallback { diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java index 95929383b..37ffb9778 100644 --- a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java +++ b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -29,6 +30,7 @@ import android.util.AttributeSet; import android.view.View; import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.TextView; import com.android.dialer.animation.AnimUtils; import com.android.dialer.common.UiUtil; import com.android.dialer.util.DialerUtils; @@ -42,14 +44,15 @@ final class SearchBarView extends FrameLayout { private final float margin; private final float animationEndHeight; + private final float animationStartHeight; private SearchBarListener listener; private EditText searchBox; + private TextView searchBoxTextView; // This useful for when the query didn't actually change. We want to avoid making excessive calls // where we can since IPCs can take a long time on slow networks. private boolean skipLatestTextChange; - private int initialHeight; private boolean isExpanded; private View searchBoxCollapsed; private View searchBoxExpanded; @@ -60,6 +63,8 @@ final class SearchBarView extends FrameLayout { margin = getContext().getResources().getDimension(R.dimen.search_bar_margin); animationEndHeight = getContext().getResources().getDimension(R.dimen.expanded_search_bar_height); + animationStartHeight = + getContext().getResources().getDimension(R.dimen.collapsed_search_bar_height); } @Override @@ -67,6 +72,7 @@ final class SearchBarView extends FrameLayout { super.onFinishInflate(); clearButton = findViewById(R.id.search_clear_button); searchBox = findViewById(R.id.search_view); + searchBoxTextView = findViewById(R.id.search_box_start_search); searchBoxCollapsed = findViewById(R.id.search_box_collapsed); searchBoxExpanded = findViewById(R.id.search_box_expanded); @@ -104,7 +110,6 @@ final class SearchBarView extends FrameLayout { if (isExpanded) { return; } - initialHeight = getHeight(); int duration = animate ? ANIMATION_DURATION : 0; searchBoxExpanded.setVisibility(VISIBLE); @@ -177,7 +182,7 @@ final class SearchBarView extends FrameLayout { params.leftMargin = margin; params.rightMargin = margin; searchBoxExpanded.getLayoutParams().height = - (int) (animationEndHeight - (animationEndHeight - initialHeight) * fraction); + (int) (animationEndHeight - (animationEndHeight - animationStartHeight) * fraction); requestLayout(); } @@ -207,6 +212,11 @@ final class SearchBarView extends FrameLayout { UiUtil.openKeyboardFrom(getContext(), searchBox); } + public void setHint(@StringRes int hint) { + searchBox.setHint(hint); + searchBoxTextView.setText(hint); + } + /** Handles logic for text changes in the search box. */ private class SearchBoxTextWatcher implements TextWatcher { diff --git a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml index 4e49accae..3bd71b63a 100644 --- a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml +++ b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml @@ -17,7 +17,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/search_box_expanded" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/expanded_search_bar_height" android:visibility="invisible"> <ImageButton diff --git a/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml b/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml index 59cc35451..378b20f47 100644 --- a/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml +++ b/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml @@ -17,7 +17,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" + android:layout_height="@dimen/expanded_search_bar_height" android:background="@color/dialer_theme_color" app:contentInsetStart="0dp" app:contentInsetEnd="0dp"> @@ -27,6 +27,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/search_bar_margin" + android:minHeight="@dimen/collapsed_search_bar_height" android:background="@drawable/rounded_corner" android:elevation="4dp"> @@ -58,8 +59,8 @@ android:layout_marginStart="8dp" android:layout_centerVertical="true" android:fontFamily="sans-serif" - android:hint="@string/dialer_hint_find_contact" - android:textColorHint="@color/dialer_secondary_text_color" + android:text="@string/dialer_hint_find_contact" + android:textColor="@color/dialer_secondary_text_color" android:textSize="16dp"/> <ImageView @@ -128,4 +129,4 @@ android:textSize="16sp"/> </LinearLayout> </com.android.dialer.app.list.RemoveView> -</com.android.dialer.main.impl.toolbar.MainToolbar>
\ No newline at end of file +</com.android.dialer.main.impl.toolbar.MainToolbar> diff --git a/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml index f54f053da..ed6f197fa 100644 --- a/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml +++ b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml @@ -16,5 +16,7 @@ --> <resources> <dimen name="search_bar_margin">8dp</dimen> - <dimen name="expanded_search_bar_height">60dp</dimen> + <dimen name="collapsed_search_bar_height">48dp</dimen> + <!-- collapsed + margin * 2 --> + <dimen name="expanded_search_bar_height">64dp</dimen> </resources>
\ No newline at end of file diff --git a/java/com/android/dialer/metrics/Metrics.java b/java/com/android/dialer/metrics/Metrics.java index 3922a8cfa..9488f3068 100644 --- a/java/com/android/dialer/metrics/Metrics.java +++ b/java/com/android/dialer/metrics/Metrics.java @@ -17,17 +17,34 @@ package com.android.dialer.metrics; import android.app.Application; -import android.content.Context; /** Logs metrics. */ public interface Metrics { + String APPLICATION_ON_CREATE_EVENT_NAME = "Application.onCreate"; + String DIALTACTS_ON_CREATE_EVENT_NAME = "GoogleDialtactsActivity.onCreate"; + String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING = + "CallList.onCallAdded_To_InCallActivity.onCreate_Incoming"; + String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING = + "CallList.onCallAdded_To_InCallActivity.onCreate_Outgoing"; + String DIALTACTS_ON_RESUME_MEMORY_EVENT_NAME = "GoogleDialtactsActivity.onResume"; + String INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME = "IncallActivity.OnResume"; + String INCALL_ACTIVITY_ON_STOP_MEMORY_EVENT_NAME = "IncallActivity.OnStop"; + String OLD_CALL_LOG_JANK_EVENT_NAME = "OldCallLog.Jank"; + String NEW_CALL_LOG_JANK_EVENT_NAME = "NewCallLog.Jank"; + /** Start a timer. */ - void startTimer(Context context, String timerEventName); + void startTimer(String timerEventName); /** Stop a timer. */ void stopTimer(String timerEventName); + /** Start a jank recorder. */ + void startJankRecorder(String eventName); + + /** Stop a jank recorder. */ + void stopJankRecorder(String eventName); + /** Record memory. */ void recordMemory(String memoryEventName); diff --git a/java/com/android/dialer/metrics/StubMetrics.java b/java/com/android/dialer/metrics/StubMetrics.java index 114eb4308..99c3d7691 100644 --- a/java/com/android/dialer/metrics/StubMetrics.java +++ b/java/com/android/dialer/metrics/StubMetrics.java @@ -16,7 +16,6 @@ package com.android.dialer.metrics; -import android.content.Context; import javax.inject.Inject; /** Stub {@link Metrics}. */ @@ -26,11 +25,17 @@ public final class StubMetrics implements Metrics { StubMetrics() {} @Override - public void startTimer(Context context, String timerEventName) {} + public void startTimer(String timerEventName) {} @Override public void stopTimer(String timerEventName) {} @Override + public void startJankRecorder(String eventName) {} + + @Override + public void stopJankRecorder(String eventName) {} + + @Override public void recordMemory(String memoryEventName) {} } diff --git a/java/com/android/dialer/metrics/jank/RecyclerViewJankLogger.java b/java/com/android/dialer/metrics/jank/RecyclerViewJankLogger.java new file mode 100644 index 000000000..c9f389285 --- /dev/null +++ b/java/com/android/dialer/metrics/jank/RecyclerViewJankLogger.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.metrics.jank; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.OnScrollListener; +import com.android.dialer.metrics.Metrics; + +/** Logs jank for {@link RecyclerView} scrolling events. */ +public final class RecyclerViewJankLogger extends OnScrollListener { + + private final Metrics metrics; + private final String eventName; + + private boolean isScrolling; + + public RecyclerViewJankLogger(Metrics metrics, String eventName) { + this.metrics = metrics; + this.eventName = eventName; + } + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (!isScrolling && newState == RecyclerView.SCROLL_STATE_DRAGGING) { + isScrolling = true; + metrics.startJankRecorder(eventName); + } else if (isScrolling && newState == RecyclerView.SCROLL_STATE_IDLE) { + isScrolling = false; + metrics.stopJankRecorder(eventName); + } + } +} diff --git a/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java b/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java index 79abff08e..81f6b607c 100644 --- a/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java +++ b/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java @@ -22,7 +22,11 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.support.annotation.VisibleForTesting; import com.android.dialer.common.LogUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.regex.Pattern; /** @@ -30,13 +34,14 @@ import java.util.regex.Pattern; */ public class MotorolaHiddenMenuKeySequence { private static final String EXTRA_HIDDEN_MENU_CODE = "HiddenMenuCode"; + private static MotorolaHiddenMenuKeySequence instance = null; - private static String[] hiddenKeySequenceArray = null; - private static String[] hiddenKeySequenceIntentArray = null; - private static String[] hiddenKeyPatternArray = null; - private static String[] hiddenKeyPatternIntentArray = null; - private static boolean featureHiddenMenuEnabled = false; + @VisibleForTesting final List<String> hiddenKeySequences = new ArrayList<>(); + @VisibleForTesting final List<String> hiddenKeySequenceIntents = new ArrayList<>(); + @VisibleForTesting final List<String> hiddenKeyPatterns = new ArrayList<>(); + @VisibleForTesting final List<String> hiddenKeyPatternIntents = new ArrayList<>(); + @VisibleForTesting boolean featureHiddenMenuEnabled = false; /** * Handle input char sequence. @@ -46,8 +51,7 @@ public class MotorolaHiddenMenuKeySequence { * @return true if the input matches any pattern */ static boolean handleCharSequence(Context context, String input) { - getInstance(context); - if (!featureHiddenMenuEnabled) { + if (!getInstance(context).featureHiddenMenuEnabled) { return false; } return handleKeySequence(context, input) || handleKeyPattern(context, input); @@ -66,60 +70,81 @@ public class MotorolaHiddenMenuKeySequence { return instance; } - private MotorolaHiddenMenuKeySequence(Context context) { - featureHiddenMenuEnabled = MotorolaUtils.isSupportingHiddenMenu(context); - // In case we do have a SPN from resource we need to match from service; otherwise we are - // free to go - if (featureHiddenMenuEnabled) { - - hiddenKeySequenceArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence); - hiddenKeySequenceIntentArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence_intents); - hiddenKeyPatternArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern); - hiddenKeyPatternIntentArray = - context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern_intents); - - if (hiddenKeySequenceArray.length != hiddenKeySequenceIntentArray.length - || hiddenKeyPatternArray.length != hiddenKeyPatternIntentArray.length - || (hiddenKeySequenceArray.length == 0 && hiddenKeyPatternArray.length == 0)) { - LogUtil.e( - "MotorolaHiddenMenuKeySequence", - "the key sequence array is not matching, turn off feature." - + "key sequence: %d != %d, key pattern %d != %d", - hiddenKeySequenceArray.length, - hiddenKeySequenceIntentArray.length, - hiddenKeyPatternArray.length, - hiddenKeyPatternIntentArray.length); - featureHiddenMenuEnabled = false; - } + @VisibleForTesting + MotorolaHiddenMenuKeySequence(Context context) { + if (MotorolaUtils.isSupportingHiddenMenu(context)) { + Collections.addAll( + hiddenKeySequences, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence)); + Collections.addAll( + hiddenKeySequenceIntents, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence_intents)); + Collections.addAll( + hiddenKeyPatterns, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern)); + Collections.addAll( + hiddenKeyPatternIntents, + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern_intents)); + featureHiddenMenuEnabled = true; + } + + if ("tracfone".equals(System.getProperty("ro.carrier"))) { + addHiddenKeySequence("#83865625#", "com.motorola.extensions.TFUnlock"); + addHiddenKeySequence("#83782887#", "com.motorola.extensions.TFStatus"); + featureHiddenMenuEnabled = true; } + + if (hiddenKeySequences.size() != hiddenKeySequenceIntents.size() + || hiddenKeyPatterns.size() != hiddenKeyPatternIntents.size() + || (hiddenKeySequences.isEmpty() && hiddenKeyPatterns.isEmpty())) { + LogUtil.e( + "MotorolaHiddenMenuKeySequence", + "the key sequence array is not matching, turn off feature." + + "key sequence: %d != %d, key pattern %d != %d", + hiddenKeySequences.size(), + hiddenKeySequenceIntents.size(), + hiddenKeyPatterns.size(), + hiddenKeyPatternIntents.size()); + featureHiddenMenuEnabled = false; + } + } + + private void addHiddenKeySequence(String keySequence, String intentAction) { + hiddenKeySequences.add(keySequence); + hiddenKeySequenceIntents.add(intentAction); } private static boolean handleKeyPattern(Context context, String input) { + MotorolaHiddenMenuKeySequence instance = getInstance(context); + int len = input.length(); - if (len <= 3 || hiddenKeyPatternArray == null || hiddenKeyPatternIntentArray == null) { + if (len <= 3 + || instance.hiddenKeyPatterns == null + || instance.hiddenKeyPatternIntents == null) { return false; } - for (int i = 0; i < hiddenKeyPatternArray.length; i++) { - if ((Pattern.compile(hiddenKeyPatternArray[i])).matcher(input).matches()) { - return sendIntent(context, input, hiddenKeyPatternIntentArray[i]); + for (int i = 0; i < instance.hiddenKeyPatterns.size(); i++) { + if (Pattern.matches(instance.hiddenKeyPatterns.get(i), input)) { + return sendIntent(context, input, instance.hiddenKeyPatternIntents.get(i)); } } return false; } private static boolean handleKeySequence(Context context, String input) { + MotorolaHiddenMenuKeySequence instance = getInstance(context); + int len = input.length(); - if (len <= 3 || hiddenKeySequenceArray == null || hiddenKeySequenceIntentArray == null) { + if (len <= 3 + || instance.hiddenKeySequences == null + || instance.hiddenKeySequenceIntents == null) { return false; } - for (int i = 0; i < hiddenKeySequenceArray.length; i++) { - if (hiddenKeySequenceArray[i].equals(input)) { - return sendIntent(context, input, hiddenKeySequenceIntentArray[i]); + for (int i = 0; i < instance.hiddenKeySequences.size(); i++) { + if (instance.hiddenKeySequences.get(i).equals(input)) { + return sendIntent(context, input, instance.hiddenKeySequenceIntents.get(i)); } } return false; diff --git a/java/com/android/dialer/oem/MotorolaUtils.java b/java/com/android/dialer/oem/MotorolaUtils.java index c1e2da256..1446a0219 100644 --- a/java/com/android/dialer/oem/MotorolaUtils.java +++ b/java/com/android/dialer/oem/MotorolaUtils.java @@ -43,7 +43,8 @@ public class MotorolaUtils { // package is enabled. @VisibleForTesting public static final String WIFI_CALL_PACKAGE_NAME = "com.motorola.sprintwfc"; // Thi is used to check if a Motorola device supports hidden menu feature. - private static final String HIDDEN_MENU_FEATURE = "com.motorola.software.sprint.hidden_menu"; + @VisibleForTesting + static final String HIDDEN_MENU_FEATURE = "com.motorola.software.sprint.hidden_menu"; private static boolean hasCheckedSprintWifiCall; private static boolean supportSprintWifiCall; diff --git a/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml b/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml index c5cb0d1f7..417a4b845 100644 --- a/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml +++ b/java/com/android/dialer/oem/res/values-mcc310-mnc120/motorola_config.xml @@ -17,4 +17,60 @@ <resources> <bool name="motorola_sprint_hd_codec">true</bool> + + <!-- Hidden menu configuration for Motorola. --> + <!-- This defines the specific key sequence that will be caught in the SpecialCharSequenceMgr + such as, ##OMADM# --> + <string-array name="motorola_hidden_menu_key_sequence"> + <item>##66236#</item> <!--##OMADM#--> + <item>##2539#</item> <!--##AKEY#--> + <item>##786#</item> <!--##RTN#--> + <item>##72786#</item> <!--##SCRTN#--> + <item>##3282#</item> <!--##DATA#--> + <item>##33284#</item> <!--##DEBUG#--> + <item>##3424#</item> <!--##DIAG#--> + <item>##564#</item> <!--##LOG#--> + <item>##4567257#</item> <!--##GLMSCLR#--> + <item>##873283#</item> <!--##UPDATE#--> + <item>##6343#</item> <!--##MEID#--> + <item>##27263#</item> <!--##BRAND#--> + <item>##258#</item> <!--##BLV#--> + <item>##8422#</item> <!--##UICC#--> + <item>##4382#</item> <!--CMAS/WEA--> + </string-array> + + <string name="motorola_hidden_menu_intent">com.motorola.intent.action.LAUNCH_HIDDEN_MENU</string> + + <!-- This defines the intents that will be send out when the key sequence is matched, this must be + in the same order with he KeySequence array. --> + <string-array name="motorola_hidden_menu_key_sequence_intents"> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>com.motorola.android.intent.action.omadm.sprint.hfa</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + <item>@string/motorola_hidden_menu_intent</item> + </string-array> + + <!-- This defines the specific key patterns that will be caught in the SpecialCharSequenceMgr + such as, ##[0-9]{3,7}# --> + <string-array name="motorola_hidden_menu_key_pattern"> + <!--##MSL#, here MSL is 6 digits SPC code, ##OTKSL#, OTKSL is also digits code --> + <item>##[0-9]{6}#</item> + </string-array> + + <!-- This defines the intents that will be send out when the key sequence is matched, this must be + in the same order with he KeyPattern array. --> + <string-array name="motorola_hidden_menu_key_pattern_intents"> + <item>@string/motorola_hidden_menu_intent</item> + </string-array> </resources>
\ No newline at end of file diff --git a/java/com/android/dialer/oem/res/values/motorola_config.xml b/java/com/android/dialer/oem/res/values/motorola_config.xml index ba451e715..fd9cee0a9 100644 --- a/java/com/android/dialer/oem/res/values/motorola_config.xml +++ b/java/com/android/dialer/oem/res/values/motorola_config.xml @@ -20,59 +20,26 @@ <bool name="motorola_sprint_hd_codec">false</bool> <!-- Hidden menu configuration for Motorola. --> - <!-- This defines the specific key seuquence that will be catched in the SpecialCharSequenceMgr + <!-- This defines the specific key sequence that will be caught in the SpecialCharSequenceMgr such as, ##OMADM# --> <string-array name="motorola_hidden_menu_key_sequence"> - <item>##66236#</item> <!--##OMADM#--> - <item>##2539#</item> <!--##AKEY#--> - <item>##786#</item> <!--##RTN#--> - <item>##72786#</item> <!--##SCRTN#--> - <item>##3282#</item> <!--##DATA#--> - <item>##33284#</item> <!--##DEBUG#--> - <item>##3424#</item> <!--##DIAG#--> - <item>##564#</item> <!--##LOG#--> - <item>##4567257#</item> <!--##GLMSCLR#--> - <item>##873283#</item> <!--##UPDATE#--> - <item>##6343#</item> <!--##MEID#--> - <item>##27263#</item> <!--##BRAND#--> - <item>##258#</item> <!--##BLV#--> - <item>##8422#</item> <!--##UICC#--> - <item>##4382#</item> <!--CMAS/WEA--> </string-array> - <string name="motorola_hidden_menu_intent">com.motorola.intent.action.LAUNCH_HIDDEN_MENU</string> + <string name="motorola_hidden_menu_intent"></string> - <!-- This defines the intents that will be send out when the key quence is matched, this must be + <!-- This defines the intents that will be send out when the key sequence is matched, this must be in the same order with he KeySequence array. --> <string-array name="motorola_hidden_menu_key_sequence_intents"> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>com.motorola.android.intent.action.omadm.sprint.hfa</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> - <item>@string/motorola_hidden_menu_intent</item> </string-array> - <!-- This defines the specific key patterns that will be catched in the SpecialCharSequenceMgr + <!-- This defines the specific key patterns that will be caught in the SpecialCharSequenceMgr such as, ##[0-9]{3,7}# --> <string-array name="motorola_hidden_menu_key_pattern"> - <!--##MSL#, here MSL is 6 digits SPC code, ##OTKSL#, OTKSL is also digits code --> - <item>##[0-9]{6}#</item> </string-array> - <!-- This defines the intents that will be send out when the key quence is matched, this must be + <!-- This defines the intents that will be send out when the key sequence is matched, this must be in the same order with he KeyPattern array. --> <string-array name="motorola_hidden_menu_key_pattern_intents"> - <item>@string/motorola_hidden_menu_intent</item> </string-array> <!-- This defines the provider names for cequint callerid applications @@ -80,4 +47,4 @@ <string-array name="cequint_providers"> <item>com.cequint.ecid</item> </string-array> -</resources>
\ No newline at end of file +</resources> diff --git a/java/com/android/dialer/phonelookup/PhoneLookup.java b/java/com/android/dialer/phonelookup/PhoneLookup.java index 76ff98e7c..a7974ad10 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/PhoneLookup.java @@ -84,14 +84,5 @@ public interface PhoneLookup<T> { ListenableFuture<Void> onSuccessfulBulkUpdate(); @MainThread - void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks); - - /** - * Methods which may optionally be called as a result of a phone lookup's content observer firing. - */ - interface ContentObserverCallbacks { - @MainThread - void markDirtyAndNotify(Context appContext); - } + void registerContentObservers(Context appContext); } diff --git a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java index 2271c7580..b6b02e135 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java @@ -22,6 +22,7 @@ import android.support.annotation.WorkerThread; import android.util.ArraySet; import com.android.dialer.DialerPhoneNumber; import com.android.dialer.blocking.FilteredNumberCompat; +import com.android.dialer.calllog.observer.MarkDirtyObserver; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; @@ -51,13 +52,16 @@ public final class DialerBlockedNumberPhoneLookup implements PhoneLookup<DialerB private final Context appContext; private final ListeningExecutorService executorService; + private final MarkDirtyObserver markDirtyObserver; @Inject DialerBlockedNumberPhoneLookup( @ApplicationContext Context appContext, - @BackgroundExecutor ListeningExecutorService executorService) { + @BackgroundExecutor ListeningExecutorService executorService, + MarkDirtyObserver markDirtyObserver) { this.appContext = appContext; this.executorService = executorService; + this.markDirtyObserver = markDirtyObserver; } @Override @@ -165,13 +169,12 @@ public final class DialerBlockedNumberPhoneLookup implements PhoneLookup<DialerB } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { appContext .getContentResolver() .registerContentObserver( FilteredNumber.CONTENT_URI, true, // FilteredNumberProvider notifies on the item - new MarkDirtyObserver(appContext, contentObserverCallbacks)); + markDirtyObserver); } } diff --git a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java index e0ff995e7..d791e9b9e 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java @@ -27,6 +27,7 @@ import android.support.annotation.WorkerThread; import android.util.ArraySet; import com.android.dialer.DialerPhoneNumber; import com.android.dialer.blocking.FilteredNumberCompat; +import com.android.dialer.calllog.observer.MarkDirtyObserver; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; @@ -54,13 +55,16 @@ public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlocked private final Context appContext; private final ListeningExecutorService executorService; + private final MarkDirtyObserver markDirtyObserver; @Inject SystemBlockedNumberPhoneLookup( @ApplicationContext Context appContext, - @BackgroundExecutor ListeningExecutorService executorService) { + @BackgroundExecutor ListeningExecutorService executorService, + MarkDirtyObserver markDirtyObserver) { this.appContext = appContext; this.executorService = executorService; + this.markDirtyObserver = markDirtyObserver; } @Override @@ -166,8 +170,7 @@ public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlocked } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { if (VERSION.SDK_INT < VERSION_CODES.N) { return; } @@ -176,6 +179,6 @@ public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlocked .registerContentObserver( BlockedNumbers.CONTENT_URI, true, // BlockedNumbers notifies on the item - new MarkDirtyObserver(appContext, contentObserverCallbacks)); + markDirtyObserver); } } diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java index b77a86ca0..abe18f7dc 100644 --- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java @@ -179,10 +179,9 @@ public final class CompositePhoneLookup implements PhoneLookup<PhoneLookupInfo> @Override @MainThread - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { for (PhoneLookup phoneLookup : phoneLookups) { - phoneLookup.registerContentObservers(appContext, contentObserverCallbacks); + phoneLookup.registerContentObservers(appContext); } } } diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java index e051f473c..8db308892 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java @@ -619,8 +619,7 @@ public final class Cp2LocalPhoneLookup implements PhoneLookup<Cp2Info> { } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { // Do nothing since CP2 changes are too noisy. } diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java index cc4fbf19f..7efe039eb 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java @@ -237,8 +237,7 @@ public final class Cp2RemotePhoneLookup implements PhoneLookup<Cp2Info> { } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + public void registerContentObservers(Context appContext) { // No content observer needed for remote contacts } } diff --git a/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java b/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java index 9f0b5cf52..7661a15da 100644 --- a/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/spam/SpamPhoneLookup.java @@ -152,8 +152,8 @@ public final class SpamPhoneLookup implements PhoneLookup<SpamInfo> { } @Override - public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { - // No content observer needed for spam info + public void registerContentObservers(Context appContext) { + // No content observer can be registered as Spam is not based on a content provider. + // Each Spam implementation should be responsible for notifying any data changes. } } diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java index 04226552d..e99533a40 100644 --- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java +++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java @@ -15,7 +15,6 @@ package com.android.dialer.phonenumbercache; import android.annotation.TargetApi; -import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -299,12 +298,12 @@ public class ContactInfoHelper { VERSION.SDK_INT >= VERSION_CODES.N ? Directory.ENTERPRISE_CONTENT_URI : Directory.CONTENT_URI; - ContentResolver cr = context.getContentResolver(); - Cursor cursor = cr.query(uri, new String[] {Directory._ID}, null, null, null); - int idIndex = cursor.getColumnIndex(Directory._ID); + Cursor cursor = + context.getContentResolver().query(uri, new String[] {Directory._ID}, null, null, null); if (cursor == null) { return remoteDirectories; } + int idIndex = cursor.getColumnIndex(Directory._ID); try { while (cursor.moveToNext()) { long directoryId = cursor.getLong(idIndex); diff --git a/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java b/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java index 39c1187a4..dbe11dd96 100644 --- a/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java +++ b/java/com/android/dialer/searchfragment/directories/DirectoriesCursorLoader.java @@ -31,7 +31,12 @@ import com.google.auto.value.AutoValue; import java.util.ArrayList; import java.util.List; -/** {@link CursorLoader} to load the list of all directories (local and remote). */ +/** + * {@link CursorLoader} to load information about all directories (local and remote). + * + * <p>Information about a directory includes its ID, display name, etc, but doesn't include the + * contacts in it. + */ public final class DirectoriesCursorLoader extends CursorLoader { public static final String[] PROJECTION = { diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java index 4be96fe58..ff321fc75 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactViewHolder.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.dialer.searchfragment.remote; +package com.android.dialer.searchfragment.directories; import android.content.Context; import android.content.res.Resources; @@ -40,8 +40,8 @@ import com.android.dialer.searchfragment.common.QueryBoldingUtil; import com.android.dialer.searchfragment.common.R; import com.android.dialer.searchfragment.common.SearchCursor; -/** ViewHolder for a nearby place row. */ -public final class RemoteContactViewHolder extends RecyclerView.ViewHolder +/** ViewHolder for a directory contact row. */ +public final class DirectoryContactViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private final Context context; @@ -52,7 +52,7 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder private String number; - public RemoteContactViewHolder(View view) { + public DirectoryContactViewHolder(View view) { super(view); view.setOnClickListener(this); photo = view.findViewById(R.id.photo); @@ -63,8 +63,8 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder } /** - * Binds the ViewHolder with a cursor from {@link RemoteContactsCursorLoader} with the data found - * at the cursors current position. + * Binds the ViewHolder with a cursor from {@link DirectoryContactsCursorLoader} with the data + * found at the cursors current position. */ public void bind(SearchCursor cursor, String query) { number = cursor.getString(Projections.PHONE_NUMBER); diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java index 653c67041..bf0bdc057 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursor.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.dialer.searchfragment.remote; +package com.android.dialer.searchfragment.directories; import android.content.Context; import android.database.Cursor; @@ -25,23 +25,22 @@ import android.support.annotation.VisibleForTesting; import com.android.contacts.common.compat.DirectoryCompat; import com.android.dialer.common.Assert; import com.android.dialer.searchfragment.common.SearchCursor; -import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** - * {@link MergeCursor} used for combining remote directory cursors into one cursor. + * {@link MergeCursor} used for combining directory cursors into one cursor. * - * <p>Usually a device with multiple Google accounts will have multiple remote directories returned - * by {@link DirectoriesCursorLoader}, each represented as a {@link Directory}. + * <p>Usually a device with multiple Google accounts will have multiple directories returned by + * {@link DirectoriesCursorLoader}, each represented as a {@link Directory}. * * <p>This cursor merges them together with a header at the start of each cursor/list using {@link * Directory#getDisplayName()} as the header text. */ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) -public final class RemoteContactsCursor extends MergeCursor implements SearchCursor { +public final class DirectoryContactsCursor extends MergeCursor implements SearchCursor { /** * {@link SearchCursor#HEADER_PROJECTION} with {@link #COLUMN_DIRECTORY_ID} appended on the end. @@ -59,7 +58,7 @@ public final class RemoteContactsCursor extends MergeCursor implements SearchCur */ @Nullable @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static RemoteContactsCursor newInstance( + public static DirectoryContactsCursor newInstance( Context context, Cursor[] cursors, List<Directory> directories) { Assert.checkArgument( cursors.length == directories.size(), @@ -68,12 +67,12 @@ public final class RemoteContactsCursor extends MergeCursor implements SearchCur cursors.length); Cursor[] cursorsWithHeaders = insertHeaders(context, cursors, directories); if (cursorsWithHeaders.length > 0) { - return new RemoteContactsCursor(cursorsWithHeaders); + return new DirectoryContactsCursor(cursorsWithHeaders); } return null; } - private RemoteContactsCursor(Cursor[] cursors) { + private DirectoryContactsCursor(Cursor[] cursors) { super(cursors); } diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java index cf495e49c..fc36f59bb 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/directories/DirectoryContactsCursorLoader.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.dialer.searchfragment.remote; +package com.android.dialer.searchfragment.directories; import android.content.Context; import android.content.CursorLoader; @@ -37,10 +37,10 @@ import java.util.List; * Cursor loader to load extended contacts on device. * * <p>This loader performs several database queries in serial and merges the resulting cursors - * together into {@link RemoteContactsCursor}. If there are no results, the loader will return a + * together into {@link DirectoryContactsCursor}. If there are no results, the loader will return a * null cursor. */ -public final class RemoteContactsCursorLoader extends CursorLoader { +public final class DirectoryContactsCursorLoader extends CursorLoader { private static final Uri ENTERPRISE_CONTENT_FILTER_URI = Uri.withAppendedPath(Phone.CONTENT_URI, "filter_enterprise"); @@ -53,7 +53,7 @@ public final class RemoteContactsCursorLoader extends CursorLoader { private final List<Directory> directories; private final Cursor[] cursors; - public RemoteContactsCursorLoader(Context context, String query, List<Directory> directories) { + public DirectoryContactsCursorLoader(Context context, String query, List<Directory> directories) { super( context, null, @@ -71,14 +71,14 @@ public final class RemoteContactsCursorLoader extends CursorLoader { for (int i = 0; i < directories.size(); i++) { Directory directory = directories.get(i); - // Filter out local directories + // Only load contacts in the enterprise directory & remote directories. if (!DirectoryCompat.isRemoteDirectoryId(directory.getId()) && !DirectoryCompat.isEnterpriseDirectoryId(directory.getId())) { cursors[i] = null; continue; } - // Filter out invisible directories + // Filter out invisible directories. if (DirectoryCompat.isInvisibleDirectory(directory.getId())) { cursors[i] = null; continue; @@ -98,7 +98,7 @@ public final class RemoteContactsCursorLoader extends CursorLoader { // number. In this case just hide the row entirely. See a bug. cursors[i] = createMatrixCursorFilteringNullNumbers(cursor); } - return RemoteContactsCursor.newInstance(getContext(), cursors, directories); + return DirectoryContactsCursor.newInstance(getContext(), cursors, directories); } private MatrixCursor createMatrixCursorFilteringNullNumbers(Cursor cursor) { diff --git a/java/com/android/dialer/searchfragment/remote/res/values/strings.xml b/java/com/android/dialer/searchfragment/directories/res/values/strings.xml index beabba135..beabba135 100644 --- a/java/com/android/dialer/searchfragment/remote/res/values/strings.xml +++ b/java/com/android/dialer/searchfragment/directories/res/values/strings.xml diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index 2d45457d2..aff946206 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -65,9 +65,9 @@ import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.cp2.SearchContactsCursorLoader; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; +import com.android.dialer.searchfragment.directories.DirectoryContactsCursorLoader; import com.android.dialer.searchfragment.list.SearchActionViewHolder.Action; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader; -import com.android.dialer.searchfragment.remote.RemoteContactsCursorLoader; import com.android.dialer.storage.StorageComponent; import com.android.dialer.util.CallUtil; import com.android.dialer.util.DialerUtils; @@ -103,8 +103,11 @@ public final class NewSearchFragment extends Fragment private static final int CONTACTS_LOADER_ID = 0; private static final int NEARBY_PLACES_LOADER_ID = 1; - private static final int REMOTE_DIRECTORIES_LOADER_ID = 2; - private static final int REMOTE_CONTACTS_LOADER_ID = 3; + + // ID for the loader that loads info about all directories (local & remote). + private static final int DIRECTORIES_LOADER_ID = 2; + + private static final int DIRECTORY_CONTACTS_LOADER_ID = 3; private static final String KEY_QUERY = "key_query"; private static final String KEY_CALL_INITIATION_TYPE = "key_call_initiation_type"; @@ -117,15 +120,17 @@ public final class NewSearchFragment extends Fragment // for actions to add contact or send sms. private String rawNumber; private CallInitiationType.Type callInitiationType = CallInitiationType.Type.UNKNOWN_INITIATION; - private boolean remoteDirectoriesDisabledForTesting; + private boolean directoriesDisabledForTesting; + // Information about all local & remote directories (including ID, display name, etc, but not + // the contacts in them). private final List<Directory> directories = new ArrayList<>(); private final Runnable loaderCp2ContactsRunnable = () -> getLoaderManager().restartLoader(CONTACTS_LOADER_ID, null, this); private final Runnable loadNearbyPlacesRunnable = () -> getLoaderManager().restartLoader(NEARBY_PLACES_LOADER_ID, null, this); - private final Runnable loadRemoteContactsRunnable = - () -> getLoaderManager().restartLoader(REMOTE_CONTACTS_LOADER_ID, null, this); + private final Runnable loadDirectoryContactsRunnable = + () -> getLoaderManager().restartLoader(DIRECTORY_CONTACTS_LOADER_ID, null, this); private final Runnable capabilitiesUpdatedRunnable = () -> adapter.notifyDataSetChanged(); private Runnable updatePositionRunnable; @@ -184,7 +189,7 @@ public final class NewSearchFragment extends Fragment private void initLoaders() { getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this); - loadRemoteDirectoriesCursor(); + loadDirectoriesCursor(); } @Override @@ -201,10 +206,10 @@ public final class NewSearchFragment extends Fragment directoryIds.add(directory.getId()); } return new NearbyPlacesCursorLoader(getContext(), query, directoryIds); - } else if (id == REMOTE_DIRECTORIES_LOADER_ID) { + } else if (id == DIRECTORIES_LOADER_ID) { return new DirectoriesCursorLoader(getContext()); - } else if (id == REMOTE_CONTACTS_LOADER_ID) { - return new RemoteContactsCursorLoader(getContext(), query, directories); + } else if (id == DIRECTORY_CONTACTS_LOADER_ID) { + return new DirectoryContactsCursorLoader(getContext(), query, directories); } else { throw new IllegalStateException("Invalid loader id: " + id); } @@ -225,14 +230,14 @@ public final class NewSearchFragment extends Fragment } else if (loader instanceof NearbyPlacesCursorLoader) { adapter.setNearbyPlacesCursor((SearchCursor) cursor); - } else if (loader instanceof RemoteContactsCursorLoader) { - adapter.setRemoteContactsCursor((SearchCursor) cursor); + } else if (loader instanceof DirectoryContactsCursorLoader) { + adapter.setDirectoryContactsCursor((SearchCursor) cursor); } else if (loader instanceof DirectoriesCursorLoader) { directories.clear(); directories.addAll(DirectoriesCursorLoader.toDirectories(cursor)); loadNearbyPlacesCursor(); - loadRemoteContactsCursors(); + loadDirectoryContactsCursors(); } else { throw new IllegalStateException("Invalid loader: " + loader); @@ -246,8 +251,8 @@ public final class NewSearchFragment extends Fragment adapter.setContactsCursor(null); } else if (loader instanceof NearbyPlacesCursorLoader) { adapter.setNearbyPlacesCursor(null); - } else if (loader instanceof RemoteContactsCursorLoader) { - adapter.setRemoteContactsCursor(null); + } else if (loader instanceof DirectoryContactsCursorLoader) { + adapter.setDirectoryContactsCursor(null); } } @@ -264,7 +269,7 @@ public final class NewSearchFragment extends Fragment adapter.setZeroSuggestVisible(isRegularSearch()); loadCp2ContactsCursor(); loadNearbyPlacesCursor(); - loadRemoteContactsCursors(); + loadDirectoryContactsCursors(); } } @@ -306,7 +311,7 @@ public final class NewSearchFragment extends Fragment super.onDestroy(); ThreadUtil.getUiThreadHandler().removeCallbacks(loaderCp2ContactsRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable); - ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(loadDirectoryContactsRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(capabilitiesUpdatedRunnable); } @@ -342,23 +347,27 @@ public final class NewSearchFragment extends Fragment } } - // Loads remote directories. - private void loadRemoteDirectoriesCursor() { - if (!remoteDirectoriesDisabledForTesting) { - getLoaderManager().initLoader(REMOTE_DIRECTORIES_LOADER_ID, null, this); + /** Loads info about all directories (local & remote). */ + private void loadDirectoriesCursor() { + if (!directoriesDisabledForTesting) { + getLoaderManager().initLoader(DIRECTORIES_LOADER_ID, null, this); } } - // Should not be called before remote directories have finished loading. - private void loadRemoteContactsCursors() { - if (remoteDirectoriesDisabledForTesting) { + /** + * Loads contacts stored in directories. + * + * <p>Should not be called before finishing loading info about all directories (local & remote). + */ + private void loadDirectoryContactsCursors() { + if (directoriesDisabledForTesting) { return; } // Cancel existing load if one exists. - ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(loadDirectoryContactsRunnable); ThreadUtil.getUiThreadHandler() - .postDelayed(loadRemoteContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); + .postDelayed(loadDirectoryContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); } private void loadCp2ContactsCursor() { @@ -368,7 +377,11 @@ public final class NewSearchFragment extends Fragment .postDelayed(loaderCp2ContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); } - // Should not be called before remote directories (not contacts) have finished loading. + /** + * Loads nearby places. + * + * <p>Should not be called before finishing loading info about all directories (local and remote). + */ private void loadNearbyPlacesCursor() { if (!PermissionsUtil.hasLocationPermissions(getContext()) && !StorageComponent.get(getContext()) @@ -443,8 +456,8 @@ public final class NewSearchFragment extends Fragment // being untestable while it can query multiple datasources. This is a temporary fix. // TODO(a bug): Remove this method and test this fragment with multiple data sources @VisibleForTesting - public void setRemoteDirectoriesDisabled(boolean disabled) { - remoteDirectoriesDisabledForTesting = disabled; + public void setDirectoriesDisabled(boolean disabled) { + directoriesDisabledForTesting = disabled; } /** diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java index 1681097bf..462426943 100644 --- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java +++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java @@ -31,9 +31,9 @@ import com.android.dialer.common.Assert; import com.android.dialer.searchfragment.common.RowClickListener; import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.cp2.SearchContactViewHolder; +import com.android.dialer.searchfragment.directories.DirectoryContactViewHolder; import com.android.dialer.searchfragment.list.SearchCursorManager.RowType; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder; -import com.android.dialer.searchfragment.remote.RemoteContactViewHolder; import java.util.List; /** RecyclerView adapter for {@link NewSearchFragment}. */ @@ -77,7 +77,7 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> { return new HeaderViewHolder( LayoutInflater.from(context).inflate(R.layout.header_layout, root, false)); case RowType.DIRECTORY_ROW: - return new RemoteContactViewHolder( + return new DirectoryContactViewHolder( LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false)); case RowType.SEARCH_ACTION: return new SearchActionViewHolder( @@ -104,8 +104,8 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> { ((SearchContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query); } else if (holder instanceof NearbyPlaceViewHolder) { ((NearbyPlaceViewHolder) holder).bind(searchCursorManager.getCursor(position), query); - } else if (holder instanceof RemoteContactViewHolder) { - ((RemoteContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query); + } else if (holder instanceof DirectoryContactViewHolder) { + ((DirectoryContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query); } else if (holder instanceof HeaderViewHolder) { String header = searchCursorManager.getCursor(position).getString(SearchCursor.HEADER_TEXT_POSITION); @@ -200,8 +200,8 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> { } } - public void setRemoteContactsCursor(SearchCursor remoteContactsCursor) { - if (searchCursorManager.setCorpDirectoryCursor(remoteContactsCursor)) { + void setDirectoryContactsCursor(SearchCursor directoryContactsCursor) { + if (searchCursorManager.setCorpDirectoryCursor(directoryContactsCursor)) { notifyDataSetChanged(); } } diff --git a/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml b/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml deleted file mode 100644 index e52f5319e..000000000 --- a/java/com/android/dialer/searchfragment/remote/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ -<!-- - ~ Copyright (C) 2017 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> -<manifest package="com.android.dialer.searchfragment.remote"/>
\ No newline at end of file diff --git a/java/com/android/dialer/simulator/impl/RttChatBot.java b/java/com/android/dialer/simulator/impl/RttChatBot.java index 9c2989a07..b2860e387 100644 --- a/java/com/android/dialer/simulator/impl/RttChatBot.java +++ b/java/com/android/dialer/simulator/impl/RttChatBot.java @@ -16,6 +16,7 @@ package com.android.dialer.simulator.impl; +import android.annotation.TargetApi; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Random; /** Chat bot to generate remote RTT chat messages. */ +@TargetApi(28) public class RttChatBot { interface Callback { @@ -95,7 +97,6 @@ public class RttChatBot { break; case SEND_MESSAGE: String message = (String) msg.obj; - LogUtil.w("test", "type: %s, to stream: %s", message, rttTextStream); try { rttTextStream.write(message); } catch (IOException e) { diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java index c832a5051..3aa3296ea 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java +++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java @@ -16,9 +16,12 @@ package com.android.dialer.simulator.impl; +import android.annotation.TargetApi; import android.content.Context; import android.support.annotation.NonNull; +import android.support.v4.os.BuildCompat; import android.telecom.Connection; +import android.telecom.Connection.RttTextStream; import android.telecom.ConnectionRequest; import android.telecom.VideoProfile; import com.android.dialer.common.Assert; @@ -31,11 +34,14 @@ import java.util.ArrayList; import java.util.List; /** Represents a single phone call on the device. */ +@TargetApi(28) public final class SimulatorConnection extends Connection { private final List<Listener> listeners = new ArrayList<>(); private final List<Event> events = new ArrayList<>(); private final SimulatorConnectionsBank simulatorConnectionsBank; private int currentState = STATE_NEW; + private RttTextStream rttTextStream; + private RttChatBot rttChatBot; SimulatorConnection(@NonNull Context context, @NonNull ConnectionRequest request) { Assert.isNotNull(context); @@ -54,6 +60,9 @@ public final class SimulatorConnection extends Connection { getConnectionCapabilities() | CAPABILITY_SEPARATE_FROM_CONFERENCE); } } + if (BuildCompat.isAtLeastP()) { + rttTextStream = request.getRttTextStream(); + } setVideoProvider(new SimulatorVideoProvider(context, this)); simulatorConnectionsBank = SimulatorComponent.get(context).getSimulatorConnectionsBank(); } @@ -66,6 +75,10 @@ public final class SimulatorConnection extends Connection { listeners.remove(Assert.isNotNull(listener)); } + RttTextStream getRttTextStream() { + return rttTextStream; + } + @NonNull public List<Event> getEvents() { return events; @@ -101,6 +114,11 @@ public final class SimulatorConnection extends Connection { LogUtil.enterBlock("SimulatorConnection.onDisconnect"); simulatorConnectionsBank.remove(this); onEvent(new Event(Event.DISCONNECT)); + rttTextStream = null; + if (rttChatBot != null) { + rttChatBot.stop(); + rttChatBot = null; + } } @Override @@ -124,12 +142,21 @@ public final class SimulatorConnection extends Connection { @Override public void onStartRtt(@NonNull RttTextStream rttTextStream) { LogUtil.enterBlock("SimulatorConnection.onStartRtt"); + if (this.rttTextStream != null || rttChatBot != null) { + LogUtil.e("SimulatorConnection.onStartRtt", "rttTextStream or rttChatBot is not null!"); + } + this.rttTextStream = rttTextStream; + rttChatBot = new RttChatBot(rttTextStream); + rttChatBot.start(); onEvent(new Event(Event.START_RTT)); } @Override public void onStopRtt() { LogUtil.enterBlock("SimulatorConnection.onStopRtt"); + rttChatBot.stop(); + rttChatBot = null; + rttTextStream = null; onEvent(new Event(Event.STOP_RTT)); } @@ -159,6 +186,4 @@ public final class SimulatorConnection extends Connection { public interface Listener { void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event); } - - } diff --git a/java/com/android/dialer/simulator/impl/SimulatorImpl.java b/java/com/android/dialer/simulator/impl/SimulatorImpl.java index 24f34102e..c8b8af92e 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorImpl.java +++ b/java/com/android/dialer/simulator/impl/SimulatorImpl.java @@ -19,6 +19,7 @@ package com.android.dialer.simulator.impl; import android.support.v7.app.AppCompatActivity; import android.view.ActionProvider; import com.android.dialer.buildtype.BuildType; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.common.LogUtil; import com.android.dialer.simulator.Simulator; import javax.inject.Inject; @@ -33,7 +34,7 @@ final class SimulatorImpl implements Simulator { @Override public boolean shouldShow() { - return BuildType.get() == BuildType.BUGFOOD || LogUtil.isDebugEnabled(); + return BuildType.get() == Type.BUGFOOD || LogUtil.isDebugEnabled(); } @Override diff --git a/java/com/android/dialer/simulator/impl/SimulatorRttCall.java b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java index 7b0066719..352b9e4ef 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorRttCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java @@ -34,6 +34,7 @@ final class SimulatorRttCall @NonNull private final Context context; @Nullable private String connectionTag; + private RttChatBot rttChatBot; static ActionProvider getActionProvider(@NonNull Context context) { return new SimulatorSubMenu(context) @@ -112,24 +113,29 @@ final class SimulatorRttCall switch (event.type) { case Event.NONE: throw Assert.createIllegalStateFailException(); - case Event.ANSWER: - connection.setActive(); - break; case Event.REJECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED)); break; case Event.HOLD: connection.setOnHold(); break; + case Event.ANSWER: case Event.UNHOLD: connection.setActive(); break; case Event.DISCONNECT: + rttChatBot.stop(); connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); break; case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; + case Event.STATE_CHANGE: + if (Connection.stateToString(Connection.STATE_ACTIVE).equals(event.data2)) { + rttChatBot = new RttChatBot(connection.getRttTextStream()); + rttChatBot.start(); + } + break; default: LogUtil.i("SimulatorRttCall.onEvent", "unexpected event: " + event.type); break; diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java index d51e06816..c56afb21f 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java @@ -115,7 +115,7 @@ public class SimulatorSimCallManager { TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, callType == CALL_TYPE_VIDEO ? getVideoProviderHandle(context) - : getSystemPhoneAccountHandle(context)); + : getSimCallManagerHandle(context)); if (callType == CALL_TYPE_RTT) { outgoingCallExtras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true); } diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java index d4c7ee458..e59cddd51 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -22,6 +22,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.telecom.Connection; +import android.telecom.Connection.RttModifyStatus; import android.telecom.DisconnectCause; import android.view.ActionProvider; import com.android.dialer.common.Assert; @@ -223,15 +224,13 @@ final class SimulatorVoiceCall switch (event.type) { case Event.NONE: throw Assert.createIllegalStateFailException(); - case Event.ANSWER: - connection.setActive(); - break; case Event.REJECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED)); break; case Event.HOLD: connection.setOnHold(); break; + case Event.ANSWER: case Event.UNHOLD: connection.setActive(); break; @@ -244,6 +243,15 @@ final class SimulatorVoiceCall case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; + case Event.START_RTT: + // TODO(wangqi): Add random accept/decline. + boolean accept = true; + if (accept) { + connection.sendRttInitiationSuccess(); + } else { + connection.sendRttInitiationFailure(RttModifyStatus.SESSION_MODIFY_REQUEST_FAIL); + } + break; default: LogUtil.i("SimulatorVoiceCall.onEvent", "unexpected event: " + event.type); break; diff --git a/java/com/android/dialer/strictmode/StrictModeUtils.java b/java/com/android/dialer/strictmode/StrictModeUtils.java index c07138f81..27f8142d8 100644 --- a/java/com/android/dialer/strictmode/StrictModeUtils.java +++ b/java/com/android/dialer/strictmode/StrictModeUtils.java @@ -25,6 +25,7 @@ import android.preference.PreferenceManager; import android.support.annotation.AnyThread; import android.support.v4.os.UserManagerCompat; import com.android.dialer.buildtype.BuildType; +import com.android.dialer.buildtype.BuildType.Type; import com.android.dialer.function.Supplier; import com.android.dialer.storage.StorageComponent; @@ -86,7 +87,7 @@ public final class StrictModeUtils { } public static boolean isStrictModeAllowed() { - return BuildType.get() == BuildType.BUGFOOD; + return BuildType.get() == Type.BUGFOOD; } private static boolean onMainThread() { diff --git a/java/com/android/dialer/theme/res/values/dimens.xml b/java/com/android/dialer/theme/res/values/dimens.xml index e7c947782..88b8a0423 100644 --- a/java/com/android/dialer/theme/res/values/dimens.xml +++ b/java/com/android/dialer/theme/res/values/dimens.xml @@ -22,7 +22,7 @@ <dimen name="call_log_primary_text_size">16sp</dimen> <dimen name="call_log_detail_text_size">12sp</dimen> <dimen name="call_log_day_group_heading_size">14sp</dimen> - <dimen name="call_log_voicemail_transcription_text_size">16sp</dimen> + <dimen name="call_log_voicemail_transcription_text_size">14sp</dimen> <!-- Height of the call log actions section for each call log entry --> <dimen name="call_log_action_height">48dp</dimen> <dimen name="call_log_day_group_padding_top">15dp</dimen> diff --git a/java/com/android/dialer/theme/res/values/styles.xml b/java/com/android/dialer/theme/res/values/styles.xml index ac94d0687..d65d2af9c 100644 --- a/java/com/android/dialer/theme/res/values/styles.xml +++ b/java/com/android/dialer/theme/res/values/styles.xml @@ -45,7 +45,7 @@ </style> <style name="DialerButtonTextStyle" parent="@android:style/TextAppearance.Material.Widget.Button"> - <item name="android:textColor">#fff</item> + <item name="android:textColor">@color/dialer_primary_text_color_white</item> </style> <style name="DialerActionBarBaseStyle" diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java index 8b6fcbc07..9296d04e7 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java @@ -24,18 +24,16 @@ import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; +import android.support.v4.content.LocalBroadcastManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.dialer.calllog.CallLogComponent; -import com.android.dialer.calllog.CallLogFramework; -import com.android.dialer.calllog.CallLogFramework.CallLogUi; -import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker; +import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; -import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.common.concurrent.UiListener; import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent; import com.android.dialer.voicemail.listui.error.VoicemailStatus; @@ -48,24 +46,11 @@ import java.util.List; // TODO(uabdullah): Register content observer for VoicemailContract.Status.CONTENT_URI in onStart /** Fragment for Dialer Voicemail Tab. */ -public final class NewVoicemailFragment extends Fragment - implements LoaderCallbacks<Cursor>, CallLogUi { - - /* - * This is a reasonable time that it might take between related call log writes, that also - * shouldn't slow down single-writes too much. For example, when populating the database using - * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 - * call log entries. - */ - private static final long WAIT_MILLIS = 100L; - - private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; - private UiListener<Void> refreshAnnotatedCallLogListener; - @Nullable private Runnable refreshAnnotatedCallLogRunnable; - - private UiListener<ImmutableList<VoicemailStatus>> queryVoicemailStatusTableListener; +public final class NewVoicemailFragment extends Fragment implements LoaderCallbacks<Cursor> { private RecyclerView recyclerView; + private RefreshAnnotatedCallLogReceiver refreshAnnotatedCallLogReceiver; + private UiListener<ImmutableList<VoicemailStatus>> queryVoicemailStatusTableListener; public NewVoicemailFragment() { LogUtil.enterBlock("NewVoicemailFragment.NewVoicemailFragment"); @@ -77,23 +62,12 @@ public final class NewVoicemailFragment extends Fragment LogUtil.enterBlock("NewVoicemailFragment.onActivityCreated"); - CallLogComponent component = CallLogComponent.get(getContext()); - CallLogFramework callLogFramework = component.callLogFramework(); - callLogFramework.attachUi(this); - - // TODO(zachh): Use support fragment manager and add support for them in executors library. - refreshAnnotatedCallLogListener = - DialerExecutorComponent.get(getContext()) - .createUiListener( - getActivity().getFragmentManager(), "NewVoicemailFragment.refreshAnnotatedCallLog"); - + refreshAnnotatedCallLogReceiver = new RefreshAnnotatedCallLogReceiver(getContext()); queryVoicemailStatusTableListener = DialerExecutorComponent.get(getContext()) .createUiListener( getActivity().getFragmentManager(), "NewVoicemailFragment.queryVoicemailStatusTable"); - - refreshAnnotatedCallLogWorker = component.getRefreshAnnotatedCallLogWorker(); } @Override @@ -106,29 +80,69 @@ public final class NewVoicemailFragment extends Fragment public void onResume() { super.onResume(); - LogUtil.enterBlock("NewCallLogFragment.onResume"); + boolean isHidden = isHidden(); + LogUtil.i("NewVoicemailFragment.onResume", "isHidden = %s", isHidden); - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.attachUi(this); - - // TODO(zachh): Consider doing this when fragment becomes visible. - refreshAnnotatedCallLog(true /* checkDirty */); + // As a fragment's onResume() is tied to the containing Activity's onResume(), being resumed is + // not equivalent to becoming visible. + // For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + // will be called but it is not visible. + if (!isHidden) { + onFragmentShown(); + } } @Override public void onPause() { super.onPause(); - LogUtil.enterBlock("NewVoicemailFragment.onPause"); - // This is pending work that we don't actually need to follow through with. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); + onFragmentHidden(); + } - CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); - callLogFramework.detachUi(); + @Override + public void onHiddenChanged(boolean hidden) { + super.onHiddenChanged(hidden); + LogUtil.i("NewVoicemailFragment.onHiddenChanged", "hidden = %s", hidden); + + if (hidden) { + onFragmentHidden(); + } else { + onFragmentShown(); + } + } + + /** + * To be called when the fragment becomes visible. + * + * <p>Note that for a fragment, being resumed is not equivalent to becoming visible. + * + * <p>For example, when an activity with a hidden fragment is resumed, the fragment's onResume() + * will be called but it is not visible. + */ + private void onFragmentShown() { + registerRefreshAnnotatedCallLogReceiver(); + + CallLogComponent.get(getContext()) + .getRefreshAnnotatedCallLogNotifier() + .notify(/* checkDirty = */ true); + } + + /** + * To be called when the fragment becomes hidden. + * + * <p>This can happen in the following two cases: + * + * <ul> + * <li>hide the fragment but keep the parent activity visible (e.g., calling {@link + * android.support.v4.app.FragmentTransaction#hide(Fragment)} in an activity, or + * <li>the parent activity is paused. + * </ul> + */ + private void onFragmentHidden() { + unregisterRefreshAnnotatedCallLogReceiver(); } - @Nullable @Override public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -139,43 +153,6 @@ public final class NewVoicemailFragment extends Fragment return view; } - private void refreshAnnotatedCallLog(boolean checkDirty) { - LogUtil.enterBlock("NewVoicemailFragment.refreshAnnotatedCallLog"); - - // If we already scheduled a refresh, cancel it and schedule a new one so that repeated requests - // in quick succession don't result in too much work. For example, if we get 10 requests in - // 10ms, and a complete refresh takes a constant 200ms, the refresh will take 300ms (100ms wait - // and 1 iteration @200ms) instead of 2 seconds (10 iterations @ 200ms) since the work requests - // are serialized in RefreshAnnotatedCallLogWorker. - // - // We might get many requests in quick succession, for example, when the simulator inserts - // hundreds of rows into the system call log, or when the data for a new call is incrementally - // written to different columns as it becomes available. - ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); - - refreshAnnotatedCallLogRunnable = - () -> { - ListenableFuture<Void> future = - checkDirty - ? refreshAnnotatedCallLogWorker.refreshWithDirtyCheck() - : refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); - refreshAnnotatedCallLogListener.listen( - getContext(), - future, - unused -> {}, - throwable -> { - throw new RuntimeException(throwable); - }); - }; - ThreadUtil.getUiThreadHandler().postDelayed(refreshAnnotatedCallLogRunnable, WAIT_MILLIS); - } - - @Override - public void invalidateUi() { - LogUtil.enterBlock("NewVoicemailFragment.invalidateUi"); - refreshAnnotatedCallLog(false /* checkDirty */); - } - @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { LogUtil.enterBlock("NewVoicemailFragment.onCreateLoader"); @@ -210,6 +187,24 @@ public final class NewVoicemailFragment extends Fragment queryAndUpdateVoicemailStatusAlert(); } + private void registerRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewVoicemailFragment.registerRefreshAnnotatedCallLogReceiver"); + + LocalBroadcastManager.getInstance(getContext()) + .registerReceiver( + refreshAnnotatedCallLogReceiver, RefreshAnnotatedCallLogReceiver.getIntentFilter()); + } + + private void unregisterRefreshAnnotatedCallLogReceiver() { + LogUtil.enterBlock("NewVoicemailFragment.unregisterRefreshAnnotatedCallLogReceiver"); + + // Cancel pending work as we don't need it any more. + CallLogComponent.get(getContext()).getRefreshAnnotatedCallLogNotifier().cancel(); + + LocalBroadcastManager.getInstance(getContext()) + .unregisterReceiver(refreshAnnotatedCallLogReceiver); + } + private void queryAndUpdateVoicemailStatusAlert() { queryVoicemailStatusTableListener.listen( getContext(), diff --git a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java index 5ae26f5f7..7f5bb796a 100644 --- a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java +++ b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java @@ -32,12 +32,14 @@ import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; +import android.text.Html; 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.dialer.widget.TextViewPreference; import com.android.voicemail.VoicemailClient; import com.android.voicemail.VoicemailClient.ActivationStateListener; import com.android.voicemail.VoicemailComponent; @@ -73,6 +75,7 @@ public class VoicemailSettingsFragment extends PreferenceFragment private SwitchPreference donateVoicemailSwitchPreference; private Preference voicemailChangePinPreference; private PreferenceScreen advancedSettings; + private TextViewPreference voicemailTranscriptionInstructionText; @Override public void onCreate(Bundle icicle) { @@ -209,6 +212,10 @@ public class VoicemailSettingsFragment extends PreferenceFragment return false; } }); + + voicemailTranscriptionInstructionText = + (TextViewPreference) findPreference(getString(R.string.voicemail_transcription_text_key)); + voicemailTranscriptionInstructionText.setTitle(getVoicemailTranscriptionInstructionsText()); } @Override @@ -348,4 +355,20 @@ public class VoicemailSettingsFragment extends PreferenceFragment builder.setCancelable(true); builder.show(); } + + /** + * Builds a spannable string containing the voicemail transcription instructions text containing + * the appropriate "Learn More" urls. + * + * @return The voicemail transcription instructions text. + */ + private CharSequence getVoicemailTranscriptionInstructionsText() { + String settingText = + getString( + R.string.voicemail_transcription_instruction_text, + getString(R.string.transcription_learn_more_url), + getString(R.string.donation_learn_more_url)); + CharSequence settingSeq = Html.fromHtml(settingText); + return settingSeq; + } } diff --git a/java/com/android/dialer/voicemail/settings/res/values/strings.xml b/java/com/android/dialer/voicemail/settings/res/values/strings.xml index 10fa459ff..47228b70b 100644 --- a/java/com/android/dialer/voicemail/settings/res/values/strings.xml +++ b/java/com/android/dialer/voicemail/settings/res/values/strings.xml @@ -44,7 +44,7 @@ <string name="voicemail_change_pin_key" translatable="false">voicemail_change_pin_key</string> <!-- Visual voicemail on/off title [CHAR LIMIT=40] --> - <string name="voicemail_visual_voicemail_switch_title">Visual Voicemail</string> + <string name="voicemail_visual_voicemail_switch_title">Visual voicemail</string> <!-- Visual voicemail archive on/off title [CHAR LIMIT=40] --> <string name="voicemail_visual_voicemail_auto_archive_switch_title">Extra backup and storage</string> @@ -125,4 +125,18 @@ <!-- The label for the confirm-disable-voicemail button [CHAR LIMIT=16] --> <string name="confirm_disable_voicemail_accept_dialog_label">TURN OFF</string> + <!-- Internal preferences key for static instruction text. --> + <string name="voicemail_transcription_text_key" translatable="false">voicemail_transcription_text_key</string> + + <!-- Additional information text and links for visual voicemail and voicemail donation setting page + [CHAR LIMIT=NONE] --> + <string name="voicemail_transcription_instruction_text"> + Visual voicemail allows you to check voicemail messages without having to call voicemail. Transcripts provided by Google. <a href="<xliff:g example="http://www.google.com" id="url1">%1$s</xliff:g>">Learn more</a> + <br><br> + For voicemail transcription analysis, your voicemail messages are stored anonymously. <a href="<xliff:g example="http://www.google.com" id="url2">%2$s</xliff:g>">Learn more</a> + </string> + + <string translatable="false" name="transcription_learn_more_url">https://support.google.com/phoneapp/answer/2811844?hl=en%26ref_topic=7539039</string> + <string translatable="false" name="donation_learn_more_url">https://support.google.com/phoneapp/answer/2811844#voicemail_transcript</string> + </resources> diff --git a/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml b/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml index 9b0391ad4..75c8cfe2b 100644 --- a/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml +++ b/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml @@ -43,4 +43,8 @@ android:key="@string/voicemail_advanced_settings_key" android:title="@string/voicemail_advanced_settings_title"> </PreferenceScreen> + + <com.android.dialer.widget.TextViewPreference + android:key="@string/voicemail_transcription_text_key"/> + </PreferenceScreen> diff --git a/java/com/android/dialer/widget/TextViewPreference.java b/java/com/android/dialer/widget/TextViewPreference.java new file mode 100644 index 000000000..2c1885c4d --- /dev/null +++ b/java/com/android/dialer/widget/TextViewPreference.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018 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.widget; + +import android.content.Context; +import android.preference.Preference; +import android.text.method.LinkMovementMethod; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +/** + * Provides a {@link TextView} inside a preference. Useful for displaying static text which may + * contain hyperlinks. + */ +public class TextViewPreference extends Preference { + + /** + * The resource ID of the text to be populated in the {@link TextView} when a resource ID is used. + */ + private int textResourceId = 0; + + /** The text to be populated in the {@link TextView} when a {@link CharSequence} is used. */ + private CharSequence text; + + /** The {@link TextView} containing the text. */ + private TextView textView; + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the preference. + * @param defStyleAttr An attribute in the current theme that contains a reference to a style + * resource that supplies default values for the view. Can be 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that supplies default values for + * the view, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not + * look for defaults. + */ + public TextViewPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + setLayoutResource(R.layout.text_view_preference); + } + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the preference. + * @param defStyleAttr An attribute in the current theme that contains a reference to a style + * resource that supplies default values for the view. Can be 0 to not look for defaults. + */ + public TextViewPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the preference. + */ + public TextViewPreference(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.preferenceStyle, 0); + } + + /** + * Instantiates the {@link TextViewPreference} instance. + * + * @param context The Context this is associated with, through which it can access the current + * theme, resources, etc. + */ + public TextViewPreference(Context context) { + super(context, null); + + setLayoutResource(R.layout.text_view_preference); + } + + /** + * Handles binding the preference. + * + * @param view The view. + */ + @Override + protected void onBindView(View view) { + super.onBindView(view); + textView = (TextView) view.findViewById(R.id.text); + if (textResourceId != 0) { + setTitle(textResourceId); + } else if (text != null) { + setTitle(text); + } + } + + /** + * Sets the preference title from a {@link CharSequence}. + * + * @param text The text. + */ + @Override + public void setTitle(CharSequence text) { + textResourceId = 0; + this.text = text; + if (textView == null) { + return; + } + + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setText(text); + } + + /** + * Sets the preference title from a resource id. + * + * @param textResId The string resource Id. + */ + @Override + public void setTitle(int textResId) { + textResourceId = textResId; + setTitle(getContext().getString(textResId)); + } +} diff --git a/java/com/android/dialer/widget/res/layout/text_view_preference.xml b/java/com/android/dialer/widget/res/layout/text_view_preference.xml new file mode 100644 index 000000000..39b550657 --- /dev/null +++ b/java/com/android/dialer/widget/res/layout/text_view_preference.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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 + --> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:fontFamily="sans-serif" + android:linksClickable="true" + android:singleLine="false"/> diff --git a/java/com/android/incallui/CallCardPresenter.java b/java/com/android/incallui/CallCardPresenter.java index da5d1a8dd..b945b0810 100644 --- a/java/com/android/incallui/CallCardPresenter.java +++ b/java/com/android/incallui/CallCardPresenter.java @@ -690,7 +690,7 @@ public class CallCardPresenter if (primary == null) { // Clear the primary display info. - inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo()); + inCallScreen.setPrimary(PrimaryInfo.empty()); return; } @@ -713,26 +713,22 @@ public class CallCardPresenter "update primary display info for conference call."); inCallScreen.setPrimary( - new PrimaryInfo( - null /* number */, - CallerInfoUtils.getConferenceString( - context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)), - false /* nameIsNumber */, - null /* location */, - null /* label */, - null /* photo */, - ContactPhotoType.DEFAULT_PLACEHOLDER, - false /* isSipCall */, - showContactPhoto, - hasWorkCallProperty, - false /* isSpam */, - false /* isLocalContact */, - false /* answeringDisconnectsOngoingCall */, - shouldShowLocation(), - null /* contactInfoLookupKey */, - null /* enrichedCallMultimediaData */, - true /* showInCallButtonGrid */, - primary.getNumberPresentation())); + PrimaryInfo.builder() + .setName( + CallerInfoUtils.getConferenceString( + context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE))) + .setNameIsNumber(false) + .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER) + .setIsSipCall(false) + .setIsContactPhotoShown(showContactPhoto) + .setIsWorkCall(hasWorkCallProperty) + .setIsSpam(false) + .setIsLocalContact(false) + .setAnsweringDisconnectsOngoingCall(false) + .setShouldShowLocation(shouldShowLocation()) + .setShowInCallButtonGrid(true) + .setNumberPresentation(primary.getNumberPresentation()) + .build()); } else if (primaryContactInfo != null) { LogUtil.v( "CallCardPresenter.updatePrimaryDisplayInfo", @@ -761,30 +757,33 @@ public class CallCardPresenter // DialerCall with caller that is a work contact. boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK); inCallScreen.setPrimary( - new PrimaryInfo( - number, - primary.updateNameIfRestricted(name), - nameIsNumber, - shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation) - ? primaryContactInfo.location - : null, - isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label, - primaryContactInfo.photo, - primaryContactInfo.photoType, - primaryContactInfo.isSipCall, - showContactPhoto, - hasWorkCallProperty || isWorkContact, - primary.isSpam(), - primaryContactInfo.isLocalContact(), - primary.answeringDisconnectsForegroundVideoCall(), - shouldShowLocation(), - primaryContactInfo.lookupKey, - multimediaData, - true /* showInCallButtonGrid */, - primary.getNumberPresentation())); + PrimaryInfo.builder() + .setNumber(number) + .setName(primary.updateNameIfRestricted(name)) + .setNameIsNumber(nameIsNumber) + .setLabel( + shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation) + ? primaryContactInfo.location + : null) + .setLocation( + isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label) + .setPhoto(primaryContactInfo.photo) + .setPhotoType(primaryContactInfo.photoType) + .setIsSipCall(primaryContactInfo.isSipCall) + .setIsContactPhotoShown(showContactPhoto) + .setIsWorkCall(hasWorkCallProperty || isWorkContact) + .setIsSpam(primary.isSpam()) + .setIsLocalContact(primaryContactInfo.isLocalContact()) + .setAnsweringDisconnectsOngoingCall(primary.answeringDisconnectsForegroundVideoCall()) + .setShouldShowLocation(shouldShowLocation()) + .setContactInfoLookupKey(primaryContactInfo.lookupKey) + .setMultimediaData(multimediaData) + .setShowInCallButtonGrid(true) + .setNumberPresentation(primary.getNumberPresentation()) + .build()); } else { // Clear the primary display info. - inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo()); + inCallScreen.setPrimary(PrimaryInfo.empty()); } if (isInCallScreenReady) { @@ -1192,6 +1191,7 @@ public class CallCardPresenter return inCallScreen; } + /** Callback for contact lookup. */ public static class ContactLookupCallback implements ContactInfoCacheCallback { private final WeakReference<CallCardPresenter> callCardPresenter; diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index 67f5cfe4f..f842aedf7 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -61,8 +61,9 @@ import com.android.dialer.compat.ActivityCompat; import com.android.dialer.compat.CompatUtils; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.logging.Logger; -import com.android.dialer.logging.LoggingBindings; import com.android.dialer.logging.ScreenEvent; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.util.ViewUtil; import com.android.incallui.answer.bindings.AnswerBindings; import com.android.incallui.answer.protocol.AnswerScreen; @@ -182,6 +183,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN); didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN); didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN); + didShowRttCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN); } setWindowFlags(); @@ -248,10 +250,12 @@ public class InCallActivity extends TransactionSafeFragmentActivity pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay); sendBroadcast(CallPendingActivity.getFinishBroadcast()); Trace.endSection(); - Logger.get(this) - .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); - Logger.get(this) - .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); + MetricsComponent.get(this) + .metrics() + .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); + MetricsComponent.get(this) + .metrics() + .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); } private void setWindowFlags() { @@ -387,6 +391,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen); out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen); out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen); + out.putBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN, didShowRttCallScreen); super.onSaveInstanceState(out); isVisible = false; @@ -468,8 +473,9 @@ public class InCallActivity extends TransactionSafeFragmentActivity // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. ThreadUtil.postDelayedOnUiThread( () -> - Logger.get(this) - .logRecordMemory(LoggingBindings.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME), + MetricsComponent.get(this) + .metrics() + .recordMemory(Metrics.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME), 1000); } @@ -1593,6 +1599,7 @@ public class InCallActivity extends TransactionSafeFragmentActivity static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen"; static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen"; static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen"; + static final String DID_SHOW_RTT_CALL_SCREEN = "did_show_rtt_call_screen"; } /** Request codes for pending intents. */ diff --git a/java/com/android/incallui/RttCallPresenter.java b/java/com/android/incallui/RttCallPresenter.java index b90d56b36..939c9d00b 100644 --- a/java/com/android/incallui/RttCallPresenter.java +++ b/java/com/android/incallui/RttCallPresenter.java @@ -16,28 +16,145 @@ package com.android.incallui; -import android.content.Context; +import android.annotation.TargetApi; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.telecom.Call.RttCall; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.incallui.InCallPresenter.InCallState; +import com.android.incallui.InCallPresenter.InCallStateListener; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; import com.android.incallui.rtt.protocol.RttCallScreen; import com.android.incallui.rtt.protocol.RttCallScreenDelegate; +import java.io.IOException; /** * Logic related to the {@link RttCallScreen} and for managing changes to the RTT calling surfaces * based on other user interface events and incoming events. */ -public class RttCallPresenter implements RttCallScreenDelegate { +@TargetApi(28) +public class RttCallPresenter implements RttCallScreenDelegate, InCallStateListener { - private Context appContext; private RttCallScreen rttCallScreen; + private RttCall rttCall; + private HandlerThread handlerThread; + private RemoteMessageHandler remoteMessageHandler; @Override - public void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen) { - this.appContext = context.getApplicationContext(); + public void initRttCallScreenDelegate(RttCallScreen rttCallScreen) { this.rttCallScreen = rttCallScreen; } @Override - public void onRttCallScreenUiReady() {} + public void onLocalMessage(String message) { + if (rttCall == null) { + LogUtil.w("RttCallPresenter.onLocalMessage", "Rtt Call is not started yet"); + return; + } + remoteMessageHandler.writeMessage(message); + } + + @Override + public void onRttCallScreenUiReady() { + LogUtil.enterBlock("RttCallPresenter.onRttCallScreenUiReady"); + InCallPresenter.getInstance().addListener(this); + startListenOnRemoteMessage(); + } + + @Override + public void onRttCallScreenUiUnready() { + LogUtil.enterBlock("RttCallPresenter.onRttCallScreenUiUnready"); + InCallPresenter.getInstance().removeListener(this); + stopListenOnRemoteMessage(); + } @Override - public void onRttCallScreenUiUnready() {} + public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { + LogUtil.enterBlock("RttCallPresenter.onStateChange"); + if (newState == InCallState.INCALL) { + startListenOnRemoteMessage(); + } + } + + private void startListenOnRemoteMessage() { + DialerCall call = CallList.getInstance().getActiveCall(); + if (call == null) { + LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "call is active yet"); + return; + } + rttCall = call.getRttCall(); + if (rttCall == null) { + LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "RTT Call is not started yet"); + return; + } + if (handlerThread != null && handlerThread.isAlive()) { + LogUtil.i("RttCallPresenter.startListenOnRemoteMessage", "already running"); + return; + } + handlerThread = new HandlerThread("RttCallRemoteMessageHandler"); + handlerThread.start(); + remoteMessageHandler = + new RemoteMessageHandler(handlerThread.getLooper(), rttCall, rttCallScreen); + remoteMessageHandler.start(); + } + + private void stopListenOnRemoteMessage() { + if (handlerThread != null && handlerThread.isAlive()) { + handlerThread.quit(); + } + } + + private static class RemoteMessageHandler extends Handler { + private static final int START = 1; + private static final int READ_MESSAGE = 2; + private static final int WRITE_MESSAGE = 3; + + private final RttCall rttCall; + private final RttCallScreen rttCallScreen; + + RemoteMessageHandler(Looper looper, RttCall rttCall, RttCallScreen rttCallScreen) { + super(looper); + this.rttCall = rttCall; + this.rttCallScreen = rttCallScreen; + } + + @Override + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case START: + sendEmptyMessage(READ_MESSAGE); + break; + case READ_MESSAGE: + try { + final String message = rttCall.readImmediately(); + if (message != null) { + ThreadUtil.postOnUiThread(() -> rttCallScreen.onRemoteMessage(message)); + } + } catch (IOException e) { + LogUtil.e("RttCallPresenter.RemoteMessageHandler.handleMessage", "read message", e); + } + sendEmptyMessageDelayed(READ_MESSAGE, 200); + break; + case WRITE_MESSAGE: + try { + rttCall.write((String) msg.obj); + } catch (IOException e) { + LogUtil.e("RttCallPresenter.RemoteMessageHandler.handleMessage", "write message", e); + } + break; + default: // fall out + } + } + + void start() { + sendEmptyMessage(START); + } + + void writeMessage(String message) { + sendMessage(obtainMessage(WRITE_MESSAGE, message)); + } + } } diff --git a/java/com/android/incallui/answer/impl/AnswerFragment.java b/java/com/android/incallui/answer/impl/AnswerFragment.java index 8626e6d0e..3439a3e3c 100644 --- a/java/com/android/incallui/answer/impl/AnswerFragment.java +++ b/java/com/android/incallui/answer/impl/AnswerFragment.java @@ -149,7 +149,7 @@ public class AnswerFragment extends Fragment private boolean buttonAcceptClicked; private boolean buttonRejectClicked; private boolean hasAnimatedEntry; - private PrimaryInfo primaryInfo = PrimaryInfo.createEmptyPrimaryInfo(); + private PrimaryInfo primaryInfo = PrimaryInfo.empty(); private PrimaryCallState primaryCallState; private ArrayList<CharSequence> textResponses; private SmsBottomSheetFragment textResponsesFragment; @@ -523,13 +523,13 @@ public class AnswerFragment extends Fragment return; } contactGridManager.setPrimary(primaryInfo); - getAnswerMethod().setShowIncomingWillDisconnect(primaryInfo.answeringDisconnectsOngoingCall); + getAnswerMethod().setShowIncomingWillDisconnect(primaryInfo.answeringDisconnectsOngoingCall()); getAnswerMethod() .setContactPhoto( - primaryInfo.photoType == ContactPhotoType.CONTACT ? primaryInfo.photo : null); + primaryInfo.photoType() == ContactPhotoType.CONTACT ? primaryInfo.photo() : null); updateDataFragment(); - if (primaryInfo.shouldShowLocation) { + if (primaryInfo.shouldShowLocation()) { // Hide the avatar to make room for location contactGridManager.setAvatarHidden(true); } @@ -562,8 +562,8 @@ public class AnswerFragment extends Fragment MultimediaFragment.newInstance( multimediaData, false /* isInteractive */, - !primaryInfo.isSpam /* showAvatar */, - primaryInfo.isSpam); + !primaryInfo.isSpam() /* showAvatar */, + primaryInfo.isSpam()); } } else if (shouldShowAvatar()) { // Needs Avatar @@ -1067,7 +1067,7 @@ public class AnswerFragment extends Fragment return; } - if (!getResources().getBoolean(R.bool.answer_important_call_allowed) || primaryInfo.isSpam) { + if (!getResources().getBoolean(R.bool.answer_important_call_allowed) || primaryInfo.isSpam()) { importanceBadge.setVisibility(View.GONE); return; } @@ -1088,7 +1088,7 @@ public class AnswerFragment extends Fragment if (isVideoUpgradeRequest()) { return null; } - return primaryInfo.multimediaData; + return primaryInfo.multimediaData(); } /** Shows the Avatar image if available. */ diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java index e5948084d..9a0902639 100644 --- a/java/com/android/incallui/call/CallList.java +++ b/java/com/android/incallui/call/CallList.java @@ -36,7 +36,8 @@ import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; -import com.android.dialer.logging.LoggingBindings; +import com.android.dialer.metrics.Metrics; +import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.shortcuts.ShortcutUsageReporter; import com.android.dialer.spam.Spam; import com.android.dialer.spam.SpamComponent; @@ -119,11 +120,13 @@ public class CallList implements DialerCallDelegate { final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) { Trace.beginSection("CallList.onCallAdded"); if (telecomCall.getState() == Call.STATE_CONNECTING) { - Logger.get(context) - .logStartLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); + MetricsComponent.get(context) + .metrics() + .startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); } else if (telecomCall.getState() == Call.STATE_RINGING) { - Logger.get(context) - .logStartLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); + MetricsComponent.get(context) + .metrics() + .startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); } if (uiListeners != null) { uiListeners.onCallAdded(); diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index cbe7c57a6..90a01401c 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -17,6 +17,7 @@ package com.android.incallui.call; import android.Manifest.permission; +import android.annotation.TargetApi; import android.content.Context; import android.hardware.camera2.CameraCharacteristics; import android.net.Uri; @@ -929,6 +930,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa return getVideoTech().isTransmittingOrReceiving() || VideoProfile.isVideo(getVideoState()); } + @TargetApi(28) public boolean isRttCall() { if (BuildCompat.isAtLeastP()) { return getTelecomCall().isRttActive(); @@ -937,6 +939,14 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } } + @TargetApi(28) + public RttCall getRttCall() { + if (!isRttCall()) { + return null; + } + return getTelecomCall().getRttCall(); + } + public boolean hasReceivedVideoUpgradeRequest() { return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState()); } @@ -946,7 +956,6 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } public boolean hasSentRttUpgradeRequest() { - // TODO(wangqi): Implement this. return false; } diff --git a/java/com/android/incallui/callpending/CallPendingActivity.java b/java/com/android/incallui/callpending/CallPendingActivity.java index 47325d823..306eed8fb 100644 --- a/java/com/android/incallui/callpending/CallPendingActivity.java +++ b/java/com/android/incallui/callpending/CallPendingActivity.java @@ -177,25 +177,25 @@ public class CallPendingActivity extends FragmentActivity String number = getNumber(); // DialerCall with caller that is a work contact. - return new PrimaryInfo( - number, - name, - name != null && name.equals(number), - null /* location */, - getPhoneLabel(), - photo, - ContactPhotoType.CONTACT, - false /* isSipCall */, - true /* isContactPhotoShown */, - false /* isWorkCall */, - false /* isSpam */, - true /* isLocalContact */, - false /* answeringDisconnectsOngoingCall */, - false /* shouldShowLocation */, - getLookupKey(), - multimediaData, - false /*showInCallButtonGrid */, - TelecomManager.PRESENTATION_ALLOWED); + return PrimaryInfo.builder() + .setNumber(number) + .setName(name) + .setNameIsNumber(name != null && name.equals(number)) + .setLabel(getPhoneLabel()) + .setPhoto(photo) + .setPhotoType(ContactPhotoType.CONTACT) + .setIsSipCall(false) + .setIsContactPhotoShown(true) + .setIsWorkCall(false) + .setIsSpam(false) + .setIsLocalContact(true) + .setAnsweringDisconnectsOngoingCall(false) + .setShouldShowLocation(false) + .setContactInfoLookupKey(getLookupKey()) + .setMultimediaData(multimediaData) + .setShowInCallButtonGrid(false) + .setNumberPresentation(TelecomManager.PRESENTATION_ALLOWED) + .build(); } @Override diff --git a/java/com/android/incallui/contactgrid/BottomRow.java b/java/com/android/incallui/contactgrid/BottomRow.java index f9fc870dc..610eeca39 100644 --- a/java/com/android/incallui/contactgrid/BottomRow.java +++ b/java/com/android/incallui/contactgrid/BottomRow.java @@ -82,7 +82,7 @@ public class BottomRow { boolean isSpamIconVisible = false; boolean shouldPopulateAccessibilityEvent = true; - if (isIncoming(state) && primaryInfo.isSpam) { + if (isIncoming(state) && primaryInfo.isSpam()) { label = context.getString(R.string.contact_grid_incoming_suspected_spam); isSpamIconVisible = true; isHdIconVisible = false; @@ -99,7 +99,7 @@ public class BottomRow { } } else { label = getLabelForPhoneNumber(primaryInfo); - shouldPopulateAccessibilityEvent = primaryInfo.nameIsNumber; + shouldPopulateAccessibilityEvent = primaryInfo.nameIsNumber(); } return new Info( @@ -114,15 +114,15 @@ public class BottomRow { } private static CharSequence getLabelForPhoneNumber(PrimaryInfo primaryInfo) { - if (primaryInfo.location != null) { - return primaryInfo.location; + if (primaryInfo.location() != null) { + return primaryInfo.location(); } - if (!primaryInfo.nameIsNumber && !TextUtils.isEmpty(primaryInfo.number)) { - CharSequence spannedNumber = spanDisplayNumber(primaryInfo.number); - if (primaryInfo.label == null) { + if (!primaryInfo.nameIsNumber() && !TextUtils.isEmpty(primaryInfo.number())) { + CharSequence spannedNumber = spanDisplayNumber(primaryInfo.number()); + if (primaryInfo.label() == null) { return spannedNumber; } else { - return TextUtils.concat(primaryInfo.label, " ", spannedNumber); + return TextUtils.concat(primaryInfo.label(), " ", spannedNumber); } } return null; diff --git a/java/com/android/incallui/contactgrid/ContactGridManager.java b/java/com/android/incallui/contactgrid/ContactGridManager.java index 58d318892..6dec7646f 100644 --- a/java/com/android/incallui/contactgrid/ContactGridManager.java +++ b/java/com/android/incallui/contactgrid/ContactGridManager.java @@ -85,7 +85,7 @@ public class ContactGridManager { private final TextView deviceNumberTextView; private final View deviceNumberDivider; - private PrimaryInfo primaryInfo = PrimaryInfo.createEmptyPrimaryInfo(); + private PrimaryInfo primaryInfo = PrimaryInfo.empty(); private PrimaryCallState primaryCallState = PrimaryCallState.createEmptyPrimaryCallState(); private final LetterTileDrawable letterTile; private boolean isInMultiWindowMode; @@ -213,7 +213,7 @@ public class ContactGridManager { } boolean hasPhoto = - primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT; + primaryInfo.photo() != null && primaryInfo.photoType() == ContactPhotoType.CONTACT; if (!hasPhoto && !showAnonymousAvatar) { avatarImageView.setVisibility(View.GONE); return false; @@ -271,17 +271,17 @@ public class ContactGridManager { * </ul> */ private void updatePrimaryNameAndPhoto() { - if (TextUtils.isEmpty(primaryInfo.name)) { + if (TextUtils.isEmpty(primaryInfo.name())) { contactNameTextView.setText(null); } else { contactNameTextView.setText( - primaryInfo.nameIsNumber - ? PhoneNumberUtilsCompat.createTtsSpannable(primaryInfo.name) - : primaryInfo.name); + primaryInfo.nameIsNumber() + ? PhoneNumberUtilsCompat.createTtsSpannable(primaryInfo.name()) + : primaryInfo.name()); // Set direction of the name field int nameDirection = View.TEXT_DIRECTION_INHERIT; - if (primaryInfo.nameIsNumber) { + if (primaryInfo.nameIsNumber()) { nameDirection = View.TEXT_DIRECTION_LTR; } contactNameTextView.setTextDirection(nameDirection); @@ -292,23 +292,23 @@ public class ContactGridManager { avatarImageView.setVisibility(View.GONE); } else if (avatarSize > 0 && updateAvatarVisibility()) { boolean hasPhoto = - primaryInfo.photo != null && primaryInfo.photoType == ContactPhotoType.CONTACT; + primaryInfo.photo() != null && primaryInfo.photoType() == ContactPhotoType.CONTACT; // Contact has a photo, don't render a letter tile. if (hasPhoto) { avatarImageView.setBackground( DrawableConverter.getRoundedDrawable( - context, primaryInfo.photo, avatarSize, avatarSize)); + context, primaryInfo.photo(), avatarSize, avatarSize)); // Contact has a name, that isn't a number. } else { letterTile.setCanonicalDialerLetterTileDetails( - primaryInfo.name, - primaryInfo.contactInfoLookupKey, + primaryInfo.name(), + primaryInfo.contactInfoLookupKey(), LetterTileDrawable.SHAPE_CIRCLE, LetterTileDrawable.getContactTypeFromPrimitives( primaryCallState.isVoiceMailNumber, - primaryInfo.isSpam, + primaryInfo.isSpam(), primaryCallState.isBusinessNumber, - primaryInfo.numberPresentation, + primaryInfo.numberPresentation(), primaryCallState.isConference)); // By invalidating the avatarImageView we force a redraw of the letter tile. // This is required to properly display the updated letter tile iconography based on the @@ -413,7 +413,7 @@ public class ContactGridManager { deviceNumberTextView.setText( context.getString(R.string.contact_grid_callback_number, primaryCallState.callbackNumber)); deviceNumberTextView.setVisibility(View.VISIBLE); - if (primaryInfo.shouldShowLocation) { + if (primaryInfo.shouldShowLocation()) { deviceNumberDivider.setVisibility(View.VISIBLE); } } diff --git a/java/com/android/incallui/contactgrid/TopRow.java b/java/com/android/incallui/contactgrid/TopRow.java index 556b11ba0..5eb38f98c 100644 --- a/java/com/android/incallui/contactgrid/TopRow.java +++ b/java/com/android/incallui/contactgrid/TopRow.java @@ -81,7 +81,7 @@ public class TopRow { // Show phone number if it's not displayed in name (center row) or location field (bottom // row). if (shouldShowNumber(primaryInfo, true /* isIncoming */)) { - label = TextUtils.concat(label, " ", spanDisplayNumber(primaryInfo.number)); + label = TextUtils.concat(label, " ", spanDisplayNumber(primaryInfo.number())); } } } else if (VideoUtils.hasSentVideoUpgradeRequest(state.sessionModificationState) @@ -97,7 +97,7 @@ public class TopRow { label = context.getString(R.string.incall_remotely_held); } else if (state.state == State.ACTIVE && shouldShowNumber(primaryInfo, false /* isIncoming */)) { - label = spanDisplayNumber(primaryInfo.number); + label = spanDisplayNumber(primaryInfo.number()); } else if (state.state == State.CALL_PENDING && !TextUtils.isEmpty(state.customLabel)) { label = state.customLabel; } else { @@ -115,18 +115,18 @@ public class TopRow { } private static boolean shouldShowNumber(PrimaryInfo primaryInfo, boolean isIncoming) { - if (primaryInfo.nameIsNumber) { + if (primaryInfo.nameIsNumber()) { return false; } // Don't show number since it's already shown in bottom row of incoming screen if there is no // location info. - if (primaryInfo.location == null && isIncoming) { + if (primaryInfo.location() == null && isIncoming) { return false; } - if (primaryInfo.isLocalContact && !isIncoming) { + if (primaryInfo.isLocalContact() && !isIncoming) { return false; } - if (TextUtils.isEmpty(primaryInfo.number)) { + if (TextUtils.isEmpty(primaryInfo.number())) { return false; } return true; diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java index a4dcd72a8..9a6d1c41e 100644 --- a/java/com/android/incallui/incall/impl/InCallFragment.java +++ b/java/com/android/incallui/incall/impl/InCallFragment.java @@ -253,10 +253,10 @@ public class InCallFragment extends Fragment @Override public void setPrimary(@NonNull PrimaryInfo primaryInfo) { LogUtil.i("InCallFragment.setPrimary", primaryInfo.toString()); - setAdapterMedia(primaryInfo.multimediaData, primaryInfo.showInCallButtonGrid); + setAdapterMedia(primaryInfo.multimediaData(), primaryInfo.showInCallButtonGrid()); contactGridManager.setPrimary(primaryInfo); - if (primaryInfo.shouldShowLocation) { + if (primaryInfo.shouldShowLocation()) { // Hide the avatar to make room for location contactGridManager.setAvatarHidden(true); diff --git a/java/com/android/incallui/incall/protocol/PrimaryInfo.java b/java/com/android/incallui/incall/protocol/PrimaryInfo.java index f7457c3ff..63dc39ed3 100644 --- a/java/com/android/incallui/incall/protocol/PrimaryInfo.java +++ b/java/com/android/incallui/incall/protocol/PrimaryInfo.java @@ -20,91 +20,116 @@ import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import com.android.dialer.common.LogUtil; import com.android.dialer.multimedia.MultimediaData; +import com.google.auto.value.AutoValue; import java.util.Locale; /** Information about the primary call. */ -public class PrimaryInfo { - @Nullable public final String number; - @Nullable public final String name; - public final boolean nameIsNumber; +@AutoValue +public abstract class PrimaryInfo { + @Nullable + public abstract String number(); + + @Nullable + public abstract String name(); + + public abstract boolean nameIsNumber(); // This is from contacts and shows the type of number. For example, "Mobile". - @Nullable public final String label; - @Nullable public final String location; - @Nullable public final Drawable photo; - @ContactPhotoType public final int photoType; - public final boolean isSipCall; - public final boolean isContactPhotoShown; - public final boolean isWorkCall; - public final boolean isSpam; - public final boolean isLocalContact; - public final boolean answeringDisconnectsOngoingCall; - public final boolean shouldShowLocation; + @Nullable + public abstract String label(); + + @Nullable + public abstract String location(); + + @Nullable + public abstract Drawable photo(); + + @ContactPhotoType + public abstract int photoType(); + + public abstract boolean isSipCall(); + + public abstract boolean isContactPhotoShown(); + + public abstract boolean isWorkCall(); + + public abstract boolean isSpam(); + + public abstract boolean isLocalContact(); + + public abstract boolean answeringDisconnectsOngoingCall(); + + public abstract boolean shouldShowLocation(); // Used for consistent LetterTile coloring. - @Nullable public final String contactInfoLookupKey; - @Nullable public final MultimediaData multimediaData; - public final boolean showInCallButtonGrid; - public final int numberPresentation; - - // TODO: Convert to autovalue. a bug - public static PrimaryInfo createEmptyPrimaryInfo() { - return new PrimaryInfo( - null, - null, - false, - null, - null, - null, - ContactPhotoType.DEFAULT_PLACEHOLDER, - false, - false, - false, - false, - false, - false, - false, - null, - null, - true, - -1); + @Nullable + public abstract String contactInfoLookupKey(); + + @Nullable + public abstract MultimediaData multimediaData(); + + public abstract boolean showInCallButtonGrid(); + + public abstract int numberPresentation(); + + public static Builder builder() { + return new AutoValue_PrimaryInfo.Builder(); + } + /** Builder class for primary call info. */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setNumber(String number); + + public abstract Builder setName(String name); + + public abstract Builder setNameIsNumber(boolean nameIsNumber); + + public abstract Builder setLabel(String label); + + public abstract Builder setLocation(String location); + + public abstract Builder setPhoto(Drawable photo); + + public abstract Builder setPhotoType(@ContactPhotoType int photoType); + + public abstract Builder setIsSipCall(boolean isSipCall); + + public abstract Builder setIsContactPhotoShown(boolean isContactPhotoShown); + + public abstract Builder setIsWorkCall(boolean isWorkCall); + + public abstract Builder setIsSpam(boolean isSpam); + + public abstract Builder setIsLocalContact(boolean isLocalContact); + + public abstract Builder setAnsweringDisconnectsOngoingCall( + boolean answeringDisconnectsOngoingCall); + + public abstract Builder setShouldShowLocation(boolean shouldShowLocation); + + public abstract Builder setContactInfoLookupKey(String contactInfoLookupKey); + + public abstract Builder setMultimediaData(MultimediaData multimediaData); + + public abstract Builder setShowInCallButtonGrid(boolean showInCallButtonGrid); + + public abstract Builder setNumberPresentation(int numberPresentation); + + public abstract PrimaryInfo build(); } - public PrimaryInfo( - @Nullable String number, - @Nullable String name, - boolean nameIsNumber, - @Nullable String location, - @Nullable String label, - @Nullable Drawable photo, - @ContactPhotoType int phototType, - boolean isSipCall, - boolean isContactPhotoShown, - boolean isWorkCall, - boolean isSpam, - boolean isLocalContact, - boolean answeringDisconnectsOngoingCall, - boolean shouldShowLocation, - @Nullable String contactInfoLookupKey, - @Nullable MultimediaData multimediaData, - boolean showInCallButtonGrid, - int numberPresentation) { - this.number = number; - this.name = name; - this.nameIsNumber = nameIsNumber; - this.location = location; - this.label = label; - this.photo = photo; - this.photoType = phototType; - this.isSipCall = isSipCall; - this.isContactPhotoShown = isContactPhotoShown; - this.isWorkCall = isWorkCall; - this.isSpam = isSpam; - this.isLocalContact = isLocalContact; - this.answeringDisconnectsOngoingCall = answeringDisconnectsOngoingCall; - this.shouldShowLocation = shouldShowLocation; - this.contactInfoLookupKey = contactInfoLookupKey; - this.multimediaData = multimediaData; - this.showInCallButtonGrid = showInCallButtonGrid; - this.numberPresentation = numberPresentation; + public static PrimaryInfo empty() { + return PrimaryInfo.builder() + .setNameIsNumber(false) + .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER) + .setIsSipCall(false) + .setIsContactPhotoShown(false) + .setIsWorkCall(false) + .setIsSpam(false) + .setIsLocalContact(false) + .setAnsweringDisconnectsOngoingCall(false) + .setShouldShowLocation(false) + .setShowInCallButtonGrid(true) + .setNumberPresentation(-1) + .build(); } @Override @@ -113,13 +138,13 @@ public class PrimaryInfo { Locale.US, "PrimaryInfo, number: %s, name: %s, location: %s, label: %s, " + "photo: %s, photoType: %d, isPhotoVisible: %b, MultimediaData: %s", - LogUtil.sanitizePhoneNumber(number), - LogUtil.sanitizePii(name), - LogUtil.sanitizePii(location), - label, - photo, - photoType, - isContactPhotoShown, - multimediaData); + LogUtil.sanitizePhoneNumber(number()), + LogUtil.sanitizePii(name()), + LogUtil.sanitizePii(location()), + label(), + photo(), + photoType(), + isContactPhotoShown(), + multimediaData()); } } diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java index 1ea7f31b1..69837188a 100644 --- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java +++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java @@ -110,7 +110,17 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde notifyItemInserted(lastIndexOfLocalMessage); } else { rttChatMessage.append(newMessage); - notifyItemChanged(lastIndexOfLocalMessage); + // Clear empty message bubble. + if (TextUtils.isEmpty(rttChatMessage.getContent())) { + rttMessages.remove(lastIndexOfLocalMessage); + notifyItemRemoved(lastIndexOfLocalMessage); + if (lastIndexOfRemoteMessage > lastIndexOfLocalMessage) { + lastIndexOfRemoteMessage -= 1; + } + lastIndexOfLocalMessage = -1; + } else { + notifyItemChanged(lastIndexOfLocalMessage); + } } } diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index c7ee2ff67..a33029501 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -132,7 +132,7 @@ public class RttChatFragment extends Fragment FragmentUtils.getParentUnsafe(this, RttCallScreenDelegateFactory.class) .newRttCallScreenDelegate(this); - rttCallScreenDelegate.initRttCallScreenDelegate(getContext(), this); + rttCallScreenDelegate.initRttCallScreenDelegate(this); inCallScreenDelegate.onInCallScreenDelegateInit(this); inCallScreenDelegate.onInCallScreenReady(); @@ -193,7 +193,24 @@ public class RttChatFragment extends Fragment if (isClearingInput) { return; } - adapter.addLocalMessage(RttChatMessage.getChangedString(s, start, before, count)); + String messageToAppend = RttChatMessage.getChangedString(s, start, before, count); + if (!TextUtils.isEmpty(messageToAppend)) { + adapter.addLocalMessage(messageToAppend); + rttCallScreenDelegate.onLocalMessage(messageToAppend); + } + } + + @Override + public void onRemoteMessage(String message) { + adapter.addRemoteMessage(message); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + LogUtil.enterBlock("RttChatFragment.onDestroyView"); + inCallButtonUiDelegate.onInCallButtonUiUnready(); + inCallScreenDelegate.onInCallScreenUnready(); } @Override @@ -260,7 +277,7 @@ public class RttChatFragment extends Fragment @Override public void setPrimary(@NonNull PrimaryInfo primaryInfo) { LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString()); - nameTextView.setText(primaryInfo.name); + nameTextView.setText(primaryInfo.name()); } @Override diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreen.java b/java/com/android/incallui/rtt/protocol/RttCallScreen.java index afacbae48..916dfb84d 100644 --- a/java/com/android/incallui/rtt/protocol/RttCallScreen.java +++ b/java/com/android/incallui/rtt/protocol/RttCallScreen.java @@ -25,6 +25,8 @@ public interface RttCallScreen { void onRttScreenStop(); + void onRemoteMessage(String message); + Fragment getRttCallScreenFragment(); String getCallId(); diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java index e29c43d70..8c484a844 100644 --- a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java +++ b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java @@ -16,14 +16,14 @@ package com.android.incallui.rtt.protocol; -import android.content.Context; - /** Callbacks from the module out to the container. */ public interface RttCallScreenDelegate { - void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen); + void initRttCallScreenDelegate(RttCallScreen rttCallScreen); void onRttCallScreenUiReady(); void onRttCallScreenUiUnready(); + + void onLocalMessage(String message); } diff --git a/java/com/android/voicemail/impl/res/xml/vvm_config.xml b/java/com/android/voicemail/impl/res/xml/vvm_config.xml index ed7761abe..4c5efcc1c 100644 --- a/java/com/android/voicemail/impl/res/xml/vvm_config.xml +++ b/java/com/android/voicemail/impl/res/xml/vvm_config.xml @@ -123,6 +123,21 @@ </pbundle_as_map> <pbundle_as_map> + <!-- tracfone USA--> + <string name="feature_flag_name">vvm_carrier_flag_tracfone_usa</string> + + <string-array name="mccmnc"> + <item value="310260?gid1=4d4b"/> + <item value="310260?gid1=534d"/> + <item value="310260?gid1=6134"/> + <item value="310260?gid1=ddff"/> + <item value="310260?gid1=deff"/> + </string-array> + + <string name="vvm_type_string">vvm_type_disable</string>> + </pbundle_as_map> + + <pbundle_as_map> <!-- Telus Canada --> <string name="feature_flag_name">vvm_carrier_flag_302220</string> <string-array name="mccmnc"> |