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

Commit 56b2bad0 authored by Matthew Fritze's avatar Matthew Fritze
Browse files

Handle special case slices

Create a handler for any slice that doesn't include anything
from a PreferenceController.

Test: robotests
Change-Id: If23947152d61877537d0cac6240e96b9ab977bce
Bug: 80263568
parent 2d41659c
Loading
Loading
Loading
Loading
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.slices;

import android.content.Context;
import android.net.Uri;
import android.util.ArrayMap;

import java.util.Map;

/**
 * Manages custom {@link androidx.slice.Slice Slices}, which are all Slices not backed by
 * preferences.
 * <p>
 *     By default, all Slices in Settings should be built by a
 * </p>
 */
public class CustomSliceManager {

    protected final Map<Uri, Class<? extends CustomSliceable>> mUriMap;

    private final Context mContext;

    public CustomSliceManager(Context context) {
        mContext = context;
        mUriMap = new ArrayMap<>();
        addSlices();
    }

    /**
     * Return a {@link CustomSliceable} associated to the Uri.
     * <p>
     * Do not change this method signature to accommodate for a special-case slicable - a context is
     * the only thing that should be needed to create the object.
     */
    public CustomSliceable getSliceableFromUri(Uri uri) {
        final Class clazz = mUriMap.get(uri);

        if (clazz == null) {
            throw new IllegalArgumentException("No Slice found for uri: " + uri);
        }

        return CustomSliceable.createInstance(mContext, clazz);
    }

    /**
     * Return a {@link CustomSliceable} associated to the Action.
     * <p>
     * Do not change this method signature to accommodate for a special-case sliceable - a context
     * is the only thing that should be needed to create the object.
     */
    public CustomSliceable getSliceableFromIntentAction(String action) {
        return getSliceableFromUri(Uri.parse(action));
    }

    /**
     * Returns {@code true} if {@param uri} is a valid Slice Uri handled by
     * {@link CustomSliceManager}.
     */
    public boolean isValidUri(Uri uri) {
        return mUriMap.containsKey(uri);
    }

    /**
     * Returns {@code true} if {@param action} is a valid intent action handled by
     * {@link CustomSliceManager}.
     */
    public boolean isValidAction(String action) {
        return isValidUri(Uri.parse(action));
    }

    private void addSlices() {
    }
}
 No newline at end of file
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.slices;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import androidx.slice.Slice;


/**
 * Common functions for custom Slices.
 * <p>
 *     A template for all Settings slices which are not represented by a Preference. By
 *     standardizing the methods used by the Slice helpers, we can use generically take actions
 *     rather than maintaining a list of all of the custom slices every time we reference Slices in
 *     Settings.
 * <p>
 *     By default, all Slices in Settings should be built through Preference Controllers extending
 *     {@link com.android.settings.core.BasePreferenceController}, which are automatically piped
 *     into Settings-Slices infrastructure. Cases where you should implement this interface are:
 *     <ul>
 *         <li>Multi-line slices</li>
 *         <li>Slices that don't exist in the UI</li>
 *         <li>Preferences that use a supported component, like a Switch Bar</li>
 *     </ul>
 * <p>
 *      Note that if your UI is supported because the Preference is not backed by a
 *      {@link com.android.settings.dashboard.DashboardFragment}, then you should first convert the
 *      existing fragment into a dashboard fragment, and then extend
 *      {@link com.android.settings.core.BasePreferenceController}.
 * <p>
 *     If you implement this interface, you should add your Slice to {@link CustomSliceManager}.
 */
public interface CustomSliceable {

    /**
     * @return an complete instance of the {@link Slice}.
     */
    Slice getSlice(Context context);

    /**
     * @return a {@link android.content.ContentResolver#SCHEME_CONTENT content} {@link Uri} which
     * backs the {@link Slice} returned by {@link #getSlice(Context)}.
     */
    Uri getUri();

    /**
     * Handles the actions sent by the {@link Intent intents} bound to the {@link Slice} returned by
     * {@link #getSlice(Context)}.
     *
     * @param intent which has the action taken on a {@link Slice}.
     */
    void onNotifyChange(Intent intent);

    /**
     * Settings Slices which can represent components that are updatable by the framework should
     * listen to changes matched to the {@link IntentFilter} returned here.
     *
     * @return an {@link IntentFilter} for updates related to the {@link Slice} returned by
     * {@link #getSlice(Context)}.
     */
    default IntentFilter getIntentFilter() {
        return null;
    }

    /**
     * Build an instance of a {@link CustomSliceable} which has a {@link Context}-only constructor.
     */
    static CustomSliceable createInstance(Context context, Class<CustomSliceable> sliceableClass) {
        try {
            //final Class<CustomSliceable> clazz = Class.forName(sliceableClassName);
            final Constructor<CustomSliceable> sliceable =
                    sliceableClass.getConstructor(Context.class);
            final Object[] params = new Object[]{context};
            return sliceable.newInstance(params);
        } catch (NoSuchMethodException | InstantiationException |
                IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
            throw new IllegalStateException(
                    "Invalid sliceable class: " + sliceableClass, e);
        }
    }
}
 No newline at end of file
+27 −6
Original line number Diff line number Diff line
@@ -32,15 +32,15 @@ import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;

import com.android.settings.bluetooth.BluetoothSliceBuilder;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.flashlight.FlashlightSliceBuilder;
import com.android.settings.location.LocationSliceBuilder;
import com.android.settings.mobilenetwork.Enhanced4gLteSliceHelper;
import com.android.settings.notification.ZenModeSliceBuilder;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.wifi.WifiSliceBuilder;
import com.android.settings.wifi.calling.WifiCallingSliceHelper;
import com.android.settings.bluetooth.BluetoothSliceBuilder;
import com.android.settingslib.SliceBroadcastRelay;
import com.android.settingslib.utils.ThreadUtils;

@@ -113,11 +113,15 @@ public class SettingsSliceProvider extends SliceProvider {
    public static final String EXTRA_SLICE_PLATFORM_DEFINED =
            "com.android.settings.slice.extra.platform";

    @VisibleForTesting
    CustomSliceManager mCustomSliceManager;

    @VisibleForTesting
    SlicesDatabaseAccessor mSlicesDatabaseAccessor;

    @VisibleForTesting
    Map<Uri, SliceData> mSliceWeakDataCache;

    @VisibleForTesting
    Map<Uri, SliceData> mSliceDataCache;

@@ -135,6 +139,8 @@ public class SettingsSliceProvider extends SliceProvider {
        mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext());
        mSliceDataCache = new ConcurrentHashMap<>();
        mSliceWeakDataCache = new WeakHashMap<>();
        mCustomSliceManager = FeatureFactory.getFactory(
                getContext()).getSlicesFeatureProvider().getCustomSliceManager(getContext());
        return true;
    }

@@ -151,6 +157,15 @@ public class SettingsSliceProvider extends SliceProvider {

    @Override
    public void onSlicePinned(Uri sliceUri) {
        if (mCustomSliceManager.isValidUri(sliceUri)) {
            final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri(sliceUri);
            final IntentFilter filter = sliceable.getIntentFilter();
            if (filter != null) {
                registerIntentToUri(filter, sliceUri);
            }
            return;
        }

        if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) {
            registerIntentToUri(WifiSliceBuilder.INTENT_FILTER, sliceUri);
            mRegisteredUris.add(sliceUri);
@@ -197,8 +212,14 @@ public class SettingsSliceProvider extends SliceProvider {
                return null;
            }

            // If adding a new Slice, do not directly match Slice URIs.
            // Use {@link SlicesDatabaseAccessor}.
            // Before adding a slice to {@link CustomSliceManager}, please get approval
            // from the Settings team.
            if (mCustomSliceManager.isValidUri(sliceUri)) {
                final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri(
                        sliceUri);
                return sliceable.getSlice(getContext());
            }

            if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) {
                return FeatureFactory.getFactory(getContext())
                        .getSlicesFeatureProvider()
+9 −0
Original line number Diff line number Diff line
@@ -71,6 +71,15 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
        final boolean isPlatformSlice = intent.getBooleanExtra(EXTRA_SLICE_PLATFORM_DEFINED,
                false /* default */);

        final CustomSliceManager mCustomSliceManager = FeatureFactory.getFactory(
                context).getSlicesFeatureProvider().getCustomSliceManager(context);
        if (mCustomSliceManager.isValidAction(action)) {
            final CustomSliceable sliceable =
                    mCustomSliceManager.getSliceableFromIntentAction(action);
            sliceable.onNotifyChange(intent);
            return;
        }

        switch (action) {
            case ACTION_TOGGLE_CHANGED:
                final boolean isChecked = intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE, false);
+2 −0
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ public interface SlicesFeatureProvider {
     */
    void indexSliceData(Context context);

    CustomSliceManager getCustomSliceManager(Context context);

    /**
     * Gets new WifiCallingSliceHelper object
     */
Loading