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

Commit 499a579c authored by Jason Chiu's avatar Jason Chiu
Browse files

[SettingsLib] Support pure switch of inline toggle of Settings Injection v2

Bug: 132808482
Test: robotest
Change-Id: I2e76bb1f11b2556ea10a45a920530a7f93c6506d
parent dd96c883
Loading
Loading
Loading
Loading
+14 −9
Original line number Diff line number Diff line
@@ -52,15 +52,6 @@ public class ActivityTile extends Tile {
        return getPackageName() + "/" + getComponentName();
    }

    @Override
    protected CharSequence getComponentLabel(Context context) {
        final PackageManager pm = context.getPackageManager();
        final ComponentInfo info = getComponentInfo(context);
        return info == null
                ? null
                : info.loadLabel(pm);
    }

    @Override
    protected ComponentInfo getComponentInfo(Context context) {
        if (mComponentInfo == null) {
@@ -78,4 +69,18 @@ public class ActivityTile extends Tile {
        }
        return mComponentInfo;
    }

    @Override
    protected CharSequence getComponentLabel(Context context) {
        final PackageManager pm = context.getPackageManager();
        final ComponentInfo info = getComponentInfo(context);
        return info == null
                ? null
                : info.loadLabel(pm);
    }

    @Override
    protected int getComponentIcon(ComponentInfo componentInfo) {
        return componentInfo.icon;
    }
}
+105 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.settingslib.drawer;

import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;

import java.util.List;
import java.util.Objects;

/**
 * Description of a single dashboard tile which is generated from a content provider.
 */
public class ProviderTile extends Tile {
    private static final String TAG = "ProviderTile";

    private static final boolean DEBUG_TIMING = false;

    private String mAuthority;
    private String mKey;

    public ProviderTile(ComponentInfo info, String category, Bundle metaData) {
        super(info, category);
        setMetaData(metaData);
        mAuthority = ((ProviderInfo) info).authority;
        mKey = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
    }

    ProviderTile(Parcel in) {
        super(in);
        mAuthority = ((ProviderInfo) mComponentInfo).authority;
        mKey = getMetaData().getString(META_DATA_PREFERENCE_KEYHINT);
    }

    @Override
    public int getId() {
        return Objects.hash(mAuthority, mKey);
    }

    @Override
    public String getDescription() {
        return mAuthority + "/" + mKey;
    }

    @Override
    protected ComponentInfo getComponentInfo(Context context) {
        if (mComponentInfo == null) {
            final long startTime = System.currentTimeMillis();
            final PackageManager pm = context.getApplicationContext().getPackageManager();
            final Intent intent = getIntent();
            final List<ResolveInfo> infoList =
                    pm.queryIntentContentProviders(intent, 0 /* flags */);
            if (infoList != null && !infoList.isEmpty()) {
                final ProviderInfo providerInfo = infoList.get(0).providerInfo;
                mComponentInfo = providerInfo;
                setMetaData(TileUtils.getSwitchDataFromProvider(context, providerInfo.authority,
                        mKey));
            } else {
                Log.e(TAG, "Cannot find package info for "
                        + intent.getComponent().flattenToString());
            }

            if (DEBUG_TIMING) {
                Log.d(TAG, "getComponentInfo took "
                        + (System.currentTimeMillis() - startTime) + " ms");
            }
        }
        return mComponentInfo;
    }

    @Override
    protected CharSequence getComponentLabel(Context context) {
        // Getting provider label for a tile title isn't supported.
        return null;
    }

    @Override
    protected int getComponentIcon(ComponentInfo info) {
        // Getting provider icon for a tile title isn't supported.
        return 0;
    }
}
+256 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.settingslib.drawer;

import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED;
import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;

import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;

/**
 * A controller that manages events for switch.
 */
public abstract class SwitchController {

    private String mAuthority;

    /**
     * Returns the key for this switch.
     */
    public abstract String getSwitchKey();

    /**
     * Returns the {@link MetaData} for this switch.
     */
    protected abstract MetaData getMetaData();

    /**
     * Returns the checked state of this switch.
     */
    protected abstract boolean isChecked();

    /**
     * Called when the checked state of this switch is changed.
     *
     * @return true if the checked state was successfully changed, otherwise false
     */
    protected abstract boolean onCheckedChanged(boolean checked);

    /**
     * Returns the error message which will be toasted when {@link #onCheckedChanged} returns false.
     */
    protected abstract String getErrorMessage(boolean attemptedChecked);

    /**
     * Notify registered observers that title was updated and attempt to sync changes.
     */
    public void notifyTitleChanged(Context context) {
        if (this instanceof DynamicTitle) {
            notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
        }
    }

    /**
     * Notify registered observers that summary was updated and attempt to sync changes.
     */
    public void notifySummaryChanged(Context context) {
        if (this instanceof DynamicSummary) {
            notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
        }
    }

    /**
     * Notify registered observers that checked state was updated and attempt to sync changes.
     */
    public void notifyCheckedChanged(Context context) {
        notifyChanged(context, METHOD_IS_CHECKED);
    }

    void setAuthority(String authority) {
        mAuthority = authority;
    }

    Bundle getBundle() {
        final MetaData metaData = getMetaData();
        if (metaData == null) {
            throw new IllegalArgumentException("Should not return null in getMetaData()");
        }

        final Bundle bundle = metaData.build();
        final String uriString = new Uri.Builder()
                .scheme(ContentResolver.SCHEME_CONTENT)
                .authority(mAuthority)
                .build()
                .toString();
        bundle.putString(META_DATA_PREFERENCE_KEYHINT, getSwitchKey());
        bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
        if (this instanceof ProviderIcon) {
            bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
        }
        if (this instanceof DynamicTitle) {
            bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
        }
        if (this instanceof DynamicSummary) {
            bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
        }
        return bundle;
    }

    private void notifyChanged(Context context, String method) {
        final Uri uri = TileUtils.buildUri(mAuthority, method, getSwitchKey());
        context.getContentResolver().notifyChange(uri, null);
    }

    /**
     * Collects all meta data of the item.
     */
    protected static class MetaData {
        private String mCategory;
        private int mOrder;
        @DrawableRes
        private int mIcon;
        private int mIconBackgroundHint;
        private int mIconBackgroundArgb;
        private Boolean mIconTintable;
        @StringRes
        private int mTitleId;
        private String mTitle;
        @StringRes
        private int mSummaryId;
        private String mSummary;

        /**
         * @param category the category of the switch. This value must be from {@link CategoryKey}.
         */
        public MetaData(@NonNull String category) {
            mCategory = category;
        }

        /**
         * Set the order of the item that should be displayed on screen. Bigger value items displays
         * closer on top.
         */
        public MetaData setOrder(int order) {
            mOrder = order;
            return this;
        }

        /** Set the icon that should be displayed for the item. */
        public MetaData setIcon(@DrawableRes int icon) {
            mIcon = icon;
            return this;
        }

        /** Set the icon background color. The value may or may not be used by Settings app. */
        public MetaData setIconBackgoundHint(int hint) {
            mIconBackgroundHint = hint;
            return this;
        }

        /** Set the icon background color as raw ARGB. */
        public MetaData setIconBackgoundArgb(int argb) {
            mIconBackgroundArgb = argb;
            return this;
        }

        /** Specify whether the icon is tintable. */
        public MetaData setIconTintable(boolean tintable) {
            mIconTintable = tintable;
            return this;
        }

        /** Set the title that should be displayed for the item. */
        public MetaData setTitle(@StringRes int id) {
            mTitleId = id;
            return this;
        }

        /** Set the title that should be displayed for the item. */
        public MetaData setTitle(String title) {
            mTitle = title;
            return this;
        }

        /** Set the summary text that should be displayed for the item. */
        public MetaData setSummary(@StringRes int id) {
            mSummaryId = id;
            return this;
        }

        /** Set the summary text that should be displayed for the item. */
        public MetaData setSummary(String summary) {
            mSummary = summary;
            return this;
        }

        private Bundle build() {
            final Bundle bundle = new Bundle();
            bundle.putString(EXTRA_CATEGORY_KEY, mCategory);

            if (mOrder != 0) {
                bundle.putInt(META_DATA_KEY_ORDER, mOrder);
            }

            if (mIcon != 0) {
                bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
            }
            if (mIconBackgroundHint != 0) {
                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
            }
            if (mIconBackgroundArgb != 0) {
                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
            }
            if (mIconTintable != null) {
                bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
            }

            if (mTitleId != 0) {
                bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
            } else if (mTitle != null) {
                bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
            }

            if (mSummaryId != 0) {
                bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
            } else if (mSummary != null) {
                bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
            }
            return bundle;
        }
    }
}
+186 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.settingslib.drawer;

import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * An abstract class for injecting switches to Settings.
 */
public abstract class SwitchesProvider extends ContentProvider {
    private static final String TAG = "SwitchesProvider";

    public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
    public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
    public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
    public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
    public static final String METHOD_IS_CHECKED = "isChecked";
    public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";

    public static final String EXTRA_SWITCH_DATA = "switch_data";
    public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";

    private String mAuthority;
    private final Map<String, SwitchController> mControllerMap = new LinkedHashMap<>();
    private final List<Bundle> mSwitchList = new ArrayList<>();

    /**
     * Get a list of {@link SwitchController} for this provider.
     */
    protected abstract List<SwitchController> createSwitchControllers();

    @Override
    public void attachInfo(Context context, ProviderInfo info) {
        mAuthority = info.authority;
        Log.i(TAG, mAuthority);
        super.attachInfo(context, info);
    }

    @Override
    public boolean onCreate() {
        final List<SwitchController> controllers = createSwitchControllers();
        if (controllers == null || controllers.isEmpty()) {
            throw new IllegalArgumentException();
        }

        controllers.forEach(controller -> {
            final String key = controller.getSwitchKey();
            if (TextUtils.isEmpty(key)) {
                throw new NullPointerException("Switch key cannot be null: "
                        + controller.getClass().getSimpleName());
            } else if (mControllerMap.containsKey(key)) {
                throw new IllegalArgumentException("Switch key " + key + " is duplicated by: "
                        + controller.getClass().getSimpleName());
            }

            controller.setAuthority(mAuthority);
            mControllerMap.put(key, controller);
            mSwitchList.add(controller.getBundle());
        });
        return true;
    }

    @Override
    public Bundle call(String method, String uriString, Bundle extras) {
        final Bundle bundle = new Bundle();
        final String key = extras != null
                ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
                : null;
        if (TextUtils.isEmpty(key)) {
            if (METHOD_GET_SWITCH_DATA.equals(method)) {
                bundle.putParcelableList(EXTRA_SWITCH_DATA, mSwitchList);
                return bundle;
            }
            return null;
        }

        final SwitchController controller = mControllerMap.get(key);
        if (controller == null) {
            return null;
        }

        switch (method) {
            case METHOD_GET_SWITCH_DATA:
                return controller.getBundle();
            case METHOD_GET_PROVIDER_ICON:
                if (controller instanceof ProviderIcon) {
                    return ((ProviderIcon) controller).getProviderIcon();
                }
                break;
            case METHOD_GET_DYNAMIC_TITLE:
                if (controller instanceof DynamicTitle) {
                    bundle.putString(META_DATA_PREFERENCE_TITLE,
                            ((DynamicTitle) controller).getDynamicTitle());
                    return bundle;
                }
                break;
            case METHOD_GET_DYNAMIC_SUMMARY:
                if (controller instanceof DynamicSummary) {
                    bundle.putString(META_DATA_PREFERENCE_SUMMARY,
                            ((DynamicSummary) controller).getDynamicSummary());
                    return bundle;
                }
                break;
            case METHOD_IS_CHECKED:
                bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE, controller.isChecked());
                return bundle;
            case METHOD_ON_CHECKED_CHANGED:
                return onCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE), controller);
        }
        return null;
    }

    private Bundle onCheckedChanged(boolean checked, SwitchController controller) {
        final boolean success = controller.onCheckedChanged(checked);
        final Bundle bundle = new Bundle();
        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
        if (success) {
            if (controller instanceof DynamicSummary) {
                controller.notifySummaryChanged(getContext());
            }
        } else {
            bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
                    controller.getErrorMessage(checked));
        }
        return bundle;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }
}
+22 −7
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
@@ -79,6 +80,7 @@ public abstract class Tile implements Parcelable {
    }

    Tile(Parcel in) {
        final boolean isProviderTile = in.readBoolean();
        mComponentPackage = in.readString();
        mComponentName = in.readString();
        mIntent = new Intent().setClassName(mComponentPackage, mComponentName);
@@ -97,6 +99,7 @@ public abstract class Tile implements Parcelable {

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeBoolean(this instanceof ProviderTile);
        dest.writeString(mComponentPackage);
        dest.writeString(mComponentName);
        final int size = userHandle.size();
@@ -118,6 +121,12 @@ public abstract class Tile implements Parcelable {
     */
    public abstract String getDescription();

    protected abstract ComponentInfo getComponentInfo(Context context);

    protected abstract CharSequence getComponentLabel(Context context);

    protected abstract int getComponentIcon(ComponentInfo info);

    public String getPackageName() {
        return mComponentPackage;
    }
@@ -163,6 +172,13 @@ public abstract class Tile implements Parcelable {
                && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
    }

    /**
     * Check whether tile has a switch.
     */
    public boolean hasSwitch() {
        return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SWITCH_URI);
    }

    /**
     * Title of the tile that is shown to the user.
     */
@@ -195,8 +211,6 @@ public abstract class Tile implements Parcelable {
        return title;
    }

    protected abstract CharSequence getComponentLabel(Context context);

    /**
     * Overrides the summary. This can happen when injected tile wants to provide dynamic summary.
     */
@@ -293,7 +307,7 @@ public abstract class Tile implements Parcelable {
            // ICON_URI should be loaded in app UI when need the icon object. Handling IPC at this
            // level is too complex because we don't have a strong threading contract for this class
            if (!mMetaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
                iconResId = componentInfo.icon;
                iconResId = getComponentIcon(componentInfo);
            }
        }
        if (iconResId != 0) {
@@ -345,11 +359,12 @@ public abstract class Tile implements Parcelable {
        }
    }

    protected abstract ComponentInfo getComponentInfo(Context context);

    public static final Creator<Tile> CREATOR = new Creator<Tile>() {
        public Tile createFromParcel(Parcel source) {
            return new ActivityTile(source);
            final boolean isProviderTile = source.readBoolean();
            // reset the Parcel pointer before delegating to the real constructor.
            source.setDataPosition(0);
            return isProviderTile ? new ProviderTile(source) : new ActivityTile(source);
        }

        public Tile[] newArray(int size) {
@@ -358,7 +373,7 @@ public abstract class Tile implements Parcelable {
    };

    /**
     * Check whether title is only have primary profile
     * Check whether tile only has primary profile.
     */
    public boolean isPrimaryProfileOnly() {
        String profile = mMetaData != null
Loading