Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fcdfc2d8 authored by Matthew Fritze's avatar Matthew Fritze
Browse files

Return back-up slices for unavailable settings

When an inline slice is requested, and the setting is unavailable,
we should present more useful information to the user. This CL handles:
- Unsupported: return intent slice to Settings home page
- Disabled for user: intent to the setting page
- Unknown reason: intent to setting page
- Disabled dependency: Create intent-based Slice rather
than the requested inline slice.

Bug: 71640747
Test: robotests
Change-Id: I9c1a0ee36119d4f9f3b205e0824c251f4356db55
parent 80bf63a2
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -9615,6 +9615,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
+6 −0
Original line number Diff line number Diff line
@@ -90,6 +90,12 @@ public class SettingsSliceProvider extends SliceProvider {
     */
    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
+21 −4
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.settings.slices.SettingsSliceProvider.ACTION_SLIDER_CH
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.content.Intent;
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 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
     */
    @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 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
        }
    }

    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 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
            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 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
        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 */);
    }
}
+66 −12
Original line number Diff line number Diff line
@@ -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.app.PendingIntent;
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 com.android.settingslib.core.AbstractPreferenceController;
import androidx.slice.Slice;
import androidx.slice.builders.SliceAction;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;


/**
@@ -61,8 +66,12 @@ public class SliceBuilderUtils {
     * {@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 @@ public class SliceBuilderUtils {
    }

    /**
     * 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 @@ public class SliceBuilderUtils {
        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 @@ public class SliceBuilderUtils {
            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 @@ public class SliceBuilderUtils {
        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 @@ public class SliceBuilderUtils {
        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 @@ public class SliceBuilderUtils {
        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();
    }
}
+21 −0
Original line number Diff line number Diff line
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);
    }
}
Loading