Merge "Return back-up slices for unavailable settings" into pi-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9ff0a08..8f82ae0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9668,6 +9668,27 @@
          show both names, with the directory name wrapped in parenthesis -->
     <string name="directory_on_volume"><xliff:g id="volume" example="SD Card">%1$s</xliff:g> (<xliff:g id="directory" example="Movies">%2$s</xliff:g>)</string>
 
+    <!-- Slices Strings -->
+
+    <!-- Summary text on a card explaining that a setting does not exist / is not supported on the device [CHAR_LIMIT=NONE]-->
+    <string name="unsupported_setting_summary" product="default">Setting isn’t supported on this phone</string>
+
+    <!-- Summary text on a card explaining that a setting does not exist / is not supported on the device [CHAR_LIMIT=NONE]-->
+    <string name="unsupported_setting_summary" product="tablet">Setting isn’t supported on this tablet</string>
+
+    <!-- Summary text on a card explaining that a setting does not exist / is not supported on the device [CHAR_LIMIT=NONE]-->
+    <string name="unsupported_setting_summary" product="device">Setting isn’t supported on this device</string>
+
+    <!-- Summary text on a card explaining that a setting cannot be changed by the current user. [CHAR_LIMIT=NONE] -->
+    <string name="disabled_for_user_setting_summary">Setting can’t be changed by current user</string>
+
+    <!-- Summary text on a card explaining a setting cannot be changed right now because it needs another setting to be changed. [CHAR_LIMIT=NONE] -->
+    <string name="disabled_dependent_setting_summary">Depends on another setting</string>
+
+    <!-- Summary text on a card explaining a setting cannot be changed right now, but we don't know the reason. [CHAR_LIMIT=NONE] -->
+    <string name="unknown_unavailability_setting_summary">Setting unavailable</string>
+
+
     <!-- Account type associated with the backup account. Empty for AOSP. [DO NOT TRANSLATE] -->
     <string name="account_type" translatable="false"></string>
     <!-- Package to target for Account credential confirmation. This will allow users to
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 8b3bdbd..b07bcb0 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -90,6 +90,12 @@
      */
     public static final String EXTRA_SLICE_KEY = "com.android.settings.slice.extra.key";
 
+    /**
+     * Boolean extra to indicate if the Slice is platform-defined.
+     */
+    public static final String EXTRA_SLICE_PLATFORM_DEFINED =
+            "com.android.settings.slice.extra.platform";
+
     // TODO -- Associate slice URI with search result instead of separate hardcoded thing
 
     @VisibleForTesting
diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java
index c455640..f47aeb2 100644
--- a/src/com/android/settings/slices/SliceBroadcastReceiver.java
+++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java
@@ -20,6 +20,7 @@
 import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CHANGED;
 import static com.android.settings.slices.SettingsSliceProvider.ACTION_WIFI_CHANGED;
 import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
+import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -27,7 +28,9 @@
 import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
+import android.provider.SettingsSlicesContract;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.core.SliderPreferenceController;
@@ -49,12 +52,14 @@
      */
     @Override
     public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-        String key = intent.getStringExtra(EXTRA_SLICE_KEY);
+        final String action = intent.getAction();
+        final String key = intent.getStringExtra(EXTRA_SLICE_KEY);
+        final boolean isPlatformDefined = intent.getBooleanExtra(EXTRA_SLICE_PLATFORM_DEFINED,
+                false /* default */);
 
         switch (action) {
             case ACTION_TOGGLE_CHANGED:
-                handleToggleAction(context, key);
+                handleToggleAction(context, key, isPlatformDefined);
                 break;
             case ACTION_SLIDER_CHANGED:
                 int newPosition = intent.getIntExtra(SliceHints.EXTRA_RANGE_VALUE, -1);
@@ -76,7 +81,7 @@
         }
     }
 
-    private void handleToggleAction(Context context, String key) {
+    private void handleToggleAction(Context context, String key, boolean isPlatformSlice) {
         if (TextUtils.isEmpty(key)) {
             throw new IllegalStateException("No key passed to Intent for toggle controller");
         }
@@ -87,11 +92,17 @@
             throw new IllegalStateException("Toggle action passed for a non-toggle key: " + key);
         }
 
+        if (!controller.isAvailable()) {
+            Log.d(TAG, "Can't update " + key + " since the setting is unavailable");
+            updateUri(context, key, isPlatformSlice);
+        }
+
         // TODO post context.getContentResolver().notifyChanged(uri, null) in the Toggle controller
         // so that it's automatically broadcast to any slice.
         final TogglePreferenceController toggleController = (TogglePreferenceController) controller;
         final boolean currentValue = toggleController.isChecked();
         toggleController.setChecked(!currentValue);
+        updateUri(context, key, isPlatformSlice);
     }
 
     private void handleSliderAction(Context context, String key, int newPosition) {
@@ -126,4 +137,10 @@
         final SliceData sliceData = accessor.getSliceDataFromKey(key);
         return SliceBuilderUtils.getPreferenceController(context, sliceData);
     }
+
+    private void updateUri(Context context, String key, boolean isPlatformDefined) {
+        final String path = SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key;
+        final Uri uri = SliceBuilderUtils.getUri(path, isPlatformDefined);
+        context.getContentResolver().notifyChange(uri, null /* observer */);
+    }
 }
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
index 43cba73..af9a336 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -16,7 +16,12 @@
 
 package com.android.settings.slices;
 
+import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING;
+import static com.android.settings.core.BasePreferenceController.DISABLED_FOR_USER;
+import static com.android.settings.core.BasePreferenceController.DISABLED_UNSUPPORTED;
+import static com.android.settings.core.BasePreferenceController.UNAVAILABLE_UNKNOWN;
 import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
+import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED;
 
 import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
 
@@ -24,6 +29,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.provider.SettingsSlicesContract;
@@ -42,7 +48,6 @@
 import androidx.slice.Slice;
 import androidx.slice.builders.SliceAction;
 import androidx.slice.builders.ListBuilder;
-import androidx.slice.builders.ListBuilder.RowBuilder;
 
 
 /**
@@ -61,8 +66,12 @@
      * {@param sliceData} is an inline controller.
      */
     public static Slice buildSlice(Context context, SliceData sliceData) {
-        // TODO (b/71640747) Respect setting availability.
         final BasePreferenceController controller = getPreferenceController(context, sliceData);
+
+        if (!controller.isAvailable()) {
+            return buildUnavailableSlice(context, sliceData, controller);
+        }
+
         switch (sliceData.getSliceType()) {
             case SliceData.SliceType.INTENT:
                 return buildIntentSlice(context, sliceData, controller);
@@ -120,8 +129,8 @@
     }
 
     /**
-     * Looks at the {@link SliceData#preferenceController} from {@param sliceData} and attempts to
-     * build an {@link AbstractPreferenceController}.
+     * Looks at the controller classname in in {@link SliceData} from {@param sliceData}
+     * and attempts to build an {@link AbstractPreferenceController}.
      */
     public static BasePreferenceController getPreferenceController(Context context,
             SliceData sliceData) {
@@ -147,7 +156,7 @@
         final CharSequence subtitleText = getSubtitleText(context, controller, sliceData);
         final TogglePreferenceController toggleController =
                 (TogglePreferenceController) controller;
-        final SliceAction sliceAction = getToggleAction(context, sliceData.getKey(),
+        final SliceAction sliceAction = getToggleAction(context, sliceData,
                 toggleController.isChecked());
 
         return new ListBuilder(context, sliceData.getUri())
@@ -179,7 +188,7 @@
             BasePreferenceController controller) {
         final SliderPreferenceController sliderController =
                 (SliderPreferenceController) controller;
-        final PendingIntent actionIntent = getSliderAction(context, sliceData.getKey());
+        final PendingIntent actionIntent = getSliderAction(context, sliceData);
         return new ListBuilder(context, sliceData.getUri())
                 .addInputRange(builder -> builder
                         .setTitle(sliceData.getTitle())
@@ -200,20 +209,22 @@
         return BasePreferenceController.createInstance(context, controllerClassName, controllerKey);
     }
 
-    private static SliceAction getToggleAction(Context context, String key, boolean isChecked) {
+    private static SliceAction getToggleAction(Context context, SliceData sliceData,
+            boolean isChecked) {
         PendingIntent actionIntent = getActionIntent(context,
-                SettingsSliceProvider.ACTION_TOGGLE_CHANGED, key);
+                SettingsSliceProvider.ACTION_TOGGLE_CHANGED, sliceData);
         return new SliceAction(actionIntent, null, isChecked);
     }
 
-    private static PendingIntent getSliderAction(Context context, String key) {
-        return getActionIntent(context, SettingsSliceProvider.ACTION_SLIDER_CHANGED, key);
+    private static PendingIntent getSliderAction(Context context, SliceData sliceData) {
+        return getActionIntent(context, SettingsSliceProvider.ACTION_SLIDER_CHANGED, sliceData);
     }
 
-    private static PendingIntent getActionIntent(Context context, String action, String key) {
+    private static PendingIntent getActionIntent(Context context, String action, SliceData data) {
         Intent intent = new Intent(action);
         intent.setClass(context, SliceBroadcastReceiver.class);
-        intent.putExtra(EXTRA_SLICE_KEY, key);
+        intent.putExtra(EXTRA_SLICE_KEY, data.getKey());
+        intent.putExtra(EXTRA_SLICE_PLATFORM_DEFINED, data.isPlatformDefined());
         return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
                 PendingIntent.FLAG_CANCEL_CURRENT);
     }
@@ -226,6 +237,12 @@
         return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */);
     }
 
+    private static PendingIntent getSettingsIntent(Context context) {
+        final PackageManager manager = context.getPackageManager();
+        final Intent intent = manager.getLaunchIntentForPackage(context.getPackageName());
+        return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */);
+    }
+
     @VisibleForTesting
     static CharSequence getSubtitleText(Context context, AbstractPreferenceController controller,
             SliceData sliceData) {
@@ -257,4 +274,41 @@
         return !(TextUtils.equals(summary, placeHolder)
                 || TextUtils.equals(summary, doublePlaceHolder));
     }
+
+    private static Slice buildUnavailableSlice(Context context, SliceData data,
+            BasePreferenceController controller) {
+        final String title = data.getTitle();
+        final String summary;
+        final SliceAction primaryAction;
+
+        switch (controller.getAvailabilityStatus()) {
+            case DISABLED_UNSUPPORTED:
+                summary = context.getString(R.string.unsupported_setting_summary);
+                primaryAction = new SliceAction(getSettingsIntent(context), null /* actionIcon */,
+                        null /* actionTitle */);
+                break;
+            case DISABLED_FOR_USER:
+                summary = context.getString(R.string.disabled_for_user_setting_summary);
+                primaryAction = new SliceAction(getContentIntent(context, data),
+                        null /* actionIcon */, null /* actionTitle */);
+                break;
+            case DISABLED_DEPENDENT_SETTING:
+                summary = context.getString(R.string.disabled_dependent_setting_summary);
+                primaryAction = new SliceAction(getContentIntent(context, data),
+                        null /* actionIcon */, null /* actionTitle */);
+                break;
+            case UNAVAILABLE_UNKNOWN:
+            default:
+                summary = context.getString(R.string.unknown_unavailability_setting_summary);
+                primaryAction = new SliceAction(getSettingsIntent(context),
+                        null /* actionIcon */, null /* actionTitle */);
+        }
+
+        return new ListBuilder(context, data.getUri())
+                .addRow(builder -> builder
+                        .setTitle(title)
+                        .setSubtitle(summary)
+                        .setPrimaryAction(primaryAction))
+                .build();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/slices/FakeUnavailablePreferenceController.java b/tests/robotests/src/com/android/settings/slices/FakeUnavailablePreferenceController.java
new file mode 100644
index 0000000..a7e5d75
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/FakeUnavailablePreferenceController.java
@@ -0,0 +1,21 @@
+package com.android.settings.slices;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class FakeUnavailablePreferenceController extends BasePreferenceController {
+
+    public static final String AVAILABILITY_KEY = "fake_availability_key";
+
+    public FakeUnavailablePreferenceController(Context context) {
+        super(context, "key");
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return Settings.System.getInt(mContext.getContentResolver(),
+                AVAILABILITY_KEY, 0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
index d0c0326..d21fc05 100644
--- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
@@ -17,12 +17,14 @@
 package com.android.settings.slices;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.Uri;
+import android.provider.Settings;
 import android.provider.SettingsSlicesContract;
 import android.util.Pair;
 
@@ -132,7 +134,7 @@
     @Test
     public void testGetPreferenceController_buildsMatchingController() {
         BasePreferenceController controller =
-            SliceBuilderUtils.getPreferenceController(mContext, getDummyData());
+                SliceBuilderUtils.getPreferenceController(mContext, getDummyData());
 
         assertThat(controller).isInstanceOf(FakeToggleController.class);
     }
@@ -140,7 +142,7 @@
     @Test
     public void testGetPreferenceController_contextOnly_buildsMatchingController() {
         BasePreferenceController controller =
-            SliceBuilderUtils.getPreferenceController(mContext, getDummyData(PREF_CONTROLLER2));
+                SliceBuilderUtils.getPreferenceController(mContext, getDummyData(PREF_CONTROLLER2));
 
         assertThat(controller).isInstanceOf(FakeContextOnlyPreferenceController.class);
     }
@@ -251,6 +253,54 @@
         assertThat(pathPair.second).isEqualTo(KEY + "/" + KEY);
     }
 
+    @Test
+    public void testUnsupportedSlice_validTitleSummary() {
+        SliceData data = getDummyData(FakeUnavailablePreferenceController.class.getName());
+        Settings.System.putInt(mContext.getContentResolver(),
+                FakeUnavailablePreferenceController.AVAILABILITY_KEY,
+                BasePreferenceController.DISABLED_UNSUPPORTED);
+
+        Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
+
+        assertThat(slice).isNotNull();
+    }
+
+    @Test
+    public void testDisabledForUserSlice_validTitleSummary() {
+        SliceData data = getDummyData(FakeUnavailablePreferenceController.class.getName());
+        Settings.System.putInt(mContext.getContentResolver(),
+                FakeUnavailablePreferenceController.AVAILABILITY_KEY,
+                BasePreferenceController.DISABLED_FOR_USER);
+
+        Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
+
+        assertThat(slice).isNotNull();
+    }
+
+    @Test
+    public void testDisabledDependententSettingSlice_validTitleSummary() {
+        SliceData data = getDummyData(FakeUnavailablePreferenceController.class.getName());
+        Settings.System.putInt(mContext.getContentResolver(),
+                FakeUnavailablePreferenceController.AVAILABILITY_KEY,
+                BasePreferenceController.DISABLED_DEPENDENT_SETTING);
+
+        Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
+
+        assertThat(slice).isNotNull();
+    }
+
+    @Test
+    public void testUnavailableUnknownSlice_validTitleSummary() {
+        SliceData data = getDummyData(FakeUnavailablePreferenceController.class.getName());
+        Settings.System.putInt(mContext.getContentResolver(),
+                FakeUnavailablePreferenceController.AVAILABILITY_KEY,
+                BasePreferenceController.UNAVAILABLE_UNKNOWN);
+
+        Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
+
+        assertThat(slice).isNotNull();
+    }
+
     private SliceData getDummyData() {
         return getDummyData(PREF_CONTROLLER, SUMMARY);
     }