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

Commit be473bf8 authored by Xin Li's avatar Xin Li
Browse files

Merge ab/7061308 into stage.

Bug: 180401296
Merged-In: I4bf82035631ccff6d5a6144d6d9b1d203b076851
Change-Id: I1b5f3a672a55eaabba0f5389bab110b395553559
parents 6cc86364 3078660c
Loading
Loading
Loading
Loading
+46 −25
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;

import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;

import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -85,9 +86,12 @@ public final class ConnectivityController extends RestrictingController implemen
    @GuardedBy("mLock")
    private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();

    /** List of currently available networks. */
    /**
     * Set of currently available networks mapped to their latest network capabilities. Cache the
     * latest capabilities to avoid unnecessary calls into ConnectivityManager.
     */
    @GuardedBy("mLock")
    private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();
    private final ArrayMap<Network, NetworkCapabilities> mAvailableNetworks = new ArrayMap<>();

    private static final int MSG_DATA_SAVER_TOGGLED = 0;
    private static final int MSG_UID_RULES_CHANGES = 1;
@@ -164,9 +168,8 @@ public final class ConnectivityController extends RestrictingController implemen
    public boolean isNetworkAvailable(JobStatus job) {
        synchronized (mLock) {
            for (int i = 0; i < mAvailableNetworks.size(); ++i) {
                final Network network = mAvailableNetworks.valueAt(i);
                final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(
                        network);
                final Network network = mAvailableNetworks.keyAt(i);
                final NetworkCapabilities capabilities = mAvailableNetworks.valueAt(i);
                final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
                if (DEBUG) {
                    Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
@@ -424,9 +427,33 @@ public final class ConnectivityController extends RestrictingController implemen
        return false;
    }

    @Nullable
    private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
        if (network == null) {
            return null;
        }
        synchronized (mLock) {
            // There is technically a race here if the Network object is reused. This can happen
            // only if that Network disconnects and the auto-incrementing network ID in
            // ConnectivityService wraps. This should no longer be a concern if/when we only make
            // use of asynchronous calls.
            if (mAvailableNetworks.get(network) != null) {
                return mAvailableNetworks.get(network);
            }

            // This should almost never happen because any time a new network connects, the
            // NetworkCallback would populate mAvailableNetworks. However, it's currently necessary
            // because we also call synchronous methods such as getActiveNetworkForUid.
            // TODO(134978280): remove after switching to callback-based APIs
            final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
            mAvailableNetworks.put(network, capabilities);
            return capabilities;
        }
    }

    private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
        final Network network = mConnManager.getActiveNetworkForUid(jobStatus.getSourceUid());
        final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
        final NetworkCapabilities capabilities = getNetworkCapabilities(network);
        return updateConstraintsSatisfied(jobStatus, network, capabilities);
    }

@@ -467,19 +494,13 @@ public final class ConnectivityController extends RestrictingController implemen
     */
    private void updateTrackedJobs(int filterUid, Network filterNetwork) {
        synchronized (mLock) {
            // Since this is a really hot codepath, temporarily cache any
            // answers that we get from ConnectivityManager.
            final ArrayMap<Network, NetworkCapabilities> networkToCapabilities = new ArrayMap<>();

            boolean changed = false;
            if (filterUid == -1) {
                for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
                    changed |= updateTrackedJobsLocked(mTrackedJobs.valueAt(i),
                            filterNetwork, networkToCapabilities);
                    changed |= updateTrackedJobsLocked(mTrackedJobs.valueAt(i), filterNetwork);
                }
            } else {
                changed = updateTrackedJobsLocked(mTrackedJobs.get(filterUid),
                        filterNetwork, networkToCapabilities);
                changed = updateTrackedJobsLocked(mTrackedJobs.get(filterUid), filterNetwork);
            }
            if (changed) {
                mStateChangedListener.onControllerStateChanged();
@@ -487,18 +508,13 @@ public final class ConnectivityController extends RestrictingController implemen
        }
    }

    private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs, Network filterNetwork,
            ArrayMap<Network, NetworkCapabilities> networkToCapabilities) {
    private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs, Network filterNetwork) {
        if (jobs == null || jobs.size() == 0) {
            return false;
        }

        final Network network = mConnManager.getActiveNetworkForUid(jobs.valueAt(0).getSourceUid());
        NetworkCapabilities capabilities = networkToCapabilities.get(network);
        if (capabilities == null) {
            capabilities = mConnManager.getNetworkCapabilities(network);
            networkToCapabilities.put(network, capabilities);
        }
        final NetworkCapabilities capabilities = getNetworkCapabilities(network);
        final boolean networkMatch = (filterNetwork == null
                || Objects.equals(filterNetwork, network));

@@ -541,9 +557,9 @@ public final class ConnectivityController extends RestrictingController implemen
        @Override
        public void onAvailable(Network network) {
            if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
            synchronized (mLock) {
                mAvailableNetworks.add(network);
            }
            // Documentation says not to call getNetworkCapabilities here but wait for
            // onCapabilitiesChanged instead.  onCapabilitiesChanged should be called immediately
            // after this, so no need to update mAvailableNetworks here.
        }

        @Override
@@ -551,6 +567,9 @@ public final class ConnectivityController extends RestrictingController implemen
            if (DEBUG) {
                Slog.v(TAG, "onCapabilitiesChanged: " + network);
            }
            synchronized (mLock) {
                mAvailableNetworks.put(network, capabilities);
            }
            updateTrackedJobs(-1, network);
        }

@@ -627,6 +646,8 @@ public final class ConnectivityController extends RestrictingController implemen
            pw.println("Available networks:");
            pw.increaseIndent();
            for (int i = 0; i < mAvailableNetworks.size(); i++) {
                pw.print(mAvailableNetworks.keyAt(i));
                pw.print(": ");
                pw.println(mAvailableNetworks.valueAt(i));
            }
            pw.decreaseIndent();
@@ -664,7 +685,7 @@ public final class ConnectivityController extends RestrictingController implemen
                    mRequestedWhitelistJobs.keyAt(i));
        }
        for (int i = 0; i < mAvailableNetworks.size(); i++) {
            Network network = mAvailableNetworks.valueAt(i);
            Network network = mAvailableNetworks.keyAt(i);
            if (network != null) {
                network.dumpDebug(proto,
                        StateControllerProto.ConnectivityController.AVAILABLE_NETWORKS);
+24 −2
Original line number Diff line number Diff line
@@ -44,7 +44,6 @@ java_library {
    libs: [
        "framework_media_annotation",
    ],

    static_libs: [
        "exoplayer2-extractor"
    ],
@@ -115,10 +114,33 @@ java_sdk_library {
    impl_library_visibility: ["//frameworks/av/apex:__subpackages__"],
}


java_library {
    name: "framework_media_annotation",
    srcs: [":framework-media-annotation-srcs"],
    installable: false,
    sdk_version: "core_current",
}

cc_library_shared {
    name: "libmediaparser-jni",
    srcs: [
        "jni/android_media_MediaParserJNI.cpp",
    ],
    header_libs: ["jni_headers"],
    shared_libs: [
        "libandroid",
        "liblog",
        "libmediametrics",
    ],
    cflags: [
        "-Wall",
        "-Werror",
        "-Wno-unused-parameter",
        "-Wunreachable-code",
        "-Wunused",
    ],
    apex_available: [
        "com.android.media",
    ],
    min_sdk_version: "29",
}
+148 −11
Original line number Diff line number Diff line
@@ -75,6 +75,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;

/**
 * Parses media container formats and extracts contained media samples and metadata.
@@ -882,6 +884,7 @@ public final class MediaParser {
    // Private constants.

    private static final String TAG = "MediaParser";
    private static final String JNI_LIBRARY_NAME = "mediaparser-jni";
    private static final Map<String, ExtractorFactory> EXTRACTOR_FACTORIES_BY_NAME;
    private static final Map<String, Class> EXPECTED_TYPE_BY_PARAMETER_NAME;
    private static final String TS_MODE_SINGLE_PMT = "single_pmt";
@@ -889,6 +892,14 @@ public final class MediaParser {
    private static final String TS_MODE_HLS = "hls";
    private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6;
    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
    private static final String MEDIAMETRICS_ELEMENT_SEPARATOR = "|";
    private static final int MEDIAMETRICS_MAX_STRING_SIZE = 200;
    private static final int MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH;
    /**
     * Intentional error introduced to reported metrics to prevent identification of the parsed
     * media. Note: Increasing this value may cause older hostside CTS tests to fail.
     */
    private static final float MEDIAMETRICS_DITHER = .02f;

    @IntDef(
            value = {
@@ -920,7 +931,7 @@ public final class MediaParser {
            @NonNull @ParserName String name, @NonNull OutputConsumer outputConsumer) {
        String[] nameAsArray = new String[] {name};
        assertValidNames(nameAsArray);
        return new MediaParser(outputConsumer, /* sniff= */ false, name);
        return new MediaParser(outputConsumer, /* createdByName= */ true, name);
    }

    /**
@@ -940,7 +951,7 @@ public final class MediaParser {
        if (parserNames.length == 0) {
            parserNames = EXTRACTOR_FACTORIES_BY_NAME.keySet().toArray(new String[0]);
        }
        return new MediaParser(outputConsumer, /* sniff= */ true, parserNames);
        return new MediaParser(outputConsumer, /* createdByName= */ false, parserNames);
    }

    // Misc static methods.
@@ -1052,6 +1063,14 @@ public final class MediaParser {
    private long mPendingSeekPosition;
    private long mPendingSeekTimeMicros;
    private boolean mLoggedSchemeInitDataCreationException;
    private boolean mReleased;

    // MediaMetrics fields.
    private final boolean mCreatedByName;
    private final SparseArray<Format> mTrackFormats;
    private String mLastObservedExceptionName;
    private long mDurationMillis;
    private long mResourceByteCount;

    // Public methods.

@@ -1166,11 +1185,15 @@ public final class MediaParser {
        if (mExtractorInput == null) {
            // TODO: For efficiency, the same implementation should be used, by providing a
            // clearBuffers() method, or similar.
            long resourceLength = seekableInputReader.getLength();
            if (mResourceByteCount == 0) {
                // For resource byte count metric collection, we only take into account the length
                // of the first provided input reader.
                mResourceByteCount = resourceLength;
            }
            mExtractorInput =
                    new DefaultExtractorInput(
                            mExoDataReader,
                            seekableInputReader.getPosition(),
                            seekableInputReader.getLength());
                            mExoDataReader, seekableInputReader.getPosition(), resourceLength);
        }
        mExoDataReader.mInputReader = seekableInputReader;

@@ -1195,7 +1218,10 @@ public final class MediaParser {
                    }
                }
                if (mExtractor == null) {
                    throw UnrecognizedInputFormatException.createForExtractors(mParserNamesPool);
                    UnrecognizedInputFormatException exception =
                            UnrecognizedInputFormatException.createForExtractors(mParserNamesPool);
                    mLastObservedExceptionName = exception.getClass().getName();
                    throw exception;
                }
                return true;
            }
@@ -1223,8 +1249,13 @@ public final class MediaParser {
        int result;
        try {
            result = mExtractor.read(mExtractorInput, mPositionHolder);
        } catch (ParserException e) {
            throw new ParsingException(e);
        } catch (Exception e) {
            mLastObservedExceptionName = e.getClass().getName();
            if (e instanceof ParserException) {
                throw new ParsingException((ParserException) e);
            } else {
                throw e;
            }
        }
        if (result == Extractor.RESULT_END_OF_INPUT) {
            mExtractorInput = null;
@@ -1264,21 +1295,64 @@ public final class MediaParser {
     * invoked.
     */
    public void release() {
        // TODO: Dump media metrics here.
        mExtractorInput = null;
        mExtractor = null;
        if (mReleased) {
            // Nothing to do.
            return;
        }
        mReleased = true;

        String trackMimeTypes = buildMediaMetricsString(format -> format.sampleMimeType);
        String trackCodecs = buildMediaMetricsString(format -> format.codecs);
        int videoWidth = -1;
        int videoHeight = -1;
        for (int i = 0; i < mTrackFormats.size(); i++) {
            Format format = mTrackFormats.valueAt(i);
            if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) {
                videoWidth = format.width;
                videoHeight = format.height;
                break;
            }
        }

        String alteredParameters =
                String.join(
                        MEDIAMETRICS_ELEMENT_SEPARATOR,
                        mParserParameters.keySet().toArray(new String[0]));
        alteredParameters =
                alteredParameters.substring(
                        0,
                        Math.min(
                                alteredParameters.length(),
                                MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));

        nativeSubmitMetrics(
                mParserName,
                mCreatedByName,
                String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
                mLastObservedExceptionName,
                addDither(mResourceByteCount),
                addDither(mDurationMillis),
                trackMimeTypes,
                trackCodecs,
                alteredParameters,
                videoWidth,
                videoHeight);
    }

    // Private methods.

    private MediaParser(OutputConsumer outputConsumer, boolean sniff, String... parserNamesPool) {
    private MediaParser(
            OutputConsumer outputConsumer, boolean createdByName, String... parserNamesPool) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
            throw new UnsupportedOperationException("Android version must be R or greater.");
        }
        mParserParameters = new HashMap<>();
        mOutputConsumer = outputConsumer;
        mParserNamesPool = parserNamesPool;
        mParserName = sniff ? PARSER_NAME_UNKNOWN : parserNamesPool[0];
        mCreatedByName = createdByName;
        mParserName = createdByName ? parserNamesPool[0] : PARSER_NAME_UNKNOWN;
        mPositionHolder = new PositionHolder();
        mExoDataReader = new InputReadingDataReader();
        removePendingSeek();
@@ -1286,6 +1360,24 @@ public final class MediaParser {
        mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter();
        mSchemeInitDataConstructor = getSchemeInitDataConstructor();
        mMuxedCaptionFormats = new ArrayList<>();

        // MediaMetrics.
        mTrackFormats = new SparseArray<>();
        mLastObservedExceptionName = "";
        mDurationMillis = -1;
    }

    private String buildMediaMetricsString(Function<Format, String> formatFieldGetter) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < mTrackFormats.size(); i++) {
            if (i > 0) {
                stringBuilder.append(MEDIAMETRICS_ELEMENT_SEPARATOR);
            }
            String fieldValue = formatFieldGetter.apply(mTrackFormats.valueAt(i));
            stringBuilder.append(fieldValue != null ? fieldValue : "");
        }
        return stringBuilder.substring(
                0, Math.min(stringBuilder.length(), MEDIAMETRICS_MAX_STRING_SIZE));
    }

    private void setMuxedCaptionFormats(List<MediaFormat> mediaFormats) {
@@ -1528,6 +1620,10 @@ public final class MediaParser {

        @Override
        public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
            long durationUs = exoplayerSeekMap.getDurationUs();
            if (durationUs != C.TIME_UNSET) {
                mDurationMillis = C.usToMs(durationUs);
            }
            if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) {
                ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap;
                MediaFormat mediaFormat = new MediaFormat();
@@ -1575,6 +1671,7 @@ public final class MediaParser {

        @Override
        public void format(Format format) {
            mTrackFormats.put(mTrackIndex, format);
            mOutputConsumer.onTrackDataFound(
                    mTrackIndex,
                    new TrackData(
@@ -2031,6 +2128,20 @@ public final class MediaParser {
        return new SeekPoint(exoPlayerSeekPoint.timeUs, exoPlayerSeekPoint.position);
    }

    /**
     * Introduces random error to the given metric value in order to prevent the identification of
     * the parsed media.
     */
    private static long addDither(long value) {
        // Generate a random in [0, 1].
        double randomDither = ThreadLocalRandom.current().nextFloat();
        // Clamp the random number to [0, 2 * MEDIAMETRICS_DITHER].
        randomDither *= 2 * MEDIAMETRICS_DITHER;
        // Translate the random number to [1 - MEDIAMETRICS_DITHER, 1 + MEDIAMETRICS_DITHER].
        randomDither += 1 - MEDIAMETRICS_DITHER;
        return value != -1 ? (long) (value * randomDither) : -1;
    }

    private static void assertValidNames(@NonNull String[] names) {
        for (String name : names) {
            if (!EXTRACTOR_FACTORIES_BY_NAME.containsKey(name)) {
@@ -2070,9 +2181,26 @@ public final class MediaParser {
        }
    }

    // Native methods.

    private native void nativeSubmitMetrics(
            String parserName,
            boolean createdByName,
            String parserPool,
            String lastObservedExceptionName,
            long resourceByteCount,
            long durationMillis,
            String trackMimeTypes,
            String trackCodecs,
            String alteredParameters,
            int videoWidth,
            int videoHeight);

    // Static initialization.

    static {
        System.loadLibrary(JNI_LIBRARY_NAME);

        // Using a LinkedHashMap to keep the insertion order when iterating over the keys.
        LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>();
        // Parsers are ordered to match ExoPlayer's DefaultExtractorsFactory extractor ordering,
@@ -2125,6 +2253,15 @@ public final class MediaParser {
        // We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters
        // instead. Checking that the value is a List is insufficient to catch wrong parameter
        // value types.
        int sumOfParameterNameLengths =
                expectedTypeByParameterName.keySet().stream()
                        .map(String::length)
                        .reduce(0, Integer::sum);
        sumOfParameterNameLengths += PARAMETER_EXPOSE_CAPTION_FORMATS.length();
        // Add space for any required separators.
        MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH =
                sumOfParameterNameLengths + expectedTypeByParameterName.size();

        EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName);
    }
}
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020, 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.
 */

#include <jni.h>
#include <media/MediaMetrics.h>

#define JNI_FUNCTION(RETURN_TYPE, NAME, ...)                                               \
    extern "C" {                                                                           \
    JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
                                                                ##__VA_ARGS__);            \
    }                                                                                      \
    JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
                                                                ##__VA_ARGS__)

namespace {

constexpr char kMediaMetricsKey[] = "mediaparser";

constexpr char kAttributeParserName[] = "android.media.mediaparser.parserName";
constexpr char kAttributeCreatedByName[] = "android.media.mediaparser.createdByName";
constexpr char kAttributeParserPool[] = "android.media.mediaparser.parserPool";
constexpr char kAttributeLastException[] = "android.media.mediaparser.lastException";
constexpr char kAttributeResourceByteCount[] = "android.media.mediaparser.resourceByteCount";
constexpr char kAttributeDurationMillis[] = "android.media.mediaparser.durationMillis";
constexpr char kAttributeTrackMimeTypes[] = "android.media.mediaparser.trackMimeTypes";
constexpr char kAttributeTrackCodecs[] = "android.media.mediaparser.trackCodecs";
constexpr char kAttributeAlteredParameters[] = "android.media.mediaparser.alteredParameters";
constexpr char kAttributeVideoWidth[] = "android.media.mediaparser.videoWidth";
constexpr char kAttributeVideoHeight[] = "android.media.mediaparser.videoHeight";

// Util class to handle string resource management.
class JstringHandle {
public:
    JstringHandle(JNIEnv* env, jstring value) : mEnv(env), mJstringValue(value) {
        mCstringValue = env->GetStringUTFChars(value, /* isCopy= */ nullptr);
    }

    ~JstringHandle() {
        if (mCstringValue != nullptr) {
            mEnv->ReleaseStringUTFChars(mJstringValue, mCstringValue);
        }
    }

    [[nodiscard]] const char* value() const {
        return mCstringValue != nullptr ? mCstringValue : "";
    }

    JNIEnv* mEnv;
    jstring mJstringValue;
    const char* mCstringValue;
};

} // namespace

JNI_FUNCTION(void, nativeSubmitMetrics, jstring parserNameJstring, jboolean createdByName,
             jstring parserPoolJstring, jstring lastExceptionJstring, jlong resourceByteCount,
             jlong durationMillis, jstring trackMimeTypesJstring, jstring trackCodecsJstring,
             jstring alteredParameters, jint videoWidth, jint videoHeight) {
    mediametrics_handle_t item(mediametrics_create(kMediaMetricsKey));
    mediametrics_setCString(item, kAttributeParserName,
                            JstringHandle(env, parserNameJstring).value());
    mediametrics_setInt32(item, kAttributeCreatedByName, createdByName ? 1 : 0);
    mediametrics_setCString(item, kAttributeParserPool,
                            JstringHandle(env, parserPoolJstring).value());
    mediametrics_setCString(item, kAttributeLastException,
                            JstringHandle(env, lastExceptionJstring).value());
    mediametrics_setInt64(item, kAttributeResourceByteCount, resourceByteCount);
    mediametrics_setInt64(item, kAttributeDurationMillis, durationMillis);
    mediametrics_setCString(item, kAttributeTrackMimeTypes,
                            JstringHandle(env, trackMimeTypesJstring).value());
    mediametrics_setCString(item, kAttributeTrackCodecs,
                            JstringHandle(env, trackCodecsJstring).value());
    mediametrics_setCString(item, kAttributeAlteredParameters,
                            JstringHandle(env, alteredParameters).value());
    mediametrics_setInt32(item, kAttributeVideoWidth, videoWidth);
    mediametrics_setInt32(item, kAttributeVideoHeight, videoHeight);
    mediametrics_selfRecord(item);
    mediametrics_delete(item);
}
+14 −0
Original line number Diff line number Diff line
@@ -364,6 +364,18 @@ public class AccessibilityServiceInfo implements Parcelable {
     */
    public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000;

    /**
     * This flag requests that when when {@link #FLAG_REQUEST_MULTI_FINGER_GESTURES} is enabled,
     * two-finger passthrough gestures are re-enabled. Two-finger swipe gestures are not detected,
     * but instead passed through as one-finger gestures. In addition, three-finger swipes from the
     * bottom of the screen are not detected, and instead are passed through unchanged. If {@link
     * #FLAG_REQUEST_MULTI_FINGER_GESTURES} is disabled this flag has no effect.
     *
     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
     * @hide
     */
    public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000;

    /** {@hide} */
    public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;

@@ -1261,6 +1273,8 @@ public class AccessibilityServiceInfo implements Parcelable {
                return "FLAG_SERVICE_HANDLES_DOUBLE_TAP";
            case FLAG_REQUEST_MULTI_FINGER_GESTURES:
                return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
            case FLAG_REQUEST_2_FINGER_PASSTHROUGH:
                return "FLAG_REQUEST_2_FINGER_PASSTHROUGH";
            case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
                return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
            case FLAG_REPORT_VIEW_IDS:
Loading