diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-02-22 22:46:58 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-02-22 22:46:58 +0000 |
commit | c6943fd4f2050576dd1688d7f22d9683478e114e (patch) | |
tree | 012b1dd73ba554b09be6eee250d9fc1a9311feb2 /java/com | |
parent | 494552349aefe18dd3dea0dd656d5cd48f813e64 (diff) | |
parent | dde326ea2878112b0e59412cabd2ff98ce51d46e (diff) |
Merge changes I099f22b7,Ia014e5e1,I3950071e,I1863a28e,I622b52f4, ...
* changes:
Removed item animator in Call Log RecyclerView.
NUI bug fixes: handling intents and onActivityResult.
Use the vector spam icon in the new call log.
Fixed some Dialer theming bugs in NUI.
Make MarkDirtyObserver available to all call log data sources & PhoneLookups.
Updating PrimaryInfo value class to use AutoValue with builder pattern.
When there is no VM support hide the tab in the Old Main Activity
Rebuild Call Log on first launch after NUI is enabled
Changing the voicemail transcription font size
Moved metrics related methods out of LoggingBindings.
Record jank metrics in old and new call logs.
Fixed some bugs in NUI search.
Adding information section to voicemail settings
Add Tracfone to Motorola menu, move existing menu to Sprint only
Fixed some issues in NUI search bar.
Disable VVM for tracfone USA
Fix an NPE when ContactInfoHelper tries to retrieve remote directories.
Merge searchfragment/remote into searchfragment/directories and renaming things accordingly.
Initialize/Update the UI of New{CallLog|Voicemail}Fragment iff the fragment is truly visible.
Use a broadcast receiver to refresh the annotated call log.
Rename invalid resource folder
Fix a few UI issues based around multiselect in new nav.
Include the primary callback action in the new call log's bottom sheet.
Implement read/write text stream to RttCall.
Add configuration for when we show decimal and barcode device id
Annotate RTT as @TargetApi(28).
Diffstat (limited to 'java/com')
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"> |