Merge "Cleanup notification channel slice" into rvc-dev
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
index a1fb076..8d6e07d 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
@@ -26,7 +26,6 @@
 import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector;
 import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
 import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
-import com.android.settings.fuelgauge.batterytip.detectors.RestrictAppDetector;
 import com.android.settings.fuelgauge.batterytip.detectors.SmartBatteryDetector;
 import com.android.settings.fuelgauge.batterytip.detectors.SummaryDetector;
 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
@@ -74,7 +73,9 @@
         tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect());
         tips.add(new EarlyWarningDetector(policy, context).detect());
         tips.add(new SummaryDetector(policy, batteryInfo.averageTimeToDischarge).detect());
-        tips.add(new RestrictAppDetector(context, policy).detect());
+        // Disable this feature now since it introduces false positive cases. We will try to improve
+        // it in the future.
+        // tips.add(new RestrictAppDetector(context, policy).detect());
 
         Collections.sort(tips);
         return tips;
diff --git a/src/com/android/settings/media/MediaOutputIndicatorWorker.java b/src/com/android/settings/media/MediaOutputIndicatorWorker.java
index 0c6c434..f094d47 100644
--- a/src/com/android/settings/media/MediaOutputIndicatorWorker.java
+++ b/src/com/android/settings/media/MediaOutputIndicatorWorker.java
@@ -38,6 +38,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.utils.ThreadUtils;
 
 import com.google.common.annotations.VisibleForTesting;
 
@@ -81,25 +82,29 @@
         mContext.registerReceiver(mReceiver, intentFilter);
         mLocalBluetoothManager.getEventManager().registerCallback(this);
 
-        final MediaController controller = getActiveLocalMediaController();
-        if (controller == null) {
-            mPackageName = null;
-        } else {
-            mPackageName = controller.getPackageName();
-        }
-        if (mLocalMediaManager == null || !TextUtils.equals(mPackageName,
-                mLocalMediaManager.getPackageName())) {
-            mLocalMediaManager = new LocalMediaManager(mContext, mPackageName,
-                    null /* notification */);
-        }
-        mLocalMediaManager.registerCallback(this);
-        mLocalMediaManager.startScan();
+        ThreadUtils.postOnBackgroundThread(() -> {
+            final MediaController controller = getActiveLocalMediaController();
+            if (controller == null) {
+                mPackageName = null;
+            } else {
+                mPackageName = controller.getPackageName();
+            }
+            if (mLocalMediaManager == null || !TextUtils.equals(mPackageName,
+                    mLocalMediaManager.getPackageName())) {
+                mLocalMediaManager = new LocalMediaManager(mContext, mPackageName,
+                        null /* notification */);
+            }
+            mLocalMediaManager.registerCallback(this);
+            mLocalMediaManager.startScan();
+        });
     }
 
     @Override
     protected void onSliceUnpinned() {
-        mLocalMediaManager.unregisterCallback(this);
-        mLocalMediaManager.stopScan();
+        if (mLocalMediaManager != null) {
+            mLocalMediaManager.unregisterCallback(this);
+            mLocalMediaManager.stopScan();
+        }
 
         if (mLocalBluetoothManager == null) {
             Log.e(TAG, "Bluetooth is not supported on this device");
diff --git a/src/com/android/settings/media/MediaOutputSlice.java b/src/com/android/settings/media/MediaOutputSlice.java
index 4e54d7b..5a1afda 100644
--- a/src/com/android/settings/media/MediaOutputSlice.java
+++ b/src/com/android/settings/media/MediaOutputSlice.java
@@ -287,11 +287,16 @@
 
         if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
                 && !device.isConnected()) {
-            if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
+            final int state = device.getState();
+            if (state == LocalMediaManager.MediaDeviceState.STATE_CONNECTING
+                    || state == LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED) {
                 rowBuilder.setTitle(deviceName);
                 rowBuilder.setPrimaryAction(SliceAction.create(broadcastAction, deviceIcon,
                         ListBuilder.ICON_IMAGE, deviceName));
-                rowBuilder.setSubtitle(mContext.getText(R.string.media_output_switching));
+                rowBuilder.setSubtitle(
+                        (state == LocalMediaManager.MediaDeviceState.STATE_CONNECTING)
+                                ? mContext.getText(R.string.media_output_switching)
+                                : mContext.getText(R.string.bluetooth_connect_failed));
             } else {
                 // Append status to title only for the disconnected Bluetooth device.
                 final SpannableString spannableTitle = new SpannableString(
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index f55d65c..81d5036 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -297,7 +297,8 @@
                     getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
 
             // Can only run during setup if factory reset protection has already been cleared
-            return (pdbm != null && pdbm.getDataBlockSize() == 0);
+            // or if the device does not support FRP.
+            return (pdbm == null || pdbm.getDataBlockSize() == 0);
         }
 
         protected Class<? extends ChooseLockGeneric.InternalActivity> getInternalActivityClass() {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
index 2f5fa1a..116033b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
@@ -32,7 +32,6 @@
 import com.android.settings.fuelgauge.batterytip.tips.AppLabelPredicate;
 import com.android.settings.fuelgauge.batterytip.tips.AppRestrictionPredicate;
 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
-import com.android.settings.testutils.BatteryTestUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -43,15 +42,14 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.ReflectionHelpers;
 
 import java.util.List;
-import org.robolectric.util.ReflectionHelpers;
 
 @RunWith(RobolectricTestRunner.class)
 public class BatteryTipLoaderTest {
 
     private static final int[] TIP_ORDER = {
-            BatteryTip.TipType.APP_RESTRICTION,
             BatteryTip.TipType.BATTERY_SAVER,
             BatteryTip.TipType.HIGH_DEVICE_USAGE,
             BatteryTip.TipType.LOW_BATTERY,
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
index dd3a236..0aec952 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
@@ -103,7 +103,13 @@
     @Test
     public void onSlicePinned_registerCallback() {
         mMediaOutputIndicatorWorker.mLocalMediaManager = mLocalMediaManager;
+        initPlayback();
+        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+        when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mLocalMediaManager.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
         mMediaOutputIndicatorWorker.onSlicePinned();
+        waitForLocalMediaManagerInit();
 
         verify(mBluetoothEventManager).registerCallback(mMediaOutputIndicatorWorker);
         verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
@@ -119,11 +125,14 @@
         when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
 
         mMediaOutputIndicatorWorker.onSlicePinned();
+        waitForLocalMediaManagerInit();
         assertThat(mMediaOutputIndicatorWorker.mLocalMediaManager.getPackageName()).matches(
                 TEST_PACKAGE_NAME);
 
         when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME2);
+        mMediaOutputIndicatorWorker.mLocalMediaManager = null;
         mMediaOutputIndicatorWorker.onSlicePinned();
+        waitForLocalMediaManagerInit();
 
         assertThat(mMediaOutputIndicatorWorker.mLocalMediaManager.getPackageName()).matches(
                 TEST_PACKAGE_NAME2);
@@ -134,14 +143,35 @@
         mMediaControllers.clear();
 
         mMediaOutputIndicatorWorker.onSlicePinned();
+        waitForLocalMediaManagerInit();
 
         assertThat(mMediaOutputIndicatorWorker.mLocalMediaManager.getPackageName()).isNull();
     }
 
+    private void waitForLocalMediaManagerInit() {
+        for (int i = 0; i < 20; i++) {
+            if (mMediaOutputIndicatorWorker.mLocalMediaManager != null) {
+                return;
+            }
+            try {
+                Thread.sleep(50);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
     @Test
     public void onSliceUnpinned_unRegisterCallback() {
         mMediaOutputIndicatorWorker.mLocalMediaManager = mLocalMediaManager;
+        initPlayback();
+        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+        when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mLocalMediaManager.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
         mMediaOutputIndicatorWorker.onSlicePinned();
+        waitForLocalMediaManagerInit();
         mMediaOutputIndicatorWorker.onSliceUnpinned();
 
         verify(mBluetoothEventManager).unregisterCallback(mMediaOutputIndicatorWorker);
@@ -153,6 +183,7 @@
     @Test
     public void onReceive_shouldNotifyChange() {
         mMediaOutputIndicatorWorker.onSlicePinned();
+        waitForLocalMediaManagerInit();
         // onSlicePinned will registerCallback() and get first callback. Callback triggers this at
         // the first time.
         verify(mResolver, times(1)).notifyChange(URI, null);
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java
index 4e25801..0a8ffa7 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java
@@ -513,6 +513,44 @@
     }
 
     @Test
+    public void getSlice_disconnectedBtOnTransferringFailed_containTransferringFailedSubtitle() {
+        final List<MediaDevice> mSelectedDevices = new ArrayList<>();
+        final List<MediaDevice> mSelectableDevices = new ArrayList<>();
+        mDevices.clear();
+        final MediaDevice device = mock(MediaDevice.class);
+        when(device.getName()).thenReturn(TEST_DEVICE_1_NAME);
+        when(device.getIcon()).thenReturn(mTestDrawable);
+        when(device.getMaxVolume()).thenReturn(100);
+        when(device.isConnected()).thenReturn(true);
+        when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
+        when(device.getId()).thenReturn(TEST_DEVICE_1_ID);
+        final MediaDevice device2 = mock(MediaDevice.class);
+        when(device2.getName()).thenReturn(TEST_DEVICE_2_NAME);
+        when(device2.getIcon()).thenReturn(mTestDrawable);
+        when(device2.getMaxVolume()).thenReturn(100);
+        when(device2.isConnected()).thenReturn(false);
+        when(device2.getState()).thenReturn(
+                LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
+        when(device2.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
+        when(device2.getId()).thenReturn(TEST_DEVICE_2_ID);
+        mSelectedDevices.add(device);
+        mSelectableDevices.add(device2);
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device);
+        mDevices.add(device);
+        mDevices.add(device2);
+        when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mSelectedDevices);
+        when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(mSelectableDevices);
+        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);
+
+        final Slice mediaSlice = mMediaOutputSlice.getSlice();
+        final String sliceInfo = SliceQuery.findAll(mediaSlice, FORMAT_SLICE, HINT_LIST_ITEM,
+                null).toString();
+
+        assertThat(TextUtils.indexOf(sliceInfo,
+                mContext.getText(R.string.bluetooth_connect_failed))).isNotEqualTo(-1);
+    }
+
+    @Test
     public void onNotifyChange_foundMediaDevice_connect() {
         mDevices.clear();
         final MediaDevice device = mock(MediaDevice.class);
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
index b535bc1..036df2c 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
@@ -34,6 +34,7 @@
 
 import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.provider.Settings.Global;
@@ -48,7 +49,6 @@
 import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
 import com.android.settings.search.SearchFeatureProvider;
 import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
-import com.android.settings.testutils.shadow.ShadowPersistentDataBlockManager;
 import com.android.settings.testutils.shadow.ShadowStorageManager;
 import com.android.settings.testutils.shadow.ShadowUserManager;
 import com.android.settings.testutils.shadow.ShadowUtils;
@@ -62,6 +62,8 @@
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowPersistentDataBlockManager;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(
@@ -100,6 +102,17 @@
     }
 
     @Test
+    public void onCreate_deviceNotProvisioned_persistentDataServiceNotAvailable_shouldNotFinish() {
+        Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 0);
+        ShadowPersistentDataBlockManager.setDataBlockSize(1000);
+        ShadowApplication.getInstance().setSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE,
+                null);
+
+        initActivity(null);
+        assertThat(mActivity.isFinishing()).isFalse();
+    }
+
+    @Test
     public void onActivityResult_nullIntentData_shouldNotCrash() {
         initActivity(null);
         mFragment.onActivityResult(
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPersistentDataBlockManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPersistentDataBlockManager.java
deleted file mode 100644
index dbbdd3d..0000000
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPersistentDataBlockManager.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 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.settings.testutils.shadow;
-
-import android.service.persistentdata.PersistentDataBlockManager;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.Resetter;
-
-@Implements(PersistentDataBlockManager.class)
-public class ShadowPersistentDataBlockManager {
-    private static int sDataBlockSize = 0;
-
-    @Resetter
-    public static void reset() {
-        sDataBlockSize = 0;
-    }
-
-    @Implementation
-    protected int getDataBlockSize() {
-        return sDataBlockSize;
-    }
-
-    public static void setDataBlockSize(int dataBlockSize) {
-        sDataBlockSize = dataBlockSize;
-    }
-}