Merge "Add bottom padding in SliceView for output switcher panel" into rvc-dev
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index abd45af..dc23c3d 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -423,8 +423,9 @@
 
     <!-- Output switcher panel related dimensions -->
     <dimen name="output_switcher_slice_padding_top">11dp</dimen>
-    <dimen name="output_switcher_slice_max_height">506dp</dimen>
+    <dimen name="output_switcher_slice_max_height">458dp</dimen>
     <dimen name="output_switcher_panel_icon_size">52dp</dimen>
+    <dimen name="output_switcher_panel_icon_corner_radius">16dp</dimen>
 
     <!-- Text padding for EmptyTextSettings -->
     <dimen name="empty_text_padding">24dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9a56264..1dbc0a0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11322,7 +11322,7 @@
     <string name="platform_compat_default_disabled_title">Default disabled changes</string>
     <!-- Title for target SDK gated app compat changes category (do not translate 'targetSdkVersion') [CHAR LIMIT=50] -->
     <string name="platform_compat_target_sdk_title">Enabled for targetSdkVersion &gt; <xliff:g id="number" example="29">%d</xliff:g></string>
-    <!-- Title for the dialog shown when no debuggable apps are available [CHAR LIMIT=20] -->
+    <!-- Title for the dialog shown when no debuggable apps are available [CHAR LIMIT=30] -->
     <string name="platform_compat_dialog_title_no_apps">No apps available</string>
     <!-- Explanatory text shown when no debuggable apps are available [CHAR LIMIT=NONE] -->
     <string name="platform_compat_dialog_text_no_apps">App compatibility changes can only be modified for debuggable apps. Install a debuggable app and try again.</string>
@@ -12135,7 +12135,7 @@
 
     <!-- Title for media control settings [CHAR LIMIT=50]-->
     <string name="media_controls_title">Media</string>
-    <!-- Summary for media control settings [CHAR LIMIT=50]-->
+    <!-- Summary for media control settings [CHAR LIMIT=60]-->
     <string name="media_controls_summary">Hide player when the media session has ended</string>
     <!-- Description of toggle to enable or disable the media resumption feature in quick settings [CHAR LIMIT=NONE]-->
     <string name="media_controls_resume_description">The player allows you to resume a session from the expanded Quick Settings panel.</string>
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index e5fd340..64919d9 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -92,8 +92,11 @@
 import android.widget.ListView;
 import android.widget.TabWidget;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
 import androidx.core.graphics.drawable.IconCompat;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 import androidx.fragment.app.Fragment;
 import androidx.lifecycle.Lifecycle;
 import androidx.preference.Preference;
@@ -1113,4 +1116,25 @@
                 context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
         return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
     }
+
+    /**
+     * Returns a bitmap with rounded corner.
+     *
+     * @param context application context.
+     * @param source bitmap to apply round corner.
+     * @param cornerRadius corner radius value.
+     */
+    public static Bitmap convertCornerRadiusBitmap(@NonNull Context context,
+            @NonNull Bitmap source, @NonNull float cornerRadius) {
+        final Bitmap roundedBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(),
+                Bitmap.Config.ARGB_8888);
+        final RoundedBitmapDrawable drawable =
+                RoundedBitmapDrawableFactory.create(context.getResources(), source);
+        drawable.setAntiAlias(true);
+        drawable.setCornerRadius(cornerRadius);
+        final Canvas canvas = new Canvas(roundedBitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+        return roundedBitmap;
+    }
 }
diff --git a/src/com/android/settings/display/TwilightLocationPreferenceController.java b/src/com/android/settings/display/TwilightLocationPreferenceController.java
index 131b0a8..31845c0 100644
--- a/src/com/android/settings/display/TwilightLocationPreferenceController.java
+++ b/src/com/android/settings/display/TwilightLocationPreferenceController.java
@@ -21,7 +21,6 @@
 import android.location.LocationManager;
 import android.view.View;
 
-import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
@@ -58,12 +57,8 @@
     }
 
     @Override
-    public void updateState(Preference preference) {
-        preference.setVisible(!mLocationManager.isLocationEnabled());
-    }
-
-    @Override
     public int getAvailabilityStatus() {
-        return AVAILABLE_UNSEARCHABLE;
+        return mLocationManager.isLocationEnabled() ? CONDITIONALLY_UNAVAILABLE
+                : AVAILABLE_UNSEARCHABLE;
     }
 }
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
index d30fe59..e4a644c 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
@@ -184,6 +184,10 @@
 
     @VisibleForTesting
     List<ContextualCard> filterEligibleCards(List<ContextualCard> candidates) {
+        if (candidates.isEmpty()) {
+            return candidates;
+        }
+
         final ExecutorService executor = Executors.newFixedThreadPool(candidates.size());
         final List<ContextualCard> cards = new ArrayList<>();
         List<Future<ContextualCard>> eligibleCards = new ArrayList<>();
diff --git a/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java b/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java
index 46b4c86..4340330 100644
--- a/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java
+++ b/src/com/android/settings/homepage/contextualcards/EligibleCardChecker.java
@@ -22,7 +22,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -33,7 +32,6 @@
 
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -117,12 +115,7 @@
         // Register a trivial callback to pin the slice
         manager.registerSliceCallback(uri, callback);
         final Slice slice = manager.bindSlice(uri);
-
-        // Workaround of unpinning slice in the same SerialExecutor of AsyncTask as SliceCallback's
-        // observer.
-        ThreadUtils.postOnMainThread(() ->
-                AsyncTask.execute(() -> manager.unregisterSliceCallback(uri, callback))
-        );
+        manager.unregisterSliceCallback(uri, callback);
 
         return slice;
     }
diff --git a/src/com/android/settings/panel/MediaOutputPanel.java b/src/com/android/settings/panel/MediaOutputPanel.java
index d8dbb34..6a296b7 100644
--- a/src/com/android/settings/panel/MediaOutputPanel.java
+++ b/src/com/android/settings/panel/MediaOutputPanel.java
@@ -116,7 +116,11 @@
         if (metadata != null) {
             final Bitmap bitmap = metadata.getDescription().getIconBitmap();
             if (bitmap != null) {
-                return IconCompat.createWithBitmap(bitmap);
+                final Bitmap roundBitmap = Utils.convertCornerRadiusBitmap(mContext, bitmap,
+                        (float) mContext.getResources().getDimensionPixelSize(
+                                R.dimen.output_switcher_panel_icon_corner_radius));
+
+                return IconCompat.createWithBitmap(roundBitmap);
             }
         }
         Log.d(TAG, "Media meta data does not contain icon information");
diff --git a/src/com/android/settings/slices/SlicesDatabaseAccessor.java b/src/com/android/settings/slices/SlicesDatabaseAccessor.java
index e31715d..c0bb8de 100644
--- a/src/com/android/settings/slices/SlicesDatabaseAccessor.java
+++ b/src/com/android/settings/slices/SlicesDatabaseAccessor.java
@@ -127,10 +127,12 @@
         int numResults = resultCursor.getCount();
 
         if (numResults == 0) {
+            resultCursor.close();
             throw new IllegalStateException("Invalid Slices key from path: " + path);
         }
 
         if (numResults > 1) {
+            resultCursor.close();
             throw new IllegalStateException(
                     "Should not match more than 1 slice with path: " + path);
         }
diff --git a/tests/robotests/src/com/android/settings/display/TwilightLocationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/TwilightLocationPreferenceControllerTest.java
new file mode 100644
index 0000000..983fc51
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/TwilightLocationPreferenceControllerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.display;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.location.LocationManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class TwilightLocationPreferenceControllerTest {
+    private static final String TEST_KEY = "test_key";
+
+    private Context mContext;
+    private TwilightLocationPreferenceController mController;
+
+    @Mock
+    private LocationManager mLocationManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getSystemService(LocationManager.class)).thenReturn(mLocationManager);
+        mController = new TwilightLocationPreferenceController(mContext, TEST_KEY);
+    }
+
+    @Test
+    public void getAvailabilityStatus_locationEnabled_shouldBeCONDITIONALLY_UNAVAILABLE() {
+        when(mLocationManager.isLocationEnabled()).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_locationDisabled_shouldBeAVAILABLE_UNSEARCHABLE() {
+        when(mLocationManager.isLocationEnabled()).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
index be86b6e..7a3243b 100644
--- a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
@@ -232,13 +232,20 @@
     public void onSlicePinned_packageUpdated_checkPackageName() {
         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
+
         mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, URI1);
+        mMediaDeviceUpdateWorker.mLocalMediaManager = mock(LocalMediaManager.class);
+        when(mMediaDeviceUpdateWorker.mLocalMediaManager.getPackageName())
+                .thenReturn(TEST_DEVICE_PACKAGE_NAME1);
         mMediaDeviceUpdateWorker.onSlicePinned();
 
         assertThat(mMediaDeviceUpdateWorker.mLocalMediaManager.getPackageName()).matches(
                 TEST_DEVICE_PACKAGE_NAME1);
 
         mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, URI2);
+        mMediaDeviceUpdateWorker.mLocalMediaManager = mock(LocalMediaManager.class);
+        when(mMediaDeviceUpdateWorker.mLocalMediaManager.getPackageName())
+                .thenReturn(TEST_DEVICE_PACKAGE_NAME2);
         mMediaDeviceUpdateWorker.onSlicePinned();
 
         assertThat(mMediaDeviceUpdateWorker.mLocalMediaManager.getPackageName()).matches(
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
index 423c7ac..d9b2247 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
@@ -121,10 +121,12 @@
 
     @Test
     public void onSlicePinned_packageUpdated_checkPackageName() {
+        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();
@@ -132,7 +134,7 @@
                 TEST_PACKAGE_NAME);
 
         when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME2);
-        mMediaOutputIndicatorWorker.mLocalMediaManager = null;
+        when(mLocalMediaManager.getPackageName()).thenReturn(TEST_PACKAGE_NAME2);
         mMediaOutputIndicatorWorker.onSlicePinned();
         waitForLocalMediaManagerInit();
 
diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
index 0fa249d..2f0d60a 100644
--- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
@@ -270,23 +270,27 @@
 
     @Test
     public void onBindSlice_nightModeChanged_shouldReloadTheme() {
-        mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
-
+        mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_NO;
         final SliceData data = getDummyData();
         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
         mProvider.onBindSlice(data.getUri());
 
+        mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
+        mProvider.onBindSlice(data.getUri());
+
         assertThat(ShadowTheme.isThemeRebased()).isTrue();
     }
 
     @Test
     public void onBindSlice_nightModeNotChanged_shouldNotReloadTheme() {
         mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_NO;
-
         SliceData data = getDummyData();
         mProvider.mSliceWeakDataCache.put(data.getUri(), data);
         mProvider.onBindSlice(data.getUri());
 
+        mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_NO;
+        mProvider.onBindSlice(data.getUri());
+
         assertThat(ShadowTheme.isThemeRebased()).isFalse();
     }