From 68ea8cf4e1d5a19bf8ef44e9686305ded95852de Mon Sep 17 00:00:00 2001 From: zachh Date: Tue, 27 Mar 2018 18:13:49 -0700 Subject: Support new call log fragment in old peer. This makes the old peer read the CallLogConfig#isNewCallLogFragmentEnabled and show the old or new fragment accordingly. If the user is viewing the NewCallLog and the CallLogConfig needs to disable the framework, the new fragment is immediately replaced with the old one. This is necessary because if the user were to scroll the fragment, the AnnotatedCallLog and PhoneLookupHistory databases would be read, which would trigger creation. I don't expect this to be a common case because 1) we hopefully never have to disable the framework and 2) Framework is only updated on Phenotype broadcasts and JobScheduler jobs, which hopefully don't typically happen when user is viewing the call log. However, I still want to make sure that if it happens we don't irreversibly break users when we turn the framework back on. I tested this by flipping flags and observing underlying data being removed: > dialer-cmd configprovider set new_voicemail_fragment_enabled false > adb shell ls /data/data/com.google.android.dialer/databases/ && echo && adb shell cat /data/user_de/0/com.google.android.dialer/shared_prefs/com.google.android.dialer_preferences.xml I test flipping flags back and forth on the call log tab, speed dial tab, and while the activity was paused (pressing Home after viewing call log). Note that this CL doesn't address showing missed calls and badge counts correctly with the new fragment; that will come in a later CL. Bug: 74821995 Test: unit and manual PiperOrigin-RevId: 190706481 Change-Id: I618d9c1649169abd65733502cfebc662a835e787 --- .../android/dialer/calllog/CallLogFramework.java | 34 ++++- java/com/android/dialer/calllog/CallLogState.java | 9 ++ .../systemcalllog/SystemCallLogDataSource.java | 4 - .../dialer/main/impl/OldMainActivityPeer.java | 152 +++++++++++++++++++-- 4 files changed, 182 insertions(+), 17 deletions(-) (limited to 'java') diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java index 53ff43057..b86bfbc10 100644 --- a/java/com/android/dialer/calllog/CallLogFramework.java +++ b/java/com/android/dialer/calllog/CallLogFramework.java @@ -16,11 +16,17 @@ package com.android.dialer.calllog; +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.LocalBroadcastManager; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.DataSources; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.Annotations.Ui; +import com.android.dialer.inject.ApplicationContext; 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 java.util.ArrayList; import java.util.List; @@ -35,13 +41,24 @@ import javax.inject.Singleton; @Singleton public final class CallLogFramework { + private final Context appContext; private final DataSources dataSources; private final AnnotatedCallLogMigrator annotatedCallLogMigrator; + private final ListeningExecutorService uiExecutor; + private final CallLogState callLogState; @Inject - CallLogFramework(DataSources dataSources, AnnotatedCallLogMigrator annotatedCallLogMigrator) { + CallLogFramework( + @ApplicationContext Context appContext, + DataSources dataSources, + AnnotatedCallLogMigrator annotatedCallLogMigrator, + @Ui ListeningExecutorService uiExecutor, + CallLogState callLogState) { + this.appContext = appContext; this.dataSources = dataSources; this.annotatedCallLogMigrator = annotatedCallLogMigrator; + this.uiExecutor = uiExecutor; + this.callLogState = callLogState; } /** Registers the content observers for all data sources. */ @@ -73,12 +90,25 @@ public final class CallLogFramework { dataSource.unregisterContentObservers(); } + callLogState.clearData(); + // Clear data only after all content observers have been disabled. List> allFutures = new ArrayList<>(); for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { allFutures.add(dataSource.clearData()); } + return Futures.transform( - Futures.allAsList(allFutures), unused -> null, MoreExecutors.directExecutor()); + Futures.allAsList(allFutures), + unused -> { + // Send a broadcast to the OldMainActivityPeer to remove the NewCallLogFragment if it is + // currently attached. If this is not done, user interaction with the fragment could cause + // call log framework state to be unexpectedly written. For example scrolling could cause + // the AnnotatedCallLog to be read (which would trigger database creation). + LocalBroadcastManager.getInstance(appContext) + .sendBroadcastSync(new Intent("disableNewCallLogFragment")); + return null; + }, + uiExecutor); } } diff --git a/java/com/android/dialer/calllog/CallLogState.java b/java/com/android/dialer/calllog/CallLogState.java index bdb30a769..6d2ec1e5e 100644 --- a/java/com/android/dialer/calllog/CallLogState.java +++ b/java/com/android/dialer/calllog/CallLogState.java @@ -53,6 +53,15 @@ public final class CallLogState { sharedPreferences.edit().putBoolean(ANNOTATED_CALL_LOG_BUILT_PREF, true).apply(); } + /** + * Clear the call log state. This is useful for example if the annotated call log needs to be + * disabled because there was a problem. + */ + @AnyThread + public void clearData() { + sharedPreferences.edit().remove(ANNOTATED_CALL_LOG_BUILT_PREF).apply(); + } + /** * Returns true if the annotated call log has been built at least once. * diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index 9332acdb1..8362a81ac 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -30,7 +30,6 @@ import android.provider.CallLog.Calls; import android.provider.VoicemailContract; import android.provider.VoicemailContract.Voicemails; import android.support.annotation.ColorInt; -import android.support.annotation.MainThread; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.annotation.VisibleForTesting; @@ -102,11 +101,8 @@ public class SystemCallLogDataSource implements CallLogDataSource { this.annotatedCallLogDatabaseHelper = annotatedCallLogDatabaseHelper; } - @MainThread @Override public void registerContentObservers() { - Assert.isMainThread(); - LogUtil.enterBlock("SystemCallLogDataSource.registerContentObservers"); if (!PermissionsUtil.hasCallLogReadPermissions(appContext)) { diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java index 9e8bd1ad6..6d78a5171 100644 --- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java @@ -21,9 +21,11 @@ import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.KeyguardManager; +import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; @@ -37,6 +39,7 @@ 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.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.telecom.PhoneAccount; @@ -65,6 +68,8 @@ import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.calldetails.OldCallDetailsActivity; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.CallSpecificAppData; +import com.android.dialer.calllog.config.CallLogConfigComponent; +import com.android.dialer.calllog.ui.NewCallLogFragment; import com.android.dialer.common.FragmentUtils.FragmentUtilListener; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutorComponent; @@ -110,7 +115,6 @@ import com.android.dialer.voicemailstatus.VoicemailStatusHelper; import com.android.voicemail.VoicemailComponent; import com.google.common.util.concurrent.ListenableFuture; import java.util.Locale; -import java.util.Objects; import java.util.concurrent.TimeUnit; /** @@ -132,6 +136,23 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen // TODO(calderwoodra): change to AppCompatActivity once new speed dial ships private final TransactionSafeActivity activity; + private final BroadcastReceiver disableNewCallLogReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (bottomNavTabListener == null) { + return; + } + /* + * Remove the NewCallLogFragment if it is currently attached. If this is not done, user + * interaction with the fragment could cause call log framework state to be unexpectedly + * written. For example scrolling could cause the AnnotatedCallLog to be read (which would + * trigger database creation). + */ + bottomNavTabListener.disableNewCallLogFragment(); + } + }; + // Contacts private MainOnContactSelectedListener onContactSelectedListener; @@ -161,6 +182,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private LastTabController lastTabController; private BottomNavBar bottomNav; + private MainBottomNavBarBottomNavTabListener bottomNavTabListener; private View snackbarContainer; private UiListener getLastOutgoingCallListener; @@ -217,8 +239,9 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen activity.setSupportActionBar(activity.findViewById(R.id.toolbar)); bottomNav = activity.findViewById(R.id.bottom_nav_bar); - MainBottomNavBarBottomNavTabListener bottomNavTabListener = - new MainBottomNavBarBottomNavTabListener(activity, activity.getFragmentManager(), fab); + bottomNavTabListener = + new MainBottomNavBarBottomNavTabListener( + activity, activity.getFragmentManager(), activity.getSupportFragmentManager(), fab); bottomNav.addOnTabSelectedListener(bottomNavTabListener); // TODO(uabdullah): Handle case of when a sim is inserted/removed while the activity is open. boolean showVoicemailTab = canVoicemailTabBeShown(activity); @@ -432,6 +455,22 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen bottomNav.setVisibility(View.VISIBLE); } + /* + * While the activity is running, listen for the call log framework being disabled. If this is + * not done, user interaction with the fragment could cause call log framework state to be + * unexpectedly written. For example scrolling could cause the AnnotatedCallLog to be read + * (which would trigger database creation). + */ + LocalBroadcastManager.getInstance(activity) + .registerReceiver(disableNewCallLogReceiver, new IntentFilter("disableNewCallLogFragment")); + + /* + * Similar to above, if the new call log is being shown and then the activity is paused, when + * the user returns we need to remove the NewCallLogFragment if the framework has been disabled + * in the meantime. + */ + bottomNavTabListener.ensureCorrectCallLogShown(); + // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. ThreadUtil.postDelayedOnUiThread( () -> @@ -449,6 +488,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen @Override public void onActivityPause() { searchController.onActivityPause(); + LocalBroadcastManager.getInstance(activity).unregisterReceiver(disableNewCallLogReceiver); } @Override @@ -1122,14 +1162,19 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private final AppCompatActivity activity; private final FragmentManager fragmentManager; + private final android.support.v4.app.FragmentManager supportFragmentManager; private final FloatingActionButton fab; @TabIndex private int selectedTab = -1; private MainBottomNavBarBottomNavTabListener( - AppCompatActivity activity, FragmentManager fragmentManager, FloatingActionButton fab) { + AppCompatActivity activity, + FragmentManager fragmentManager, + android.support.v4.app.FragmentManager supportFragmentManager, + FloatingActionButton fab) { this.activity = activity; this.fragmentManager = fragmentManager; + this.supportFragmentManager = supportFragmentManager; this.fab = fab; } @@ -1154,11 +1199,46 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_CALL_LOG, activity); selectedTab = TabIndex.CALL_LOG; - Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG); - showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG); + + if (CallLogConfigComponent.get(activity).callLogConfig().isNewCallLogFragmentEnabled()) { + android.support.v4.app.Fragment supportFragment = + supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); + showSupportFragment( + supportFragment == null ? new NewCallLogFragment() : supportFragment, CALL_LOG_TAG); + } else { + Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG); + showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG); + } fab.show(); } + void disableNewCallLogFragment() { + LogUtil.i("MainBottomNavBarBottomNavTabListener.disableNewCallLogFragment", "disabled"); + android.support.v4.app.Fragment supportFragment = + supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); + if (supportFragment != null) { + supportFragmentManager.beginTransaction().remove(supportFragment).commit(); + // If the NewCallLogFragment was showing, immediately show the old call log fragment + // instead. + if (selectedTab == TabIndex.CALL_LOG) { + LogUtil.i( + "MainBottomNavBarBottomNavTabListener.disableNewCallLogFragment", "showing old"); + Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG); + showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG); + } + } + } + + void ensureCorrectCallLogShown() { + android.support.v4.app.Fragment supportFragment = + supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); + if (supportFragment != null + && !CallLogConfigComponent.get(activity).callLogConfig().isNewCallLogFragmentEnabled()) { + LogUtil.i("MainBottomNavBarBottomNavTabListener.ensureCorrectCallLogShown", "disabling"); + disableNewCallLogFragment(); + } + } + @Override public void onContactsSelected() { LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onContactsSelected"); @@ -1193,34 +1273,65 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen fragment.onVisible(); } + private void showFragment(@NonNull Fragment fragment, String tag) { + showFragment(fragment, null, tag); + } + /** * Shows the passed in fragment and hides all of the others in one transaction. * + *

Exactly one of fragment or supportFragment should be provided. + * *

Executes all fragment shows/hides in one transaction with no conflicting transactions * (like showing and hiding the same fragment in the same transaction). See a bug. * *

Special care should be taken to avoid calling this method several times in a short window * as it can lead to fragments overlapping. */ - private void showFragment(@NonNull Fragment fragment, String tag) { + private void showFragment( + @Nullable Fragment fragment, + @Nullable android.support.v4.app.Fragment supportFragment, + String tag) { LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.showFragment"); Fragment speedDial = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG); - Fragment callLog = fragmentManager.findFragmentByTag(CALL_LOG_TAG); + Fragment oldCallLog = fragmentManager.findFragmentByTag(CALL_LOG_TAG); Fragment contacts = fragmentManager.findFragmentByTag(CONTACTS_TAG); Fragment voicemail = fragmentManager.findFragmentByTag(VOICEMAIL_TAG); FragmentTransaction transaction = fragmentManager.beginTransaction(); boolean fragmentShown = showIfEqualElseHide(transaction, fragment, speedDial); - fragmentShown |= showIfEqualElseHide(transaction, fragment, callLog); + fragmentShown |= showIfEqualElseHide(transaction, fragment, oldCallLog); fragmentShown |= showIfEqualElseHide(transaction, fragment, contacts); fragmentShown |= showIfEqualElseHide(transaction, fragment, voicemail); - if (!fragmentShown) { + if (!fragmentShown && fragment != null) { LogUtil.i( "MainBottomNavBarBottomNavTabListener.showFragment", "Not added yet: " + fragment); transaction.add(R.id.fragment_container, fragment, tag); } transaction.commit(); + + // Handle support fragments. + // TODO(calderwoodra): Handle other new fragments. + android.support.v4.app.Fragment newCallLog = + supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); + + android.support.v4.app.FragmentTransaction supportTransaction = + supportFragmentManager.beginTransaction(); + boolean supportFragmentShown = + showIfEqualElseHideSupport(supportTransaction, supportFragment, newCallLog); + if (!supportFragmentShown && supportFragment != null) { + LogUtil.i( + "MainBottomNavBarBottomNavTabListener.showFragment", + "Not added yet: " + supportFragment); + supportTransaction.add(R.id.fragment_container, supportFragment, tag); + } + supportTransaction.commit(); + } + + private void showSupportFragment( + @NonNull android.support.v4.app.Fragment supportFragment, String tag) { + showFragment(null, supportFragment, tag); } /** @@ -1231,7 +1342,7 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen private boolean showIfEqualElseHide( FragmentTransaction transaction, Fragment fragment1, Fragment fragment2) { boolean shown = false; - if (Objects.equals(fragment1, fragment2)) { + if (fragment1 != null && fragment1.equals(fragment2)) { transaction.show(fragment1); shown = true; } else if (fragment2 != null) { @@ -1243,6 +1354,25 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen } return shown; } + + /** + * @param supportFragment1 will be shown if equal to {@code fragment2} + * @param supportFragment2 will be hidden if unequal to {@code fragment1} + * @return {@code true} if {@code fragment1} was shown + */ + private boolean showIfEqualElseHideSupport( + android.support.v4.app.FragmentTransaction supportTransaction, + android.support.v4.app.Fragment supportFragment1, + android.support.v4.app.Fragment supportFragment2) { + boolean shown = false; + if (supportFragment1 != null && supportFragment1.equals(supportFragment2)) { + supportTransaction.show(supportFragment1); + shown = true; + } else if (supportFragment2 != null) { + supportTransaction.hide(supportFragment2); + } + return shown; + } } private static final class LastTabController { -- cgit v1.2.3