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

Commit c2653261 authored by Rhed Jao's avatar Rhed Jao Committed by Android (Google) Code Review
Browse files

Merge "Accessibility shortcut improvement (3/n)"

parents 2f33b593 0090ad7f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10417,6 +10417,7 @@ package android.content {
    field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
    field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
    field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
    field public static final String CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET = "android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET";
    field public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE";
    field public static final String CATEGORY_APP_BROWSER = "android.intent.category.APP_BROWSER";
    field public static final String CATEGORY_APP_CALCULATOR = "android.intent.category.APP_CALCULATOR";
+225 −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 android.accessibilityservice;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;

/**
 * Activities of interest to users with accessibility needs may request to be targets of the
 * accessibility shortcut. These activities must handle the
 * {@link Intent#ACTION_MAIN} intent with category
 * {@link Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET}, which will be dispatched by the system
 * when the user activates the shortcut when it is configured to point at this target.
 *
 * @see Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET
 *
 * @hide
 */
public final class AccessibilityShortcutInfo {
    private static final String TAG_ACCESSIBILITY_SHORTCUT = "accessibility-shortcut-target";

    /**
     * Name under which an activity component of the accessibility shortcut publishes information
     * about itself. This meta-data must reference an XML resource containing an
     * <code>&lt;accessibility-shortcut-target&gt;</code> tag.
     */
    public static final String META_DATA = "android.accessibilityshortcut.target";

    /**
     * The component name of the accessibility shortcut target.
     */
    private final ComponentName mComponentName;

    /**
     * The activity info of the accessibility shortcut target.
     */
    private final ActivityInfo mActivityInfo;

    /**
     * Resource id of the summary of the accessibility shortcut target.
     */
    private final int mSummaryResId;

    /**
     * Resource id of the description of the accessibility shortcut target.
     */
    private final int mDescriptionResId;

    /**
     * Creates a new instance.
     *
     * @param context Context for accessing resources.
     * @param activityInfo The activity info.
     * @throws XmlPullParserException If a XML parsing error occurs.
     * @throws IOException If a XML parsing error occurs.
     */
    public AccessibilityShortcutInfo(@NonNull Context context, @NonNull ActivityInfo activityInfo)
            throws XmlPullParserException, IOException {
        final PackageManager packageManager = context.getPackageManager();
        mComponentName = activityInfo.getComponentName();
        mActivityInfo = activityInfo;

        try (XmlResourceParser parser = mActivityInfo.loadXmlMetaData(
                packageManager, META_DATA)) {
            if (parser == null) {
                throw new XmlPullParserException("Meta-data "
                        + TAG_ACCESSIBILITY_SHORTCUT + " does not exist");
            }

            int type = 0;
            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
                type = parser.next();
            }

            final String nodeName = parser.getName();
            if (!TAG_ACCESSIBILITY_SHORTCUT.equals(nodeName)) {
                throw new XmlPullParserException("Meta-data does not start with"
                        + TAG_ACCESSIBILITY_SHORTCUT + " tag");
            }

            final AttributeSet allAttributes = Xml.asAttributeSet(parser);
            final Resources resources = packageManager.getResourcesForApplication(
                    mActivityInfo.applicationInfo);
            final TypedArray asAttributes = resources.obtainAttributes(allAttributes,
                    com.android.internal.R.styleable.AccessibilityShortcutTarget);

            // Gets description
            mDescriptionResId = asAttributes.getResourceId(
                    com.android.internal.R.styleable.AccessibilityShortcutTarget_description, 0);
            // Gets summary
            mSummaryResId = asAttributes.getResourceId(
                    com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0);
            asAttributes.recycle();

            if (mDescriptionResId == 0 || mSummaryResId == 0) {
                throw new XmlPullParserException("No description or summary in meta-data");
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new XmlPullParserException("Unable to create context for: "
                    + mActivityInfo.packageName);
        }
    }

    /**
     * The {@link ActivityInfo} of accessibility shortcut target.
     *
     * @return The activity info.
     */
    @NonNull
    public ActivityInfo getActivityInfo() {
        return mActivityInfo;
    }

    /**
     * The localized summary of the accessibility shortcut target.
     *
     * @return The localized summary if available, and {@code null} if a summary
     * has not been provided.
     */
    @Nullable
    public String loadSummary(@NonNull PackageManager packageManager) {
        return loadResourceString(packageManager, mActivityInfo, mSummaryResId);
    }

    /**
     * The localized description of the accessibility shortcut target.
     *
     * @return The localized description.
     */
    @Nullable
    public String loadDescription(@NonNull PackageManager packageManager) {
        return loadResourceString(packageManager, mActivityInfo, mDescriptionResId);
    }

    /**
     * Gets string resource by the given activity and resource id.
     */
    @Nullable
    private String loadResourceString(@NonNull PackageManager packageManager,
            @NonNull ActivityInfo activityInfo, int resId) {
        if (resId == 0) {
            return null;
        }
        final CharSequence text = packageManager.getText(activityInfo.packageName,
                resId, activityInfo.applicationInfo);
        if (text != null) {
            return text.toString().trim();
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final AccessibilityShortcutInfo other = (AccessibilityShortcutInfo) obj;
        if (mComponentName == null) {
            if (other.mComponentName != null) {
                return false;
            }
        } else if (!mComponentName.equals(other.mComponentName)) {
            return false;
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("AccessibilityShortcutInfo[");
        stringBuilder.append("activityInfo: ").append(mActivityInfo);
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}
+53 −0
Original line number Diff line number Diff line
@@ -4654,6 +4654,59 @@ public class Intent implements Parcelable, Cloneable {
     */
    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
    public static final String CATEGORY_VR_HOME = "android.intent.category.VR_HOME";

    /**
     * The accessibility shortcut is a global gesture for users with disabilities to trigger an
     * important for them accessibility feature to help developers determine whether they want to
     * make their activity a shortcut target.
     * <p>
     * An activity of interest to users with accessibility needs may request to be the target of
     * the accessibility shortcut. It handles intent {@link #ACTION_MAIN} with this category,
     * which will be dispatched by the system when the user activates the shortcut when it is
     * configured to point at this target.
     * </p>
     * <p>
     * An activity declared itself to be a target of the shortcut in AndroidManifest.xml. It must
     * also do two things:
     * <ul>
     *     <ol>
     *         Specify that it handles the <code>android.intent.action.MAIN</code>
     *         {@link android.content.Intent}
     *         with category <code>android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET</code>.
     *     </ol>
     *     <ol>
     *         Provide a meta-data entry <code>android.accessibilityshortcut.target</code> in the
     *         manifest when declaring the activity.
     *     </ol>
     * </ul>
     * If either of these items is missing, the system will ignore the accessibility shortcut
     * target. Following is an example declaration:
     * </p>
     * <pre>
     * &lt;activity android:name=".MainActivity"
     * . . .
     *   &lt;intent-filter&gt;
     *       &lt;action android:name="android.intent.action.MAIN" /&gt;
     *       &lt;category android:name="android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET" /&gt;
     *   &lt;/intent-filter&gt;
     *   &lt;meta-data android:name="android.accessibilityshortcut.target"
     *                   android:resource="@xml/accessibilityshortcut" /&gt;
     * &lt;/activity&gt;
     * </pre>
     * <p> This is a sample XML file configuring a accessibility shortcut target: </p>
     * <pre>
     * &lt;accessibility-shortcut-target
     *     android:description="@string/shortcut_target_description"
     *     android:summary="@string/shortcut_target_summary" /&gt;
     * </pre>
     * <p>
     * Both description and summary are necessary. The system will ignore the accessibility
     * shortcut target if they are missing.
     * </p>
     */
    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
    public static final String CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET =
            "android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET";
    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    // Application launch intent categories (see addCategory()).
+66 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_
import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,9 +31,13 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.os.Binder;
@@ -56,6 +61,9 @@ import android.view.accessibility.AccessibilityEvent.EventType;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -1257,6 +1265,64 @@ public final class AccessibilityManager {
        return null;
    }

    /**
     * Returns the {@link AccessibilityShortcutInfo}s of the installed accessibility shortcut
     * targets, for specific user.
     *
     * @param context The context of the application.
     * @param userId The user id.
     * @return A list with {@link AccessibilityShortcutInfo}s.
     * @hide
     */
    @NonNull
    public List<AccessibilityShortcutInfo> getInstalledAccessibilityShortcutListAsUser(
            @NonNull Context context, @UserIdInt int userId) {
        final List<AccessibilityShortcutInfo> shortcutInfos = new ArrayList<>();
        final int flags = PackageManager.GET_ACTIVITIES
                | PackageManager.GET_META_DATA
                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                | PackageManager.MATCH_DIRECT_BOOT_AWARE
                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
        final Intent actionMain = new Intent(Intent.ACTION_MAIN);
        actionMain.addCategory(Intent.CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET);

        final PackageManager packageManager = context.getPackageManager();
        final List<ResolveInfo> installedShortcutList =
                packageManager.queryIntentActivitiesAsUser(actionMain, flags, userId);
        for (int i = 0; i < installedShortcutList.size(); i++) {
            final AccessibilityShortcutInfo shortcutInfo =
                    getShortcutInfo(context, installedShortcutList.get(i));
            if (shortcutInfo != null) {
                shortcutInfos.add(shortcutInfo);
            }
        }
        return shortcutInfos;
    }

    /**
     * Returns an {@link AccessibilityShortcutInfo} according to the given {@link ResolveInfo} of
     * an activity.
     *
     * @param context The context of the application.
     * @param resolveInfo The resolve info of an activity.
     * @return The AccessibilityShortcutInfo.
     */
    @Nullable
    private AccessibilityShortcutInfo getShortcutInfo(@NonNull Context context,
            @NonNull ResolveInfo resolveInfo) {
        final ActivityInfo activityInfo = resolveInfo.activityInfo;
        if (activityInfo == null || activityInfo.metaData == null
                || activityInfo.metaData.getInt(AccessibilityShortcutInfo.META_DATA) == 0) {
            return null;
        }
        try {
            return new AccessibilityShortcutInfo(context, activityInfo);
        } catch (XmlPullParserException | IOException exp) {
            Log.e(LOG_TAG, "Error while initializing AccessibilityShortcutInfo", exp);
        }
        return null;
    }

    private IAccessibilityManager getServiceLocked() {
        if (mService == null) {
            tryConnectToServiceLocked(null);
+10 −0
Original line number Diff line number Diff line
@@ -3755,6 +3755,16 @@
        <attr name="summary" />
    </declare-styleable>

    <!-- Use <code>accessibility-shortcut-target</code> as the root tag of the XML resource that
         describes an activity, which is referenced from the
         <code>android.accessibilityshortcut.target</code> meta-data entry. -->
    <declare-styleable name="AccessibilityShortcutTarget">
        <!-- Short description of the target of accessibility shortcut purpose or behavior.-->
        <attr name="description" />
        <!-- Brief summary of the target of accessibility shortcut purpose or behavior. -->
        <attr name="summary" />
    </declare-styleable>

    <!-- Use <code>print-service</code> as the root tag of the XML resource that
         describes an {@link android.printservice.PrintService} service, which is
         referenced from its {@link android.printservice.PrintService#SERVICE_META_DATA}
Loading