From 62b15ac6694f5132453ae0c711bcbb790acf1b47 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Thu, 11 Jun 2020 12:36:53 +0800 Subject: [PATCH 1/5] Update App info behavior for mainline module As requested by framework team, the mainline module should be considered as system apps and not stoppable and disable-able. Since many of these modules provide critical functionality, disabling them can result in a very unstable device. According to the request, Settings will apply below changes to App info page for protecting mainline modules: - Hide "Force stop" and "Disable" in App info. - Disable "Clear storage" and "Clear cache" in "Storage & cache". Since the mainline module checking API interface changed, also change the dependent files and test cases. Fixes: 156955322 Test: robotest and verify "Cell Broadcast Service" in App info Change-Id: Ibc239bdaf3364eda541a33add382364cfdc6fc9b --- .../applications/AppStorageSettings.java | 5 +- .../AppButtonsPreferenceController.java | 4 +- .../AppInstallerInfoPreferenceController.java | 2 +- .../applications/AppStorageSettingsTest.java | 53 +++++++++++++++++-- .../AppButtonsPreferenceControllerTest.java | 24 +++++++++ ...InstallerInfoPreferenceControllerTest.java | 30 +++++++++-- 6 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java index f07c66cd8a5..8a54c730e10 100644 --- a/src/com/android/settings/applications/AppStorageSettings.java +++ b/src/com/android/settings/applications/AppStorageSettings.java @@ -54,6 +54,7 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.deviceinfo.StorageWizardMoveConfirm; import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState.Callbacks; import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; @@ -321,7 +322,7 @@ public class AppStorageSettings extends AppInfoWithHeader .setButton1OnClickListener(v -> handleClearDataClick()); } - if (mAppsControlDisallowedBySystem) { + if (mAppsControlDisallowedBySystem || AppUtils.isMainlineModule(mPm, mPackageName)) { mButtonsPref.setButton1Enabled(false); } } @@ -579,7 +580,7 @@ public class AppStorageSettings extends AppInfoWithHeader .setButton2OnClickListener(v -> handleClearCacheClick()); } } - if (mAppsControlDisallowedBySystem) { + if (mAppsControlDisallowedBySystem || AppUtils.isMainlineModule(mPm, mPackageName)) { mButtonsPref.setButton1Enabled(false).setButton2Enabled(false); } } diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java index 147c412f92c..36ad2ce9746 100644 --- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java @@ -744,7 +744,9 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp } private boolean isSystemModule() { - return mAppEntry != null && AppUtils.isSystemModule(mContext, mAppEntry.info.packageName); + return mAppEntry != null + && (AppUtils.isSystemModule(mContext, mAppEntry.info.packageName) + || AppUtils.isMainlineModule(mPm, mAppEntry.info.packageName)); } /** diff --git a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java index 2e119537a2c..5e99e8bedb1 100644 --- a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java @@ -43,7 +43,7 @@ public class AppInstallerInfoPreferenceController extends AppInfoPreferenceContr return DISABLED_FOR_USER; } - if (AppUtils.isMainlineModule(mContext, mPackageName)) { + if (AppUtils.isMainlineModule(mContext.getPackageManager(), mPackageName)) { return DISABLED_FOR_USER; } diff --git a/tests/robotests/src/com/android/settings/applications/AppStorageSettingsTest.java b/tests/robotests/src/com/android/settings/applications/AppStorageSettingsTest.java index 35177024b38..2027ace9ead 100644 --- a/tests/robotests/src/com/android/settings/applications/AppStorageSettingsTest.java +++ b/tests/robotests/src/com/android/settings/applications/AppStorageSettingsTest.java @@ -16,9 +16,9 @@ package com.android.settings.applications; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -28,6 +28,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.ModuleInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.view.View; import android.widget.Button; @@ -51,6 +55,8 @@ public class AppStorageSettingsTest { private AppStorageSettings mSettings; private Button mLeftButton; private Button mRightButton; + @Mock + private PackageManager mPackageManager; @Before public void setUp() { @@ -58,6 +64,8 @@ public class AppStorageSettingsTest { mLeftButton = new Button(RuntimeEnvironment.application); mRightButton = new Button(RuntimeEnvironment.application); mSettings = spy(new AppStorageSettings()); + mSettings.mPm = mPackageManager; + mSettings.mPackageName = "Package"; mSettings.mSizeController = mSizesController; mButtonsPref = createMock(); mSettings.mButtonsPref = mButtonsPref; @@ -77,7 +85,9 @@ public class AppStorageSettingsTest { } @Test - public void updateUiWithSize_noAppStats_shouldDisableClearButtons() { + public void updateUiWithSize_noAppStats_shouldDisableClearButtons() + throws PackageManager.NameNotFoundException { + mockMainlineModule(mSettings.mPackageName, false /* isMainlineModule */); mSettings.updateUiWithSize(null); verify(mSizesController).updateUi(nullable(Context.class)); @@ -86,12 +96,15 @@ public class AppStorageSettingsTest { } @Test - public void updateUiWithSize_hasDataAndCache_shouldEnableClearButtons() { + public void updateUiWithSize_hasDataAndCache_shouldEnableClearButtons() + throws PackageManager.NameNotFoundException { final AppStorageStats stats = mock(AppStorageStats.class); when(stats.getCacheBytes()).thenReturn(5000L); when(stats.getDataBytes()).thenReturn(10000L); doNothing().when(mSettings).handleClearCacheClick(); doNothing().when(mSettings).handleClearDataClick(); + mockMainlineModule(mSettings.mPackageName, false /* isMainlineModule */); + mSettings.updateUiWithSize(stats); verify(mButtonsPref).setButton1Enabled(true); @@ -105,6 +118,22 @@ public class AppStorageSettingsTest { verify(mSettings).handleClearCacheClick(); } + @Test + public void updateUiWithSize_mainlineModule_shouldDisableClearButtons() + throws PackageManager.NameNotFoundException { + final AppStorageStats stats = mock(AppStorageStats.class); + when(stats.getCacheBytes()).thenReturn(5000L); + when(stats.getDataBytes()).thenReturn(10000L); + doNothing().when(mSettings).handleClearCacheClick(); + doNothing().when(mSettings).handleClearDataClick(); + mockMainlineModule(mSettings.mPackageName, true /* isMainlineModule */); + + + mSettings.updateUiWithSize(stats); + verify(mButtonsPref).setButton1Enabled(false); + verify(mButtonsPref).setButton2Enabled(false); + } + private ActionButtonsPreference createMock() { final ActionButtonsPreference pref = mock(ActionButtonsPreference.class); when(pref.setButton1Text(anyInt())).thenReturn(pref); @@ -121,5 +150,23 @@ public class AppStorageSettingsTest { return pref; } + + private void mockMainlineModule(String packageName, boolean isMainlineModule) + throws PackageManager.NameNotFoundException { + final PackageInfo packageInfo = new PackageInfo(); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.sourceDir = "apex"; + packageInfo.applicationInfo = applicationInfo; + + if (isMainlineModule) { + when(mPackageManager.getModuleInfo(packageName, 0 /* flags */)).thenReturn( + new ModuleInfo()); + } else { + when(mPackageManager.getPackageInfo(packageName, 0 /* flags */)).thenReturn( + packageInfo); + when(mPackageManager.getModuleInfo(packageName, 0 /* flags */)).thenThrow( + new PackageManager.NameNotFoundException()); + } + } } diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java index c2bcd1c7793..a5ac209b941 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java @@ -543,6 +543,19 @@ public class AppButtonsPreferenceControllerTest { assertThat(i.getBooleanExtra(KEY_REMOVE_TASK_WHEN_FINISHING, false)).isFalse(); } + @Test + @Config(shadows = ShadowAppUtils.class) + public void isAvailable_nonMainlineModule_isTrue() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + @Config(shadows = ShadowAppUtils.class) + public void isAvailable_mainlineModule_isFalse() { + ShadowAppUtils.addMainlineModule(mController.mPackageName); + assertThat(mController.isAvailable()).isFalse(); + } + /** * The test fragment which implements * {@link ButtonActionDialogFragment.AppButtonsDialogListener} @@ -597,16 +610,22 @@ public class AppButtonsPreferenceControllerTest { public static class ShadowAppUtils { public static Set sSystemModules = new ArraySet<>(); + public static Set sMainlineModules = new ArraySet<>(); @Resetter public static void reset() { sSystemModules.clear(); + sMainlineModules.clear(); } public static void addHiddenModule(String pkg) { sSystemModules.add(pkg); } + public static void addMainlineModule(String pkg) { + sMainlineModules.add(pkg); + } + @Implementation protected static boolean isInstant(ApplicationInfo info) { return false; @@ -616,5 +635,10 @@ public class AppButtonsPreferenceControllerTest { protected static boolean isSystemModule(Context context, String packageName) { return sSystemModules.contains(packageName); } + + @Implementation + protected static boolean isMainlineModule(PackageManager pm, String packageName) { + return sMainlineModules.contains(packageName); + } } } diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java index 00bd5f80724..d37a6850a54 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java @@ -99,12 +99,15 @@ public class AppInstallerInfoPreferenceControllerTest { } @Test - public void getAvailabilityStatus_hasAppLabel_shouldReturnAvailable() { + public void getAvailabilityStatus_hasAppLabel_shouldReturnAvailable() + throws PackageManager.NameNotFoundException { + final String packageName = "Package1"; when(mUserManager.isManagedProfile()).thenReturn(false); when(mAppInfo.loadLabel(mPackageManager)).thenReturn("Label1"); mController = new AppInstallerInfoPreferenceController(mContext, "test_key"); - mController.setPackageName("Package1"); + mController.setPackageName(packageName); mController.setParentFragment(mFragment); + mockMainlineModule(packageName, false /* isMainlineModule */); assertThat(mController.getAvailabilityStatus()) .isEqualTo(BasePreferenceController.AVAILABLE); @@ -153,12 +156,31 @@ public class AppInstallerInfoPreferenceControllerTest { @Test public void getAvailabilityStatus_isMainlineModule_shouldReturnDisabled() throws PackageManager.NameNotFoundException { + final String packageName = "Package"; when(mUserManager.isManagedProfile()).thenReturn(false); when(mAppInfo.loadLabel(mPackageManager)).thenReturn("Label"); - mController.setPackageName("Package"); - when(mPackageManager.getModuleInfo("Package", 0 /* flags */)).thenReturn(new ModuleInfo()); + mController.setPackageName(packageName); + mockMainlineModule(packageName, true /* isMainlineModule */); assertThat(mController.getAvailabilityStatus()).isEqualTo( BasePreferenceController.DISABLED_FOR_USER); } + + private void mockMainlineModule(String packageName, boolean isMainlineModule) + throws PackageManager.NameNotFoundException { + final PackageInfo packageInfo = new PackageInfo(); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.sourceDir = "apex"; + packageInfo.applicationInfo = applicationInfo; + + if (isMainlineModule) { + when(mPackageManager.getModuleInfo(packageName, 0 /* flags */)).thenReturn( + new ModuleInfo()); + } else { + when(mPackageManager.getPackageInfo(packageName, 0 /* flags */)).thenReturn( + packageInfo); + when(mPackageManager.getModuleInfo(packageName, 0 /* flags */)).thenThrow( + new PackageManager.NameNotFoundException()); + } + } } From 3c3f362125daecdda16f814a9bc6695a4f82a2fb Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Thu, 11 Jun 2020 17:40:30 +0800 Subject: [PATCH 2/5] Improve BT slice card loading performance The bottleneck is getting LocalBluetoothManager the first time. 1. Initialize LocalBluetoothManager earlier and asynchronously. 2. Don't block in slice's constructor and getSlice(). - Initialize the bt updaters until the manager is ready. - Just show a header if the manager is not ready yet. Fixes: 157702021 Test: robotest Change-Id: I427df55f259b45ba4c37557b22e09dcc24079e93 --- .../ContextualCardsFragment.java | 2 + .../slices/BluetoothDevicesSlice.java | 57 ++++++----- .../slices/BluetoothUpdateWorker.java | 95 +++++++++++++++++-- 3 files changed, 123 insertions(+), 31 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java index c0527c90049..ebfaca9544e 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java @@ -37,6 +37,7 @@ import androidx.recyclerview.widget.ItemTouchHelper; import com.android.settings.R; import com.android.settings.core.InstrumentedFragment; +import com.android.settings.homepage.contextualcards.slices.BluetoothUpdateWorker; import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate; import com.android.settings.overlay.FeatureFactory; import com.android.settings.wifi.slice.ContextualWifiScanWorker; @@ -67,6 +68,7 @@ public class ContextualCardsFragment extends InstrumentedFragment implements final Context context = getContext(); if (savedInstanceState == null) { FeatureFactory.getFactory(context).getSlicesFeatureProvider().newUiSession(); + BluetoothUpdateWorker.initLocalBtManager(getContext()); } mContextualCardManager = new ContextualCardManager(context, getSettingsLifecycle(), savedInstanceState); diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java index 8b74313b8bf..19de4030e30 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java @@ -81,15 +81,12 @@ public class BluetoothDevicesSlice implements CustomSliceable { private static boolean sBluetoothEnabling; private final Context mContext; - private final AvailableMediaBluetoothDeviceUpdater mAvailableMediaBtDeviceUpdater; - private final SavedBluetoothDeviceUpdater mSavedBtDeviceUpdater; + private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBtDeviceUpdater; + private SavedBluetoothDeviceUpdater mSavedBtDeviceUpdater; public BluetoothDevicesSlice(Context context) { mContext = context; - mAvailableMediaBtDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(mContext, - null /* fragment */, null /* devicePreferenceCallback */); - mSavedBtDeviceUpdater = new SavedBluetoothDeviceUpdater(mContext, - null /* fragment */, null /* devicePreferenceCallback */); + BluetoothUpdateWorker.initLocalBtManager(context); } @Override @@ -119,16 +116,8 @@ public class BluetoothDevicesSlice implements CustomSliceable { // Add the header of Bluetooth on listBuilder.addRow(getBluetoothOnHeader()); - // Get row builders of Bluetooth devices. - final List rows = getBluetoothRowBuilders(); - - // Determine the displayable row count. - final int displayableCount = Math.min(rows.size(), DEFAULT_EXPANDED_ROW_COUNT); - - // Add device rows up to the count. - for (int i = 0; i < displayableCount; i++) { - listBuilder.addRow(rows.get(i)); - } + // Add row builders of Bluetooth devices. + getBluetoothRowBuilders().forEach(row -> listBuilder.addRow(row)); return listBuilder.build(); } @@ -181,19 +170,18 @@ public class BluetoothDevicesSlice implements CustomSliceable { List getPairedBluetoothDevices() { final List bluetoothDeviceList = new ArrayList<>(); - // If Bluetooth is disable, skip to get the Bluetooth devices. + // If Bluetooth is disable, skip getting the Bluetooth devices. if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) { Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is disabled."); return bluetoothDeviceList; } - // Get the Bluetooth devices from LocalBluetoothManager. - final LocalBluetoothManager localBtManager = - com.android.settings.bluetooth.Utils.getLocalBtManager(mContext); + final LocalBluetoothManager localBtManager = BluetoothUpdateWorker.getLocalBtManager(); if (localBtManager == null) { - Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is unsupported."); + Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is not ready."); return bluetoothDeviceList; } + final Collection cachedDevices = localBtManager.getCachedDeviceManager().getCachedDevicesCopy(); @@ -292,8 +280,21 @@ public class BluetoothDevicesSlice implements CustomSliceable { private List getBluetoothRowBuilders() { final List bluetoothRows = new ArrayList<>(); + final List pairedDevices = getPairedBluetoothDevices(); + if (pairedDevices.isEmpty()) { + return bluetoothRows; + } + + // Initialize updaters without being blocked after paired devices is available because + // LocalBluetoothManager is ready. + lazyInitUpdaters(); + // Create row builders based on paired devices. - for (CachedBluetoothDevice device : getPairedBluetoothDevices()) { + for (CachedBluetoothDevice device : pairedDevices) { + if (bluetoothRows.size() >= DEFAULT_EXPANDED_ROW_COUNT) { + break; + } + String summary = device.getConnectionSummary(); if (summary == null) { summary = mContext.getString( @@ -321,6 +322,18 @@ public class BluetoothDevicesSlice implements CustomSliceable { return bluetoothRows; } + private void lazyInitUpdaters() { + if (mAvailableMediaBtDeviceUpdater == null) { + mAvailableMediaBtDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(mContext, + null /* fragment */, null /* devicePreferenceCallback */); + } + + if (mSavedBtDeviceUpdater == null) { + mSavedBtDeviceUpdater = new SavedBluetoothDeviceUpdater(mContext, + null /* fragment */, null /* devicePreferenceCallback */); + } + } + @VisibleForTesting SliceAction buildPrimaryBluetoothAction(CachedBluetoothDevice bluetoothDevice) { final Intent intent = new Intent(getUri().toString()) diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java index 1602f564998..8f15f2cd131 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java +++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java @@ -18,9 +18,14 @@ package com.android.settings.homepage.contextualcards.slices; import android.content.Context; import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; import android.util.Log; -import com.android.settings.bluetooth.Utils; +import androidx.annotation.Nullable; + import com.android.settings.slices.SliceBackgroundWorker; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -30,29 +35,46 @@ public class BluetoothUpdateWorker extends SliceBackgroundWorker implements Blue private static final String TAG = "BluetoothUpdateWorker"; - private final LocalBluetoothManager mLocalBluetoothManager; + private static LocalBluetoothManager sLocalBluetoothManager; + + private LoadBtManagerHandler mLoadBtManagerHandler; public BluetoothUpdateWorker(Context context, Uri uri) { super(context, uri); - mLocalBluetoothManager = Utils.getLocalBtManager(context); + mLoadBtManagerHandler = LoadBtManagerHandler.getInstance(context); + if (sLocalBluetoothManager == null) { + mLoadBtManagerHandler.startLoadingBtManager(this); + } + } + + /** Initialize {@link LocalBluetoothManager} in the background */ + public static void initLocalBtManager(Context context) { + if (sLocalBluetoothManager == null) { + LoadBtManagerHandler.getInstance(context).startLoadingBtManager(); + } + } + + @Nullable + static LocalBluetoothManager getLocalBtManager() { + return sLocalBluetoothManager; } @Override protected void onSlicePinned() { - if (mLocalBluetoothManager == null) { - Log.i(TAG, "onSlicePinned() Bluetooth is unsupported."); + final LocalBluetoothManager localBtManager = mLoadBtManagerHandler.getLocalBtManager(); + if (localBtManager == null) { return; } - mLocalBluetoothManager.getEventManager().registerCallback(this); + localBtManager.getEventManager().registerCallback(this); } @Override protected void onSliceUnpinned() { - if (mLocalBluetoothManager == null) { - Log.i(TAG, "onSliceUnpinned() Bluetooth is unsupported."); + final LocalBluetoothManager localBtManager = mLoadBtManagerHandler.getLocalBtManager(); + if (localBtManager == null) { return; } - mLocalBluetoothManager.getEventManager().unregisterCallback(this); + localBtManager.getEventManager().unregisterCallback(this); } @Override @@ -84,4 +106,59 @@ public class BluetoothUpdateWorker extends SliceBackgroundWorker implements Blue int bluetoothProfile) { notifySliceChange(); } + + private static class LoadBtManagerHandler extends Handler { + + private static LoadBtManagerHandler sHandler; + + private final Runnable mLoadBtManagerTask; + private final Context mContext; + private BluetoothUpdateWorker mWorker; + + private static LoadBtManagerHandler getInstance(Context context) { + if (sHandler == null) { + final HandlerThread workerThread = new HandlerThread(TAG, + Process.THREAD_PRIORITY_BACKGROUND); + workerThread.start(); + sHandler = new LoadBtManagerHandler(context, workerThread.getLooper()); + } + return sHandler; + } + + private LoadBtManagerHandler(Context context, Looper looper) { + super(looper); + mContext = context; + mLoadBtManagerTask = () -> { + Log.d(TAG, "LoadBtManagerHandler: start loading..."); + final long startTime = System.currentTimeMillis(); + sLocalBluetoothManager = getLocalBtManager(); + Log.d(TAG, "LoadBtManagerHandler took " + (System.currentTimeMillis() - startTime) + + " ms"); + }; + } + + private LocalBluetoothManager getLocalBtManager() { + if (sLocalBluetoothManager != null) { + return sLocalBluetoothManager; + } + return LocalBluetoothManager.getInstance(mContext, + (context, btManager) -> { + if (mWorker != null) { + // notify change if the worker is ready + mWorker.notifySliceChange(); + } + }); + } + + private void startLoadingBtManager() { + if (!hasCallbacks(mLoadBtManagerTask)) { + post(mLoadBtManagerTask); + } + } + + private void startLoadingBtManager(BluetoothUpdateWorker worker) { + mWorker = worker; + startLoadingBtManager(); + } + } } \ No newline at end of file From 9bdeef43fdd77053e639af1249fba5e1beba8da5 Mon Sep 17 00:00:00 2001 From: Tim Peng Date: Wed, 27 May 2020 13:29:55 +0800 Subject: [PATCH 3/5] Refine layout for output switcher panel 1. only make the output switcher tall enough to show 6.5 devices 2. enlarge album art to 52*52 and keep same size on group panel 3. reduce panel header top padding 4. enlarge slice first item top padding 5. update slider bar right padding 6. update panel height 7. align slice title item left with panel title icon 8. enlarge slice end item right padding Bug: 156045699 Test: manual test Change-Id: I51321d98ef483665689b7e258d6071df76382759 --- res/layout/panel_layout.xml | 16 ++++------- res/values/dimens.xml | 4 ++- res/values/styles.xml | 9 +++--- .../android/settings/panel/PanelFragment.java | 28 +++++++++++++++++++ 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/res/layout/panel_layout.xml b/res/layout/panel_layout.xml index 5dd53c00023..326fee8bf43 100644 --- a/res/layout/panel_layout.xml +++ b/res/layout/panel_layout.xml @@ -30,21 +30,20 @@ + android:layout_width="@dimen/output_switcher_panel_icon_size" + android:layout_height="@dimen/output_switcher_panel_icon_size"/> + android:orientation="vertical"> 16dp - 12dp + 11dp + 506dp + 52dp 24dp diff --git a/res/values/styles.xml b/res/values/styles.xml index bf89a2300c1..12b06b09510 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -564,16 +564,17 @@