diff options
258 files changed, 7217 insertions, 2873 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index 9604466a0edd..754c4e94e73a 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -77,6 +77,7 @@ stubs_defaults { "android.hardware.vibrator-V1.3-java", "framework-protos", ], + high_mem: true, // Lots of sources => high memory use, see b/170701554 installable: false, annotations_enabled: true, previous_api: ":android.api.public.latest", diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 813631e28a88..b3c9a9aca444 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -35,7 +35,6 @@ java_library { libs: [ "framework_media_annotation", ], - static_libs: [ "exoplayer2-extractor" ], @@ -111,10 +110,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", +} diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index e4b5d19e67c9..045b4136a710 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -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,16 @@ 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 (resourceLength == -1) { + mResourceByteCount = -1; + } + if (mResourceByteCount != -1) { + mResourceByteCount += resourceLength; + } mExtractorInput = new DefaultExtractorInput( - mExoDataReader, - seekableInputReader.getPosition(), - seekableInputReader.getLength()); + mExoDataReader, seekableInputReader.getPosition(), resourceLength); } mExoDataReader.mInputReader = seekableInputReader; @@ -1195,7 +1219,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 +1250,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 +1296,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 +1361,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 +1621,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 +1672,7 @@ public final class MediaParser { @Override public void format(Format format) { + mTrackFormats.put(mTrackIndex, format); mOutputConsumer.onTrackDataFound( mTrackIndex, new TrackData( @@ -2031,6 +2129,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 +2182,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 +2254,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); } } diff --git a/apex/media/framework/jni/android_media_MediaParserJNI.cpp b/apex/media/framework/jni/android_media_MediaParserJNI.cpp new file mode 100644 index 000000000000..7fc4628984f5 --- /dev/null +++ b/apex/media/framework/jni/android_media_MediaParserJNI.cpp @@ -0,0 +1,92 @@ +/* + * 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); +} diff --git a/api/current.txt b/api/current.txt index 1581960852d8..b35940449f9e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -97,6 +97,7 @@ package android { field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS"; field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE"; + field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS"; field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS"; field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; @@ -17864,6 +17865,7 @@ package android.hardware.camera2 { public class CaptureResult extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureResult.Key<?>> { method @Nullable public <T> T get(android.hardware.camera2.CaptureResult.Key<T>); + method @NonNull public String getCameraId(); method public long getFrameNumber(); method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getKeys(); method @NonNull public android.hardware.camera2.CaptureRequest getRequest(); @@ -38882,6 +38884,9 @@ package android.provider { ctor public CallLog.Calls(); method public static String getLastOutgoingCall(android.content.Context); field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7 + field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L + field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L + field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L field public static final int BLOCKED_TYPE = 6; // 0x6 field public static final String BLOCK_REASON = "block_reason"; field public static final int BLOCK_REASON_BLOCKED_NUMBER = 3; // 0x3 @@ -38927,6 +38932,8 @@ package android.provider { field public static final String IS_READ = "is_read"; field public static final String LAST_MODIFIED = "last_modified"; field public static final String LIMIT_PARAM_KEY = "limit"; + field public static final String MISSED_REASON = "missed_reason"; + field public static final long MISSED_REASON_NOT_MISSED = 0L; // 0x0L field public static final int MISSED_TYPE = 3; // 0x3 field public static final String NEW = "new"; field public static final String NUMBER = "number"; @@ -38943,6 +38950,13 @@ package android.provider { field public static final int REJECTED_TYPE = 5; // 0x5 field public static final String TRANSCRIPTION = "transcription"; field public static final String TYPE = "type"; + field public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 4194304L; // 0x400000L + field public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 2097152L; // 0x200000L + field public static final long USER_MISSED_DND_MODE = 262144L; // 0x40000L + field public static final long USER_MISSED_LOW_RING_VOLUME = 524288L; // 0x80000L + field public static final long USER_MISSED_NO_ANSWER = 65536L; // 0x10000L + field public static final long USER_MISSED_NO_VIBRATE = 1048576L; // 0x100000L + field public static final long USER_MISSED_SHORT_RING = 131072L; // 0x20000L field public static final String VIA_NUMBER = "via_number"; field public static final int VOICEMAIL_TYPE = 4; // 0x4 field public static final String VOICEMAIL_URI = "voicemail_uri"; @@ -43850,6 +43864,7 @@ package android.service.controls.templates { field public static final int TYPE_RANGE = 2; // 0x2 field public static final int TYPE_STATELESS = 8; // 0x8 field public static final int TYPE_TEMPERATURE = 7; // 0x7 + field public static final int TYPE_THUMBNAIL = 3; // 0x3 field public static final int TYPE_TOGGLE = 1; // 0x1 field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6 } @@ -43889,6 +43904,14 @@ package android.service.controls.templates { field public static final int MODE_UNKNOWN = 0; // 0x0 } + public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate { + ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence); + method @NonNull public CharSequence getContentDescription(); + method public int getTemplateType(); + method @NonNull public android.graphics.drawable.Icon getThumbnail(); + method public boolean isActive(); + } + public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate { ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate); ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate); @@ -47883,6 +47906,7 @@ package android.telephony { method @NonNull public java.util.List<java.lang.Integer> getAvailableServices(); method @Nullable public android.telephony.CellIdentity getCellIdentity(); method public int getDomain(); + method public int getNrState(); method @Nullable public String getRegisteredPlmn(); method public int getTransportType(); method public boolean isRegistered(); diff --git a/api/system-current.txt b/api/system-current.txt index de62fd80c92c..f30f756ae3f6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -403,6 +403,7 @@ package android.app { field public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats"; field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage"; field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels"; + field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls"; field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone"; field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells"; field public static final String OPSTR_PLAY_AUDIO = "android:play_audio"; @@ -917,7 +918,6 @@ package android.app.admin { field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2 field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0 field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4 - field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5 field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2 field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3 field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1 @@ -11804,6 +11804,7 @@ package android.telephony { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int); + method public boolean isNrDualConnectivityEnabled(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String); @@ -11837,6 +11838,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean); + method public int setNrDualConnectivityState(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean); @@ -11876,6 +11878,11 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0 field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE"; @@ -11908,6 +11915,9 @@ package android.telephony { field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L + field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2 + field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3 + field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1 field public static final int RADIO_POWER_OFF = 0; // 0x0 field public static final int RADIO_POWER_ON = 1; // 0x1 field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2 diff --git a/api/test-current.txt b/api/test-current.txt index 59020068e342..edf860665fb6 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -210,6 +210,7 @@ package android.app { field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time"; field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; + field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls"; field public static final int OP_COARSE_LOCATION = 0; // 0x0 field public static final int OP_RECORD_AUDIO = 27; // 0x1b field public static final int OP_START_FOREGROUND = 76; // 0x4c @@ -2374,7 +2375,7 @@ package android.window { public class TaskOrganizer extends android.window.WindowOrganizer { ctor public TaskOrganizer(); - method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.app.ActivityManager.RunningTaskInfo createRootTask(int, int); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void createRootTask(int, int, @Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.window.WindowContainerToken getImeTarget(int); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index c9356c55e6a1..084959b5071a 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -498,6 +498,7 @@ message Atom { CarrierIdMismatchEvent carrier_id_mismatch_event = 313 [(module) = "telephony"]; CarrierIdMatchingTable carrier_id_table_update = 314 [(module) = "telephony"]; DataStallRecoveryReported data_stall_recovery_reported = 315 [(module) = "telephony"]; + MediametricsMediaParserReported mediametrics_mediaparser_reported = 316; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. @@ -4659,7 +4660,7 @@ message PrivacyIndicatorsInteracted { UNKNOWN = 0; CHIP_VIEWED = 1; CHIP_CLICKED = 2; - reserved 3; // Used only in beta builds, never shipped + reserved 3; // Used only in beta builds, never shipped DIALOG_DISMISS = 4; DIALOG_LINE_ITEM = 5; } @@ -8197,6 +8198,72 @@ message MediametricsExtractorReported { } /** + * Track MediaParser (parsing video/audio streams from containers) usage + * Logged from: + * + * frameworks/av/services/mediametrics/statsd_mediaparser.cpp + * frameworks/base/apex/media/framework/jni/android_media_MediaParserJNI.cpp + */ +message MediametricsMediaParserReported { + optional int64 timestamp_nanos = 1; + optional string package_name = 2; + optional int64 package_version_code = 3; + + // MediaParser specific data. + /** + * The name of the parser selected for parsing the media, or an empty string + * if no parser was selected. + */ + optional string parser_name = 4; + /** + * Whether the parser was created by name. 1 represents true, and 0 + * represents false. + */ + optional int32 created_by_name = 5; + /** + * The parser names in the sniffing pool separated by "|". + */ + optional string parser_pool = 6; + /** + * The fully qualified name of the last encountered exception, or an empty + * string if no exception was encountered. + */ + optional string last_exception = 7; + /** + * The size of the parsed media in bytes, or -1 if unknown. Note this value + * contains intentional random error to prevent media content + * identification. + */ + optional int64 resource_byte_count = 8; + /** + * The duration of the media in milliseconds, or -1 if unknown. Note this + * value contains intentional random error to prevent media content + * identification. + */ + optional int64 duration_millis = 9; + /** + * The MIME types of the tracks separated by "|". + */ + optional string track_mime_types = 10; + /** + * The tracks' RFC 6381 codec strings separated by "|". + */ + optional string track_codecs = 11; + /** + * Concatenation of the parameters altered by the client, separated by "|". + */ + optional string altered_parameters = 12; + /** + * The video width in pixels, or -1 if unknown or not applicable. + */ + optional int32 video_width = 13; + /** + * The video height in pixels, or -1 if unknown or not applicable. + */ + optional int32 video_height = 14; +} + +/** * Track how we arbitrate between microphone/input requests. * Logged from * frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp @@ -10737,6 +10804,10 @@ message CarrierIdMismatchEvent { // SPN value of the SIM card. optional string spn = 4; + + // First record of the PNN in the SIM card. This field is populated only if the SPN is missing + // or empty. + optional string pnn = 5; } /** diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 3d57cfe318c5..22fdf1604435 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -18,12 +18,14 @@ #include "Log.h" #include "ValueMetricProducer.h" -#include "../guardrail/StatsdStats.h" -#include "../stats_log_util.h" #include <limits.h> #include <stdlib.h> +#include "../guardrail/StatsdStats.h" +#include "../stats_log_util.h" +#include "metrics/parsing_utils/metrics_manager_util.h" + using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_BOOL; using android::util::FIELD_TYPE_DOUBLE; @@ -184,6 +186,48 @@ ValueMetricProducer::~ValueMetricProducer() { } } +bool ValueMetricProducer::onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers, + const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap, + const unordered_map<int64_t, int>& newAtomMatchingTrackerMap, + const sp<EventMatcherWizard>& matcherWizard, + const vector<sp<ConditionTracker>>& allConditionTrackers, + const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard, + const unordered_map<int64_t, int>& metricToActivationMap, + unordered_map<int, vector<int>>& trackerToMetricMap, + unordered_map<int, vector<int>>& conditionToMetricMap, + unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap, + unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap, + vector<int>& metricsWithActivation) { + if (!MetricProducer::onConfigUpdatedLocked( + config, configIndex, metricIndex, allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { + return false; + } + + const ValueMetric& metric = config.value_metric(configIndex); + // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps. + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, + trackerToMetricMap, mWhatMatcherIndex)) { + return false; + } + + if (metric.has_condition() && + !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, mConditionTrackerIndex, + conditionToMetricMap)) { + return false; + } + sp<EventMatcherWizard> tmpEventWizard = mEventMatcherWizard; + mEventMatcherWizard = matcherWizard; + return true; +} + void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey, const FieldValue& oldState, const FieldValue& newState) { diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 67de214e655c..ebd8fecd55d0 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -47,7 +47,7 @@ struct PastValueBucket { // - a condition change // - an app upgrade // - an alarm set to the end of the bucket -class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver { +class ValueMetricProducer : public MetricProducer, public virtual PullDataReceiver { public: ValueMetricProducer( const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex, @@ -155,7 +155,23 @@ private: // causes the bucket to be invalidated will not notify StatsdStats. void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason); - const int mWhatMatcherIndex; + bool onConfigUpdatedLocked( + const StatsdConfig& config, const int configIndex, const int metricIndex, + const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers, + const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap, + const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap, + const sp<EventMatcherWizard>& matcherWizard, + const std::vector<sp<ConditionTracker>>& allConditionTrackers, + const std::unordered_map<int64_t, int>& conditionTrackerMap, + const sp<ConditionWizard>& wizard, + const std::unordered_map<int64_t, int>& metricToActivationMap, + std::unordered_map<int, std::vector<int>>& trackerToMetricMap, + std::unordered_map<int, std::vector<int>>& conditionToMetricMap, + std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, + std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, + std::vector<int>& metricsWithActivation) override; + + int mWhatMatcherIndex; sp<EventMatcherWizard> mEventMatcherWizard; @@ -370,6 +386,8 @@ private: FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue); FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse); + FRIEND_TEST(ConfigUpdateTest, TestUpdateValueMetrics); + friend class ValueMetricProducerTestHelper; }; diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp index b6e5d8846fd9..335f7753e5e3 100644 --- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp +++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp @@ -580,7 +580,6 @@ bool determineAllMetricUpdateStatuses(const StatsdConfig& config, return false; } } - // TODO: determine update status for value metrics. return true; } @@ -644,7 +643,7 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 set<int64_t>& noReportMetricIds, unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap, unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap, - vector<int>& metricsWithActivation) { + vector<int>& metricsWithActivation, set<int64_t>& replacedMetrics) { sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers); sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers); const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() + @@ -690,6 +689,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 break; } case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. case UPDATE_NEW: { producer = createCountMetricProducerAndUpdateMetadata( key, config, timeBaseNs, currentTimeNs, metric, metricIndex, @@ -727,6 +728,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 break; } case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. case UPDATE_NEW: { producer = createDurationMetricProducerAndUpdateMetadata( key, config, timeBaseNs, currentTimeNs, metric, metricIndex, @@ -749,8 +752,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 newMetricProducers.push_back(producer.value()); } for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) { - newMetricProducerMap[config.event_metric(i).id()] = metricIndex; const EventMetric& metric = config.event_metric(i); + newMetricProducerMap[metric.id()] = metricIndex; optional<sp<MetricProducer>> producer; switch (metricsToUpdate[metricIndex]) { case UPDATE_PRESERVE: { @@ -764,6 +767,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 break; } case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. case UPDATE_NEW: { producer = createEventMetricProducerAndUpdateMetadata( key, config, timeBaseNs, metric, metricIndex, allAtomMatchingTrackers, @@ -784,6 +789,47 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 } newMetricProducers.push_back(producer.value()); } + + for (int i = 0; i < config.value_metric_size(); i++, metricIndex++) { + const ValueMetric& metric = config.value_metric(i); + newMetricProducerMap[metric.id()] = metricIndex; + optional<sp<MetricProducer>> producer; + switch (metricsToUpdate[metricIndex]) { + case UPDATE_PRESERVE: { + producer = updateMetric( + config, i, metricIndex, metric.id(), allAtomMatchingTrackers, + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard, + allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap, + oldMetricProducers, metricToActivationMap, trackerToMetricMap, + conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. + case UPDATE_NEW: { + producer = createValueMetricProducerAndUpdateMetadata( + key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex, + allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, matcherWizard, + stateAtomIdMap, allStateGroupMaps, metricToActivationMap, + trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation); + break; + } + default: { + ALOGE("Metric \"%lld\" update state is unknown. This should never happen", + (long long)metric.id()); + return false; + } + } + if (!producer) { + return false; + } + newMetricProducers.push_back(producer.value()); + } + for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) { const GaugeMetric& metric = config.gauge_metric(i); newMetricProducerMap[metric.id()] = metricIndex; @@ -800,6 +846,8 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 break; } case UPDATE_REPLACE: + replacedMetrics.insert(metric.id()); + [[fallthrough]]; // Intentionally fallthrough to create the new metric producer. case UPDATE_NEW: { producer = createGaugeMetricProducerAndUpdateMetadata( key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex, @@ -821,7 +869,6 @@ bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64 } newMetricProducers.push_back(producer.value()); } - // TODO: perform update for value metric. const set<int> atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(), config.whitelisted_atom_ids().end()); @@ -872,6 +919,7 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const set<int64_t>& noReportMetricIds) { set<int64_t> replacedMatchers; set<int64_t> replacedConditions; + set<int64_t> replacedMetrics; vector<ConditionState> conditionCache; unordered_map<int64_t, int> stateAtomIdMap; unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps; @@ -913,7 +961,7 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const replacedStates, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap, - deactivationTrackerToMetricMap, metricsWithActivation)) { + deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics)) { ALOGE("initMetricProducers failed"); return false; } diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h index 34d7e9c7de9e..3f1c5326b569 100644 --- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h +++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h @@ -187,7 +187,7 @@ bool updateMetrics( std::set<int64_t>& noReportMetricIds, std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, - std::vector<int>& metricsWithActivation); + std::vector<int>& metricsWithActivation, std::set<int64_t>& replacedMetrics); // Updates the existing MetricsManager from a new StatsdConfig. // Parameters are the members of MetricsManager. See MetricsManager for declaration. diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp index b7dc2c7fd0de..8fc039a7d6b3 100644 --- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp @@ -599,6 +599,108 @@ optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata( eventDeactivationMap)}; } +optional<sp<MetricProducer>> createValueMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager, + const ValueMetric& metric, const int metricIndex, + const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers, + const unordered_map<int64_t, int>& atomMatchingTrackerMap, + vector<sp<ConditionTracker>>& allConditionTrackers, + const unordered_map<int64_t, int>& conditionTrackerMap, + const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, + const sp<EventMatcherWizard>& matcherWizard, + const unordered_map<int64_t, int>& stateAtomIdMap, + const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps, + const unordered_map<int64_t, int>& metricToActivationMap, + unordered_map<int, vector<int>>& trackerToMetricMap, + unordered_map<int, vector<int>>& conditionToMetricMap, + unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap, + unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap, + vector<int>& metricsWithActivation) { + if (!metric.has_id() || !metric.has_what()) { + ALOGE("cannot find metric id or \"what\" in ValueMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + if (!metric.has_value_field()) { + ALOGE("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + std::vector<Matcher> fieldMatchers; + translateFieldMatcher(metric.value_field(), &fieldMatchers); + if (fieldMatchers.size() < 1) { + ALOGE("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); + return nullopt; + } + + int trackerIndex; + if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, + metric.has_dimensions_in_what(), + allAtomMatchingTrackers, atomMatchingTrackerMap, + trackerToMetricMap, trackerIndex)) { + return nullopt; + } + + sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex); + // If it is pulled atom, it should be simple matcher with one tagId. + if (atomMatcher->getAtomIds().size() != 1) { + return nullopt; + } + int atomTagId = *(atomMatcher->getAtomIds().begin()); + int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; + + int conditionIndex = -1; + if (metric.has_condition()) { + if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap)) { + return nullopt; + } + } else if (metric.links_size() > 0) { + ALOGE("metrics has a MetricConditionLink but doesn't have a condition"); + return nullopt; + } + + std::vector<int> slicedStateAtoms; + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + if (metric.slice_by_state_size() > 0) { + if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, + allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { + return nullopt; + } + } else if (metric.state_link_size() > 0) { + ALOGE("ValueMetric has a MetricStateLink but doesn't have a sliced state"); + return nullopt; + } + + // Check that all metric state links are a subset of dimensions_in_what fields. + std::vector<Matcher> dimensionsInWhat; + translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); + for (const auto& stateLink : metric.state_link()) { + if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { + return nullopt; + } + } + + unordered_map<int, shared_ptr<Activation>> eventActivationMap; + unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; + if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap, + atomMatchingTrackerMap, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation, + eventActivationMap, eventDeactivationMap)) { + return nullopt; + } + + uint64_t metricHash; + if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { + return nullopt; + } + + return {new ValueMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, + metricHash, trackerIndex, matcherWizard, pullTagId, timeBaseNs, + currentTimeNs, pullerManager, eventActivationMap, + eventDeactivationMap, slicedStateAtoms, stateGroupMap)}; +} + optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata( const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager, @@ -911,97 +1013,20 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t // build ValueMetricProducer for (int i = 0; i < config.value_metric_size(); i++) { - const ValueMetric& metric = config.value_metric(i); - if (!metric.has_what()) { - ALOGW("cannot find \"what\" in ValueMetric \"%lld\"", (long long)metric.id()); - return false; - } - if (!metric.has_value_field()) { - ALOGW("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); - return false; - } - std::vector<Matcher> fieldMatchers; - translateFieldMatcher(metric.value_field(), &fieldMatchers); - if (fieldMatchers.size() < 1) { - ALOGW("incorrect \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); - return false; - } - int metricIndex = allMetricProducers.size(); + const ValueMetric& metric = config.value_metric(i); metricMap.insert({metric.id(), metricIndex}); - int trackerIndex; - if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, - metric.has_dimensions_in_what(), - allAtomMatchingTrackers, atomMatchingTrackerMap, - trackerToMetricMap, trackerIndex)) { - return false; - } - - sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex); - // If it is pulled atom, it should be simple matcher with one tagId. - if (atomMatcher->getAtomIds().size() != 1) { - return false; - } - int atomTagId = *(atomMatcher->getAtomIds().begin()); - int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1; - - int conditionIndex = -1; - if (metric.has_condition()) { - bool good = handleMetricWithConditions( - metric.condition(), metricIndex, conditionTrackerMap, metric.links(), - allConditionTrackers, conditionIndex, conditionToMetricMap); - if (!good) { - return false; - } - } else { - if (metric.links_size() > 0) { - ALOGW("metrics has a MetricConditionLink but doesn't have a condition"); - return false; - } - } - - std::vector<int> slicedStateAtoms; - unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; - if (metric.slice_by_state_size() > 0) { - if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap, - allStateGroupMaps, slicedStateAtoms, stateGroupMap)) { - return false; - } - } else { - if (metric.state_link_size() > 0) { - ALOGW("ValueMetric has a MetricStateLink but doesn't have a sliced state"); - return false; - } - } - - // Check that all metric state links are a subset of dimensions_in_what fields. - std::vector<Matcher> dimensionsInWhat; - translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat); - for (const auto& stateLink : metric.state_link()) { - if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) { - return false; - } - } - - unordered_map<int, shared_ptr<Activation>> eventActivationMap; - unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap; - bool success = handleMetricActivation( - config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap, + optional<sp<MetricProducer>> producer = createValueMetricProducerAndUpdateMetadata( + key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex, + allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers, + conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap, + allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation, eventActivationMap, eventDeactivationMap); - if (!success) return false; - - uint64_t metricHash; - if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) { + metricsWithActivation); + if (!producer) { return false; } - - sp<MetricProducer> valueProducer = new ValueMetricProducer( - key, metric, conditionIndex, initialConditionCache, wizard, metricHash, - trackerIndex, matcherWizard, pullTagId, timeBaseTimeNs, currentTimeNs, - pullerManager, eventActivationMap, eventDeactivationMap, slicedStateAtoms, - stateGroupMap); - allMetricProducers.push_back(valueProducer); + allMetricProducers.push_back(producer.value()); } // Gauge metrics. diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h index 6d1e6dde7e89..e4585cd578f8 100644 --- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h @@ -148,6 +148,27 @@ optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata( std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, std::vector<int>& metricsWithActivation); +// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with +// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. +optional<sp<MetricProducer>> createValueMetricProducerAndUpdateMetadata( + const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs, + const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager, + const ValueMetric& metric, const int metricIndex, + const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers, + const std::unordered_map<int64_t, int>& atomMatchingTrackerMap, + std::vector<sp<ConditionTracker>>& allConditionTrackers, + const std::unordered_map<int64_t, int>& conditionTrackerMap, + const std::vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard, + const sp<EventMatcherWizard>& matcherWizard, + const std::unordered_map<int64_t, int>& stateAtomIdMap, + const std::unordered_map<int64_t, std::unordered_map<int, int64_t>>& allStateGroupMaps, + const std::unordered_map<int64_t, int>& metricToActivationMap, + std::unordered_map<int, std::vector<int>>& trackerToMetricMap, + std::unordered_map<int, std::vector<int>>& conditionToMetricMap, + std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, + std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, + std::vector<int>& metricsWithActivation); + // Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error. optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata( diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp index 0066030ade54..4fa9bf6ffc01 100644 --- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp +++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp @@ -29,6 +29,7 @@ #include "src/matchers/CombinationAtomMatchingTracker.h" #include "src/metrics/DurationMetricProducer.h" #include "src/metrics/GaugeMetricProducer.h" +#include "src/metrics/ValueMetricProducer.h" #include "src/metrics/parsing_utils/metrics_manager_util.h" #include "tests/statsd_test_util.h" @@ -1811,6 +1812,7 @@ TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { unordered_map<int, vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap; vector<int> metricsWithActivation; + set<int64_t> replacedMetrics; EXPECT_TRUE(updateMetrics( key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, @@ -1819,13 +1821,14 @@ TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) { /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation)); + metricsWithActivation, replacedMetrics)); unordered_map<int64_t, int> expectedMetricProducerMap = { {event1Id, event1Index}, {event2Id, event2Index}, {event3Id, event3Index}, {event4Id, event4Index}, {event6Id, event6Index}, }; EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set<int64_t>({event2Id, event3Id, event4Id})); // Make sure preserved metrics are the same. ASSERT_EQ(newMetricProducers.size(), 5); @@ -2040,6 +2043,7 @@ TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) { unordered_map<int, vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap; vector<int> metricsWithActivation; + set<int64_t> replacedMetrics; EXPECT_TRUE(updateMetrics( key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, @@ -2048,13 +2052,14 @@ TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) { oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation)); + metricsWithActivation, replacedMetrics)); unordered_map<int64_t, int> expectedMetricProducerMap = { {count1Id, count1Index}, {count2Id, count2Index}, {count3Id, count3Index}, {count4Id, count4Index}, {count6Id, count6Index}, }; EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set<int64_t>({count2Id, count3Id, count4Id})); // Make sure preserved metrics are the same. ASSERT_EQ(newMetricProducers.size(), 5); @@ -2249,6 +2254,7 @@ TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) { unordered_map<int, vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap; vector<int> metricsWithActivation; + set<int64_t> replacedMetrics; EXPECT_TRUE(updateMetrics( key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, @@ -2257,13 +2263,14 @@ TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) { /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation)); + metricsWithActivation, replacedMetrics)); unordered_map<int64_t, int> expectedMetricProducerMap = { {gauge1Id, gauge1Index}, {gauge2Id, gauge2Index}, {gauge3Id, gauge3Index}, {gauge4Id, gauge4Index}, {gauge6Id, gauge6Index}, }; EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set<int64_t>({gauge2Id, gauge3Id, gauge4Id})); // Make sure preserved metrics are the same. ASSERT_EQ(newMetricProducers.size(), 5); @@ -2566,6 +2573,7 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { unordered_map<int, vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap; vector<int> metricsWithActivation; + set<int64_t> replacedMetrics; EXPECT_TRUE(updateMetrics( key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, @@ -2574,7 +2582,7 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation)); + metricsWithActivation, replacedMetrics)); unordered_map<int64_t, int> expectedMetricProducerMap = { {duration1Id, duration1Index}, {duration2Id, duration2Index}, @@ -2582,7 +2590,7 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { {duration6Id, duration6Index}, }; EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); - + EXPECT_EQ(replacedMetrics, set<int64_t>({duration2Id, duration3Id, duration4Id})); // Make sure preserved metrics are the same. ASSERT_EQ(newMetricProducers.size(), 5); EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(duration1Id)], @@ -2685,6 +2693,245 @@ TEST_F(ConfigUpdateTest, TestUpdateDurationMetrics) { EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); } +TEST_F(ConfigUpdateTest, TestUpdateValueMetrics) { + StatsdConfig config; + + // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig. + AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher(); + int64_t matcher1Id = matcher1.id(); + *config.add_atom_matcher() = matcher1; + + AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher(); + int64_t matcher2Id = matcher2.id(); + *config.add_atom_matcher() = matcher2; + + AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher(); + int64_t matcher3Id = matcher3.id(); + *config.add_atom_matcher() = matcher3; + + AtomMatcher matcher4 = CreateTemperatureAtomMatcher(); + int64_t matcher4Id = matcher4.id(); + *config.add_atom_matcher() = matcher4; + + AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE); + int64_t matcher5Id = matcher5.id(); + *config.add_atom_matcher() = matcher5; + + Predicate predicate1 = CreateScreenIsOnPredicate(); + int64_t predicate1Id = predicate1.id(); + *config.add_predicate() = predicate1; + + Predicate predicate2 = CreateScreenIsOffPredicate(); + int64_t predicate2Id = predicate2.id(); + *config.add_predicate() = predicate2; + + State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321); + int64_t state1Id = state1.id(); + *config.add_state() = state1; + + State state2 = CreateScreenState(); + int64_t state2Id = state2.id(); + *config.add_state() = state2; + + // Add a few value metrics. + // Note that these will not work as "real" metrics since the value field is always 2. + // Will be preserved. + ValueMetric value1 = createValueMetric("VALUE1", matcher4, predicate1Id, {state1Id}); + int64_t value1Id = value1.id(); + *config.add_value_metric() = value1; + + // Will be replaced - definition change. + ValueMetric value2 = createValueMetric("VALUE2", matcher1, nullopt, {}); + int64_t value2Id = value2.id(); + *config.add_value_metric() = value2; + + // Will be replaced - condition change. + ValueMetric value3 = createValueMetric("VALUE3", matcher5, predicate2Id, {}); + int64_t value3Id = value3.id(); + *config.add_value_metric() = value3; + + // Will be replaced - state change. + ValueMetric value4 = createValueMetric("VALUE4", matcher3, nullopt, {state2Id}); + int64_t value4Id = value4.id(); + *config.add_value_metric() = value4; + + // Will be deleted. + ValueMetric value5 = createValueMetric("VALUE5", matcher2, nullopt, {}); + int64_t value5Id = value5.id(); + *config.add_value_metric() = value5; + + EXPECT_TRUE(initConfig(config)); + + // Used later to ensure the condition wizard is replaced. Get it before doing the update. + sp<EventMatcherWizard> oldMatcherWizard = + static_cast<ValueMetricProducer*>(oldMetricProducers[0].get())->mEventMatcherWizard; + EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6); + + // Change value2, causing it to be replaced. + value2.set_aggregation_type(ValueMetric::AVG); + + // Mark predicate 2 as replaced. Causes value3 to be replaced. + set<int64_t> replacedConditions = {predicate2Id}; + + // Mark state 2 as replaced. Causes value4 to be replaced. + set<int64_t> replacedStates = {state2Id}; + + // New value metric. + ValueMetric value6 = createValueMetric("VALUE6", matcher5, predicate1Id, {state1Id}); + int64_t value6Id = value6.id(); + + // Map the matchers and predicates in reverse order to force the indices to change. + std::unordered_map<int64_t, int> newAtomMatchingTrackerMap; + const int matcher5Index = 0; + newAtomMatchingTrackerMap[matcher5Id] = 0; + const int matcher4Index = 1; + newAtomMatchingTrackerMap[matcher4Id] = 1; + const int matcher3Index = 2; + newAtomMatchingTrackerMap[matcher3Id] = 2; + const int matcher2Index = 3; + newAtomMatchingTrackerMap[matcher2Id] = 3; + const int matcher1Index = 4; + newAtomMatchingTrackerMap[matcher1Id] = 4; + // Use the existing matchers. A bit hacky, but saves code and we don't rely on them. + vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(5); + std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(), + newAtomMatchingTrackers.begin()); + + std::unordered_map<int64_t, int> newConditionTrackerMap; + const int predicate2Index = 0; + newConditionTrackerMap[predicate2Id] = 0; + const int predicate1Index = 1; + newConditionTrackerMap[predicate1Id] = 1; + // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them. + vector<sp<ConditionTracker>> newConditionTrackers(2); + std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(), + newConditionTrackers.begin()); + // Say that predicate1 & predicate2 is unknown since the initial condition never changed. + vector<ConditionState> conditionCache = {ConditionState::kUnknown, ConditionState::kUnknown}; + + StatsdConfig newConfig; + *newConfig.add_value_metric() = value6; + const int value6Index = 0; + *newConfig.add_value_metric() = value3; + const int value3Index = 1; + *newConfig.add_value_metric() = value1; + const int value1Index = 2; + *newConfig.add_value_metric() = value4; + const int value4Index = 3; + *newConfig.add_value_metric() = value2; + const int value2Index = 4; + + *newConfig.add_state() = state1; + *newConfig.add_state() = state2; + + unordered_map<int64_t, int> stateAtomIdMap; + unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps; + map<int64_t, uint64_t> stateProtoHashes; + EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes)); + + // Output data structures to validate. + unordered_map<int64_t, int> newMetricProducerMap; + vector<sp<MetricProducer>> newMetricProducers; + unordered_map<int, vector<int>> conditionToMetricMap; + unordered_map<int, vector<int>> trackerToMetricMap; + set<int64_t> noReportMetricIds; + unordered_map<int, vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; + set<int64_t> replacedMetrics; + EXPECT_TRUE(updateMetrics( + key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), + oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, + newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions, + newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates, + oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, + conditionToMetricMap, trackerToMetricMap, noReportMetricIds, + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, replacedMetrics)); + + unordered_map<int64_t, int> expectedMetricProducerMap = { + {value1Id, value1Index}, {value2Id, value2Index}, {value3Id, value3Index}, + {value4Id, value4Index}, {value6Id, value6Index}, + }; + EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set<int64_t>({value2Id, value3Id, value4Id})); + + // Make sure preserved metrics are the same. + ASSERT_EQ(newMetricProducers.size(), 5); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(value1Id)], + newMetricProducers[newMetricProducerMap.at(value1Id)]); + + // Make sure replaced metrics are different. + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value2Id)], + newMetricProducers[newMetricProducerMap.at(value2Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value3Id)], + newMetricProducers[newMetricProducerMap.at(value3Id)]); + EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(value4Id)], + newMetricProducers[newMetricProducerMap.at(value4Id)]); + + // Verify the conditionToMetricMap. + ASSERT_EQ(conditionToMetricMap.size(), 2); + const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index]; + EXPECT_THAT(condition1Metrics, UnorderedElementsAre(value1Index, value6Index)); + const vector<int>& condition2Metrics = conditionToMetricMap[predicate2Index]; + EXPECT_THAT(condition2Metrics, UnorderedElementsAre(value3Index)); + + // Verify the trackerToMetricMap. + ASSERT_EQ(trackerToMetricMap.size(), 4); + const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index]; + EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(value2Index)); + const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index]; + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(value4Index)); + const vector<int>& matcher4Metrics = trackerToMetricMap[matcher4Index]; + EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(value1Index)); + const vector<int>& matcher5Metrics = trackerToMetricMap[matcher5Index]; + EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(value3Index, value6Index)); + + // Verify event activation/deactivation maps. + ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0); + ASSERT_EQ(metricsWithActivation.size(), 0); + + // Verify tracker indices/ids/conditions/states are correct. + ValueMetricProducer* valueProducer1 = + static_cast<ValueMetricProducer*>(newMetricProducers[value1Index].get()); + EXPECT_EQ(valueProducer1->getMetricId(), value1Id); + EXPECT_EQ(valueProducer1->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(valueProducer1->mCondition, ConditionState::kUnknown); + EXPECT_EQ(valueProducer1->mWhatMatcherIndex, matcher4Index); + ValueMetricProducer* valueProducer2 = + static_cast<ValueMetricProducer*>(newMetricProducers[value2Index].get()); + EXPECT_EQ(valueProducer2->getMetricId(), value2Id); + EXPECT_EQ(valueProducer2->mConditionTrackerIndex, -1); + EXPECT_EQ(valueProducer2->mCondition, ConditionState::kTrue); + EXPECT_EQ(valueProducer2->mWhatMatcherIndex, matcher1Index); + ValueMetricProducer* valueProducer3 = + static_cast<ValueMetricProducer*>(newMetricProducers[value3Index].get()); + EXPECT_EQ(valueProducer3->getMetricId(), value3Id); + EXPECT_EQ(valueProducer3->mConditionTrackerIndex, predicate2Index); + EXPECT_EQ(valueProducer3->mCondition, ConditionState::kUnknown); + EXPECT_EQ(valueProducer3->mWhatMatcherIndex, matcher5Index); + ValueMetricProducer* valueProducer4 = + static_cast<ValueMetricProducer*>(newMetricProducers[value4Index].get()); + EXPECT_EQ(valueProducer4->getMetricId(), value4Id); + EXPECT_EQ(valueProducer4->mConditionTrackerIndex, -1); + EXPECT_EQ(valueProducer4->mCondition, ConditionState::kTrue); + EXPECT_EQ(valueProducer4->mWhatMatcherIndex, matcher3Index); + ValueMetricProducer* valueProducer6 = + static_cast<ValueMetricProducer*>(newMetricProducers[value6Index].get()); + EXPECT_EQ(valueProducer6->getMetricId(), value6Id); + EXPECT_EQ(valueProducer6->mConditionTrackerIndex, predicate1Index); + EXPECT_EQ(valueProducer6->mCondition, ConditionState::kUnknown); + EXPECT_EQ(valueProducer6->mWhatMatcherIndex, matcher5Index); + + sp<EventMatcherWizard> newMatcherWizard = valueProducer1->mEventMatcherWizard; + EXPECT_NE(newMatcherWizard, oldMatcherWizard); + EXPECT_EQ(newMatcherWizard->getStrongCount(), 6); + oldMetricProducers.clear(); + // Only reference to the old wizard should be the one in the test. + EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1); +} + TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) { StatsdConfig config; // Add atom matchers @@ -2767,6 +3014,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) { unordered_map<int, vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap; vector<int> metricsWithActivation; + set<int64_t> replacedMetrics; EXPECT_TRUE(updateMetrics( key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, @@ -2775,7 +3023,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) { /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation)); + metricsWithActivation, replacedMetrics)); // Verify event activation/deactivation maps. ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 3); @@ -2850,6 +3098,11 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { *config.add_gauge_metric() = gaugeMetric; // Preserved. + ValueMetric valueMetric = createValueMetric("VALUE1", matcher3, predicate1Id, {}); + int64_t valueMetricId = valueMetric.id(); + *config.add_value_metric() = valueMetric; + + // Preserved. DurationMetric durationMetric = createDurationMetric("DURATION1", predicate1Id, nullopt, {}); int64_t durationMetricId = durationMetric.id(); *config.add_duration_metric() = durationMetric; @@ -2858,7 +3111,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { // Used later to ensure the condition wizard is replaced. Get it before doing the update. sp<ConditionWizard> oldConditionWizard = oldMetricProducers[0]->mWizard; - EXPECT_EQ(oldConditionWizard->getStrongCount(), 5); + EXPECT_EQ(oldConditionWizard->getStrongCount(), 6); // Mark matcher 2 as replaced. Causes eventMetric to be replaced. set<int64_t> replacedMatchers; @@ -2889,6 +3142,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { newConditionTrackers.begin()); vector<ConditionState> conditionCache = {ConditionState::kUnknown}; + // The order matters. we parse in the order of: count, duration, event, value, gauge. StatsdConfig newConfig; *newConfig.add_count_metric() = countMetric; const int countMetricIndex = 0; @@ -2896,8 +3150,10 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { const int durationMetricIndex = 1; *newConfig.add_event_metric() = eventMetric; const int eventMetricIndex = 2; + *newConfig.add_value_metric() = valueMetric; + const int valueMetricIndex = 3; *newConfig.add_gauge_metric() = gaugeMetric; - const int gaugeMetricIndex = 3; + const int gaugeMetricIndex = 4; // Add the predicate since duration metric needs it. *newConfig.add_predicate() = predicate1; @@ -2911,6 +3167,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { unordered_map<int, vector<int>> activationAtomTrackerToMetricMap; unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap; vector<int> metricsWithActivation; + set<int64_t> replacedMetrics; EXPECT_TRUE(updateMetrics( key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(), oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers, @@ -2919,22 +3176,25 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, - metricsWithActivation)); + metricsWithActivation, replacedMetrics)); unordered_map<int64_t, int> expectedMetricProducerMap = { - {countMetricId, countMetricIndex}, - {durationMetricId, durationMetricIndex}, - {eventMetricId, eventMetricIndex}, + {countMetricId, countMetricIndex}, {durationMetricId, durationMetricIndex}, + {eventMetricId, eventMetricIndex}, {valueMetricId, valueMetricIndex}, {gaugeMetricId, gaugeMetricIndex}, }; EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap)); + EXPECT_EQ(replacedMetrics, set<int64_t>({eventMetricId, gaugeMetricId})); + // Make sure preserved metrics are the same. - ASSERT_EQ(newMetricProducers.size(), 4); + ASSERT_EQ(newMetricProducers.size(), 5); EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(countMetricId)], newMetricProducers[newMetricProducerMap.at(countMetricId)]); EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(durationMetricId)], newMetricProducers[newMetricProducerMap.at(durationMetricId)]); + EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(valueMetricId)], + newMetricProducers[newMetricProducerMap.at(valueMetricId)]); // Make sure replaced metrics are different. EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(eventMetricId)], @@ -2945,7 +3205,8 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { // Verify the conditionToMetricMap. ASSERT_EQ(conditionToMetricMap.size(), 1); const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index]; - EXPECT_THAT(condition1Metrics, UnorderedElementsAre(countMetricIndex, gaugeMetricIndex)); + EXPECT_THAT(condition1Metrics, + UnorderedElementsAre(countMetricIndex, gaugeMetricIndex, valueMetricIndex)); // Verify the trackerToMetricMap. ASSERT_EQ(trackerToMetricMap.size(), 3); @@ -2954,7 +3215,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index]; EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex, durationMetricIndex)); const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index]; - EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex)); + EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex, valueMetricIndex)); // Verify event activation/deactivation maps. ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0); @@ -2977,7 +3238,7 @@ TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) { sp<ConditionWizard> newConditionWizard = newMetricProducers[0]->mWizard; EXPECT_NE(newConditionWizard, oldConditionWizard); - EXPECT_EQ(newConditionWizard->getStrongCount(), 5); + EXPECT_EQ(newConditionWizard->getStrongCount(), 6); oldMetricProducers.clear(); // Only reference to the old wizard should be the one in the test. EXPECT_EQ(oldConditionWizard->getStrongCount(), 1); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index f3b37891876d..3bcb87aa73f2 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -865,6 +865,8 @@ public class AppOpsManager { @UnsupportedAppUsage public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS; /** @hide */ + public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS; + /** @hide */ @UnsupportedAppUsage public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS; /** @hide */ @@ -1155,7 +1157,7 @@ public class AppOpsManager { /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 103; + public static final int _NUM_OP = 104; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1466,6 +1468,15 @@ public class AppOpsManager { public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats"; /** + * Grants an app access to the {@link android.telecom.InCallService} API to see + * information about ongoing calls and to enable control of calls. + * @hide + */ + @SystemApi + @TestApi + public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls"; + + /** * AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage} * * <p>MediaProvider is the only component (outside of system server) that should care about this @@ -1574,6 +1585,7 @@ public class AppOpsManager { OP_MANAGE_EXTERNAL_STORAGE, OP_INTERACT_ACROSS_PROFILES, OP_LOADER_USAGE_STATS, + OP_MANAGE_ONGOING_CALLS, }; /** @@ -1688,6 +1700,7 @@ public class AppOpsManager { OP_PHONE_CALL_MICROPHONE, // OP_PHONE_CALL_MICROPHONE OP_PHONE_CALL_CAMERA, // OP_PHONE_CALL_CAMERA OP_RECORD_AUDIO_HOTWORD, // RECORD_AUDIO_HOTWORD + OP_MANAGE_ONGOING_CALLS, // MANAGE_ONGOING_CALLS }; /** @@ -1797,6 +1810,7 @@ public class AppOpsManager { OPSTR_PHONE_CALL_MICROPHONE, OPSTR_PHONE_CALL_CAMERA, OPSTR_RECORD_AUDIO_HOTWORD, + OPSTR_MANAGE_ONGOING_CALLS, }; /** @@ -1907,6 +1921,7 @@ public class AppOpsManager { "PHONE_CALL_MICROPHONE", "PHONE_CALL_CAMERA", "RECORD_AUDIO_HOTWORD", + "MANAGE_ONGOING_CALLS", }; /** @@ -2018,6 +2033,7 @@ public class AppOpsManager { null, // no permission for OP_PHONE_CALL_MICROPHONE null, // no permission for OP_PHONE_CALL_CAMERA null, // no permission for OP_RECORD_AUDIO_HOTWORD + Manifest.permission.MANAGE_ONGOING_CALLS, }; /** @@ -2129,6 +2145,7 @@ public class AppOpsManager { null, // PHONE_CALL_MICROPHONE null, // PHONE_CALL_MICROPHONE null, // RECORD_AUDIO_HOTWORD + null, // MANAGE_ONGOING_CALLS }; /** @@ -2239,6 +2256,7 @@ public class AppOpsManager { null, // PHONE_CALL_MICROPHONE null, // PHONE_CALL_CAMERA null, // RECORD_AUDIO_HOTWORD + null, // MANAGE_ONGOING_CALLS }; /** @@ -2348,6 +2366,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD + AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS }; /** @@ -2461,6 +2480,7 @@ public class AppOpsManager { false, // PHONE_CALL_MICROPHONE false, // PHONE_CALL_CAMERA false, // RECORD_AUDIO_HOTWORD + true, // MANAGE_ONGOING_CALLS }; /** diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 861e4378c68e..1f81c44e54cb 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -397,6 +397,7 @@ public final class PendingIntent implements Parcelable { * parameters. May return null only if {@link #FLAG_NO_CREATE} has been * supplied. */ + @SuppressWarnings("AndroidFrameworkPendingIntentMutability") public static PendingIntent getActivity(Context context, int requestCode, @NonNull Intent intent, @Flags int flags, @Nullable Bundle options) { // Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null @@ -528,6 +529,7 @@ public final class PendingIntent implements Parcelable { * parameters. May return null only if {@link #FLAG_NO_CREATE} has been * supplied. */ + @SuppressWarnings("AndroidFrameworkPendingIntentMutability") public static PendingIntent getActivities(Context context, int requestCode, @NonNull Intent[] intents, @Flags int flags, @Nullable Bundle options) { // Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is null diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 849f679c9439..5caf3057c840 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -24,6 +24,8 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; @@ -181,6 +183,20 @@ public class TaskInfo { public boolean isResizeable; /** + * Activity bounds if this task or its top activity is presented in letterbox mode and + * {@code null} otherwise. + * @hide + */ + @Nullable + public Rect letterboxActivityBounds; + + /** + * Relative position of the task's top left corner in the parent container. + * @hide + */ + public Point positionInParent; + + /** * The launch cookies associated with activities in this task if any. * @see ActivityOptions#setLaunchCookie(IBinder) * @hide @@ -225,6 +241,7 @@ public class TaskInfo { /** @hide */ public void addLaunchCookie(IBinder cookie) { + if (cookie == null || launchCookies.contains(cookie)) return; launchCookies.add(cookie); } @@ -256,6 +273,8 @@ public class TaskInfo { topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); isResizeable = source.readBoolean(); source.readBinderList(launchCookies); + letterboxActivityBounds = source.readTypedObject(Rect.CREATOR); + positionInParent = source.readTypedObject(Point.CREATOR); } /** @@ -287,6 +306,8 @@ public class TaskInfo { dest.writeTypedObject(topActivityInfo, flags); dest.writeBoolean(isResizeable); dest.writeBinderList(launchCookies); + dest.writeTypedObject(letterboxActivityBounds, flags); + dest.writeTypedObject(positionInParent, flags); } @Override @@ -306,6 +327,9 @@ public class TaskInfo { + " topActivityType=" + topActivityType + " pictureInPictureParams=" + pictureInPictureParams + " topActivityInfo=" + topActivityInfo - + " launchCookies" + launchCookies; + + " launchCookies" + launchCookies + + " letterboxActivityBounds=" + letterboxActivityBounds + + " positionInParent=" + positionInParent + + "}"; } } diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 4ae1670e9041..c4af4edb467b 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -796,6 +796,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu /** * Returns {@code true} if the windowingMode represents a window in multi-window mode. * I.e. sharing the screen with another activity. + * + * TODO(b/171672645): This API could be misleading - in 'undefined' mode it's determined by the + * parent's mode * @hide */ public static boolean inMultiWindowMode(int windowingMode) { diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 3cc7f1e5df42..4c541b3f6b76 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -55,22 +55,6 @@ public final class DeviceAdminInfo implements Parcelable { static final String TAG = "DeviceAdminInfo"; /** - * A type of policy that this device admin can use: device owner meta-policy - * for an admin that is designated as owner of the device. - * - * @hide - */ - public static final int USES_POLICY_DEVICE_OWNER = -2; - - /** - * A type of policy that this device admin can use: profile owner meta-policy - * for admins that have been installed as owner of some user profile. - * - * @hide - */ - public static final int USES_POLICY_PROFILE_OWNER = -1; - - /** * A type of policy that this device admin can use: limit the passwords * that the user can select, via {@link DevicePolicyManager#setPasswordQuality} * and {@link DevicePolicyManager#setPasswordMinimumLength}. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 054e8429ff86..ad902a028f13 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1830,15 +1830,6 @@ public class DevicePolicyManager { public static final int STATE_USER_PROFILE_COMPLETE = 4; /** - * Management setup on a managed profile. - * <p>This is used as an intermediate state after {@link #STATE_USER_PROFILE_COMPLETE} once the - * work profile has been created. - * @hide - */ - @SystemApi - public static final int STATE_USER_PROFILE_FINALIZED = 5; - - /** * @hide */ @IntDef(prefix = { "STATE_USER_" }, value = { @@ -1846,8 +1837,7 @@ public class DevicePolicyManager { STATE_USER_SETUP_INCOMPLETE, STATE_USER_SETUP_COMPLETE, STATE_USER_SETUP_FINALIZED, - STATE_USER_PROFILE_COMPLETE, - STATE_USER_PROFILE_FINALIZED + STATE_USER_PROFILE_COMPLETE }) @Retention(RetentionPolicy.SOURCE) public @interface UserProvisioningState {} diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java index 802655b0d364..2b689899af01 100644 --- a/core/java/android/hardware/biometrics/BiometricTestSession.java +++ b/core/java/android/hardware/biometrics/BiometricTestSession.java @@ -46,7 +46,7 @@ public class BiometricTestSession implements AutoCloseable { mContext = context; mTestSession = testSession; mTestedUsers = new ArraySet<>(); - enableTestHal(true); + setTestHalEnabled(true); } /** @@ -56,12 +56,12 @@ public class BiometricTestSession implements AutoCloseable { * secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its * equivalent for the secret key. * - * @param enableTestHal If true, enable testing with a fake HAL instead of the real HAL. + * @param enabled If true, enable testing with a fake HAL instead of the real HAL. */ @RequiresPermission(TEST_BIOMETRIC) - private void enableTestHal(boolean enableTestHal) { + private void setTestHalEnabled(boolean enabled) { try { - mTestSession.enableTestHal(enableTestHal); + mTestSession.setTestHalEnabled(enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -178,10 +178,12 @@ public class BiometricTestSession implements AutoCloseable { @Override @RequiresPermission(TEST_BIOMETRIC) public void close() { + // Disable the test HAL first, so that enumerate is run on the real HAL, which should have + // no enrollments. Test-only framework enrollments will be deleted. + setTestHalEnabled(false); + for (int user : mTestedUsers) { cleanupInternalState(user); } - - enableTestHal(false); } } diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl index 6112f17949d7..fa7a62c53531 100644 --- a/core/java/android/hardware/biometrics/ITestSession.aidl +++ b/core/java/android/hardware/biometrics/ITestSession.aidl @@ -27,7 +27,7 @@ interface ITestSession { // portion of the framework code that would otherwise require human interaction. Note that // secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its // equivalent for the secret key. - void enableTestHal(boolean enableTestHal); + void setTestHalEnabled(boolean enableTestHal); // Starts the enrollment process. This should generally be used when the test HAL is enabled. void startEnroll(int userId); diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 8cfa0866f13a..228617c74388 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -190,6 +190,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { } } + private final String mCameraId; @UnsupportedAppUsage private final CameraMetadataNative mResults; private final CaptureRequest mRequest; @@ -202,7 +203,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>For internal use only</p> * @hide */ - public CaptureResult(CameraMetadataNative results, CaptureRequest parent, + public CaptureResult(String cameraId, CameraMetadataNative results, CaptureRequest parent, CaptureResultExtras extras) { if (results == null) { throw new IllegalArgumentException("results was null"); @@ -221,6 +222,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { throw new AssertionError("Results must not be empty"); } setNativeInstance(mResults); + mCameraId = cameraId; mRequest = parent; mSequenceId = extras.getRequestId(); mFrameNumber = extras.getFrameNumber(); @@ -251,12 +253,27 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { } setNativeInstance(mResults); + mCameraId = "none"; mRequest = null; mSequenceId = sequenceId; mFrameNumber = -1; } /** + * Get the camera ID of the camera that produced this capture result. + * + * For a logical multi-camera, the ID may be the logical or the physical camera ID, depending on + * whether the capture result was obtained from + * {@link TotalCaptureResult#getPhysicalCameraResults} or not. + * + * @return The camera ID for the camera that produced this capture result. + */ + @NonNull + public String getCameraId() { + return mCameraId; + } + + /** * Get a capture result field value. * * <p>The field definitions can be found in {@link CaptureResult}.</p> diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index 7cc2623a29ba..da65f71ce02c 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -70,10 +70,10 @@ public final class TotalCaptureResult extends CaptureResult { * @param partials a list of partial results; {@code null} will be substituted for an empty list * @hide */ - public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, - CaptureResultExtras extras, List<CaptureResult> partials, int sessionId, - PhysicalCaptureResultInfo physicalResults[]) { - super(results, parent, extras); + public TotalCaptureResult(String logicalCameraId, CameraMetadataNative results, + CaptureRequest parent, CaptureResultExtras extras, List<CaptureResult> partials, + int sessionId, PhysicalCaptureResultInfo[] physicalResults) { + super(logicalCameraId, results, parent, extras); if (partials == null) { mPartialResults = new ArrayList<>(); @@ -85,7 +85,7 @@ public final class TotalCaptureResult extends CaptureResult { mPhysicalCaptureResults = new HashMap<String, CaptureResult>(); for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) { - CaptureResult physicalResult = new CaptureResult( + CaptureResult physicalResult = new CaptureResult(onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(), parent, extras); mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(), physicalResult); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 48ec3fd808fe..819d966e3bfe 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -1980,7 +1980,7 @@ public class CameraDeviceImpl extends CameraDevice // Either send a partial result or the final capture completed result if (isPartialResult) { final CaptureResult resultAsCapture = - new CaptureResult(result, request, resultExtras); + new CaptureResult(getId(), result, request, resultExtras); // Partial result resultDispatch = new Runnable() { @Override @@ -1992,7 +1992,7 @@ public class CameraDeviceImpl extends CameraDevice for (int i = 0; i < holder.getRequestCount(); i++) { CameraMetadataNative resultLocal = new CameraMetadataNative(resultCopy); - CaptureResult resultInBatch = new CaptureResult( + CaptureResult resultInBatch = new CaptureResult(getId(), resultLocal, holder.getRequest(i), resultExtras); holder.getCallback().onCaptureProgressed( @@ -2019,8 +2019,8 @@ public class CameraDeviceImpl extends CameraDevice final Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); final int subsequenceId = resultExtras.getSubsequenceId(); - final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, - request, resultExtras, partialResults, holder.getSessionId(), + final TotalCaptureResult resultAsCapture = new TotalCaptureResult(getId(), + result, request, resultExtras, partialResults, holder.getSessionId(), physicalResults); // Final capture result resultDispatch = new Runnable() { @@ -2038,9 +2038,9 @@ public class CameraDeviceImpl extends CameraDevice new CameraMetadataNative(resultCopy); // No logical multi-camera support for batched output mode. TotalCaptureResult resultInBatch = new TotalCaptureResult( - resultLocal, holder.getRequest(i), resultExtras, - partialResults, holder.getSessionId(), - new PhysicalCaptureResultInfo[0]); + getId(), resultLocal, holder.getRequest(i), + resultExtras, partialResults, holder.getSessionId(), + new PhysicalCaptureResultInfo[0]); holder.getCallback().onCaptureCompleted( CameraDeviceImpl.this, diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java index 1d8b2a123c6a..eb2ff88ec1b2 100644 --- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java @@ -334,7 +334,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession // Either send a partial result or the final capture completed result if (isPartialResult) { final CaptureResult resultAsCapture = - new CaptureResult(result, request, resultExtras); + new CaptureResult(mCameraId, result, request, resultExtras); // Partial result resultDispatch = new Runnable() { @Override @@ -349,7 +349,8 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession CameraMetadataNative resultLocal = new CameraMetadataNative(resultCopy); final CaptureResult resultInBatch = new CaptureResult( - resultLocal, holder.getRequest(i), resultExtras); + mCameraId, resultLocal, holder.getRequest(i), + resultExtras); final CaptureRequest cbRequest = holder.getRequest(i); callback.onCaptureProgressed(CameraOfflineSessionImpl.this, @@ -372,8 +373,8 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession final Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); final int subsequenceId = resultExtras.getSubsequenceId(); - final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, - request, resultExtras, partialResults, holder.getSessionId(), + final TotalCaptureResult resultAsCapture = new TotalCaptureResult(mCameraId, + result, request, resultExtras, partialResults, holder.getSessionId(), physicalResults); // Final capture result resultDispatch = new Runnable() { @@ -393,9 +394,9 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession new CameraMetadataNative(resultCopy); // No logical multi-camera support for batched output mode. TotalCaptureResult resultInBatch = new TotalCaptureResult( - resultLocal, holder.getRequest(i), resultExtras, - partialResults, holder.getSessionId(), - new PhysicalCaptureResultInfo[0]); + mCameraId, resultLocal, holder.getRequest(i), + resultExtras, partialResults, holder.getSessionId(), + new PhysicalCaptureResultInfo[0]); final CaptureRequest cbRequest = holder.getRequest(i); callback.onCaptureCompleted(CameraOfflineSessionImpl.this, diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index c39fd4d1bc43..8c98362fa909 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -44,7 +44,7 @@ import android.util.proto.ProtoOutputStream; * public void run() { * Looper.prepare(); * - * mHandler = new Handler() { + * mHandler = new Handler(Looper.myLooper()) { * public void handleMessage(Message msg) { * // process incoming messages here * } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index c3b6d8e2cfe3..105ffaa4718e 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -17,6 +17,7 @@ package android.provider; +import android.annotation.LongDef; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentProvider; import android.content.ContentResolver; @@ -43,6 +44,8 @@ import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -611,6 +614,144 @@ public class CallLog { */ public static final String BLOCK_REASON = "block_reason"; + /** @hide */ + @LongDef(flag = true, value = { + MISSED_REASON_NOT_MISSED, + AUTO_MISSED_EMERGENCY_CALL, + AUTO_MISSED_MAXIMUM_RINGING, + AUTO_MISSED_MAXIMUM_DIALING, + USER_MISSED_NO_ANSWER, + USER_MISSED_SHORT_RING, + USER_MISSED_DND_MODE, + USER_MISSED_LOW_RING_VOLUME, + USER_MISSED_NO_VIBRATE, + USER_MISSED_CALL_SCREENING_SERVICE_SILENCED, + USER_MISSED_CALL_FILTERS_TIMEOUT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MissedReason {} + + /** + * Value for {@link CallLog.Calls#MISSED_REASON}, set as the default value when a call was + * not missed. + */ + public static final long MISSED_REASON_NOT_MISSED = 0; + + /** + * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is + * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by + * system because an ongoing emergency call. + */ + public static final long AUTO_MISSED_EMERGENCY_CALL = 1 << 0; + + /** + * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is + * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by + * system because the system cannot support any more ringing calls. + */ + public static final long AUTO_MISSED_MAXIMUM_RINGING = 1 << 1; + + /** + * Value for {@link CallLog.Calls#MISSED_REASON}, set when {@link CallLog.Calls#TYPE} is + * {@link CallLog.Calls#MISSED_TYPE} to indicate that a call was automatically rejected by + * system because the system cannot support any more dialing calls. + */ + public static final long AUTO_MISSED_MAXIMUM_DIALING = 1 << 2; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * the call was missed just because user didn't answer it. + */ + public static final long USER_MISSED_NO_ANSWER = 1 << 16; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * this call rang for a short period of time. + */ + public static final long USER_MISSED_SHORT_RING = 1 << 17; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, when this call + * rings less than this defined time in millisecond, set + * {@link CallLog.Calls#USER_MISSED_SHORT_RING} bit. + * @hide + */ + public static final long SHORT_RING_THRESHOLD = 5000L; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * this call is silenced because the phone is in 'do not disturb mode'. + */ + public static final long USER_MISSED_DND_MODE = 1 << 18; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * this call rings with a low ring volume. + */ + public static final long USER_MISSED_LOW_RING_VOLUME = 1 << 19; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, when this call + * rings in volume less than this defined volume threshold, set + * {@link CallLog.Calls#USER_MISSED_LOW_RING_VOLUME} bit. + * @hide + */ + public static final int LOW_RING_VOLUME = 0; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE} set this bit when + * this call rings without vibration. + */ + public static final long USER_MISSED_NO_VIBRATE = 1 << 20; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * this call is silenced by the call screening service. + */ + public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 1 << 21; + + /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * the call filters timed out. + */ + public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 1 << 22; + + /** + * Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, + * indicates factors which may have lead the user to miss the call. + * <P>Type: INTEGER</P> + * + * <p> + * There are two main cases. Auto missed cases and user missed cases. Default value is: + * <ul> + * <li>{@link CallLog.Calls#MISSED_REASON_NOT_MISSED}</li> + * </ul> + * </p> + * <P> + * Auto missed cases are those where a call was missed because it was not possible for the + * incoming call to be presented to the user at all. Possible values are: + * <ul> + * <li>{@link CallLog.Calls#AUTO_MISSED_EMERGENCY_CALL}</li> + * <li>{@link CallLog.Calls#AUTO_MISSED_MAXIMUM_RINGING}</li> + * <li>{@link CallLog.Calls#AUTO_MISSED_MAXIMUM_DIALING}</li> + * </ul> + * </P> + * <P> + * User missed cases are those where the incoming call was presented to the user, but + * factors such as a low ringing volume may have contributed to the call being missed. + * Following bits can be set to indicate possible reasons for this: + * <ul> + * <li>{@link CallLog.Calls#USER_MISSED_SHORT_RING}</li> + * <li>{@link CallLog.Calls#USER_MISSED_DND_MODE}</li> + * <li>{@link CallLog.Calls#USER_MISSED_LOW_RING_VOLUME}</li> + * <li>{@link CallLog.Calls#USER_MISSED_NO_VIBRATE}</li> + * <li>{@link CallLog.Calls#USER_MISSED_CALL_SCREENING_SERVICE_SILENCED}</li> + * <li>{@link CallLog.Calls#USER_MISSED_CALL_FILTERS_TIMEOUT}</li> + * </ul> + * </P> + */ + public static final String MISSED_REASON = "missed_reason"; + /** * Adds a call to the call log. * @@ -635,12 +776,13 @@ public class CallLog { public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, int features, PhoneAccountHandle accountHandle, - long start, int duration, Long dataUsage) { + long start, int duration, Long dataUsage, long missedReason) { return addCall(ci, context, number, "" /* postDialDigits */, "" /* viaNumber */, presentation, callType, features, accountHandle, start, duration, dataUsage, false /* addForAllUsers */, null /* userToBeInsertedTo */, false /* isRead */, Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */, - null /* callScreeningAppName */, null /* callScreeningComponentName */); + null /* callScreeningAppName */, null /* callScreeningComponentName */, + missedReason); } @@ -675,12 +817,13 @@ public class CallLog { public static Uri addCall(CallerInfo ci, Context context, String number, String postDialDigits, String viaNumber, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, - Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo) { + Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo, + long missedReason) { return addCall(ci, context, number, postDialDigits, viaNumber, presentation, callType, features, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, false /* isRead */ , Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */, null /* callScreeningAppName */, - null /* callScreeningComponentName */); + null /* callScreeningComponentName */, missedReason); } /** @@ -714,6 +857,7 @@ public class CallLog { * @param callBlockReason The reason why the call is blocked. * @param callScreeningAppName The call screening application name which block the call. * @param callScreeningComponentName The call screening component name which block the call. + * @param missedReason The encoded missed information of the call. * * @result The URI of the call log entry belonging to the user that made or received this * call. This could be of the shadow provider. Do not return it to non-system apps, @@ -726,7 +870,7 @@ public class CallLog { int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean isRead, int callBlockReason, CharSequence callScreeningAppName, - String callScreeningComponentName) { + String callScreeningComponentName, long missedReason) { if (VERBOSE_LOG) { Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s", number, userToBeInsertedTo, addForAllUsers)); @@ -779,6 +923,7 @@ public class CallLog { values.put(BLOCK_REASON, callBlockReason); values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName)); values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName); + values.put(MISSED_REASON, Long.valueOf(missedReason)); if ((ci != null) && (ci.getContactId() > 0)) { // Update usage information for the number associated with the contact ID. @@ -1114,5 +1259,19 @@ public class CallLog { } return countryIso; } + + /** + * Check if the missedReason code indicate that the call was user missed or automatically + * rejected by system. + * + * @param missedReason + * The result is true if the call was user missed, false if the call was automatically + * rejected by system. + * + * @hide + */ + public static boolean isUserMissed(long missedReason) { + return missedReason >= (USER_MISSED_NO_ANSWER); + } } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a4c8114159d3..60d1d477db53 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8380,18 +8380,19 @@ public final class Settings { public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled"; /** - * Whether the panic button (emergency sos) gesture should be enabled. + * Whether the emergency gesture should be enabled. * * @hide */ - public static final String PANIC_GESTURE_ENABLED = "panic_gesture_enabled"; + public static final String EMERGENCY_GESTURE_ENABLED = "emergency_gesture_enabled"; /** - * Whether the panic button (emergency sos) sound should be enabled. + * Whether the emergency gesture sound should be enabled. * * @hide */ - public static final String PANIC_SOUND_ENABLED = "panic_sound_enabled"; + public static final String EMERGENCY_GESTURE_SOUND_ENABLED = + "emergency_gesture_sound_enabled"; /** * Whether the camera launch gesture to double tap the power button when the screen is off @@ -9914,13 +9915,19 @@ public final class Settings { "hdmi_control_auto_device_off_enabled"; /** - * Property to decide which devices the playback device can send a <Standby> message to upon - * going to sleep. Supported values are: + * Property to decide which devices the playback device can send a <Standby> message to + * upon going to sleep. It additionally controls whether a playback device attempts to turn + * on the connected Audio system when waking up. Supported values are: * <ul> - * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_TO_TV} to TV only.</li> - * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_BROADCAST} to all devices in the - * network.</li> - * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_NONE} no <Standby> message sent.</li> + * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_TO_TV} Upon going to sleep, device + * sends {@code <Standby>} to TV only. Upon waking up, device does not turn on the Audio + * system via {@code <System Audio Mode Request>}.</li> + * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_BROADCAST} Upon going to sleep, + * device sends {@code <Standby>} to all devices in the network. Upon waking up, device + * attempts to turn on the Audio system via {@code <System Audio Mode Request>}.</li> + * <li>{@link HdmiControlManager#SEND_STANDBY_ON_SLEEP_NONE} Upon going to sleep, device + * does not send any {@code <Standby>} message. Upon waking up, device does not turn on the + * Audio system via {@code <System Audio Mode Request>}.</li> * </ul> * * @hide diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java index 6bd376a19fc5..c5277ee0ed5e 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -206,8 +206,8 @@ public abstract class ControlsProviderService extends Service { case MSG_SUBSCRIBE: { final SubscribeMessage sMsg = (SubscribeMessage) msg.obj; - final SubscriberProxy proxy = new SubscriberProxy(false, mToken, - sMsg.mSubscriber); + final SubscriberProxy proxy = new SubscriberProxy( + ControlsProviderService.this, false, mToken, sMsg.mSubscriber); ControlsProviderService.this.createPublisherFor(sMsg.mControlIds) .subscribe(proxy); @@ -251,6 +251,7 @@ public abstract class ControlsProviderService extends Service { private IBinder mToken; private IControlsSubscriber mCs; private boolean mEnforceStateless; + private Context mContext; SubscriberProxy(boolean enforceStateless, IBinder token, IControlsSubscriber cs) { mEnforceStateless = enforceStateless; @@ -258,6 +259,12 @@ public abstract class ControlsProviderService extends Service { mCs = cs; } + SubscriberProxy(Context context, boolean enforceStateless, IBinder token, + IControlsSubscriber cs) { + this(enforceStateless, token, cs); + mContext = context; + } + public void onSubscribe(Subscription subscription) { try { mCs.onSubscribe(mToken, new SubscriptionAdapter(subscription)); @@ -273,6 +280,9 @@ public abstract class ControlsProviderService extends Service { + "Control.StatelessBuilder() to build the control."); control = new Control.StatelessBuilder(control).build(); } + if (mContext != null) { + control.getControlTemplate().prepareTemplateForBinder(mContext); + } mCs.onNext(mToken, control); } catch (RemoteException ex) { ex.rethrowAsRuntimeException(); diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java index e592fad394b8..3902d6af69e7 100644 --- a/core/java/android/service/controls/templates/ControlTemplate.java +++ b/core/java/android/service/controls/templates/ControlTemplate.java @@ -20,6 +20,7 @@ import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.os.Bundle; import android.service.controls.Control; import android.service.controls.actions.ControlAction; @@ -78,6 +79,7 @@ public abstract class ControlTemplate { TYPE_NO_TEMPLATE, TYPE_TOGGLE, TYPE_RANGE, + TYPE_THUMBNAIL, TYPE_TOGGLE_RANGE, TYPE_TEMPERATURE, TYPE_STATELESS @@ -105,6 +107,11 @@ public abstract class ControlTemplate { public static final @TemplateType int TYPE_RANGE = 2; /** + * Type identifier of {@link ThumbnailTemplate}. + */ + public static final @TemplateType int TYPE_THUMBNAIL = 3; + + /** * Type identifier of {@link ToggleRangeTemplate}. */ public static final @TemplateType int TYPE_TOGGLE_RANGE = 6; @@ -169,6 +176,13 @@ public abstract class ControlTemplate { } /** + * Call to prepare values for Binder transport. + * + * @hide + */ + public void prepareTemplateForBinder(@NonNull Context context) {} + + /** * * @param bundle * @return @@ -187,6 +201,8 @@ public abstract class ControlTemplate { return new ToggleTemplate(bundle); case TYPE_RANGE: return new RangeTemplate(bundle); + case TYPE_THUMBNAIL: + return new ThumbnailTemplate(bundle); case TYPE_TOGGLE_RANGE: return new ToggleRangeTemplate(bundle); case TYPE_TEMPERATURE: diff --git a/core/java/android/service/controls/templates/ThumbnailTemplate.java b/core/java/android/service/controls/templates/ThumbnailTemplate.java new file mode 100644 index 000000000000..a7c481e38e55 --- /dev/null +++ b/core/java/android/service/controls/templates/ThumbnailTemplate.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 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. + */ + +package android.service.controls.templates; + +import android.annotation.NonNull; +import android.content.Context; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.service.controls.Control; + +import com.android.internal.R; +import com.android.internal.util.Preconditions; + +/** + * A template for a {@link Control} that displays an image. + */ +public final class ThumbnailTemplate extends ControlTemplate { + + private static final @TemplateType int TYPE = TYPE_THUMBNAIL; + private static final String KEY_ICON = "key_icon"; + private static final String KEY_ACTIVE = "key_active"; + private static final String KEY_CONTENT_DESCRIPTION = "key_content_description"; + + private final boolean mActive; + private final @NonNull Icon mThumbnail; + private final @NonNull CharSequence mContentDescription; + + /** + * @param templateId the identifier for this template object + * @param active whether the image corresponds to an active (live) stream. + * @param thumbnail an image to display on the {@link Control} + * @param contentDescription a description of the image for accessibility. + */ + public ThumbnailTemplate( + @NonNull String templateId, + boolean active, + @NonNull Icon thumbnail, + @NonNull CharSequence contentDescription) { + super(templateId); + Preconditions.checkNotNull(thumbnail); + Preconditions.checkNotNull(contentDescription); + mActive = active; + mThumbnail = thumbnail; + mContentDescription = contentDescription; + } + + /** + * @param b + * @hide + */ + ThumbnailTemplate(Bundle b) { + super(b); + mActive = b.getBoolean(KEY_ACTIVE); + mThumbnail = b.getParcelable(KEY_ICON); + mContentDescription = b.getCharSequence(KEY_CONTENT_DESCRIPTION, ""); + } + + /* + * @return {@code true} if the thumbnail corresponds to an active (live) stream. + */ + public boolean isActive() { + return mActive; + } + + /** + * The {@link Icon} (image) displayed by this template. + */ + @NonNull + public Icon getThumbnail() { + return mThumbnail; + } + + /** + * The description of the image returned by {@link ThumbnailTemplate#getThumbnail()} + */ + @NonNull + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * @return {@link ControlTemplate#TYPE_THUMBNAIL} + */ + @Override + public int getTemplateType() { + return TYPE; + } + + /** + * Rescales the image down if necessary (in the case of a Bitmap). + * + * @hide + */ + @Override + public void prepareTemplateForBinder(@NonNull Context context) { + int width = context.getResources() + .getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_width); + int height = context.getResources() + .getDimensionPixelSize(R.dimen.controls_thumbnail_image_max_height); + rescaleThumbnail(width, height); + } + + private void rescaleThumbnail(int width, int height) { + mThumbnail.scaleDownIfNecessary(width, height); + } + + /** + * @return + * @hide + */ + @Override + @NonNull + Bundle getDataBundle() { + Bundle b = super.getDataBundle(); + b.putBoolean(KEY_ACTIVE, mActive); + b.putObject(KEY_ICON, mThumbnail); + b.putObject(KEY_CONTENT_DESCRIPTION, mContentDescription); + return b; + } +} diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index ee98b65d2444..a7d20b56eca3 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -64,7 +64,7 @@ public class EventLog { private Exception mLastWtf; // Layout of event log entry received from Android logger. - // see system/core/liblog/include/log/log_read.h + // see system/logging/liblog/include/log/log_read.h private static final int LENGTH_OFFSET = 0; private static final int HEADER_SIZE_OFFSET = 2; private static final int PROCESS_OFFSET = 4; diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 12ea936e96a1..726176568605 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Rect; @@ -44,24 +43,16 @@ import java.util.ArrayList; public class NotificationHeaderView extends ViewGroup { private final int mChildMinWidth; private final int mContentEndMargin; - private final int mGravity; - private View mAppName; - private View mHeaderText; - private View mSecondaryHeaderText; private OnClickListener mExpandClickListener; - private OnClickListener mFeedbackListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); + private NotificationTopLineView mTopLineView; private NotificationExpandButton mExpandButton; private CachingIconView mIcon; - private View mProfileBadge; - private View mFeedbackIcon; - private boolean mShowWorkBadgeAtEnd; private int mHeaderTextMarginEnd; private Drawable mBackground; private boolean mEntireHeaderClickable; private boolean mExpandOnlyOnButton; private boolean mAcceptAllTouches; - private int mTotalWidth; ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override @@ -93,23 +84,14 @@ public class NotificationHeaderView extends ViewGroup { mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width); mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end); mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand); - - int[] attrIds = {android.R.attr.gravity}; - TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes); - mGravity = ta.getInt(0, 0); - ta.recycle(); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mAppName = findViewById(R.id.app_name_text); - mHeaderText = findViewById(R.id.header_text); - mSecondaryHeaderText = findViewById(R.id.header_text_secondary); - mExpandButton = findViewById(R.id.expand_button); mIcon = findViewById(R.id.icon); - mProfileBadge = findViewById(R.id.profile_badge); - mFeedbackIcon = findViewById(R.id.feedback); + mTopLineView = findViewById(R.id.notification_top_line); + mExpandButton = findViewById(R.id.expand_button); setClipToPadding(false); } @@ -136,7 +118,7 @@ public class NotificationHeaderView extends ViewGroup { lp.topMargin + lp.bottomMargin, lp.height); child.measure(childWidthSpec, childHeightSpec); // Icons that should go at the end - if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) { + if (child == mExpandButton) { iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); } else { totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); @@ -147,19 +129,10 @@ public class NotificationHeaderView extends ViewGroup { int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth); if (totalWidth > givenWidth - endMargin) { int overFlow = totalWidth - givenWidth + endMargin; - // We are overflowing, lets shrink the app name first - overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName, + // We are overflowing; shrink the top line + shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mTopLineView, mChildMinWidth); - - // still overflowing, we shrink the header text - overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0); - - // still overflowing, finally we shrink the secondary header text - shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText, - 0); } - totalWidth += getPaddingEnd(); - mTotalWidth = Math.min(totalWidth, givenWidth); setMeasuredDimension(givenWidth, givenHeight); } @@ -180,10 +153,6 @@ public class NotificationHeaderView extends ViewGroup { protected void onLayout(boolean changed, int l, int t, int r, int b) { int left = getPaddingStart(); int end = getMeasuredWidth(); - final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0; - if (centerAligned) { - left += getMeasuredWidth() / 2 - mTotalWidth / 2; - } int childCount = getChildCount(); int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); for (int i = 0; i < childCount; i++) { @@ -198,7 +167,7 @@ public class NotificationHeaderView extends ViewGroup { int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f); int bottom = top + childHeight; // Icons that should go at the end - if (child == mExpandButton || child == mProfileBadge || child == mFeedbackIcon) { + if (child == mExpandButton) { if (end == getMeasuredWidth()) { layoutRight = end - mContentEndMargin; } else { @@ -266,7 +235,7 @@ public class NotificationHeaderView extends ViewGroup { } private void updateTouchListener() { - if (mExpandClickListener == null && mFeedbackListener == null) { + if (mExpandClickListener == null) { setOnTouchListener(null); return; } @@ -274,15 +243,6 @@ public class NotificationHeaderView extends ViewGroup { mTouchListener.bindTouchRects(); } - /** - * Sets onclick listener for feedback icon. - */ - public void setFeedbackOnClickListener(OnClickListener l) { - mFeedbackListener = l; - mFeedbackIcon.setOnClickListener(mFeedbackListener); - updateTouchListener(); - } - @Override public void setOnClickListener(@Nullable OnClickListener l) { mExpandClickListener = l; @@ -291,16 +251,6 @@ public class NotificationHeaderView extends ViewGroup { } /** - * Sets whether or not the work badge appears at the end of the NotificationHeaderView. - * The expand button will always be closer to the end. - */ - public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) { - if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) { - mShowWorkBadgeAtEnd = showWorkBadgeAtEnd; - } - } - - /** * Sets the margin end for the text portion of the header, excluding right-aligned elements * @param headerTextMarginEnd margin size */ @@ -327,7 +277,6 @@ public class NotificationHeaderView extends ViewGroup { private final ArrayList<Rect> mTouchRects = new ArrayList<>(); private Rect mExpandButtonRect; - private Rect mFeedbackRect; private int mTouchSlop; private boolean mTrackGesture; private float mDownX; @@ -340,7 +289,6 @@ public class NotificationHeaderView extends ViewGroup { mTouchRects.clear(); addRectAroundView(mIcon); mExpandButtonRect = addRectAroundView(mExpandButton); - mFeedbackRect = addRectAroundView(mFeedbackIcon); addWidthRect(); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @@ -401,11 +349,11 @@ public class NotificationHeaderView extends ViewGroup { break; case MotionEvent.ACTION_UP: if (mTrackGesture) { - if (mFeedbackIcon.isVisibleToUser() - && (mFeedbackRect.contains((int) x, (int) y) - || mFeedbackRect.contains((int) mDownX, (int) mDownY))) { - mFeedbackIcon.performClick(); - return true; + float topLineX = mTopLineView.getX(); + float topLineY = mTopLineView.getY(); + if (mTopLineView.onTouchUp(x - topLineX, y - topLineY, + mDownX - topLineX, mDownY - topLineY)) { + break; } mExpandButton.performClick(); } @@ -427,7 +375,9 @@ public class NotificationHeaderView extends ViewGroup { return true; } } - return false; + float topLineX = x - mTopLineView.getX(); + float topLineY = y - mTopLineView.getY(); + return mTopLineView.isInTouchRect(topLineX, topLineY); } } diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java new file mode 100644 index 000000000000..9443ccfc7553 --- /dev/null +++ b/core/java/android/view/NotificationTopLineView.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2015 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.view; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.RemoteViews; + +import com.android.internal.R; + +import java.util.Arrays; +import java.util.List; + +/** + * The top line of content in a notification view. + * This includes the text views and badges but excludes the icon and the expander. + * + * @hide + */ +@RemoteViews.RemoteView +public class NotificationTopLineView extends ViewGroup { + private final int mChildMinWidth; + private final int mContentEndMargin; + private View mAppName; + private View mHeaderText; + private View mSecondaryHeaderText; + private OnClickListener mFeedbackListener; + private HeaderTouchListener mTouchListener = new HeaderTouchListener(); + private View mProfileBadge; + private View mFeedbackIcon; + private int mHeaderTextMarginEnd; + private List<View> mIconsAtEnd; + + public NotificationTopLineView(Context context) { + this(context, null); + } + + public NotificationTopLineView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public NotificationTopLineView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NotificationTopLineView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Resources res = getResources(); + mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width); + mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mAppName = findViewById(R.id.app_name_text); + mHeaderText = findViewById(R.id.header_text); + mSecondaryHeaderText = findViewById(R.id.header_text_secondary); + mProfileBadge = findViewById(R.id.profile_badge); + mFeedbackIcon = findViewById(R.id.feedback); + mIconsAtEnd = Arrays.asList(mProfileBadge, mFeedbackIcon); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int givenWidth = MeasureSpec.getSize(widthMeasureSpec); + final int givenHeight = MeasureSpec.getSize(heightMeasureSpec); + int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth, + MeasureSpec.AT_MOST); + int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight, + MeasureSpec.AT_MOST); + int totalWidth = getPaddingStart(); + int iconWidth = getPaddingEnd(); + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + // We'll give it the rest of the space in the end + continue; + } + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec, + lp.leftMargin + lp.rightMargin, lp.width); + int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec, + lp.topMargin + lp.bottomMargin, lp.height); + child.measure(childWidthSpec, childHeightSpec); + // Icons that should go at the end + if (mIconsAtEnd.contains(child)) { + iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); + } else { + totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); + } + } + + // Ensure that there is at least enough space for the icons + int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth); + if (totalWidth > givenWidth - endMargin) { + int overFlow = totalWidth - givenWidth + endMargin; + // We are overflowing, lets shrink the app name first + overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName, + mChildMinWidth); + + // still overflowing, we shrink the header text + overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0); + + // still overflowing, finally we shrink the secondary header text + shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText, + 0); + } + setMeasuredDimension(givenWidth, givenHeight); + } + + private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView, + int minimumWidth) { + final int oldWidth = targetView.getMeasuredWidth(); + if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) { + // we're still too big + int newSize = Math.max(minimumWidth, oldWidth - overFlow); + int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); + targetView.measure(childWidthSpec, heightSpec); + overFlow -= oldWidth - newSize; + } + return overFlow; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int left = getPaddingStart(); + int end = getMeasuredWidth(); + int childCount = getChildCount(); + int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + int childHeight = child.getMeasuredHeight(); + MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); + int layoutLeft; + int layoutRight; + int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f); + int bottom = top + childHeight; + // Icons that should go at the end + if (mIconsAtEnd.contains(child)) { + if (end == getMeasuredWidth()) { + layoutRight = end - mContentEndMargin; + } else { + layoutRight = end - params.getMarginEnd(); + } + layoutLeft = layoutRight - child.getMeasuredWidth(); + end = layoutLeft - params.getMarginStart(); + } else { + left += params.getMarginStart(); + int right = left + child.getMeasuredWidth(); + layoutLeft = left; + layoutRight = right; + left = right + params.getMarginEnd(); + } + if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { + int ltrLeft = layoutLeft; + layoutLeft = getWidth() - layoutRight; + layoutRight = getWidth() - ltrLeft; + } + child.layout(layoutLeft, top, layoutRight, bottom); + } + updateTouchListener(); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new MarginLayoutParams(getContext(), attrs); + } + + private void updateTouchListener() { + if (mFeedbackListener == null) { + setOnTouchListener(null); + return; + } + setOnTouchListener(mTouchListener); + mTouchListener.bindTouchRects(); + } + + /** + * Sets onclick listener for feedback icon. + */ + public void setFeedbackOnClickListener(OnClickListener l) { + mFeedbackListener = l; + mFeedbackIcon.setOnClickListener(mFeedbackListener); + updateTouchListener(); + } + + /** + * Sets the margin end for the text portion of the header, excluding right-aligned elements + * + * @param headerTextMarginEnd margin size + */ + public void setHeaderTextMarginEnd(int headerTextMarginEnd) { + if (mHeaderTextMarginEnd != headerTextMarginEnd) { + mHeaderTextMarginEnd = headerTextMarginEnd; + requestLayout(); + } + } + + /** + * Get the current margin end value for the header text + * + * @return margin size + */ + public int getHeaderTextMarginEnd() { + return mHeaderTextMarginEnd; + } + + private class HeaderTouchListener implements OnTouchListener { + + private Rect mFeedbackRect; + private int mTouchSlop; + private boolean mTrackGesture; + private float mDownX; + private float mDownY; + + HeaderTouchListener() { + } + + public void bindTouchRects() { + mFeedbackRect = getRectAroundView(mFeedbackIcon); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + } + + private Rect getRectAroundView(View view) { + float size = 48 * getResources().getDisplayMetrics().density; + float width = Math.max(size, view.getWidth()); + float height = Math.max(size, view.getHeight()); + final Rect r = new Rect(); + if (view.getVisibility() == GONE) { + view = getFirstChildNotGone(); + r.left = (int) (view.getLeft() - width / 2.0f); + } else { + r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f); + } + r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f); + r.bottom = (int) (r.top + height); + r.right = (int) (r.left + width); + return r; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + switch (event.getActionMasked() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mTrackGesture = false; + if (isInside(x, y)) { + mDownX = x; + mDownY = y; + mTrackGesture = true; + return true; + } + break; + case MotionEvent.ACTION_MOVE: + if (mTrackGesture) { + if (Math.abs(mDownX - x) > mTouchSlop + || Math.abs(mDownY - y) > mTouchSlop) { + mTrackGesture = false; + } + } + break; + case MotionEvent.ACTION_UP: + if (mTrackGesture && onTouchUp(x, y, mDownX, mDownY)) { + return true; + } + break; + } + return mTrackGesture; + } + + private boolean onTouchUp(float upX, float upY, float downX, float downY) { + if (mFeedbackIcon.isVisibleToUser() + && (mFeedbackRect.contains((int) upX, (int) upY) + || mFeedbackRect.contains((int) downX, (int) downY))) { + mFeedbackIcon.performClick(); + return true; + } + return false; + } + + private boolean isInside(float x, float y) { + return mFeedbackRect.contains((int) x, (int) y); + } + } + + private View getFirstChildNotGone() { + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + return child; + } + } + return this; + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + /** + * Determine if the given point is touching an active part of the top line. + */ + public boolean isInTouchRect(float x, float y) { + if (mFeedbackListener == null) { + return false; + } + return mTouchListener.isInside(x, y); + } + + /** + * Perform a click on an active part of the top line, if touching. + */ + public boolean onTouchUp(float upX, float upY, float downX, float downY) { + if (mFeedbackListener == null) { + return false; + } + return mTouchListener.onTouchUp(upX, upY, downX, downY); + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 667f0b9511d0..bda368ecb3a2 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -80,7 +80,6 @@ import android.graphics.drawable.GradientDrawable; import android.hardware.display.DisplayManagerGlobal; import android.net.Uri; import android.os.Build; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -26444,7 +26443,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, surface.copyFrom(surfaceControl); IBinder token = null; try { - final Canvas canvas = surface.lockCanvas(null); + final Canvas canvas = isHardwareAccelerated() + ? surface.lockHardwareCanvas() + : surface.lockCanvas(null); try { canvas.drawColor(0, PorterDuff.Mode.CLEAR); shadowBuilder.onDrawShadow(canvas); @@ -26535,7 +26536,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (mAttachInfo.mDragToken != null) { try { - Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null); + Canvas canvas = isHardwareAccelerated() + ? mAttachInfo.mDragSurface.lockHardwareCanvas() + : mAttachInfo.mDragSurface.lockCanvas(null); try { canvas.drawColor(0, PorterDuff.Mode.CLEAR); shadowBuilder.onDrawShadow(canvas); @@ -28913,33 +28916,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean mUse32BitDrawingCache; /** - * For windows that are full-screen but using insets to layout inside - * of the screen decorations, these are the current insets for the - * content of the window. - */ - @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q, - publicAlternatives = "Use {@link WindowInsets#getInsets(int)}") - final Rect mContentInsets = new Rect(); - - /** - * For windows that are full-screen but using insets to layout inside - * of the screen decorations, these are the current insets for the - * actual visible parts of the window. - */ - @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q, - publicAlternatives = "Use {@link WindowInsets#getInsets(int)}") - final Rect mVisibleInsets = new Rect(); - - /** - * For windows that are full-screen but using insets to layout inside - * of the screen decorations, these are the current insets for the - * stable system windows. - */ - @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q, - publicAlternatives = "Use {@link WindowInsets#getInsets(int)}") - final Rect mStableInsets = new Rect(); - - /** * Current caption insets to the display coordinate. */ final Rect mCaptionInsets = new Rect(); diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl index 3a84c1f98ce6..4a43a438b69d 100644 --- a/core/java/android/window/ITaskOrganizerController.aidl +++ b/core/java/android/window/ITaskOrganizerController.aidl @@ -40,7 +40,7 @@ interface ITaskOrganizerController { void unregisterTaskOrganizer(ITaskOrganizer organizer); /** Creates a persistent root task in WM for a particular windowing-mode. */ - ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode); + void createRootTask(int displayId, int windowingMode, IBinder launchCookie); /** Deletes a persistent root task in WM */ boolean deleteRootTask(in WindowContainerToken task); diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 6c739bed35a4..ad48a9f9f776 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.app.ActivityManager; +import android.os.IBinder; import android.os.RemoteException; import android.view.SurfaceControl; @@ -101,12 +102,18 @@ public class TaskOrganizer extends WindowOrganizer { @BinderThread public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {} - /** Creates a persistent root task in WM for a particular windowing-mode. */ + /** + * Creates a persistent root task in WM for a particular windowing-mode. + * @param displayId The display to create the root task on. + * @param windowingMode Windowing mode to put the root task in. + * @param launchCookie Launch cookie to associate with the task so that is can be identified + * when the {@link ITaskOrganizer#onTaskAppeared} callback is called. + */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) @Nullable - public ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode) { + public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) { try { - return mTaskOrganizerController.createRootTask(displayId, windowingMode); + mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto index 37a9f50dec89..2d2c8accc039 100644 --- a/core/proto/android/app/enums.proto +++ b/core/proto/android/app/enums.proto @@ -210,4 +210,5 @@ enum AppOpEnum { APP_OP_PHONE_CALL_MICROPHONE = 100; APP_OP_PHONE_CALL_CAMERA = 101; APP_OP_RECORD_AUDIO_HOTWORD = 102; + APP_OP_MANAGE_ONGOING_CALLS = 103; } diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 83c53915694a..d934b82e36c3 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -219,8 +219,10 @@ message SecureSettingsProto { optional SettingProto emergency_assistance_application = 22 [ (android.privacy).dest = DEST_AUTOMATIC ]; message EmergencyResponse { - optional SettingProto panic_gesture_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; - optional SettingProto panic_sound_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto emergency_gesture_enabled = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto emergency_gesture_sound_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + + reserved 1,2; } optional EmergencyResponse emergency_response = 83; diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto index a264f18f921c..a49a1adcc619 100644 --- a/core/proto/android/server/fingerprint.proto +++ b/core/proto/android/server/fingerprint.proto @@ -66,3 +66,36 @@ message PerformanceStatsProto { // Total number of permanent lockouts. optional int32 permanent_lockout = 5; } + +// Internal FingerprintService states. The above messages (FingerprintServiceDumpProto, etc) +// are used for legacy metrics and should not be modified. +message FingerprintServiceStateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + repeated SensorStateProto sensor_states = 1; +} + +// State of a single sensor. +message SensorStateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Unique sensorId + optional int32 sensor_id = 1; + + // State of the sensor's scheduler. True if currently handling an operation, false if idle. + optional bool is_busy = 2; + + // User states for this sensor. + repeated UserStateProto user_states = 3; +} + +// State of a specific user for a specific sensor. +message UserStateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Android user ID + optional int32 user_id = 1; + + // Number of fingerprints enrolled + optional int32 num_enrolled = 2; +}
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 01954510ee08..8a7fa04b3cf4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2229,6 +2229,11 @@ <permission android:name="android.permission.BIND_INCALL_SERVICE" android:protectionLevel="signature|privileged" /> + <!-- Allows to query ongoing call details and manage ongoing calls + <p>Protection level: signature|appop --> + <permission android:name="android.permission.MANAGE_ONGOING_CALLS" + android:protectionLevel="signature|appop" /> + <!-- Allows the app to request network scans from telephony. <p>Not for use by third-party applications. @SystemApi @hide--> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 88493c9505ed..d11875e0f890 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -38,81 +38,7 @@ android:layout_gravity="center" /> </FrameLayout> - <TextView - android:id="@+id/app_name_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_app_name_margin_start" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:visibility="?attr/notificationHeaderAppNameVisibility" - android:singleLine="true" - /> - <TextView - android:id="@+id/header_text_secondary_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:text="@string/notification_header_divider_symbol" - android:visibility="gone"/> - <TextView - android:id="@+id/header_text_secondary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:visibility="gone" - android:singleLine="true"/> - <TextView - android:id="@+id/header_text_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:text="@string/notification_header_divider_symbol" - android:visibility="gone"/> - <TextView - android:id="@+id/header_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:visibility="gone" - android:singleLine="true"/> - <TextView - android:id="@+id/time_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:text="@string/notification_header_divider_symbol" - android:singleLine="true" - android:visibility="gone"/> - <DateTimeView - android:id="@+id/time" - android:textAppearance="@style/TextAppearance.Material.Notification.Time" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:showRelative="true" - android:singleLine="true" - android:visibility="gone" /> - <ViewStub - android:id="@+id/chronometer" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:layout="@layout/notification_template_part_chronometer" - android:visibility="gone" - /> + <include layout="@layout/notification_template_top_line" /> <com.android.internal.widget.NotificationExpandButton android:id="@+id/expand_button" android:background="@null" @@ -123,42 +49,5 @@ android:visibility="gone" android:contentDescription="@string/expand_button_content_description_collapsed" /> - <ImageView - android:id="@+id/alerted_icon" - android:layout_width="@dimen/notification_alerted_size" - android:layout_height="@dimen/notification_alerted_size" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:paddingTop="1dp" - android:scaleType="fitCenter" - android:visibility="gone" - android:contentDescription="@string/notification_alerted_content_description" - android:src="@drawable/ic_notifications_alerted" - /> - <ImageButton - android:id="@+id/feedback" - android:layout_width="@dimen/notification_feedback_size" - android:layout_height="@dimen/notification_feedback_size" - android:layout_marginStart="6dp" - android:layout_marginEnd="6dp" - android:paddingTop="2dp" - android:layout_gravity="center" - android:scaleType="fitCenter" - android:src="@drawable/ic_feedback_indicator" - android:background="?android:selectableItemBackgroundBorderless" - android:visibility="gone" - android:contentDescription="@string/notification_feedback_indicator" - /> - <ImageView - android:id="@+id/profile_badge" - android:layout_width="@dimen/notification_badge_size" - android:layout_height="@dimen/notification_badge_size" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:paddingTop="1dp" - android:scaleType="fitCenter" - android:visibility="gone" - android:contentDescription="@string/notification_work_profile_content_description" - /> </NotificationHeaderView> diff --git a/core/res/res/layout/notification_template_top_line.xml b/core/res/res/layout/notification_template_top_line.xml new file mode 100644 index 000000000000..27fab859a045 --- /dev/null +++ b/core/res/res/layout/notification_template_top_line.xml @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 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 + --> +<!-- extends ViewGroup --> +<NotificationTopLineView + xmlns:android="http://schemas.android.com/apk/res/android" + android:theme="@style/Theme.DeviceDefault.Notification" + android:id="@+id/notification_top_line" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_header_height" + android:clipChildren="false" + > + <TextView + android:id="@+id/app_name_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:layout_marginStart="@dimen/notification_header_app_name_margin_start" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:visibility="?attr/notificationHeaderAppNameVisibility" + android:singleLine="true" + /> + <TextView + android:id="@+id/header_text_secondary_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:visibility="gone"/> + <TextView + android:id="@+id/header_text_secondary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:visibility="gone" + android:singleLine="true"/> + <TextView + android:id="@+id/header_text_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:visibility="gone"/> + <TextView + android:id="@+id/header_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:visibility="gone" + android:singleLine="true"/> + <TextView + android:id="@+id/time_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?attr/notificationHeaderTextAppearance" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:singleLine="true" + android:visibility="gone"/> + <DateTimeView + android:id="@+id/time" + android:textAppearance="@style/TextAppearance.Material.Notification.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:showRelative="true" + android:singleLine="true" + android:visibility="gone" /> + <ViewStub + android:id="@+id/chronometer" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:layout="@layout/notification_template_part_chronometer" + android:visibility="gone" + /> + <ImageView + android:id="@+id/alerted_icon" + android:layout_width="@dimen/notification_alerted_size" + android:layout_height="@dimen/notification_alerted_size" + android:layout_gravity="center" + android:layout_marginStart="4dp" + android:paddingTop="1dp" + android:scaleType="fitCenter" + android:visibility="gone" + android:contentDescription="@string/notification_alerted_content_description" + android:src="@drawable/ic_notifications_alerted" + /> + <ImageButton + android:id="@+id/feedback" + android:layout_width="@dimen/notification_feedback_size" + android:layout_height="@dimen/notification_feedback_size" + android:layout_marginStart="6dp" + android:layout_marginEnd="6dp" + android:paddingTop="2dp" + android:layout_gravity="center" + android:scaleType="fitCenter" + android:src="@drawable/ic_feedback_indicator" + android:background="?android:selectableItemBackgroundBorderless" + android:visibility="gone" + android:contentDescription="@string/notification_feedback_indicator" + /> + <ImageView + android:id="@+id/profile_badge" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" + android:layout_gravity="center" + android:layout_marginStart="4dp" + android:paddingTop="1dp" + android:scaleType="fitCenter" + android:visibility="gone" + android:contentDescription="@string/notification_work_profile_content_description" + /> +</NotificationTopLineView> + diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index e6ebd4b543c5..84e7d42f2ed9 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -207,6 +207,9 @@ <!-- Default padding for dialogs. --> <dimen name="dialog_padding">16dp</dimen> + <!-- The horizontal margin of the content in the notification shade --> + <dimen name="notification_shade_content_margin_horizontal">16dp</dimen> + <!-- The margin on the start of the content view --> <dimen name="notification_content_margin_start">16dp</dimen> @@ -857,4 +860,9 @@ <dimen name="waterfall_display_top_edge_size">0px</dimen> <dimen name="waterfall_display_right_edge_size">0px</dimen> <dimen name="waterfall_display_bottom_edge_size">0px</dimen> + + <!-- The maximum height of a thumbnail in a ThumbnailTemplate. The image will be reduced to that height in case they are bigger. --> + <dimen name="controls_thumbnail_image_max_height">140dp</dimen> + <!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.--> + <dimen name="controls_thumbnail_image_max_width">280dp</dimen> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 94397d661af0..697f7e0e898a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2852,6 +2852,7 @@ <java-symbol type="id" name="header_text_secondary" /> <java-symbol type="id" name="expand_button" /> <java-symbol type="id" name="notification_header" /> + <java-symbol type="id" name="notification_top_line" /> <java-symbol type="id" name="time_divider" /> <java-symbol type="id" name="header_text_divider" /> <java-symbol type="id" name="header_text_secondary_divider" /> @@ -2862,6 +2863,7 @@ <java-symbol type="drawable" name="ic_collapse_bundle" /> <java-symbol type="dimen" name="notification_min_content_height" /> <java-symbol type="dimen" name="notification_header_shrink_min_width" /> + <java-symbol type="dimen" name="notification_shade_content_margin_horizontal" /> <java-symbol type="dimen" name="notification_content_margin_start" /> <java-symbol type="dimen" name="notification_content_margin_end" /> <java-symbol type="dimen" name="notification_reply_inset" /> @@ -4079,4 +4081,7 @@ <java-symbol type="array" name="config_keep_warming_services" /> <java-symbol type="string" name="config_display_features" /> <java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" /> + + <java-symbol type="dimen" name="controls_thumbnail_image_max_height" /> + <java-symbol type="dimen" name="controls_thumbnail_image_max_width" /> </resources> diff --git a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java index f4ebe2f9a755..63e464240630 100644 --- a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java +++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java @@ -32,6 +32,8 @@ import android.content.Context; import android.content.IIntentSender; import android.content.Intent; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.Icon; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -39,11 +41,14 @@ import android.os.RemoteException; import android.service.controls.actions.CommandAction; import android.service.controls.actions.ControlAction; import android.service.controls.actions.ControlActionWrapper; +import android.service.controls.templates.ThumbnailTemplate; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -107,7 +112,8 @@ public class ControlProviderServiceTest { mPendingIntent = new PendingIntent(mIIntentSender); - mControlsProviderService = new FakeControlsProviderService(); + mControlsProviderService = new FakeControlsProviderService( + InstrumentationRegistry.getInstrumentation().getContext()); mControlsProvider = IControlsProvider.Stub.asInterface( mControlsProviderService.onBind(intent)); } @@ -134,7 +140,8 @@ public class ControlProviderServiceTest { verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture()); subscriptionCaptor.getValue().request(1000); - verify(mSubscriber, times(2)).onNext(eq(mToken), controlCaptor.capture()); + verify(mSubscriber, times(2)) + .onNext(eq(mToken), controlCaptor.capture()); List<Control> values = controlCaptor.getAllValues(); assertTrue(equals(values.get(0), list.get(0))); assertTrue(equals(values.get(1), list.get(1))); @@ -210,26 +217,69 @@ public class ControlProviderServiceTest { .setStatus(Control.STATUS_OK) .build(); - @SuppressWarnings("unchecked") - ArgumentCaptor<Control> controlCaptor = - ArgumentCaptor.forClass(Control.class); - ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor = - ArgumentCaptor.forClass(IControlsSubscription.Stub.class); + Control c = sendControlGetControl(control); + assertTrue(equals(c, control)); + } - ArrayList<Control> list = new ArrayList<>(); - list.add(control); + @Test + public void testThumbnailRescaled_bigger() throws RemoteException { + Context context = mControlsProviderService.getBaseContext(); + int maxWidth = context.getResources().getDimensionPixelSize( + R.dimen.controls_thumbnail_image_max_width); + int maxHeight = context.getResources().getDimensionPixelSize( + R.dimen.controls_thumbnail_image_max_height); - mControlsProviderService.setControls(list); + int min = Math.min(maxWidth, maxHeight); + int max = Math.max(maxWidth, maxHeight); - mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + Bitmap bitmap = Bitmap.createBitmap(max * 2, max * 2, Bitmap.Config.ALPHA_8); + Icon icon = Icon.createWithBitmap(bitmap); + ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, ""); - verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture()); - subscriptionCaptor.getValue().request(1); + Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent) + .setTitle("TEST_TITLE") + .setStatus(Control.STATUS_OK) + .setControlTemplate(template) + .build(); - verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture()); - Control c = controlCaptor.getValue(); - assertTrue(equals(c, list.get(0))); + Control c = sendControlGetControl(control); + + ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate(); + Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap(); + + // Aspect ratio is kept + assertEquals(sentBitmap.getWidth(), sentBitmap.getHeight()); + + assertEquals(min, sentBitmap.getWidth()); + } + + @Test + public void testThumbnailRescaled_smaller() throws RemoteException { + Context context = mControlsProviderService.getBaseContext(); + int maxWidth = context.getResources().getDimensionPixelSize( + R.dimen.controls_thumbnail_image_max_width); + int maxHeight = context.getResources().getDimensionPixelSize( + R.dimen.controls_thumbnail_image_max_height); + + int min = Math.min(maxWidth, maxHeight); + + Bitmap bitmap = Bitmap.createBitmap(min / 2, min / 2, Bitmap.Config.ALPHA_8); + Icon icon = Icon.createWithBitmap(bitmap); + ThumbnailTemplate template = new ThumbnailTemplate("ID", false, icon, ""); + + Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent) + .setTitle("TEST_TITLE") + .setStatus(Control.STATUS_OK) + .setControlTemplate(template) + .build(); + + Control c = sendControlGetControl(control); + + ThumbnailTemplate sentTemplate = (ThumbnailTemplate) c.getControlTemplate(); + Bitmap sentBitmap = sentTemplate.getThumbnail().getBitmap(); + + assertEquals(bitmap.getHeight(), sentBitmap.getHeight()); + assertEquals(bitmap.getWidth(), sentBitmap.getWidth()); } @Test @@ -257,6 +307,32 @@ public class ControlProviderServiceTest { intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL))); } + /** + * Sends the control through the publisher in {@code mControlsProviderService}, returning + * the control obtained by the subscriber + */ + private Control sendControlGetControl(Control control) throws RemoteException { + @SuppressWarnings("unchecked") + ArgumentCaptor<Control> controlCaptor = + ArgumentCaptor.forClass(Control.class); + ArgumentCaptor<IControlsSubscription.Stub> subscriptionCaptor = + ArgumentCaptor.forClass(IControlsSubscription.Stub.class); + + ArrayList<Control> list = new ArrayList<>(); + list.add(control); + + mControlsProviderService.setControls(list); + + mControlsProvider.subscribe(new ArrayList<String>(), mSubscriber); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + verify(mSubscriber).onSubscribe(eq(mToken), subscriptionCaptor.capture()); + subscriptionCaptor.getValue().request(1); + + verify(mSubscriber).onNext(eq(mToken), controlCaptor.capture()); + return controlCaptor.getValue(); + } + private static boolean equals(Control c1, Control c2) { if (c1 == c2) return true; if (c1 == null || c2 == null) return false; @@ -276,6 +352,11 @@ public class ControlProviderServiceTest { static class FakeControlsProviderService extends ControlsProviderService { + FakeControlsProviderService(Context context) { + super(); + attachBaseContext(context); + } + private List<Control> mControls; public void setControls(List<Control> controls) { diff --git a/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java index 87dc1b7c83d5..91a3ba7d0e74 100644 --- a/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java +++ b/core/tests/coretests/src/android/service/controls/templates/ControlTemplateTest.java @@ -103,6 +103,17 @@ public class ControlTemplateTest { } @Test + public void testUnparcelingCorrectClass_thumbnail() { + ControlTemplate toParcel = new ThumbnailTemplate(TEST_ID, false, mIcon, + TEST_ACTION_DESCRIPTION); + + ControlTemplate fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlTemplate.TYPE_THUMBNAIL, fromParcel.getTemplateType()); + assertTrue(fromParcel instanceof ThumbnailTemplate); + } + + @Test public void testUnparcelingCorrectClass_toggleRange() { ControlTemplate toParcel = new ToggleRangeTemplate(TEST_ID, mControlButton, new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f")); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index a430dcdda247..e51bf5e4e1f6 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2923,6 +2923,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/TaskDisplayArea.java" }, + "1396893178": { + "message": "createRootTask unknown displayId=%d", + "level": "ERROR", + "group": "WM_DEBUG_WINDOW_ORGANIZER", + "at": "com\/android\/server\/wm\/TaskOrganizerController.java" + }, "1401295262": { "message": "Mode default, asking user", "level": "WARN", diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json index f8db4477d87a..a4e69de97299 100644 --- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json +++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json @@ -37,6 +37,18 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, + "-1325223370": { + "message": "Task appeared taskId=%d listener=%s", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" + }, + "-1312360667": { + "message": "createRootTask() displayId=%d winMode=%d listener=%s", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" + }, "-1006733970": { "message": "Display added: %d", "level": "VERBOSE", @@ -55,6 +67,18 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, + "-848099324": { + "message": "Letterbox Task Appeared: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" + }, + "-842742255": { + "message": "%s onTaskAppeared unknown taskId=%d winMode=%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java" + }, "-712674749": { "message": "Clip description: %s", "level": "VERBOSE", @@ -67,11 +91,11 @@ "group": "WM_SHELL_DRAG_AND_DROP", "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java" }, - "-460572385": { - "message": "Task appeared taskId=%d", + "-679492476": { + "message": "%s onTaskAppeared Primary taskId=%d", "level": "VERBOSE", "group": "WM_SHELL_TASK_ORG", - "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" + "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java" }, "-191422040": { "message": "Transition animations finished, notifying core %s", @@ -79,6 +103,12 @@ "group": "WM_SHELL_TRANSITIONS", "at": "com\/android\/wm\/shell\/Transitions.java" }, + "154313206": { + "message": "%s onTaskAppeared Secondary taskId=%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java" + }, "157713005": { "message": "Task info changed taskId=%d", "level": "VERBOSE", @@ -115,12 +145,24 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, + "1104702476": { + "message": "Letterbox Task Changed: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" + }, "1184615936": { "message": "Set drop target window visibility: displayId=%d visibility=%d", "level": "VERBOSE", "group": "WM_SHELL_DRAG_AND_DROP", "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java" }, + "1218010718": { + "message": "Letterbox Task Vanished: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" + }, "1481772149": { "message": "Current target: %s", "level": "VERBOSE", @@ -144,6 +186,12 @@ "level": "VERBOSE", "group": "WM_SHELL_DRAG_AND_DROP", "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java" + }, + "2135461748": { + "message": "%s onTaskAppeared Supported", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java" } }, "groups": { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java index 5bd693a9311e..fc0a76e8d286 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -20,9 +20,7 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCR import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; import android.app.ActivityManager; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.view.SurfaceControl; @@ -39,7 +37,7 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { private final SyncTransactionQueue mSyncQueue; - private final ArrayMap<Integer, SurfaceControl> mTasks = new ArrayMap<>(); + private final ArraySet<Integer> mTasks = new ArraySet<>(); FullscreenTaskListener(SyncTransactionQueue syncQueue) { mSyncQueue = syncQueue; @@ -48,17 +46,17 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mTasks) { - if (mTasks.containsKey(taskInfo.taskId)) { + if (mTasks.contains(taskInfo.taskId)) { throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId); } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); - mTasks.put(taskInfo.taskId, leash); + mTasks.add(taskInfo.taskId); mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). - updateSurfacePosition(t, taskInfo, leash); t.setWindowCrop(leash, null); + t.setPosition(leash, 0, 0); // TODO(shell-transitions): Eventually set everything in transition so there's no // SF Transaction here. if (!Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -73,7 +71,7 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { synchronized (mTasks) { - if (mTasks.remove(taskInfo.taskId) == null) { + if (!mTasks.remove(taskInfo.taskId)) { Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); return; } @@ -83,23 +81,6 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { } @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - synchronized (mTasks) { - if (!mTasks.containsKey(taskInfo.taskId)) { - Slog.e(TAG, "Changed Task wasn't appeared or already vanished: #" - + taskInfo.taskId); - return; - } - final SurfaceControl leash = mTasks.get(taskInfo.taskId); - mSyncQueue.runInSync(t -> { - // Reposition the task in case the bounds has been changed (such as Task level - // letterboxing). - updateSurfacePosition(t, taskInfo, leash); - }); - } - } - - @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; @@ -112,12 +93,4 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); } - /** Places the Task surface to the latest position. */ - private static void updateSurfacePosition(SurfaceControl.Transaction t, - ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - // TODO(170725334) drop this after ag/12876439 - final Configuration config = taskInfo.getConfiguration(); - final Rect bounds = config.windowConfiguration.getBounds(); - t.setPosition(leash, bounds.left, bounds.top); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java new file mode 100644 index 000000000000..9010c2088c34 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 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. + */ + +package com.android.wm.shell; + +import android.app.ActivityManager; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Slog; +import android.util.SparseArray; +import android.view.SurfaceControl; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +/** + * Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when + * it's presented in the letterbox mode either because orientations of a top activity and a device + * don't match or because a top activity is in a size compat mode. + */ +final class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = "LetterboxTaskListener"; + + private final SyncTransactionQueue mSyncQueue; + + private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>(); + + LetterboxTaskListener(SyncTransactionQueue syncQueue) { + mSyncQueue = syncQueue; + } + + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + synchronized (mLeashByTaskId) { + if (mLeashByTaskId.get(taskInfo.taskId) != null) { + throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d", + taskInfo.taskId); + mLeashByTaskId.put(taskInfo.taskId, leash); + final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + final Rect activtyBounds = taskInfo.letterboxActivityBounds; + final Point taskPositionInParent = taskInfo.positionInParent; + mSyncQueue.runInSync(t -> { + setPositionAndWindowCrop( + t, leash, activtyBounds, taskBounds, taskPositionInParent); + if (!Transitions.ENABLE_SHELL_TRANSITIONS) { + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + } + }); + } + } + + @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + synchronized (mLeashByTaskId) { + if (mLeashByTaskId.get(taskInfo.taskId) == null) { + Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); + return; + } + mLeashByTaskId.remove(taskInfo.taskId); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d", + taskInfo.taskId); + } + } + + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + synchronized (mLeashByTaskId) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d", + taskInfo.taskId); + final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); + final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + final Rect activtyBounds = taskInfo.letterboxActivityBounds; + final Point taskPositionInParent = taskInfo.positionInParent; + mSyncQueue.runInSync(t -> { + setPositionAndWindowCrop( + t, leash, activtyBounds, taskBounds, taskPositionInParent); + }); + } + } + + private static void setPositionAndWindowCrop( + SurfaceControl.Transaction transaction, + SurfaceControl leash, + final Rect activityBounds, + final Rect taskBounds, + final Point taskPositionInParent) { + Rect activtyInTaskCoordinates = new Rect(activityBounds); + activtyInTaskCoordinates.offset(-taskBounds.left, -taskBounds.top); + transaction.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); + transaction.setWindowCrop(leash, activtyInTaskCoordinates); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index cbc1c8d6d310..d4ff275d426d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -20,8 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; @@ -29,6 +27,7 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG import android.annotation.IntDef; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration.WindowingMode; +import android.os.Binder; import android.os.IBinder; import android.util.ArrayMap; import android.util.Log; @@ -45,7 +44,6 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; import java.util.ArrayList; @@ -64,14 +62,14 @@ public class ShellTaskOrganizer extends TaskOrganizer { public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2; public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3; public static final int TASK_LISTENER_TYPE_PIP = -4; - public static final int TASK_LISTENER_TYPE_SPLIT_SCREEN = -5; + public static final int TASK_LISTENER_TYPE_LETTERBOX = -5; @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = { TASK_LISTENER_TYPE_UNDEFINED, TASK_LISTENER_TYPE_FULLSCREEN, TASK_LISTENER_TYPE_MULTI_WINDOW, TASK_LISTENER_TYPE_PIP, - TASK_LISTENER_TYPE_SPLIT_SCREEN, + TASK_LISTENER_TYPE_LETTERBOX, }) public @interface TaskListenerType {} @@ -118,6 +116,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { ShellExecutor mainExecutor, ShellExecutor animExecutor) { super(taskOrganizerController, mainExecutor); addListenerForType(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN); + addListenerForType(new LetterboxTaskListener(syncQueue), TASK_LISTENER_TYPE_LETTERBOX); mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor); if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); } @@ -137,6 +136,14 @@ public class ShellTaskOrganizer extends TaskOrganizer { } } + public void createRootTask(int displayId, int windowingMode, TaskListener listener) { + ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s", + displayId, windowingMode, listener.toString()); + final IBinder cookie = new Binder(); + setPendingLaunchCookieListener(cookie, listener); + super.createRootTask(displayId, windowingMode, cookie); + } + /** * Adds a listener for a specific task id. */ @@ -236,10 +243,10 @@ public class ShellTaskOrganizer extends TaskOrganizer { private void onTaskAppeared(TaskAppearedInfo info) { final int taskId = info.getTaskInfo().taskId; - ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d", taskId); mTasks.put(taskId, info); final TaskListener listener = getTaskListener(info.getTaskInfo(), true /*removeLaunchCookieIfNeeded*/); + ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d listener=%s", taskId, listener); if (listener != null) { listener.onTaskAppeared(info.getTaskInfo(), info.getLeash()); } @@ -329,26 +336,25 @@ public class ShellTaskOrganizer extends TaskOrganizer { if (listener != null) return listener; // Next we try type specific listeners. - final int windowingMode = getWindowingMode(runningTaskInfo); - final int taskListenerType = windowingModeToTaskListenerType(windowingMode); + final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo); return mTaskListeners.get(taskListenerType); } @WindowingMode - private static int getWindowingMode(RunningTaskInfo taskInfo) { + public static int getWindowingMode(RunningTaskInfo taskInfo) { return taskInfo.configuration.windowConfiguration.getWindowingMode(); } - private static @TaskListenerType int windowingModeToTaskListenerType( - @WindowingMode int windowingMode) { + @VisibleForTesting + static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) { + final int windowingMode = getWindowingMode(runningTaskInfo); switch (windowingMode) { case WINDOWING_MODE_FULLSCREEN: - return TASK_LISTENER_TYPE_FULLSCREEN; + return runningTaskInfo.letterboxActivityBounds != null + ? TASK_LISTENER_TYPE_LETTERBOX + : TASK_LISTENER_TYPE_FULLSCREEN; case WINDOWING_MODE_MULTI_WINDOW: return TASK_LISTENER_TYPE_MULTI_WINDOW; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: - return TASK_LISTENER_TYPE_SPLIT_SCREEN; case WINDOWING_MODE_PINNED: return TASK_LISTENER_TYPE_PIP; case WINDOWING_MODE_FREEFORM: @@ -362,10 +368,10 @@ public class ShellTaskOrganizer extends TaskOrganizer { switch (type) { case TASK_LISTENER_TYPE_FULLSCREEN: return "TASK_LISTENER_TYPE_FULLSCREEN"; + case TASK_LISTENER_TYPE_LETTERBOX: + return "TASK_LISTENER_TYPE_LETTERBOX"; case TASK_LISTENER_TYPE_MULTI_WINDOW: return "TASK_LISTENER_TYPE_MULTI_WINDOW"; - case TASK_LISTENER_TYPE_SPLIT_SCREEN: - return "TASK_LISTENER_TYPE_SPLIT_SCREEN"; case TASK_LISTENER_TYPE_PIP: return "TASK_LISTENER_TYPE_PIP"; case TASK_LISTENER_TYPE_UNDEFINED: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 9b4524a12161..a05aac9dfe3e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -772,6 +772,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (animator == null || !animator.isRunning() || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) { if (mState.isInPip() && fromRotation) { + // Update bounds state to final destination first. It's important to do this + // before finishing & cancelling the transition animation so that the MotionHelper + // bounds are synchronized to the destination bounds when the animation ends. + mPipBoundsState.setBounds(destinationBoundsOut); // If we are rotating while there is a current animation, immediately cancel the // animation (remove the listeners so we don't trigger the normal finish resize // call that should only happen on the update thread) @@ -785,7 +789,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, sendOnPipTransitionCancelled(direction); sendOnPipTransitionFinished(direction); } - mPipBoundsState.setBounds(destinationBoundsOut); // Create a reset surface transaction for the new bounds and update the window // container transaction diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java index ff617ed466d1..eb82357f2dea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerImeController.java @@ -40,7 +40,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor private static final float ADJUSTED_NONFOCUS_DIM = 0.3f; - private final SplitScreenTaskOrganizer mSplits; + private final SplitScreenTaskListener mSplits; private final TransactionPool mTransactionPool; private final Handler mHandler; private final TaskOrganizer mTaskOrganizer; @@ -92,7 +92,7 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor private boolean mPausedTargetAdjusted = false; private boolean mAdjustedWhileHidden = false; - DividerImeController(SplitScreenTaskOrganizer splits, TransactionPool pool, Handler handler, + DividerImeController(SplitScreenTaskListener splits, TransactionPool pool, Handler handler, TaskOrganizer taskOrganizer) { mSplits = splits; mTransactionPool = pool; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java index 2b14e8bf88d6..c6496ad6246f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java @@ -155,7 +155,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, private boolean mAdjustedForIme; private DividerState mState; - private SplitScreenTaskOrganizer mTiles; + private SplitScreenTaskListener mTiles; boolean mFirstLayout = true; int mDividerPositionX; int mDividerPositionY; @@ -354,7 +354,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, void injectDependencies(SplitScreenController splitScreenController, DividerWindowManager windowManager, DividerState dividerState, - DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl, + DividerCallbacks callback, SplitScreenTaskListener tiles, SplitDisplayLayout sdl, DividerImeController imeController, WindowManagerProxy wmProxy) { mSplitScreenController = splitScreenController; mWindowManager = windowManager; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java index 3c0f93906795..7d5e1a84a38d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitDisplayLayout.java @@ -47,7 +47,7 @@ public class SplitDisplayLayout { private static final int DIVIDER_WIDTH_INACTIVE_DP = 4; - SplitScreenTaskOrganizer mTiles; + SplitScreenTaskListener mTiles; DisplayLayout mDisplayLayout; Context mContext; @@ -62,7 +62,7 @@ public class SplitDisplayLayout { Rect mAdjustedPrimary = null; Rect mAdjustedSecondary = null; - public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskOrganizer taskTiles) { + public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskListener taskTiles) { mTiles = taskTiles; mDisplayLayout = dl; mContext = ctx; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 43e4d62baaf6..69d428a3ae1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -75,7 +75,7 @@ public class SplitScreenController implements SplitScreen, private final DividerState mDividerState = new DividerState(); private final ForcedResizableInfoActivityController mForcedResizableController; private final Handler mHandler; - private final SplitScreenTaskOrganizer mSplits; + private final SplitScreenTaskListener mSplits; private final SystemWindows mSystemWindows; final TransactionPool mTransactionPool; private final WindowManagerProxy mWindowManagerProxy; @@ -117,7 +117,7 @@ public class SplitScreenController implements SplitScreen, mTransactionPool = transactionPool; mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer); mTaskOrganizer = shellTaskOrganizer; - mSplits = new SplitScreenTaskOrganizer(this, shellTaskOrganizer); + mSplits = new SplitScreenTaskListener(this, shellTaskOrganizer); mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler, shellTaskOrganizer); mRotationController = @@ -164,6 +164,14 @@ public class SplitScreenController implements SplitScreen, // Don't initialize the divider or anything until we get the default display. } + void onSplitScreenSupported() { + // Set starting tile bounds based on middle target + final WindowContainerTransaction tct = new WindowContainerTransaction(); + int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; + mSplitLayout.resizeSplits(midPos, tct); + mTaskOrganizer.applyTransaction(tct); + } + @Override public boolean isSplitScreenSupported() { return mSplits.isSplitScreenSupported(); @@ -196,11 +204,6 @@ public class SplitScreenController implements SplitScreen, } try { mSplits.init(); - // Set starting tile bounds based on middle target - final WindowContainerTransaction tct = new WindowContainerTransaction(); - int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; - mSplitLayout.resizeSplits(midPos, tct); - mTaskOrganizer.applyTransaction(tct); } catch (Exception e) { Slog.e(TAG, "Failed to register docked stack listener", e); removeDivider(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java index f763d6d714c4..191a317452e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java @@ -23,25 +23,24 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_SPLIT_SCREEN; -import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; +import static com.android.wm.shell.ShellTaskOrganizer.getWindowingMode; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager.RunningTaskInfo; import android.graphics.Rect; -import android.os.RemoteException; import android.util.Log; -import android.view.Display; import android.view.SurfaceControl; import android.view.SurfaceSession; import androidx.annotation.NonNull; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import java.io.PrintWriter; -class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener { - private static final String TAG = "SplitScreenTaskOrg"; +class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = "SplitScreenTaskListener"; private static final boolean DEBUG = SplitScreenController.DEBUG; private final ShellTaskOrganizer mTaskOrganizer; @@ -58,20 +57,19 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener { final SurfaceSession mSurfaceSession = new SurfaceSession(); - SplitScreenTaskOrganizer(SplitScreenController splitScreenController, + SplitScreenTaskListener(SplitScreenController splitScreenController, ShellTaskOrganizer shellTaskOrganizer) { mSplitScreenController = splitScreenController; mTaskOrganizer = shellTaskOrganizer; - mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_SPLIT_SCREEN); } - void init() throws RemoteException { + void init() { synchronized (this) { try { - mPrimary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - mSecondary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + mTaskOrganizer.createRootTask( + DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, this); + mTaskOrganizer.createRootTask( + DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, this); } catch (Exception e) { // teardown to prevent callbacks mTaskOrganizer.removeListener(this); @@ -95,19 +93,26 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener { @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (this) { - if (mPrimary == null || mSecondary == null) { - Log.w(TAG, "Received onTaskAppeared before creating root tasks " + taskInfo); - return; - } - - if (taskInfo.token.equals(mPrimary.token)) { + final int winMode = getWindowingMode(taskInfo); + if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + ProtoLog.v(WM_SHELL_TASK_ORG, + "%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId); + mPrimary = taskInfo; mPrimarySurface = leash; - } else if (taskInfo.token.equals(mSecondary.token)) { + } else if (winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { + ProtoLog.v(WM_SHELL_TASK_ORG, + "%s onTaskAppeared Secondary taskId=%d", TAG, taskInfo.taskId); + mSecondary = taskInfo; mSecondarySurface = leash; + } else { + ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared unknown taskId=%d winMode=%d", + TAG, taskInfo.taskId, winMode); } if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) { mSplitScreenSupported = true; + mSplitScreenController.onSplitScreenSupported(); + ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG); // Initialize dim surfaces: mPrimaryDim = new SurfaceControl.Builder(mSurfaceSession) @@ -240,10 +245,13 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; pw.println(prefix + this); + pw.println(innerPrefix + "mSplitScreenSupported=" + mSplitScreenSupported); + if (mPrimary != null) pw.println(innerPrefix + "mPrimary.taskId=" + mPrimary.taskId); + if (mSecondary != null) pw.println(innerPrefix + "mSecondary.taskId=" + mSecondary.taskId); } @Override public String toString() { - return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_SPLIT_SCREEN); + return TAG; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java index 47e7c99d2268..c51bbeb7b6c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java @@ -88,7 +88,7 @@ class WindowManagerProxy { mTaskOrganizer = taskOrganizer; } - void dismissOrMaximizeDocked(final SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout, + void dismissOrMaximizeDocked(final SplitScreenTaskListener tiles, SplitDisplayLayout layout, final boolean dismissOrMaximize) { mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize)); } @@ -189,7 +189,7 @@ class WindowManagerProxy { * * @return whether the home stack is resizable */ - boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) { + boolean applyEnterSplit(SplitScreenTaskListener tiles, SplitDisplayLayout layout) { // Set launchtile first so that any stack created after // getAllRootTaskInfos and before reparent (even if unlikely) are placed // correctly. @@ -242,7 +242,7 @@ class WindowManagerProxy { * split (thus resulting in the top of the secondary split becoming * fullscreen. {@code false} resolves the other way. */ - void applyDismissSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout, + void applyDismissSplit(SplitScreenTaskListener tiles, SplitDisplayLayout layout, boolean dismissOrMaximize) { // Set launch root first so that any task created after getChildContainers and // before reparent (pretty unlikely) are put into fullscreen. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java new file mode 100644 index 000000000000..45d4d5d347dd --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 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. + */ + +package com.android.wm.shell; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.mockito.ArgumentMatchers.eq; + +import android.app.ActivityManager.RunningTaskInfo; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.view.SurfaceControl; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.TransactionPool; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link LetterboxTaskListener}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LetterboxTaskListenerTest { + + private static final Rect ACTIVITY_BOUNDS = new Rect(300, 200, 700, 400); + private static final Rect TASK_BOUNDS = new Rect(200, 100, 800, 500); + private static final Rect TASK_BOUNDS_2 = new Rect(300, 200, 800, 500); + private static final Point TASK_POSITION_IN_PARENT = new Point(100, 50); + private static final Point TASK_POSITION_IN_PARENT_2 = new Point(200, 100); + + private static final Rect EXPECTED_WINDOW_CROP = new Rect(100, 100, 500, 300); + private static final Rect EXPECTED_WINDOW_CROP_2 = new Rect(0, 0, 400, 200); + + private static final RunningTaskInfo TASK_INFO = createTaskInfo( + /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS, TASK_POSITION_IN_PARENT); + + private static final RunningTaskInfo TASK_INFO_2 = createTaskInfo( + /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS_2, TASK_POSITION_IN_PARENT_2); + + @Mock private SurfaceControl mLeash; + @Mock private SurfaceControl.Transaction mTransaction; + private LetterboxTaskListener mLetterboxTaskListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLetterboxTaskListener = new LetterboxTaskListener( + new SyncTransactionQueue( + new TransactionPool() { + @Override + public SurfaceControl.Transaction acquire() { + return mTransaction; + } + + @Override + public void release(SurfaceControl.Transaction t) { + } + }, + new Handler(Looper.getMainLooper()))); + } + + @Test + public void testOnTaskAppearedAndonTaskInfoChanged_setCorrectPositionAndCrop() { + mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); + + verify(mTransaction).setPosition( + eq(mLeash), + eq((float) TASK_POSITION_IN_PARENT.x), + eq((float) TASK_POSITION_IN_PARENT.y)); + // Should return activty coordinates offset by task coordinates + verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP)); + + mLetterboxTaskListener.onTaskInfoChanged(TASK_INFO_2); + + verify(mTransaction).setPosition( + eq(mLeash), + eq((float) TASK_POSITION_IN_PARENT_2.x), + eq((float) TASK_POSITION_IN_PARENT_2.y)); + // Should return activty coordinates offset by task coordinates + verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP_2)); + } + + @Test(expected = RuntimeException.class) + public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() { + mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); + mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); + } + + private static RunningTaskInfo createTaskInfo( + int taskId, + final Rect activityBounds, + final Rect taskBounds, + final Point taskPositionInParent) { + RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setBounds(taskBounds); + taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds); + taskInfo.positionInParent = new Point(taskPositionInParent); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 07a6bda239c7..35a2293cbf13 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -16,15 +16,18 @@ package com.android.wm.shell; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW; -import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP; - import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERBOX; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -34,6 +37,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; import android.content.pm.ParceledListSlice; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -42,6 +46,7 @@ import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; import android.window.TaskAppearedInfo; +import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -242,10 +247,41 @@ public class ShellTaskOrganizerTests { assertTrue(gotException); } - private RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { + @Test + public void testTaskInfoToTaskListenerType_whenLetterboxBoundsPassed_returnsLetterboxType() { + RunningTaskInfo taskInfo = createTaskInfo( + /* taskId */ 1, + WINDOWING_MODE_FULLSCREEN, + /* letterboxActivityBounds */ new Rect(1, 1, 1, 1)); + + assertEquals( + ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo), + TASK_LISTENER_TYPE_LETTERBOX); + } + + @Test + public void testTaskInfoToTaskListenerType_whenLetterboxBoundsIsNull_returnsFullscreenType() { + RunningTaskInfo taskInfo = createTaskInfo( + /* taskId */ 1, WINDOWING_MODE_FULLSCREEN, /* letterboxActivityBounds */ null); + + assertEquals( + ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo), + TASK_LISTENER_TYPE_FULLSCREEN); + } + + private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { + RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); + return taskInfo; + } + + private static RunningTaskInfo createTaskInfo( + int taskId, int windowingMode, @Nullable Rect letterboxActivityBounds) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); + taskInfo.letterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds); return taskInfo; } } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 42cf53b44f1e..3905e0b2b878 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -52,9 +52,11 @@ interface ILocationManager void registerLocationListener(String provider, in LocationRequest request, in ILocationListener listener, String packageName, String attributionTag, String listenerId); void unregisterLocationListener(in ILocationListener listener); - void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent intent, String packageName, String attributionTag); + void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent pendingIntent, String packageName, String attributionTag); void unregisterLocationPendingIntent(in PendingIntent intent); + void injectLocation(in Location location); + void requestGeofence(in Geofence geofence, in PendingIntent intent, String packageName, String attributionTag); void removeGeofence(in PendingIntent intent); @@ -89,7 +91,6 @@ interface ILocationManager void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String attributionTag); void flushGnssBatch(); void stopGnssBatch(); - void injectLocation(in Location location); List<String> getAllProviders(); List<String> getProviders(in Criteria criteria, boolean enabledOnly); diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java index 2738ff4ff38c..0ff0a723237b 100644 --- a/location/java/android/location/LocationListener.java +++ b/location/java/android/location/LocationListener.java @@ -19,12 +19,11 @@ package android.location; import android.annotation.NonNull; import android.os.Bundle; +import java.util.concurrent.Executor; + /** - * Used for receiving notifications from the LocationManager when - * the location has changed. These methods are called if the - * LocationListener has been registered with the location manager service - * using the {@link LocationManager#requestLocationUpdates(String, long, float, LocationListener)} - * method. + * Used for receiving notifications when the device location has changed. These methods are called + * when the listener has been registered with the LocationManager. * * <div class="special reference"> * <h3>Developer Guides</h3> @@ -32,6 +31,8 @@ import android.os.Bundle; * <a href="{@docRoot}guide/topics/location/obtaining-user-location.html">Obtaining User * Location</a> developer guide.</p> * </div> + * + * @see LocationManager#requestLocationUpdates(String, LocationRequest, Executor, LocationListener) */ public interface LocationListener { diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index ac775ca05cab..3493693ac67e 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -216,15 +216,15 @@ public class LocationManager { * Key used for an extra holding a boolean enabled/disabled status value when a provider * enabled/disabled event is broadcast using a PendingIntent. * - * @see #requestLocationUpdates(String, long, float, PendingIntent) + * @see #requestLocationUpdates(String, LocationRequest, PendingIntent) */ public static final String KEY_PROVIDER_ENABLED = "providerEnabled"; /** - * Key used for an extra holding a {@link Location} value when a location change is broadcast - * using a PendingIntent. + * Key used for an extra holding a {@link Location} value when a location change is sent using + * a PendingIntent. * - * @see #requestLocationUpdates(String, long, float, PendingIntent) + * @see #requestLocationUpdates(String, LocationRequest, PendingIntent) */ public static final String KEY_LOCATION_CHANGED = "location"; @@ -1322,27 +1322,26 @@ public class LocationManager { Preconditions.checkArgument(provider != null, "invalid null provider"); Preconditions.checkArgument(locationRequest != null, "invalid null location request"); - synchronized (sLocationListeners) { - WeakReference<LocationListenerTransport> reference = sLocationListeners.get(listener); - LocationListenerTransport transport = reference != null ? reference.get() : null; - if (transport == null) { - transport = new LocationListenerTransport(listener, executor); - sLocationListeners.put(listener, new WeakReference<>(transport)); - } else { - transport.setExecutor(executor); - } + try { + synchronized (sLocationListeners) { + WeakReference<LocationListenerTransport> reference = sLocationListeners.get( + listener); + LocationListenerTransport transport = reference != null ? reference.get() : null; + if (transport == null) { + transport = new LocationListenerTransport(listener, executor); + } else { + Preconditions.checkState(transport.isRegistered()); + transport.setExecutor(executor); + } - try { - // making the service call while under lock is less than ideal since LMS must - // make sure that callbacks are not made on the same thread - however it is the - // easiest way to guarantee that clients will not receive callbacks after - // unregistration is complete. mService.registerLocationListener(provider, locationRequest, transport, mContext.getPackageName(), mContext.getAttributionTag(), AppOpsManager.toReceiverId(listener)); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + + sLocationListeners.put(listener, new WeakReference<>(transport)); } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -1429,23 +1428,17 @@ public class LocationManager { public void removeUpdates(@NonNull LocationListener listener) { Preconditions.checkArgument(listener != null, "invalid null listener"); - synchronized (sLocationListeners) { - WeakReference<LocationListenerTransport> reference = sLocationListeners.remove( - listener); - LocationListenerTransport transport = reference != null ? reference.get() : null; - if (transport != null) { - transport.unregister(); - - try { - // making the service call while under lock is less than ideal since LMS must - // make sure that callbacks are not made on the same thread - however it is the - // easiest way to guarantee that clients will not receive callbacks after - // unregistration is complete. + try { + synchronized (sLocationListeners) { + WeakReference<LocationListenerTransport> ref = sLocationListeners.remove(listener); + LocationListenerTransport transport = ref != null ? ref.get() : null; + if (transport != null) { + transport.unregister(); mService.unregisterLocationListener(transport); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); } } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -2568,7 +2561,7 @@ public class LocationManager { @Nullable private volatile LocationListener mListener; LocationListenerTransport(LocationListener listener, Executor executor) { - Preconditions.checkArgument(listener != null, "invalid null listener/callback"); + Preconditions.checkArgument(listener != null, "invalid null listener"); mListener = listener; setExecutor(executor); } @@ -2578,6 +2571,10 @@ public class LocationManager { mExecutor = executor; } + boolean isRegistered() { + return mListener != null; + } + void unregister() { mListener = null; } diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java index 7a3a4b23d532..32ac374b33c2 100644 --- a/location/lib/java/com/android/location/provider/LocationProviderBase.java +++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java @@ -171,7 +171,9 @@ public abstract class LocationProviderBase { if (manager != null) { try { manager.onSetAllowed(mAllowed); - } catch (RemoteException | RuntimeException e) { + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { Log.w(mTag, e); } } @@ -191,7 +193,9 @@ public abstract class LocationProviderBase { if (manager != null) { try { manager.onSetProperties(mProperties); - } catch (RemoteException | RuntimeException e) { + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { Log.w(mTag, e); } } @@ -248,7 +252,9 @@ public abstract class LocationProviderBase { try { manager.onReportLocation(location); - } catch (RemoteException | RuntimeException e) { + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { Log.w(mTag, e); } } @@ -339,6 +345,8 @@ public abstract class LocationProviderBase { manager.onSetProperties(mProperties); manager.onSetAllowed(mAllowed); } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { Log.w(mTag, e); } diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index a98ea695eaf1..8adedf5779d1 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -97,6 +97,7 @@ package android { field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS"; field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE"; + field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS"; field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS"; field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; @@ -17846,6 +17847,7 @@ package android.hardware.camera2 { public class CaptureResult extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureResult.Key<?>> { method @Nullable public <T> T get(android.hardware.camera2.CaptureResult.Key<T>); + method @NonNull public String getCameraId(); method public long getFrameNumber(); method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getKeys(); method @NonNull public android.hardware.camera2.CaptureRequest getRequest(); @@ -37407,6 +37409,9 @@ package android.provider { ctor public CallLog.Calls(); method public static String getLastOutgoingCall(android.content.Context); field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7 + field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L + field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L + field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L field public static final int BLOCKED_TYPE = 6; // 0x6 field public static final String BLOCK_REASON = "block_reason"; field public static final int BLOCK_REASON_BLOCKED_NUMBER = 3; // 0x3 @@ -37452,6 +37457,8 @@ package android.provider { field public static final String IS_READ = "is_read"; field public static final String LAST_MODIFIED = "last_modified"; field public static final String LIMIT_PARAM_KEY = "limit"; + field public static final String MISSED_REASON = "missed_reason"; + field public static final long MISSED_REASON_NOT_MISSED = 0L; // 0x0L field public static final int MISSED_TYPE = 3; // 0x3 field public static final String NEW = "new"; field public static final String NUMBER = "number"; @@ -37468,6 +37475,13 @@ package android.provider { field public static final int REJECTED_TYPE = 5; // 0x5 field public static final String TRANSCRIPTION = "transcription"; field public static final String TYPE = "type"; + field public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 4194304L; // 0x400000L + field public static final long USER_MISSED_CALL_SCREENING_SERVICE_SILENCED = 2097152L; // 0x200000L + field public static final long USER_MISSED_DND_MODE = 262144L; // 0x40000L + field public static final long USER_MISSED_LOW_RING_VOLUME = 524288L; // 0x80000L + field public static final long USER_MISSED_NO_ANSWER = 65536L; // 0x10000L + field public static final long USER_MISSED_NO_VIBRATE = 1048576L; // 0x100000L + field public static final long USER_MISSED_SHORT_RING = 131072L; // 0x20000L field public static final String VIA_NUMBER = "via_number"; field public static final int VOICEMAIL_TYPE = 4; // 0x4 field public static final String VOICEMAIL_URI = "voicemail_uri"; @@ -41976,6 +41990,7 @@ package android.service.controls.templates { field public static final int TYPE_RANGE = 2; // 0x2 field public static final int TYPE_STATELESS = 8; // 0x8 field public static final int TYPE_TEMPERATURE = 7; // 0x7 + field public static final int TYPE_THUMBNAIL = 3; // 0x3 field public static final int TYPE_TOGGLE = 1; // 0x1 field public static final int TYPE_TOGGLE_RANGE = 6; // 0x6 } @@ -42015,6 +42030,14 @@ package android.service.controls.templates { field public static final int MODE_UNKNOWN = 0; // 0x0 } + public final class ThumbnailTemplate extends android.service.controls.templates.ControlTemplate { + ctor public ThumbnailTemplate(@NonNull String, boolean, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence); + method @NonNull public CharSequence getContentDescription(); + method public int getTemplateType(); + method @NonNull public android.graphics.drawable.Icon getThumbnail(); + method public boolean isActive(); + } + public final class ToggleRangeTemplate extends android.service.controls.templates.ControlTemplate { ctor public ToggleRangeTemplate(@NonNull String, @NonNull android.service.controls.templates.ControlButton, @NonNull android.service.controls.templates.RangeTemplate); ctor public ToggleRangeTemplate(@NonNull String, boolean, @NonNull CharSequence, @NonNull android.service.controls.templates.RangeTemplate); @@ -46009,6 +46032,7 @@ package android.telephony { method @NonNull public java.util.List<java.lang.Integer> getAvailableServices(); method @Nullable public android.telephony.CellIdentity getCellIdentity(); method public int getDomain(); + method public int getNrState(); method @Nullable public String getRegisteredPlmn(); method public int getTransportType(); method public boolean isRegistered(); diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index 79c82695e911..fe40892a959b 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -403,6 +403,7 @@ package android.app { field public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats"; field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage"; field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels"; + field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls"; field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone"; field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells"; field public static final String OPSTR_PLAY_AUDIO = "android:play_audio"; @@ -865,7 +866,6 @@ package android.app.admin { field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2 field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0 field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4 - field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5 field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2 field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3 field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1 @@ -10659,6 +10659,7 @@ package android.telephony { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int); + method public boolean isNrDualConnectivityEnabled(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String); @@ -10692,6 +10693,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean); + method public int setNrDualConnectivityState(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean); @@ -10731,6 +10733,11 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0 field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE"; @@ -10763,6 +10770,9 @@ package android.telephony { field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L + field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2 + field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3 + field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1 field public static final int RADIO_POWER_OFF = 0; // 0x0 field public static final int RADIO_POWER_ON = 1; // 0x1 field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2 diff --git a/packages/CarSystemUI/res/values-af/strings.xml b/packages/CarSystemUI/res/values-af/strings.xml index b377ec47f6c6..cf288d74d61c 100644 --- a/packages/CarSystemUI/res/values-af/strings.xml +++ b/packages/CarSystemUI/res/values-af/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Enige gebruiker kan programme vir al die ander gebruikers opdateer."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Laai tans"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Laai tans gebruiker (van <xliff:g id="FROM_USER">%1$d</xliff:g> na <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Maak toe"</string> </resources> diff --git a/packages/CarSystemUI/res/values-am/strings.xml b/packages/CarSystemUI/res/values-am/strings.xml index 4f2bba8aa1de..8281631312b7 100644 --- a/packages/CarSystemUI/res/values-am/strings.xml +++ b/packages/CarSystemUI/res/values-am/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"ማንኛውም ተጠቃሚ መተግበሪያዎችን ለሌሎች ተጠቃሚዎች ሁሉ ማዘመን ይችላል።"</string> <string name="car_loading_profile" msgid="4507385037552574474">"በመጫን ላይ"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ተጠቃሚን (ከ<xliff:g id="FROM_USER">%1$d</xliff:g> ወደ <xliff:g id="TO_USER">%2$d</xliff:g>) በመጫን ላይ"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ዝጋ"</string> </resources> diff --git a/packages/CarSystemUI/res/values-as/strings.xml b/packages/CarSystemUI/res/values-as/strings.xml index edc36215bcce..d8710555149b 100644 --- a/packages/CarSystemUI/res/values-as/strings.xml +++ b/packages/CarSystemUI/res/values-as/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"যিকোনো ব্যৱহাৰকাৰীয়ে অন্য ব্যৱহাৰকাৰীৰ বাবে এপ্সমূহ আপডে’ট কৰিব পাৰে।"</string> <string name="car_loading_profile" msgid="4507385037552574474">"ল’ড হৈ আছে"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ব্যৱহাৰকাৰী ল’ড হৈ আছে (<xliff:g id="FROM_USER">%1$d</xliff:g>ৰ পৰা to <xliff:g id="TO_USER">%2$d</xliff:g>লৈ)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"বন্ধ কৰক"</string> </resources> diff --git a/packages/CarSystemUI/res/values-az/strings.xml b/packages/CarSystemUI/res/values-az/strings.xml index 398f5c38c03e..89c9eb4ee258 100644 --- a/packages/CarSystemUI/res/values-az/strings.xml +++ b/packages/CarSystemUI/res/values-az/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"İstənilən istifadəçi digər bütün istifadəçilər üçün tətbiqləri güncəlləyə bilər."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Yüklənir"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"İstifadəçi yüklənir (<xliff:g id="FROM_USER">%1$d</xliff:g>-<xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Qapadın"</string> </resources> diff --git a/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml b/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml index 6c1979f7a95a..6aee01321ade 100644 --- a/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/CarSystemUI/res/values-b+sr+Latn/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Svaki korisnik može da ažurira aplikacije za sve ostale korisnike."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Učitava se"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Profil korisnika se učitava (iz<xliff:g id="FROM_USER">%1$d</xliff:g> u <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string> </resources> diff --git a/packages/CarSystemUI/res/values-be/strings.xml b/packages/CarSystemUI/res/values-be/strings.xml index 4e9794855e47..fde42732283f 100644 --- a/packages/CarSystemUI/res/values-be/strings.xml +++ b/packages/CarSystemUI/res/values-be/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Кожны карыстальнік прылады можа абнаўляць праграмы для ўсіх іншых карыстальнікаў."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Ідзе загрузка"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ідзе загрузка профілю карыстальніка (ад <xliff:g id="FROM_USER">%1$d</xliff:g> да <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрыць"</string> </resources> diff --git a/packages/CarSystemUI/res/values-bg/strings.xml b/packages/CarSystemUI/res/values-bg/strings.xml index 7dfab54b8add..25f284515b9f 100644 --- a/packages/CarSystemUI/res/values-bg/strings.xml +++ b/packages/CarSystemUI/res/values-bg/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Всеки потребител може да актуализира приложенията за всички останали потребители."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Зарежда се"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Потребителят се зарежда (от <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затваряне"</string> </resources> diff --git a/packages/CarSystemUI/res/values-bs/strings.xml b/packages/CarSystemUI/res/values-bs/strings.xml index 119f2d7bf793..588771e41740 100644 --- a/packages/CarSystemUI/res/values-bs/strings.xml +++ b/packages/CarSystemUI/res/values-bs/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Bilo koji korisnik može ažurirati aplikacije za sve druge korisnike."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Učitavanje"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Učitavanje korisnika (od korisnika <xliff:g id="FROM_USER">%1$d</xliff:g> do korisnika <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ca/strings.xml b/packages/CarSystemUI/res/values-ca/strings.xml index b1e722e95c58..cbd469b68a62 100644 --- a/packages/CarSystemUI/res/values-ca/strings.xml +++ b/packages/CarSystemUI/res/values-ca/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Qualsevol usuari pot actualitzar les aplicacions de la resta d\'usuaris."</string> <string name="car_loading_profile" msgid="4507385037552574474">"S\'està carregant"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"S\'està carregant l\'usuari (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tanca"</string> </resources> diff --git a/packages/CarSystemUI/res/values-cs/strings.xml b/packages/CarSystemUI/res/values-cs/strings.xml index dd4472f06aa3..7657e32b0223 100644 --- a/packages/CarSystemUI/res/values-cs/strings.xml +++ b/packages/CarSystemUI/res/values-cs/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Každý uživatel může aktualizovat aplikace všech ostatních uživatelů."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Načítání"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Načítání uživatele (předchozí: <xliff:g id="FROM_USER">%1$d</xliff:g>, následující: <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zavřít"</string> </resources> diff --git a/packages/CarSystemUI/res/values-da/strings.xml b/packages/CarSystemUI/res/values-da/strings.xml index 6c08aa56bc7e..120929e34347 100644 --- a/packages/CarSystemUI/res/values-da/strings.xml +++ b/packages/CarSystemUI/res/values-da/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Alle brugere kan opdatere apps for alle andre brugere."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Indlæser"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Indlæser bruger (fra <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Luk"</string> </resources> diff --git a/packages/CarSystemUI/res/values-el/strings.xml b/packages/CarSystemUI/res/values-el/strings.xml index 66f8d18472c9..9b24fa488923 100644 --- a/packages/CarSystemUI/res/values-el/strings.xml +++ b/packages/CarSystemUI/res/values-el/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Οποιοσδήποτε χρήστης μπορεί να ενημερώσει τις εφαρμογές για όλους τους άλλους χρήστες."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Φόρτωση"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Φόρτωση χρήστη (από <xliff:g id="FROM_USER">%1$d</xliff:g> έως <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Κλείσιμο"</string> </resources> diff --git a/packages/CarSystemUI/res/values-en-rAU/strings.xml b/packages/CarSystemUI/res/values-en-rAU/strings.xml index b3e358fbb6ef..8eb76c20d8a2 100644 --- a/packages/CarSystemUI/res/values-en-rAU/strings.xml +++ b/packages/CarSystemUI/res/values-en-rAU/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string> </resources> diff --git a/packages/CarSystemUI/res/values-en-rCA/strings.xml b/packages/CarSystemUI/res/values-en-rCA/strings.xml index b3e358fbb6ef..8eb76c20d8a2 100644 --- a/packages/CarSystemUI/res/values-en-rCA/strings.xml +++ b/packages/CarSystemUI/res/values-en-rCA/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string> </resources> diff --git a/packages/CarSystemUI/res/values-en-rGB/strings.xml b/packages/CarSystemUI/res/values-en-rGB/strings.xml index b3e358fbb6ef..8eb76c20d8a2 100644 --- a/packages/CarSystemUI/res/values-en-rGB/strings.xml +++ b/packages/CarSystemUI/res/values-en-rGB/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string> </resources> diff --git a/packages/CarSystemUI/res/values-en-rIN/strings.xml b/packages/CarSystemUI/res/values-en-rIN/strings.xml index b3e358fbb6ef..8eb76c20d8a2 100644 --- a/packages/CarSystemUI/res/values-en-rIN/strings.xml +++ b/packages/CarSystemUI/res/values-en-rIN/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string> </resources> diff --git a/packages/CarSystemUI/res/values-en-rXC/strings.xml b/packages/CarSystemUI/res/values-en-rXC/strings.xml index eaf6f51d3092..37a568bf3e5a 100644 --- a/packages/CarSystemUI/res/values-en-rXC/strings.xml +++ b/packages/CarSystemUI/res/values-en-rXC/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Any user can update apps for all other users."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Loading"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Loading user (from <xliff:g id="FROM_USER">%1$d</xliff:g> to <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Close"</string> </resources> diff --git a/packages/CarSystemUI/res/values-es-rUS/strings.xml b/packages/CarSystemUI/res/values-es-rUS/strings.xml index 6a5f8ce43318..16aba86f3c3f 100644 --- a/packages/CarSystemUI/res/values-es-rUS/strings.xml +++ b/packages/CarSystemUI/res/values-es-rUS/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Cualquier usuario podrá actualizar las apps de otras personas."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Cargando"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Cargando usuario (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Cerrar"</string> </resources> diff --git a/packages/CarSystemUI/res/values-es/strings.xml b/packages/CarSystemUI/res/values-es/strings.xml index c43d7e54d559..8aad2cad9cf2 100644 --- a/packages/CarSystemUI/res/values-es/strings.xml +++ b/packages/CarSystemUI/res/values-es/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Cualquier usuario puede actualizar las aplicaciones del resto de los usuarios."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Cargando"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Cargando usuario (de <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Cerrar"</string> </resources> diff --git a/packages/CarSystemUI/res/values-et/strings.xml b/packages/CarSystemUI/res/values-et/strings.xml index ad82d5fc230b..14ec9df45c2d 100644 --- a/packages/CarSystemUI/res/values-et/strings.xml +++ b/packages/CarSystemUI/res/values-et/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Iga kasutaja saab rakendusi värskendada kõigi teiste kasutajate jaoks."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Laadimine"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Kasutaja laadimine (<xliff:g id="FROM_USER">%1$d</xliff:g> > <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sule"</string> </resources> diff --git a/packages/CarSystemUI/res/values-fa/strings.xml b/packages/CarSystemUI/res/values-fa/strings.xml index ef37b654bde9..3f53b1145b96 100644 --- a/packages/CarSystemUI/res/values-fa/strings.xml +++ b/packages/CarSystemUI/res/values-fa/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"هر کاربری میتواند برنامهها را برای همه کاربران دیگر بهروزرسانی کند."</string> <string name="car_loading_profile" msgid="4507385037552574474">"درحال بارگیری"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"بارگیری کاربر (از <xliff:g id="FROM_USER">%1$d</xliff:g> تا <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"بستن"</string> </resources> diff --git a/packages/CarSystemUI/res/values-fi/strings.xml b/packages/CarSystemUI/res/values-fi/strings.xml index 10bb0c5b782c..79b53f6daa2c 100644 --- a/packages/CarSystemUI/res/values-fi/strings.xml +++ b/packages/CarSystemUI/res/values-fi/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Kaikki käyttäjät voivat päivittää muiden käyttäjien sovelluksia."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Ladataan"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ladataan käyttäjäprofiilia (<xliff:g id="FROM_USER">%1$d</xliff:g>–<xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sulje"</string> </resources> diff --git a/packages/CarSystemUI/res/values-fr-rCA/strings.xml b/packages/CarSystemUI/res/values-fr-rCA/strings.xml index bebd3f441410..b1905490f229 100644 --- a/packages/CarSystemUI/res/values-fr-rCA/strings.xml +++ b/packages/CarSystemUI/res/values-fr-rCA/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Tout utilisateur peut mettre à jour les applications pour tous les autres utilisateurs."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Chargement en cours…"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Chargement de l\'utilisateur (de <xliff:g id="FROM_USER">%1$d</xliff:g> vers <xliff:g id="TO_USER">%2$d</xliff:g>) en cours…"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fermer"</string> </resources> diff --git a/packages/CarSystemUI/res/values-fr/strings.xml b/packages/CarSystemUI/res/values-fr/strings.xml index 3d498d2f9ca7..5a905a000c42 100644 --- a/packages/CarSystemUI/res/values-fr/strings.xml +++ b/packages/CarSystemUI/res/values-fr/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"N\'importe quel utilisateur peut mettre à jour les applications pour tous les autres utilisateurs."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Chargement…"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Chargement de l\'utilisateur (de <xliff:g id="FROM_USER">%1$d</xliff:g> à <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fermer"</string> </resources> diff --git a/packages/CarSystemUI/res/values-hi/strings.xml b/packages/CarSystemUI/res/values-hi/strings.xml index 95454a53709f..83321fd630b9 100644 --- a/packages/CarSystemUI/res/values-hi/strings.xml +++ b/packages/CarSystemUI/res/values-hi/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"कोई भी उपयोगकर्ता, बाकी सभी उपयोगकर्ताओं के लिए ऐप्लिकेशन अपडेट कर सकता है."</string> <string name="car_loading_profile" msgid="4507385037552574474">"लोड हो रही है"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"उपयोगकर्ता को लोड किया जा रहा है (<xliff:g id="FROM_USER">%1$d</xliff:g> से <xliff:g id="TO_USER">%2$d</xliff:g> पर)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"बंद करें"</string> </resources> diff --git a/packages/CarSystemUI/res/values-hr/strings.xml b/packages/CarSystemUI/res/values-hr/strings.xml index f3aaf63eac18..872fc69d8cfb 100644 --- a/packages/CarSystemUI/res/values-hr/strings.xml +++ b/packages/CarSystemUI/res/values-hr/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Svaki korisnik može ažurirati aplikacije za ostale korisnike."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Učitavanje"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Učitavanje korisnika (od <xliff:g id="FROM_USER">%1$d</xliff:g> do <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zatvori"</string> </resources> diff --git a/packages/CarSystemUI/res/values-hu/strings.xml b/packages/CarSystemUI/res/values-hu/strings.xml index b63ba8b8ed60..63328f370ed3 100644 --- a/packages/CarSystemUI/res/values-hu/strings.xml +++ b/packages/CarSystemUI/res/values-hu/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Bármely felhasználó frissítheti az alkalmazásokat az összes felhasználó számára."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Betöltés"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Felhasználó betöltése (<xliff:g id="FROM_USER">%1$d</xliff:g> → <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Bezárás"</string> </resources> diff --git a/packages/CarSystemUI/res/values-hy/strings.xml b/packages/CarSystemUI/res/values-hy/strings.xml index e2a2c6bf459c..778f6954da3d 100644 --- a/packages/CarSystemUI/res/values-hy/strings.xml +++ b/packages/CarSystemUI/res/values-hy/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Ցանկացած օգտատեր կարող է թարմացնել հավելվածները բոլոր մյուս հաշիվների համար։"</string> <string name="car_loading_profile" msgid="4507385037552574474">"Բեռնում"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Օգտատերը բեռնվում է (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Փակել"</string> </resources> diff --git a/packages/CarSystemUI/res/values-in/strings.xml b/packages/CarSystemUI/res/values-in/strings.xml index 0a70d261b77d..386d79e0e88b 100644 --- a/packages/CarSystemUI/res/values-in/strings.xml +++ b/packages/CarSystemUI/res/values-in/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Setiap pengguna dapat mengupdate aplikasi untuk semua pengguna lain."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Memuat"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Memuat pengguna (dari <xliff:g id="FROM_USER">%1$d</xliff:g> menjadi <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tutup"</string> </resources> diff --git a/packages/CarSystemUI/res/values-is/strings.xml b/packages/CarSystemUI/res/values-is/strings.xml index ea6b031c72b9..5a927aa0928a 100644 --- a/packages/CarSystemUI/res/values-is/strings.xml +++ b/packages/CarSystemUI/res/values-is/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Allir notendur geta uppfært forrit fyrir alla aðra notendur."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Hleður"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Hleður notanda (frá <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Loka"</string> </resources> diff --git a/packages/CarSystemUI/res/values-it/strings.xml b/packages/CarSystemUI/res/values-it/strings.xml index ecbcd5d9dadd..41d7ad4f70ee 100644 --- a/packages/CarSystemUI/res/values-it/strings.xml +++ b/packages/CarSystemUI/res/values-it/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Qualsiasi utente può aggiornare le app per tutti gli altri."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Caricamento"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Caricamento dell\'utente (da <xliff:g id="FROM_USER">%1$d</xliff:g> a <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Chiudi"</string> </resources> diff --git a/packages/CarSystemUI/res/values-iw/strings.xml b/packages/CarSystemUI/res/values-iw/strings.xml index fe182a3b7251..f419cac56d48 100644 --- a/packages/CarSystemUI/res/values-iw/strings.xml +++ b/packages/CarSystemUI/res/values-iw/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"כל משתמש יכול לעדכן אפליקציות לכל שאר המשתמשים."</string> <string name="car_loading_profile" msgid="4507385037552574474">"בטעינה"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"המשתמש בטעינה (מהמשתמש <xliff:g id="FROM_USER">%1$d</xliff:g> אל <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"סגירה"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ja/strings.xml b/packages/CarSystemUI/res/values-ja/strings.xml index 14486758dcd1..9cf056fdf7c6 100644 --- a/packages/CarSystemUI/res/values-ja/strings.xml +++ b/packages/CarSystemUI/res/values-ja/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"どのユーザーも他のすべてのユーザーに代わってアプリを更新できます。"</string> <string name="car_loading_profile" msgid="4507385037552574474">"読み込んでいます"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ユーザーを読み込んでいます(<xliff:g id="FROM_USER">%1$d</xliff:g>~<xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"閉じる"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ka/strings.xml b/packages/CarSystemUI/res/values-ka/strings.xml index 0fef7e55f27e..7d62c62a8abe 100644 --- a/packages/CarSystemUI/res/values-ka/strings.xml +++ b/packages/CarSystemUI/res/values-ka/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"ნებისმიერ მომხმარებელს შეუძლია აპები ყველა სხვა მომხმარებლისათვის განაახლოს."</string> <string name="car_loading_profile" msgid="4507385037552574474">"იტვირთება"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"იტვირთება მომხმარებელი (<xliff:g id="FROM_USER">%1$d</xliff:g>-დან <xliff:g id="TO_USER">%2$d</xliff:g>-მდე)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"დახურვა"</string> </resources> diff --git a/packages/CarSystemUI/res/values-kk/strings.xml b/packages/CarSystemUI/res/values-kk/strings.xml index a4cf78709515..2dd1b66f1778 100644 --- a/packages/CarSystemUI/res/values-kk/strings.xml +++ b/packages/CarSystemUI/res/values-kk/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Кез келген пайдаланушы қолданбаларды басқа пайдаланушылар үшін жаңарта алады."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Жүктелуде"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Пайдаланушы профилі жүктелуде (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Жабу"</string> </resources> diff --git a/packages/CarSystemUI/res/values-km/strings.xml b/packages/CarSystemUI/res/values-km/strings.xml index 7b9a093d7f39..709cfe572ce9 100644 --- a/packages/CarSystemUI/res/values-km/strings.xml +++ b/packages/CarSystemUI/res/values-km/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"អ្នកប្រើប្រាស់ណាក៏អាចដំឡើងកំណែកម្មវិធីសម្រាប់អ្នកប្រើប្រាស់ទាំងអស់ផ្សេងទៀតបានដែរ។"</string> <string name="car_loading_profile" msgid="4507385037552574474">"កំពុងផ្ទុក"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"កំពុងផ្ទុកអ្នកប្រើប្រាស់ (ពី <xliff:g id="FROM_USER">%1$d</xliff:g> ដល់ <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"បិទ"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ko/strings.xml b/packages/CarSystemUI/res/values-ko/strings.xml index b570c5c6b81e..17a24667b547 100644 --- a/packages/CarSystemUI/res/values-ko/strings.xml +++ b/packages/CarSystemUI/res/values-ko/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"누구나 다른 모든 사용자를 위해 앱을 업데이트할 수 있습니다."</string> <string name="car_loading_profile" msgid="4507385037552574474">"로드 중"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"사용자 로드 중(<xliff:g id="FROM_USER">%1$d</xliff:g>님에서 <xliff:g id="TO_USER">%2$d</xliff:g>님으로)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"닫기"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ky/strings.xml b/packages/CarSystemUI/res/values-ky/strings.xml index c66b34ffb9c1..dd9225a1aa0b 100644 --- a/packages/CarSystemUI/res/values-ky/strings.xml +++ b/packages/CarSystemUI/res/values-ky/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Колдонмолорду бир колдонуучу калган бардык колдонуучулар үчүн да жаңырта алат."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Жүктөлүүдө"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Колдонуучу тууралуу маалымат жүктөлүүдө (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Жабуу"</string> </resources> diff --git a/packages/CarSystemUI/res/values-lo/strings.xml b/packages/CarSystemUI/res/values-lo/strings.xml index 2bf19e03b82a..bc94a5104c6b 100644 --- a/packages/CarSystemUI/res/values-lo/strings.xml +++ b/packages/CarSystemUI/res/values-lo/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"ຜູ້ໃຊ້ຕ່າງໆສາມາດອັບເດດແອັບສຳລັບຜູ້ໃຊ້ອື່ນທັງໝົດໄດ້."</string> <string name="car_loading_profile" msgid="4507385037552574474">"ກຳລັງໂຫຼດ"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"ກຳລັງໂຫຼດຜູ້ໃຊ້ (ຈາກ <xliff:g id="FROM_USER">%1$d</xliff:g> ໄປຍັງ <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ປິດ"</string> </resources> diff --git a/packages/CarSystemUI/res/values-lt/strings.xml b/packages/CarSystemUI/res/values-lt/strings.xml index 1cae1e907193..a47ad5909f12 100644 --- a/packages/CarSystemUI/res/values-lt/strings.xml +++ b/packages/CarSystemUI/res/values-lt/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Bet kuris naudotojas gali atnaujinti visų kitų naudotojų programas."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Įkeliama"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Įkeliamas naudotojo profilis (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Uždaryti"</string> </resources> diff --git a/packages/CarSystemUI/res/values-lv/strings.xml b/packages/CarSystemUI/res/values-lv/strings.xml index 62b8bf8d98eb..cb7c8b9f1575 100644 --- a/packages/CarSystemUI/res/values-lv/strings.xml +++ b/packages/CarSystemUI/res/values-lv/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Ikviens lietotājs var atjaunināt lietotnes visu lietotāju vārdā."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Notiek ielāde…"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Notiek lietotāja profila ielāde (<xliff:g id="FROM_USER">%1$d</xliff:g>–<xliff:g id="TO_USER">%2$d</xliff:g>)…"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Aizvērt"</string> </resources> diff --git a/packages/CarSystemUI/res/values-mk/strings.xml b/packages/CarSystemUI/res/values-mk/strings.xml index 3e7ad63ae2d9..cd2ae973a078 100644 --- a/packages/CarSystemUI/res/values-mk/strings.xml +++ b/packages/CarSystemUI/res/values-mk/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Секој корисник може да ажурира апликации за сите други корисници."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Се вчитува"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Се вчитува корисникот (од <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затвори"</string> </resources> diff --git a/packages/CarSystemUI/res/values-mn/strings.xml b/packages/CarSystemUI/res/values-mn/strings.xml index 45921d26172e..33bcd275117d 100644 --- a/packages/CarSystemUI/res/values-mn/strings.xml +++ b/packages/CarSystemUI/res/values-mn/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Бусад бүх хэрэглэгчийн аппыг дурын хэрэглэгч шинэчлэх боломжтой."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Ачаалж байна"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Хэрэглэгчийг ачаалж байна (<xliff:g id="FROM_USER">%1$d</xliff:g>-с <xliff:g id="TO_USER">%2$d</xliff:g> хүртэл)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Хаах"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ms/strings.xml b/packages/CarSystemUI/res/values-ms/strings.xml index 1a43d9c7cdef..0bb683b0a58d 100644 --- a/packages/CarSystemUI/res/values-ms/strings.xml +++ b/packages/CarSystemUI/res/values-ms/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Mana-mana pengguna boleh mengemas kini apl untuk semua pengguna lain."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Memuatkan"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Memuatkan pengguna (daripada <xliff:g id="FROM_USER">%1$d</xliff:g> hingga <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Tutup"</string> </resources> diff --git a/packages/CarSystemUI/res/values-my/strings.xml b/packages/CarSystemUI/res/values-my/strings.xml index 4f3922b373b5..4e7ca39a2b12 100644 --- a/packages/CarSystemUI/res/values-my/strings.xml +++ b/packages/CarSystemUI/res/values-my/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"မည်သူမဆို အသုံးပြုသူအားလုံးအတွက် အက်ပ်များကို အပ်ဒိတ်လုပ်နိုင်သည်။"</string> <string name="car_loading_profile" msgid="4507385037552574474">"ဖွင့်နေသည်"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"အသုံးပြုသူကို ဖွင့်နေသည် (<xliff:g id="FROM_USER">%1$d</xliff:g> မှ <xliff:g id="TO_USER">%2$d</xliff:g> သို့)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ပိတ်ရန်"</string> </resources> diff --git a/packages/CarSystemUI/res/values-nb/strings.xml b/packages/CarSystemUI/res/values-nb/strings.xml index 5b6166feba0f..0b2856f0d7b2 100644 --- a/packages/CarSystemUI/res/values-nb/strings.xml +++ b/packages/CarSystemUI/res/values-nb/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Alle brukere kan oppdatere apper for alle andre brukere."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Laster inn"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Laster inn brukeren (fra <xliff:g id="FROM_USER">%1$d</xliff:g> til <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Lukk"</string> </resources> diff --git a/packages/CarSystemUI/res/values-nl/strings.xml b/packages/CarSystemUI/res/values-nl/strings.xml index d79f2b1d10f4..4765f71b8b61 100644 --- a/packages/CarSystemUI/res/values-nl/strings.xml +++ b/packages/CarSystemUI/res/values-nl/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Elke gebruiker kan apps updaten voor alle andere gebruikers"</string> <string name="car_loading_profile" msgid="4507385037552574474">"Laden"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Gebruiker laden (van <xliff:g id="FROM_USER">%1$d</xliff:g> naar <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Sluiten"</string> </resources> diff --git a/packages/CarSystemUI/res/values-pl/strings.xml b/packages/CarSystemUI/res/values-pl/strings.xml index dd8c1892b63e..52b90f1b5b96 100644 --- a/packages/CarSystemUI/res/values-pl/strings.xml +++ b/packages/CarSystemUI/res/values-pl/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Każdy użytkownik może aktualizować aplikacje wszystkich innych użytkowników."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Ładuję"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ładuję użytkownika (od <xliff:g id="FROM_USER">%1$d</xliff:g> do <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zamknij"</string> </resources> diff --git a/packages/CarSystemUI/res/values-pt-rPT/strings.xml b/packages/CarSystemUI/res/values-pt-rPT/strings.xml index c7f5ecf00707..2dffa17e8f1c 100644 --- a/packages/CarSystemUI/res/values-pt-rPT/strings.xml +++ b/packages/CarSystemUI/res/values-pt-rPT/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Qualquer utilizador pode atualizar apps para todos os outros utilizadores."</string> <string name="car_loading_profile" msgid="4507385037552574474">"A carregar…"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"A carregar o utilizador (de <xliff:g id="FROM_USER">%1$d</xliff:g> para <xliff:g id="TO_USER">%2$d</xliff:g>)…"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fechar"</string> </resources> diff --git a/packages/CarSystemUI/res/values-pt/strings.xml b/packages/CarSystemUI/res/values-pt/strings.xml index 0712fb82f7fd..a7c44d2522d6 100644 --- a/packages/CarSystemUI/res/values-pt/strings.xml +++ b/packages/CarSystemUI/res/values-pt/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Qualquer usuário pode atualizar apps para os demais usuários."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Carregando"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Carregando usuário (de <xliff:g id="FROM_USER">%1$d</xliff:g> para <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Fechar"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ro/strings.xml b/packages/CarSystemUI/res/values-ro/strings.xml index 60fd4fef41c0..1a4e71d9eea8 100644 --- a/packages/CarSystemUI/res/values-ro/strings.xml +++ b/packages/CarSystemUI/res/values-ro/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Orice utilizator poate actualiza aplicațiile pentru toți ceilalți utilizatori."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Se încarcă"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Se încarcă utilizatorul (de la <xliff:g id="FROM_USER">%1$d</xliff:g> la <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Închideți"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ru/strings.xml b/packages/CarSystemUI/res/values-ru/strings.xml index a043d24789a9..330ba2f0bdbd 100644 --- a/packages/CarSystemUI/res/values-ru/strings.xml +++ b/packages/CarSystemUI/res/values-ru/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Любой пользователь устройства может обновлять приложения для всех аккаунтов."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Загрузка…"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Загрузка профиля пользователя (с <xliff:g id="FROM_USER">%1$d</xliff:g> по <xliff:g id="TO_USER">%2$d</xliff:g>)…"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрыть"</string> </resources> diff --git a/packages/CarSystemUI/res/values-si/strings.xml b/packages/CarSystemUI/res/values-si/strings.xml index e14f64a7bca8..6391d28e9568 100644 --- a/packages/CarSystemUI/res/values-si/strings.xml +++ b/packages/CarSystemUI/res/values-si/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"සියලුම අනෙක් පරිශීලකයින් සඳහා ඕනෑම පරිශීලකයෙකුට යෙදුම් යාවත්කාලීන කළ හැක."</string> <string name="car_loading_profile" msgid="4507385037552574474">"පූරණය වෙමින්"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"පරිශීලකයා පූරණය වෙමින් (<xliff:g id="FROM_USER">%1$d</xliff:g> සිට <xliff:g id="TO_USER">%2$d</xliff:g> වෙත)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"වසන්න"</string> </resources> diff --git a/packages/CarSystemUI/res/values-sk/strings.xml b/packages/CarSystemUI/res/values-sk/strings.xml index 8f98983877d9..b100a5d4cf5d 100644 --- a/packages/CarSystemUI/res/values-sk/strings.xml +++ b/packages/CarSystemUI/res/values-sk/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Ktorýkoľvek používateľ môže aktualizovať aplikácie všetkých ostatných používateľov."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Načítava sa"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Načítava sa používateľ (predchádzajúci: <xliff:g id="FROM_USER">%1$d</xliff:g>, nasledujúci: <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zavrieť"</string> </resources> diff --git a/packages/CarSystemUI/res/values-sl/strings.xml b/packages/CarSystemUI/res/values-sl/strings.xml index 6b471845b657..b67002bec7d4 100644 --- a/packages/CarSystemUI/res/values-sl/strings.xml +++ b/packages/CarSystemUI/res/values-sl/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Vsak uporabnik lahko posodobi aplikacije za vse druge uporabnike."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Nalaganje"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Nalaganje uporabnika (od uporabnika <xliff:g id="FROM_USER">%1$d</xliff:g> do uporabnika <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Zapri"</string> </resources> diff --git a/packages/CarSystemUI/res/values-sq/strings.xml b/packages/CarSystemUI/res/values-sq/strings.xml index 18ea40131817..d19e1583664b 100644 --- a/packages/CarSystemUI/res/values-sq/strings.xml +++ b/packages/CarSystemUI/res/values-sq/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Çdo përdorues mund t\'i përditësojë aplikacionet për të gjithë përdoruesit e tjerë."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Po ngarkohet"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Përdoruesi po ngarkohet (nga <xliff:g id="FROM_USER">%1$d</xliff:g> te <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Mbyll"</string> </resources> diff --git a/packages/CarSystemUI/res/values-sr/strings.xml b/packages/CarSystemUI/res/values-sr/strings.xml index 878a1c1aa425..a5fb5b4f6ad6 100644 --- a/packages/CarSystemUI/res/values-sr/strings.xml +++ b/packages/CarSystemUI/res/values-sr/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Сваки корисник може да ажурира апликације за све остале кориснике."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Учитава се"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Профил корисника се учитава (из<xliff:g id="FROM_USER">%1$d</xliff:g> у <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Затвори"</string> </resources> diff --git a/packages/CarSystemUI/res/values-sv/strings.xml b/packages/CarSystemUI/res/values-sv/strings.xml index 905dd445eeab..8a942d6af080 100644 --- a/packages/CarSystemUI/res/values-sv/strings.xml +++ b/packages/CarSystemUI/res/values-sv/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Alla användare kan uppdatera appar för andra användare."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Läser in"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Läser in användare (från <xliff:g id="FROM_USER">%1$d</xliff:g> till <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Stäng"</string> </resources> diff --git a/packages/CarSystemUI/res/values-sw/strings.xml b/packages/CarSystemUI/res/values-sw/strings.xml index 3cb6098a3f1b..be03373c48a1 100644 --- a/packages/CarSystemUI/res/values-sw/strings.xml +++ b/packages/CarSystemUI/res/values-sw/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Mtumiaji yeyote anaweza kusasisha programu za watumiaji wengine."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Inapakia"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Inapakia wasifu wa mtumiaji (kutoka <xliff:g id="FROM_USER">%1$d</xliff:g> kuwa <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Funga"</string> </resources> diff --git a/packages/CarSystemUI/res/values-ta/strings.xml b/packages/CarSystemUI/res/values-ta/strings.xml index de52edee1621..a82a2f856bd4 100644 --- a/packages/CarSystemUI/res/values-ta/strings.xml +++ b/packages/CarSystemUI/res/values-ta/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"எந்தப் பயனரும் பிற பயனர்கள் சார்பாக ஆப்ஸைப் புதுப்பிக்க முடியும்."</string> <string name="car_loading_profile" msgid="4507385037552574474">"ஏற்றுகிறது"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"பயனர் தகவலை ஏற்றுகிறது (<xliff:g id="FROM_USER">%1$d</xliff:g>லிருந்து <xliff:g id="TO_USER">%2$d</xliff:g> வரை)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"மூடுக"</string> </resources> diff --git a/packages/CarSystemUI/res/values-th/strings.xml b/packages/CarSystemUI/res/values-th/strings.xml index 8f3012b5b4a5..dacf605888f5 100644 --- a/packages/CarSystemUI/res/values-th/strings.xml +++ b/packages/CarSystemUI/res/values-th/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"ผู้ใช้ทุกคนจะอัปเดตแอปให้แก่ผู้ใช้คนอื่นๆ ได้"</string> <string name="car_loading_profile" msgid="4507385037552574474">"กำลังโหลด"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"กำลังโหลดผู้ใช้ (จาก <xliff:g id="FROM_USER">%1$d</xliff:g> ถึง <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"ปิด"</string> </resources> diff --git a/packages/CarSystemUI/res/values-tl/strings.xml b/packages/CarSystemUI/res/values-tl/strings.xml index 5b0e3b3cf19c..89d8cd201e5f 100644 --- a/packages/CarSystemUI/res/values-tl/strings.xml +++ b/packages/CarSystemUI/res/values-tl/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Puwedeng i-update ng sinumang user ang mga app para sa lahat ng iba pang user."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Naglo-load"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Nilo-load ang user (mula kay <xliff:g id="FROM_USER">%1$d</xliff:g> papunta kay <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Isara"</string> </resources> diff --git a/packages/CarSystemUI/res/values-tr/strings.xml b/packages/CarSystemUI/res/values-tr/strings.xml index 81fa01c16058..36bf694f30ab 100644 --- a/packages/CarSystemUI/res/values-tr/strings.xml +++ b/packages/CarSystemUI/res/values-tr/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Herhangi bir kullanıcı, diğer tüm kullanıcılar için uygulamaları güncelleyebilir."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Yükleniyor"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Kullanıcı yükleniyor (<xliff:g id="FROM_USER">%1$d</xliff:g> kullanıcısından <xliff:g id="TO_USER">%2$d</xliff:g> kullanıcısına)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Kapat"</string> </resources> diff --git a/packages/CarSystemUI/res/values-uk/strings.xml b/packages/CarSystemUI/res/values-uk/strings.xml index b7031c698815..391513f1b57a 100644 --- a/packages/CarSystemUI/res/values-uk/strings.xml +++ b/packages/CarSystemUI/res/values-uk/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Усі користувачі можуть оновлювати додатки для решти людей."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Завантаження"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Завантаження профілю користувача (від <xliff:g id="FROM_USER">%1$d</xliff:g> до <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Закрити"</string> </resources> diff --git a/packages/CarSystemUI/res/values-uz/strings.xml b/packages/CarSystemUI/res/values-uz/strings.xml index 471e4591265a..398d1f5ce29e 100644 --- a/packages/CarSystemUI/res/values-uz/strings.xml +++ b/packages/CarSystemUI/res/values-uz/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Qurilmaning istalgan foydalanuvchisi ilovalarni barcha hisoblar uchun yangilashi mumkin."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Yuklanmoqda"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Foydalanuvchi profili yuklanmoqda (<xliff:g id="FROM_USER">%1$d</xliff:g> – <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Yopish"</string> </resources> diff --git a/packages/CarSystemUI/res/values-vi/strings.xml b/packages/CarSystemUI/res/values-vi/strings.xml index 26bdddc750cd..f15320fd1dcd 100644 --- a/packages/CarSystemUI/res/values-vi/strings.xml +++ b/packages/CarSystemUI/res/values-vi/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Bất kỳ người dùng nào cũng có thể cập nhật ứng dụng cho tất cả những người dùng khác."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Đang tải"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Đang tải hồ sơ người dùng (từ <xliff:g id="FROM_USER">%1$d</xliff:g> sang <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Đóng"</string> </resources> diff --git a/packages/CarSystemUI/res/values-zh-rCN/strings.xml b/packages/CarSystemUI/res/values-zh-rCN/strings.xml index e7ca871337fa..a91f48c8b378 100644 --- a/packages/CarSystemUI/res/values-zh-rCN/strings.xml +++ b/packages/CarSystemUI/res/values-zh-rCN/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"任何用户均可为所有其他用户更新应用。"</string> <string name="car_loading_profile" msgid="4507385037552574474">"正在加载"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在加载用户(从 <xliff:g id="FROM_USER">%1$d</xliff:g> 到 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"关闭"</string> </resources> diff --git a/packages/CarSystemUI/res/values-zh-rHK/strings.xml b/packages/CarSystemUI/res/values-zh-rHK/strings.xml index 268243fea6f7..7aa611606274 100644 --- a/packages/CarSystemUI/res/values-zh-rHK/strings.xml +++ b/packages/CarSystemUI/res/values-zh-rHK/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"任何使用者都可以為所有其他使用者更新應用程式。"</string> <string name="car_loading_profile" msgid="4507385037552574474">"正在載入"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在載入使用者 (由 <xliff:g id="FROM_USER">%1$d</xliff:g> 至 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"關閉"</string> </resources> diff --git a/packages/CarSystemUI/res/values-zh-rTW/strings.xml b/packages/CarSystemUI/res/values-zh-rTW/strings.xml index 9dc0f1a03d3a..c062463905d7 100644 --- a/packages/CarSystemUI/res/values-zh-rTW/strings.xml +++ b/packages/CarSystemUI/res/values-zh-rTW/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"任何使用者都能為所有其他使用者更新應用程式。"</string> <string name="car_loading_profile" msgid="4507385037552574474">"載入中"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"正在載入使用者 (從 <xliff:g id="FROM_USER">%1$d</xliff:g> 到 <xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"關閉"</string> </resources> diff --git a/packages/CarSystemUI/res/values-zu/strings.xml b/packages/CarSystemUI/res/values-zu/strings.xml index 8845ff71c1bb..2dd33d827324 100644 --- a/packages/CarSystemUI/res/values-zu/strings.xml +++ b/packages/CarSystemUI/res/values-zu/strings.xml @@ -28,6 +28,5 @@ <string name="user_add_user_message_update" msgid="7061671307004867811">"Noma yimuphi umsebenzisi angabuyekeza izinhlelo zokusebenza zabanye abasebenzisi."</string> <string name="car_loading_profile" msgid="4507385037552574474">"Iyalayisha"</string> <string name="car_loading_profile_developer_message" msgid="1660962766911529611">"Ilayisha umsebenzisi (kusuka ku-<xliff:g id="FROM_USER">%1$d</xliff:g> kuya ku-<xliff:g id="TO_USER">%2$d</xliff:g>)"</string> - <!-- no translation found for rear_view_camera_close_button_text (8430918817320533693) --> - <skip /> + <string name="rear_view_camera_close_button_text" msgid="8430918817320533693">"Vala"</string> </resources> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index c4b36fb1ab29..b9e30fb950c6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -175,8 +175,8 @@ public class SecureSettings { Settings.Secure.ONE_HANDED_MODE_TIMEOUT, Settings.Secure.TAPS_APP_TO_EXIT, Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, - Settings.Secure.PANIC_GESTURE_ENABLED, - Settings.Secure.PANIC_SOUND_ENABLED, + Settings.Secure.EMERGENCY_GESTURE_ENABLED, + Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED, Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED, Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 7f694ad5d375..721bf730a343 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -264,8 +264,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Secure.TAPS_APP_TO_EXIT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.PANIC_GESTURE_ENABLED, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.PANIC_SOUND_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 94de48595b0d..f1d7e223c933 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2035,11 +2035,11 @@ class SettingsProtoDumpUtil { final long emergencyResponseToken = p.start(SecureSettingsProto.EMERGENCY_RESPONSE); dumpSetting(s, p, - Settings.Secure.PANIC_GESTURE_ENABLED, - SecureSettingsProto.EmergencyResponse.PANIC_GESTURE_ENABLED); + Settings.Secure.EMERGENCY_GESTURE_ENABLED, + SecureSettingsProto.EmergencyResponse.EMERGENCY_GESTURE_ENABLED); dumpSetting(s, p, - Settings.Secure.PANIC_SOUND_ENABLED, - SecureSettingsProto.EmergencyResponse.PANIC_SOUND_ENABLED); + Settings.Secure.EMERGENCY_GESTURE_SOUND_ENABLED, + SecureSettingsProto.EmergencyResponse.EMERGENCY_GESTURE_SOUND_ENABLED); p.end(emergencyResponseToken); dumpSetting(s, p, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 407104504132..ddd0dac0e9db 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -586,6 +586,8 @@ </intent-filter> </activity> + <activity android:name=".people.widget.LaunchConversationActivity" /> + <!-- People Space Widget --> <receiver android:name=".people.widget.PeopleSpaceWidgetProvider" diff --git a/packages/SystemUI/res/layout/people_space_widget_item.xml b/packages/SystemUI/res/layout/people_space_widget_item.xml index a40bfffaabd7..e4de6f91769c 100644 --- a/packages/SystemUI/res/layout/people_space_widget_item.xml +++ b/packages/SystemUI/res/layout/people_space_widget_item.xml @@ -15,12 +15,12 @@ ~ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/item" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:background="@drawable/people_space_tile_view_card" + android:id="@+id/item" android:orientation="vertical" android:padding="6dp" android:layout_marginBottom="6dp" diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java new file mode 100644 index 000000000000..22ffd2819c48 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 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. + */ + +package com.android.systemui.shared.pip; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.SurfaceControl; + +/** + * TODO(b/171721389): unify this class with + * {@link com.android.wm.shell.pip.PipSurfaceTransactionHelper}, for instance, there should be one + * source of truth on enabling/disabling and the actual value of corner radius. + */ +public class PipSurfaceTransactionHelper { + private final Matrix mTmpTransform = new Matrix(); + private final float[] mTmpFloat9 = new float[9]; + private final RectF mTmpSourceRectF = new RectF(); + private final Rect mTmpDestinationRect = new Rect(); + + public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, Rect destinationBounds, Rect insets) { + mTmpSourceRectF.set(sourceBounds); + mTmpDestinationRect.set(sourceBounds); + mTmpDestinationRect.inset(insets); + // Scale by the shortest edge and offset such that the top/left of the scaled inset + // source rect aligns with the top/left of the destination bounds + final float scale = sourceBounds.width() <= sourceBounds.height() + ? (float) destinationBounds.width() / sourceBounds.width() + : (float) destinationBounds.height() / sourceBounds.height(); + final float left = destinationBounds.left - insets.left * scale; + final float top = destinationBounds.top - insets.top * scale; + mTmpTransform.setScale(scale, scale); + tx.setMatrix(leash, mTmpTransform, mTmpFloat9) + .setWindowCrop(leash, mTmpDestinationRect) + .setPosition(leash, left, top); + } + + public void reset(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds) { + resetScale(tx, leash, destinationBounds); + resetCornerRadius(tx, leash); + crop(tx, leash, destinationBounds); + } + + public void resetScale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect destinationBounds) { + tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9) + .setPosition(leash, destinationBounds.left, destinationBounds.top); + } + + public void resetCornerRadius(SurfaceControl.Transaction tx, SurfaceControl leash) { + tx.setCornerRadius(leash, 0); + } + + public void crop(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect destinationBounds) { + tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()) + .setPosition(leash, destinationBounds.left, destinationBounds.top); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java index 326c2aa37175..7b9ebc0d4656 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java @@ -17,8 +17,11 @@ package com.android.systemui.shared.system; import android.app.ActivityManager; +import android.app.PictureInPictureParams; import android.app.TaskInfo; import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; public class TaskInfoCompat { @@ -34,6 +37,10 @@ public class TaskInfoCompat { return info.configuration.windowConfiguration.getWindowingMode(); } + public static Rect getWindowConfigurationBounds(TaskInfo info) { + return info.configuration.windowConfiguration.getBounds(); + } + public static boolean supportsSplitScreenMultiWindow(TaskInfo info) { return info.supportsSplitScreenMultiWindow; } @@ -45,4 +52,16 @@ public class TaskInfoCompat { public static ActivityManager.TaskDescription getTaskDescription(TaskInfo info) { return info.taskDescription; } + + public static ActivityInfo getTopActivityInfo(TaskInfo info) { + return info.topActivityInfo; + } + + public static boolean isAutoEnterPipEnabled(PictureInPictureParams params) { + return params.isAutoEnterEnabled(); + } + + public static Rect getPipSourceRectHint(PictureInPictureParams params) { + return params.getSourceRectHint(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index e3ee2a10821b..fff185b99a1e 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -120,6 +120,18 @@ public class LogModule { return buffer; } + /** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */ + @Provides + @SysUISingleton + @PrivacyLog + public static LogBuffer providePrivacyLogBuffer( + LogcatEchoTracker bufferFilter, + DumpManager dumpManager) { + LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter); + buffer.attach(dumpManager); + return buffer; + } + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java new file mode 100644 index 000000000000..e96e532f94bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java @@ -0,0 +1,33 @@ +/* + * 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.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for privacy indicator-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface PrivacyLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 2ddcc5aa77fe..1a9dd712bd0e 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -69,16 +69,29 @@ public class PeopleSpaceUtils { /** Converts {@code drawable} to a {@link Bitmap}. */ public static Bitmap convertDrawableToBitmap(Drawable drawable) { + if (drawable == null) { + return null; + } + if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (bitmapDrawable.getBitmap() != null) { + return bitmapDrawable.getBitmap(); + } + } + + Bitmap bitmap; + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + // Single color bitmap will be created of 1x1 pixel + } else { + bitmap = Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888 + ); } - // We use max below because the drawable might have no intrinsic width/height (e.g. if the - // drawable is a solid color). - Bitmap bitmap = - Bitmap.createBitmap( - Math.max(drawable.getIntrinsicWidth(), 1), - Math.max(drawable.getIntrinsicHeight(), 1), - Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java new file mode 100644 index 000000000000..44f173bc5175 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 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. + */ + +package com.android.systemui.people.widget; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.os.Bundle; +import android.util.Log; + +import com.android.systemui.people.PeopleSpaceUtils; + +/** Proxy activity to launch ShortcutInfo's conversation. */ +public class LaunchConversationActivity extends Activity { + private static final String TAG = "PeopleSpaceLaunchConv"; + private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (DEBUG) Log.d(TAG, "onCreate called"); + + Intent intent = getIntent(); + ShortcutInfo shortcutInfo = (ShortcutInfo) intent.getParcelableExtra( + PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO + ); + if (shortcutInfo != null) { + if (DEBUG) { + Log.d(TAG, "Launching conversation with shortcutInfo id " + shortcutInfo.getId()); + } + try { + LauncherApps launcherApps = + getApplicationContext().getSystemService(LauncherApps.class); + launcherApps.startShortcut( + shortcutInfo, null, null); + } catch (Exception e) { + Log.e(TAG, "Exception starting shortcut:" + e); + } + } else { + if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo."); + } + finish(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java index 85801f9e7206..aa98b61ff947 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java @@ -16,6 +16,7 @@ package com.android.systemui.people.widget; +import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; @@ -31,6 +32,8 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { private static final String TAG = "PeopleSpaceWidgetPvd"; private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; + public static final String EXTRA_SHORTCUT_INFO = "extra_shortcut_info"; + /** Called when widget updates. */ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); @@ -45,6 +48,19 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); views.setRemoteAdapter(R.id.widget_list_view, intent); + Intent activityIntent = new Intent(context, LaunchConversationActivity.class); + activityIntent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_NO_HISTORY + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + PendingIntent pendingIntent = PendingIntent.getActivity( + context, + appWidgetId, + activityIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent); + // Tell the AppWidgetManager to perform an update on the current app widget appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list_view); appWidgetManager.updateAppWidget(appWidgetId, views); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java index 093925a2664a..c68c30632b6c 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java @@ -130,6 +130,10 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R mLauncherApps.getShortcutIconDrawable(shortcutInfo, 0) ) ); + + Intent fillInIntent = new Intent(); + fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO, shortcutInfo); + personView.setOnClickFillInIntent(R.id.item, fillInIntent); } catch (Exception e) { Log.e(TAG, "Couldn't retrieve shortcut information", e); } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt index 3da1363f2a56..7359e79b26f5 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt @@ -19,20 +19,31 @@ import com.android.systemui.R typealias Privacy = PrivacyType -enum class PrivacyType(val nameId: Int, val iconId: Int) { +enum class PrivacyType(val nameId: Int, val iconId: Int, val logName: String) { // This is uses the icons used by the corresponding permission groups in the AndroidManifest - TYPE_CAMERA(R.string.privacy_type_camera, - com.android.internal.R.drawable.perm_group_camera), - TYPE_MICROPHONE(R.string.privacy_type_microphone, - com.android.internal.R.drawable.perm_group_microphone), - TYPE_LOCATION(R.string.privacy_type_location, - com.android.internal.R.drawable.perm_group_location); + TYPE_CAMERA( + R.string.privacy_type_camera, + com.android.internal.R.drawable.perm_group_camera, + "camera" + ), + TYPE_MICROPHONE( + R.string.privacy_type_microphone, + com.android.internal.R.drawable.perm_group_microphone, + "microphone" + ), + TYPE_LOCATION( + R.string.privacy_type_location, + com.android.internal.R.drawable.perm_group_location, + "location" + ); fun getName(context: Context) = context.resources.getString(nameId) fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme) } -data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) +data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) { + fun toLog(): String = "(${privacyType.logName}, ${application.packageName}(${application.uid}))" +} data class PrivacyApplication(val packageName: String, val uid: Int) diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index dc5ba693a658..87ffbd465109 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -32,6 +32,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.concurrency.DelayableExecutor @@ -48,6 +49,7 @@ class PrivacyItemController @Inject constructor( @Background private val bgExecutor: Executor, private val deviceConfigProxy: DeviceConfigProxy, private val userTracker: UserTracker, + private val logger: PrivacyLogger, dumpManager: DumpManager ) : Dumpable { @@ -158,6 +160,7 @@ class PrivacyItemController @Inject constructor( } val userId = UserHandle.getUserId(uid) if (userId in currentUserIds) { + logger.logUpdatedItemFromAppOps(code, uid, packageName, active) update(false) } } @@ -194,6 +197,7 @@ class PrivacyItemController @Inject constructor( bgExecutor.execute { if (updateUsers) { currentUserIds = userTracker.userProfiles.map { it.id } + logger.logCurrentProfilesChanged(currentUserIds) } updateListAndNotifyChanges.run() } @@ -260,6 +264,8 @@ class PrivacyItemController @Inject constructor( } val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) } .mapNotNull { toPrivacyItem(it) }.distinct() + logger.logUpdatedPrivacyItemsList( + list.joinToString(separator = ", ", transform = PrivacyItem::toLog)) privacyList = list } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt new file mode 100644 index 000000000000..c88676e713b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 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. + */ + +package com.android.systemui.privacy.logging + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogMessage +import com.android.systemui.log.dagger.PrivacyLog +import javax.inject.Inject + +private const val TAG = "PrivacyLog" + +class PrivacyLogger @Inject constructor( + @PrivacyLog private val buffer: LogBuffer +) { + + fun logUpdatedItemFromAppOps(code: Int, uid: Int, packageName: String, active: Boolean) { + log(LogLevel.INFO, { + int1 = code + int2 = uid + str1 = packageName + bool1 = active + }, { + "App Op: $int1 for $str1($int2), active=$bool1" + }) + } + + fun logUpdatedPrivacyItemsList(listAsString: String) { + log(LogLevel.INFO, { + str1 = listAsString + }, { + "Updated list: $str1" + }) + } + + fun logCurrentProfilesChanged(profiles: List<Int>) { + log(LogLevel.INFO, { + str1 = profiles.toString() + }, { + "Profiles changed: $str1" + }) + } + + fun logChipVisible(visible: Boolean) { + log(LogLevel.INFO, { + bool1 = visible + }, { + "Chip visible: $bool1" + }) + } + + fun logStatusBarIconsVisible( + showCamera: Boolean, + showMichrophone: Boolean, + showLocation: Boolean + ) { + log(LogLevel.INFO, { + bool1 = showCamera + bool2 = showMichrophone + bool3 = showLocation + }, { + "Status bar icons visible: camera=$bool1, microphone=$bool2, location=$bool3" + }) + } + + private inline fun log( + logLevel: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + buffer.log(TAG, logLevel, initializer, printer) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 7e2433a1fd33..8e0e4ac7c8ef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -205,12 +205,11 @@ public class QSContainerImpl extends FrameLayout { mQSPanelContainer.setLayoutParams(layoutParams); mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); - mContentPaddingStart = getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_content_margin_start); - int newPaddingEnd = getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_content_margin_end); - boolean marginsChanged = newPaddingEnd != mContentPaddingEnd; - mContentPaddingEnd = newPaddingEnd; + int padding = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_shade_content_margin_horizontal); + boolean marginsChanged = padding != mContentPaddingStart || padding != mContentPaddingEnd; + mContentPaddingStart = padding; + mContentPaddingEnd = padding; if (marginsChanged) { updatePaddingsAndMargins(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index 44f847c34e2b..32904a21cd3c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -43,6 +43,7 @@ import com.android.systemui.privacy.OngoingPrivacyChip; import com.android.systemui.privacy.PrivacyChipEvent; import com.android.systemui.privacy.PrivacyItem; import com.android.systemui.privacy.PrivacyItemController; +import com.android.systemui.privacy.logging.PrivacyLogger; import com.android.systemui.qs.carrier.QSCarrierGroupController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.settings.UserTracker; @@ -90,6 +91,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private final StatusIconContainer mIconContainer; private final StatusBarIconController.TintedIconManager mIconManager; private final DemoMode mDemoModeReceiver; + private final PrivacyLogger mPrivacyLogger; private boolean mListening; private AlarmClockInfo mNextAlarm; @@ -213,7 +215,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader QSTileHost qsTileHost, StatusBarIconController statusBarIconController, CommandQueue commandQueue, DemoModeController demoModeController, UserTracker userTracker, QuickQSPanelController quickQSPanelController, - QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) { + QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder, + PrivacyLogger privacyLogger) { super(view); mZenModeController = zenModeController; mNextAlarmController = nextAlarmController; @@ -228,6 +231,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mUserTracker = userTracker; mLifecycle = new LifecycleRegistry(mLifecycleOwner); mHeaderQsPanelController = quickQSPanelController; + mPrivacyLogger = privacyLogger; mQSCarrierGroupController = qsCarrierGroupControllerBuilder .setQSCarrierGroup(mView.findViewById(R.id.carrier_group)) @@ -323,6 +327,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private void setChipVisibility(boolean chipVisible) { if (chipVisible && getChipEnabled()) { mPrivacyChip.setVisibility(View.VISIBLE); + mPrivacyLogger.logChipVisible(true); // Makes sure that the chip is logged as viewed at most once each time QS is opened // mListening makes sure that the callback didn't return after the user closed QS if (!mPrivacyChipLogged && mListening) { @@ -330,6 +335,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW); } } else { + mPrivacyLogger.logChipVisible(false); mPrivacyChip.setVisibility(View.GONE); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index c0061ad97293..b2ebf3f700b9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -281,8 +281,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // cancel current pending intent (if any) since clipData isn't used for matching - PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0, - sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivityAsUser( + context, 0, sharingChooserIntent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, + null, UserHandle.CURRENT); // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, @@ -294,7 +296,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mSmartActionsEnabled) .setAction(Intent.ACTION_SEND) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, + UserHandle.SYSTEM); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_share), @@ -323,7 +326,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0, - editIntent, 0, null, UserHandle.CURRENT); + editIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. @@ -338,7 +341,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mSmartActionsEnabled) .setAction(Intent.ACTION_EDIT) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, + UserHandle.SYSTEM); Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); @@ -360,7 +364,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, mSmartActionsEnabled) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); + PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE); Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_delete), r.getString(com.android.internal.R.string.delete), deleteAction); @@ -401,7 +407,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { PendingIntent broadcastIntent = PendingIntent.getBroadcast(context, mRandom.nextInt(), intent, - PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title, broadcastIntent).setContextual(true).addExtras(extras).build()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 160b6f78d9a3..71e1d12b610e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -336,7 +336,6 @@ public class NotificationContentView extends FrameLayout { ? contractedHeader.getPaddingLeft() : paddingEnd, contractedHeader.getPaddingBottom()); - contractedHeader.setShowWorkBadgeAtEnd(false); return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 0fc4c42599cf..5aeacaba6f64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -22,8 +22,10 @@ import android.app.Notification; import android.content.Context; import android.util.ArraySet; import android.view.NotificationHeaderView; +import android.view.NotificationTopLineView; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.widget.FrameLayout; @@ -44,7 +46,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import java.util.Stack; /** - * Wraps a notification header view. + * Wraps a notification view which may or may not include a header. */ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { @@ -56,6 +58,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private CachingIconView mIcon; private NotificationExpandButton mExpandButton; protected NotificationHeaderView mNotificationHeader; + protected NotificationTopLineView mNotificationTopLine; private TextView mHeaderText; private TextView mAppNameText; private ImageView mWorkProfileImage; @@ -107,14 +110,15 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button); mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge); mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header); + mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line); mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon); mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback); } private void addFeedbackOnClickListener(ExpandableNotificationRow row) { View.OnClickListener listener = row.getFeedbackOnClickListener(); - if (mNotificationHeader != null) { - mNotificationHeader.setFeedbackOnClickListener(listener); + if (mNotificationTopLine != null) { + mNotificationTopLine.setFeedbackOnClickListener(listener); } if (mFeedbackIcon != null) { mFeedbackIcon.setOnClickListener(listener); @@ -158,13 +162,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mAppNameText.setTextAppearance( com.android.internal.R.style .TextAppearance_DeviceDefault_Notification_Conversation_AppName); - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams(); layoutParams.setMarginStart(0); } if (mIconContainer != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams(); layoutParams.width = mIconContainer.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.conversation_content_start); @@ -174,8 +176,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { layoutParams.setMarginStart(marginStart * -1); } if (mIcon != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams(); layoutParams.setMarginEnd(0); } } @@ -187,21 +188,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { com.android.internal.R.attr.notificationHeaderTextAppearance, com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info); mAppNameText.setTextAppearance(textAppearance); - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams(); final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.notification_header_app_name_margin_start); layoutParams.setMarginStart(marginStart); } if (mIconContainer != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams(); layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; layoutParams.setMarginStart(0); } if (mIcon != null) { - ViewGroup.MarginLayoutParams layoutParams = - (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams(); final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.notification_header_icon_margin_end); layoutParams.setMarginEnd(marginEnd); @@ -261,6 +259,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { @Override public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) { mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); + mExpandButton.setOnClickListener(expandable ? onClickListener : null); if (mNotificationHeader != null) { mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 6ed092f33d95..d53724159244 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -50,6 +50,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.privacy.PrivacyItem; import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.privacy.PrivacyType; +import com.android.systemui.privacy.logging.PrivacyLogger; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.screenrecord.RecordingController; @@ -145,6 +146,7 @@ public class PhoneStatusBarPolicy private final SensorPrivacyController mSensorPrivacyController; private final RecordingController mRecordingController; private final RingerModeTracker mRingerModeTracker; + private final PrivacyLogger mPrivacyLogger; private boolean mZenVisible; private boolean mVolumeVisible; @@ -172,7 +174,8 @@ public class PhoneStatusBarPolicy @Nullable TelecomManager telecomManager, @DisplayId int displayId, @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil, RingerModeTracker ringerModeTracker, - PrivacyItemController privacyItemController) { + PrivacyItemController privacyItemController, + PrivacyLogger privacyLogger) { mIconController = iconController; mCommandQueue = commandQueue; mBroadcastDispatcher = broadcastDispatcher; @@ -197,6 +200,7 @@ public class PhoneStatusBarPolicy mUiBgExecutor = uiBgExecutor; mTelecomManager = telecomManager; mRingerModeTracker = ringerModeTracker; + mPrivacyLogger = privacyLogger; mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast); mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot); @@ -675,6 +679,7 @@ public class PhoneStatusBarPolicy || mPrivacyItemController.getLocationAvailable()) { mIconController.setIconVisibility(mSlotLocation, showLocation); } + mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone, showLocation); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt index cd94f8444c45..c401fab1e1bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt @@ -23,6 +23,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpsController import com.android.systemui.dump.DumpManager +import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.DeviceConfigProxyFake @@ -65,6 +66,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { private lateinit var dumpManager: DumpManager @Mock private lateinit var userTracker: UserTracker + @Mock + private lateinit var logger: PrivacyLogger private lateinit var privacyItemController: PrivacyItemController private lateinit var executor: FakeExecutor @@ -77,8 +80,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { executor, deviceConfigProxy, userTracker, - dumpManager - ) + logger, + dumpManager) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index 16a11050f9bb..3e834986e383 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -29,10 +29,12 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpItem import com.android.systemui.appops.AppOpsController import com.android.systemui.dump.DumpManager +import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.time.FakeSystemClock import org.hamcrest.Matchers.hasItem import org.hamcrest.Matchers.not @@ -86,6 +88,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { private lateinit var userTracker: UserTracker @Mock private lateinit var dumpManager: DumpManager + @Mock + private lateinit var logger: PrivacyLogger @Captor private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>> @Captor @@ -102,8 +106,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { executor, deviceConfigProxy, userTracker, - dumpManager - ) + logger, + dumpManager) } @Before @@ -300,6 +304,45 @@ class PrivacyItemControllerTest : SysuiTestCase() { verify(callback, never()).onPrivacyItemsChanged(any()) } + @Test + fun testLogActiveChanged() { + privacyItemController.addCallback(callback) + executor.runAllReady() + + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true) + + verify(logger).logUpdatedItemFromAppOps( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true) + } + + @Test + fun testLogListUpdated() { + doReturn(listOf( + AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)) + ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + privacyItemController.addCallback(callback) + executor.runAllReady() + + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true) + executor.runAllReady() + + val expected = PrivacyItem( + PrivacyType.TYPE_LOCATION, + PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID) + ) + + val captor = argumentCaptor<String>() + verify(logger, atLeastOnce()).logUpdatedPrivacyItemsList(capture(captor)) + // Let's look at the last log + val values = captor.allValues + assertTrue(values[values.size - 1].contains(expected.toLog())) + } + private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 226995198843..26b5d26387b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.demomode.DemoModeController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.privacy.PrivacyItemController +import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.settings.UserTracker @@ -86,6 +87,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController @Mock + private lateinit var privacyLogger: PrivacyLogger + @Mock private lateinit var iconContainer: StatusIconContainer @Mock private lateinit var qsCarrierGroup: QSCarrierGroup @@ -123,7 +126,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { demoModeController, userTracker, quickQSPanelController, - qsCarrierGroupControllerBuilder + qsCarrierGroupControllerBuilder, + privacyLogger ) } diff --git a/packages/Tethering/tests/Android.bp b/packages/Tethering/tests/Android.bp new file mode 100644 index 000000000000..cb0a20bdf0e8 --- /dev/null +++ b/packages/Tethering/tests/Android.bp @@ -0,0 +1,23 @@ +// +// Copyright (C) 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. +// + +filegroup { + name: "TetheringTestsJarJarRules", + srcs: ["jarjar-rules.txt"], + visibility: [ + "//frameworks/base/packages/Tethering/tests:__subpackages__", + ] +} diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp index 02bab9ba353e..5765c01c43f3 100644 --- a/packages/Tethering/tests/integration/Android.bp +++ b/packages/Tethering/tests/integration/Android.bp @@ -79,6 +79,7 @@ android_test { // For NetworkStackUtils included in NetworkStackBase "libnetworkstackutilsjni", ], + jarjar_rules: ":TetheringTestsJarJarRules", compile_multilib: "both", manifest: "AndroidManifest_coverage.xml", -}
\ No newline at end of file +} diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/jarjar-rules.txt index 7ed89632a861..c99ff7f81877 100644 --- a/packages/Tethering/tests/unit/jarjar-rules.txt +++ b/packages/Tethering/tests/jarjar-rules.txt @@ -10,7 +10,10 @@ rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.t rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1 +# Classes from net-utils-framework-common +rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1 + # TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains. # TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils. zap android.os.test.TestLooperTest* -zap com.android.test.filters.SelectTestTests*
\ No newline at end of file +zap com.android.test.filters.SelectTestTests* diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp index 04137145a2b0..ef556cf92392 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -68,7 +68,6 @@ java_defaults { "libdexmakerjvmtiagent", "libstaticjvmtiagent", ], - jarjar_rules: "jarjar-rules.txt", } // Library containing the unit tests. This is used by the coverage test target to pull in the @@ -89,6 +88,7 @@ android_test { "device-tests", "mts", ], + jarjar_rules: ":TetheringTestsJarJarRules", defaults: ["TetheringTestsDefaults"], compile_multilib: "both", } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 4c8925bd4828..70cf04522c45 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1122,4 +1122,8 @@ public abstract class PackageManagerInternal { public abstract IncrementalStatesInfo getIncrementalStatesInfo(String packageName, int filterCallingUid, int userId); + /** + * Notifies that a package has crashed or ANR'd. + */ + public abstract void notifyPackageCrashOrAnr(String packageName); } diff --git a/services/core/java/com/android/server/Dumpable.java b/services/core/java/com/android/server/Dumpable.java new file mode 100644 index 000000000000..d2bd66f59b62 --- /dev/null +++ b/services/core/java/com/android/server/Dumpable.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 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. + */ +package com.android.server; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.IndentingPrintWriter; + +/** + * Interface used to dump {@link SystemServer} state that is not associated with any service. + */ +public interface Dumpable { + + /** + * Dumps the state. + */ + void dump(@NonNull IndentingPrintWriter pw, @Nullable String[] args); + + /** + * Gets the name of the dumpable. + * + * <p>If not overridden, will return the simple class name. + */ + default String getDumpableName() { + return Dumpable.this.getClass().getSimpleName(); + } +} diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index 20f68da1592e..d4e912b65e17 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -75,9 +75,9 @@ public class GestureLauncherService extends SystemService { @VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500; /** - * Number of taps required to launch panic ui. + * Number of taps required to launch emergency gesture ui. */ - private static final int PANIC_POWER_TAP_COUNT_THRESHOLD = 5; + private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5; /** * Number of taps required to launch camera shortcut. @@ -138,9 +138,9 @@ public class GestureLauncherService extends SystemService { private boolean mCameraDoubleTapPowerEnabled; /** - * Whether panic button gesture is currently enabled + * Whether emergency gesture is currently enabled */ - private boolean mPanicButtonGestureEnabled; + private boolean mEmergencyGestureEnabled; private long mLastPowerDown; private int mPowerButtonConsecutiveTaps; @@ -178,7 +178,7 @@ public class GestureLauncherService extends SystemService { "GestureLauncherService"); updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); - updatePanicButtonGestureEnabled(); + updateEmergencyGestureEnabled(); mUserId = ActivityManager.getCurrentUser(); mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED)); @@ -197,7 +197,7 @@ public class GestureLauncherService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED), false, mSettingObserver, mUserId); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.PANIC_GESTURE_ENABLED), + Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED), false, mSettingObserver, mUserId); } @@ -225,10 +225,10 @@ public class GestureLauncherService extends SystemService { } @VisibleForTesting - void updatePanicButtonGestureEnabled() { - boolean enabled = isPanicButtonGestureEnabled(mContext, mUserId); + void updateEmergencyGestureEnabled() { + boolean enabled = isEmergencyGestureEnabled(mContext, mUserId); synchronized (this) { - mPanicButtonGestureEnabled = enabled; + mEmergencyGestureEnabled = enabled; } } @@ -357,11 +357,11 @@ public class GestureLauncherService extends SystemService { } /** - * Whether to enable panic button gesture. + * Whether to enable emergency gesture. */ - public static boolean isPanicButtonGestureEnabled(Context context, int userId) { + public static boolean isEmergencyGestureEnabled(Context context, int userId) { return Settings.Secure.getIntForUser(context.getContentResolver(), - Settings.Secure.PANIC_GESTURE_ENABLED, 0, userId) != 0; + Settings.Secure.EMERGENCY_GESTURE_ENABLED, 0, userId) != 0; } /** @@ -409,7 +409,7 @@ public class GestureLauncherService extends SystemService { return false; } boolean launchCamera = false; - boolean launchPanic = false; + boolean launchEmergencyGesture = false; boolean intercept = false; long powerTapInterval; synchronized (this) { @@ -428,15 +428,15 @@ public class GestureLauncherService extends SystemService { mPowerButtonConsecutiveTaps++; mPowerButtonSlowConsecutiveTaps++; } - // Check if we need to launch camera or panic flows - if (mPanicButtonGestureEnabled) { + // Check if we need to launch camera or emergency gesture flows + if (mEmergencyGestureEnabled) { // Commit to intercepting the powerkey event after the second "quick" tap to avoid - // lockscreen changes between launching camera and the panic flow. + // lockscreen changes between launching camera and the emergency gesture flow. if (mPowerButtonConsecutiveTaps > 1) { intercept = interactive; } - if (mPowerButtonConsecutiveTaps == PANIC_POWER_TAP_COUNT_THRESHOLD) { - launchPanic = true; + if (mPowerButtonConsecutiveTaps == EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD) { + launchEmergencyGesture = true; } } if (mCameraDoubleTapPowerEnabled @@ -461,18 +461,18 @@ public class GestureLauncherService extends SystemService { mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) powerTapInterval); } - } else if (launchPanic) { - Slog.i(TAG, "Panic gesture detected, launching panic."); - launchPanic = handlePanicButtonGesture(); + } else if (launchEmergencyGesture) { + Slog.i(TAG, "Emergency gesture detected, launching."); + launchEmergencyGesture = handleEmergencyGesture(); // TODO(b/160006048): Add logging } mMetricsLogger.histogram("power_consecutive_short_tap_count", mPowerButtonSlowConsecutiveTaps); mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval); - outLaunched.value = launchCamera || launchPanic; - // Intercept power key event if the press is part of a gesture (camera, panic) and the user - // has completed setup. + outLaunched.value = launchCamera || launchEmergencyGesture; + // Intercept power key event if the press is part of a gesture (camera, eGesture) and the + // user has completed setup. return intercept && isUserSetupComplete(); } @@ -512,27 +512,25 @@ public class GestureLauncherService extends SystemService { } /** - * @return true if panic ui was launched, false otherwise. + * @return true if emergency gesture UI was launched, false otherwise. */ @VisibleForTesting - boolean handlePanicButtonGesture() { - // TODO(b/160006048): This is the wrong way to launch panic ui. Rewrite this to go - // through SysUI + boolean handleEmergencyGesture() { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - "GestureLauncher:handlePanicButtonGesture"); + "GestureLauncher:handleEmergencyGesture"); try { boolean userSetupComplete = isUserSetupComplete(); if (!userSetupComplete) { if (DBG) { Slog.d(TAG, String.format( - "userSetupComplete = %s, ignoring panic gesture.", + "userSetupComplete = %s, ignoring emergency gesture.", userSetupComplete)); } return false; } if (DBG) { Slog.d(TAG, String.format( - "userSetupComplete = %s, performing panic gesture.", + "userSetupComplete = %s, performing emergency gesture.", userSetupComplete)); } StatusBarManagerInternal service = LocalServices.getService( @@ -558,7 +556,7 @@ public class GestureLauncherService extends SystemService { registerContentObservers(); updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); - updatePanicButtonGestureEnabled(); + updateEmergencyGestureEnabled(); } } }; @@ -568,7 +566,7 @@ public class GestureLauncherService extends SystemService { if (userId == mUserId) { updateCameraRegistered(); updateCameraDoubleTapPowerEnabled(); - updatePanicButtonGestureEnabled(); + updateEmergencyGestureEnabled(); } } }; diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java index c0611374679b..c23f1cab0614 100644 --- a/services/core/java/com/android/server/SystemServerInitThreadPool.java +++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java @@ -19,6 +19,7 @@ package com.android.server; import android.annotation.NonNull; import android.os.Build; import android.os.Process; +import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -44,7 +45,7 @@ import java.util.concurrent.TimeUnit; * * @hide */ -public class SystemServerInitThreadPool { +public final class SystemServerInitThreadPool implements Dumpable { private static final String TAG = SystemServerInitThreadPool.class.getSimpleName(); private static final int SHUTDOWN_TIMEOUT_MILLIS = 20000; private static final boolean IS_DEBUGGABLE = Build.IS_DEBUGGABLE; @@ -53,6 +54,7 @@ public class SystemServerInitThreadPool { @GuardedBy("LOCK") private static SystemServerInitThreadPool sInstance; + private final int mSize; // used by dump() only private final ExecutorService mService; @GuardedBy("mPendingTasks") @@ -62,9 +64,9 @@ public class SystemServerInitThreadPool { private boolean mShutDown; private SystemServerInitThreadPool() { - final int size = Runtime.getRuntime().availableProcessors(); - Slog.i(TAG, "Creating instance with " + size + " threads"); - mService = ConcurrentUtils.newFixedThreadPool(size, + mSize = Runtime.getRuntime().availableProcessors(); + Slog.i(TAG, "Creating instance with " + mSize + " threads"); + mService = ConcurrentUtils.newFixedThreadPool(mSize, "system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND); } @@ -123,11 +125,13 @@ public class SystemServerInitThreadPool { * * @throws IllegalStateException if it has been started already without being shut down yet. */ - static void start() { + static SystemServerInitThreadPool start() { + SystemServerInitThreadPool instance; synchronized (LOCK) { Preconditions.checkState(sInstance == null, TAG + " already started"); - sInstance = new SystemServerInitThreadPool(); + instance = sInstance = new SystemServerInitThreadPool(); } + return instance; } /** @@ -190,4 +194,22 @@ public class SystemServerInitThreadPool { ActivityManagerService.dumpStackTraces(pids, null, null, Watchdog.getInterestingNativePids(), null); } + + @Override + public void dump(IndentingPrintWriter pw, String[] args) { + synchronized (LOCK) { + pw.printf("has instance: %b\n", (sInstance != null)); + } + pw.printf("number of threads: %d\n", mSize); + pw.printf("service: %s\n", mService); + synchronized (mPendingTasks) { + pw.printf("is shutdown: %b\n", mShutDown); + final int pendingTasks = mPendingTasks.size(); + if (pendingTasks == 0) { + pw.println("no pending tasks"); + } else { + pw.printf("%d pending tasks: %s\n", pendingTasks, mPendingTasks); + } + } + } } diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index 84d01ec3598d..6c81de6af402 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -200,21 +200,21 @@ public abstract class SystemService { /** * @hide */ - public void dump(@NonNull StringBuilder builder) { - builder.append(getUserIdentifier()); + public void dump(@NonNull PrintWriter pw) { + pw.print(getUserIdentifier()); if (!isFull() && !isManagedProfile()) return; - builder.append('('); + pw.print('('); boolean addComma = false; if (isFull()) { - builder.append("full"); + pw.print("full"); } if (isManagedProfile()) { - if (addComma) builder.append(','); - builder.append("mp"); + if (addComma) pw.print(','); + pw.print("mp"); } - builder.append(')'); + pw.print(')'); } } diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index ff2661b19b48..71a18218110e 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -27,6 +27,7 @@ import android.os.Trace; import android.os.UserManagerInternal; import android.util.ArrayMap; import android.util.EventLog; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; @@ -49,7 +50,7 @@ import java.util.ArrayList; * * {@hide} */ -public final class SystemServiceManager { +public final class SystemServiceManager implements Dumpable { private static final String TAG = SystemServiceManager.class.getSimpleName(); private static final boolean DEBUG = false; private static final int SERVICE_CALL_WARN_TIME_MS = 50; @@ -489,31 +490,39 @@ public final class SystemServiceManager { return sSystemDir; } - /** - * Outputs the state of this manager to the System log. - */ - public void dump() { - StringBuilder builder = new StringBuilder(); - builder.append("Current phase: ").append(mCurrentPhase).append('\n'); - builder.append("Services:\n"); - final int startedLen = mServices.size(); - for (int i = 0; i < startedLen; i++) { - final SystemService service = mServices.get(i); - builder.append("\t") - .append(service.getClass().getSimpleName()) - .append("\n"); - } + @Override + public void dump(IndentingPrintWriter pw, String[] args) { + pw.printf("Current phase: %d\n", mCurrentPhase); synchronized (mTargetUsers) { - builder.append("Current user: ").append(mCurrentUser).append('\n'); - builder.append("Target users: "); + if (mCurrentUser != null) { + pw.print("Current user: "); mCurrentUser.dump(pw); pw.println(); + } else { + pw.println("Current user not set!"); + } + final int targetUsersSize = mTargetUsers.size(); - for (int i = 0; i < targetUsersSize; i++) { - mTargetUsers.valueAt(i).dump(builder); - if (i != targetUsersSize - 1) builder.append(','); + if (targetUsersSize > 0) { + pw.printf("%d target users: ", targetUsersSize); + for (int i = 0; i < targetUsersSize; i++) { + mTargetUsers.valueAt(i).dump(pw); + if (i != targetUsersSize - 1) pw.print(", "); + } + pw.println(); + } else { + pw.println("No target users"); } - builder.append('\n'); } - - Slog.e(TAG, builder.toString()); + final int startedLen = mServices.size(); + if (startedLen > 0) { + pw.printf("%d started services:\n", startedLen); + pw.increaseIndent(); + for (int i = 0; i < startedLen; i++) { + final SystemService service = mServices.get(i); + pw.println(service.getClass().getCanonicalName()); + } + pw.decreaseIndent(); + } else { + pw.println("No started services"); + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 32d95f594ce9..51cbfcf64322 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7595,6 +7595,10 @@ public class ActivityManagerService extends IActivityManager.Stub eventType, r, processName, null, null, null, null, null, null, crashInfo); mAppErrors.crashApplication(r, crashInfo); + // Notify package manager service to possibly update package state + if (r != null && r.info != null && r.info.packageName != null) { + mPackageManagerInt.notifyPackageCrashOrAnr(r.info.packageName); + } } public void handleApplicationStrictModeViolation( diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 53c6758585cf..ccdd6a746239 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1764,6 +1764,12 @@ class ProcessRecord implements WindowProcessListener { makeAppNotRespondingLocked(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR", info.toString()); + // Notify package manager service to possibly update package state + if (aInfo != null && aInfo.packageName != null) { + mService.getPackageManagerInternalLocked().notifyPackageCrashOrAnr( + aInfo.packageName); + } + // mUiHandler can be null if the AMS is constructed with injector only. This will only // happen in tests. if (mService.mUiHandler != null) { diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING new file mode 100644 index 000000000000..36acc3c7344d --- /dev/null +++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsBiometricsTestCases" + } + ] +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 5dcadee20e13..9ac12ed11ded 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -52,11 +52,10 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.EventLog; import android.util.Pair; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import android.view.Surface; import com.android.internal.R; @@ -92,55 +91,6 @@ public class FingerprintService extends SystemService { private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; private final LockPatternUtils mLockPatternUtils; @NonNull private List<ServiceProvider> mServiceProviders; - @NonNull private final ArrayMap<Integer, TestSession> mTestSessions; - - private final class TestSession extends ITestSession.Stub { - private final int mSensorId; - - TestSession(int sensorId) { - mSensorId = sensorId; - } - - @Override - public void enableTestHal(boolean enableTestHal) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - - @Override - public void startEnroll(int userId) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - - @Override - public void finishEnroll(int userId) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - - @Override - public void acceptAuthentication(int userId) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - - @Override - public void rejectAuthentication(int userId) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - - @Override - public void notifyAcquired(int userId, int acquireInfo) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - - @Override - public void notifyError(int userId, int errorCode) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - - @Override - public void cleanupInternalState(int userId) { - Utils.checkPermission(getContext(), TEST_BIOMETRIC); - } - } /** * Receives the incoming binder calls from FingerprintManager. @@ -150,20 +100,22 @@ public class FingerprintService extends SystemService { public ITestSession createTestSession(int sensorId, String opPackageName) { Utils.checkPermission(getContext(), TEST_BIOMETRIC); - final TestSession session; - synchronized (mTestSessions) { - if (!mTestSessions.containsKey(sensorId)) { - mTestSessions.put(sensorId, new TestSession(sensorId)); + for (ServiceProvider provider : mServiceProviders) { + if (provider.containsSensor(sensorId)) { + return provider.createTestSession(sensorId, opPackageName); } - session = mTestSessions.get(sensorId); } - return session; + + return null; } @Override // Binder call public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal( String opPackageName) { - Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + if (getContext().checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL) + != PackageManager.PERMISSION_GRANTED) { + Utils.checkPermission(getContext(), TEST_BIOMETRIC); + } final List<FingerprintSensorPropertiesInternal> properties = FingerprintService.this.getSensorProperties(); @@ -424,12 +376,28 @@ public class FingerprintService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { - for (ServiceProvider provider : mServiceProviders) { - for (FingerprintSensorPropertiesInternal props : - provider.getSensorProperties()) { - if (args.length > 0 && "--proto".equals(args[0])) { - provider.dumpProto(props.sensorId, fd); - } else { + if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + for (ServiceProvider provider : mServiceProviders) { + for (FingerprintSensorPropertiesInternal props + : provider.getSensorProperties()) { + provider.dumpProtoState(props.sensorId, proto); + } + } + proto.flush(); + } else if (args.length > 0 && "--proto".equals(args[0])) { + for (ServiceProvider provider : mServiceProviders) { + for (FingerprintSensorPropertiesInternal props + : provider.getSensorProperties()) { + provider.dumpProtoMetrics(props.sensorId, fd); + } + } + } else { + for (ServiceProvider provider : mServiceProviders) { + for (FingerprintSensorPropertiesInternal props + : provider.getSensorProperties()) { + pw.println("Dumping for sensorId: " + props.sensorId + + ", provider: " + provider.getClass().getSimpleName()); provider.dumpInternal(props.sensorId, pw); } } @@ -622,7 +590,6 @@ public class FingerprintService extends SystemService { mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); mServiceProviders = new ArrayList<>(); - mTestSessions = new ArrayMap<>(); initializeAidlHals(); } @@ -648,7 +615,7 @@ public class FingerprintService extends SystemService { try { final SensorProps[] props = fp.getSensorProps(); final FingerprintProvider provider = - new FingerprintProvider(getContext(), props, fqName, + new FingerprintProvider(getContext(), props, instance, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); mServiceProviders.add(provider); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index c2315fdd4ccc..1ed66a247bd0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -19,11 +19,13 @@ package com.android.server.biometrics.sensors.fingerprint; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.fingerprint.Fingerprint; +import android.hardware.biometrics.ITestSession; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; +import android.util.proto.ProtoOutputStream; import android.view.Surface; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -110,7 +112,11 @@ public interface ServiceProvider { void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller); - void dumpProto(int sensorId, @NonNull FileDescriptor fd); + void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto); + + void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd); void dumpInternal(int sensorId, @NonNull PrintWriter pw); + + @NonNull ITestSession createTestSession(int sensorId, @NonNull String opPackageName); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java new file mode 100644 index 000000000000..6bb40e6630bf --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static android.Manifest.permission.TEST_BIOMETRIC; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.ITestSession; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.Binder; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * A test session implementation for {@link FingerprintProvider}. See + * {@link android.hardware.biometrics.BiometricTestSession}. + */ +class BiometricTestSessionImpl extends ITestSession.Stub { + + private static final String TAG = "BiometricTestSessionImpl"; + + @NonNull private final Context mContext; + private final int mSensorId; + @NonNull private final FingerprintProvider mProvider; + @NonNull private final Sensor mSensor; + @NonNull private final Set<Integer> mEnrollmentIds; + @NonNull private final Random mRandom; + + /** + * Internal receiver currently only used for enroll. Results do not need to be forwarded to the + * test, since enrollment is a platform-only API. The authentication path is tested through + * the public FingerprintManager APIs and does not use this receiver. + */ + private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() { + @Override + public void onEnrollResult(Fingerprint fp, int remaining) { + + } + + @Override + public void onAcquired(int acquiredInfo, int vendorCode) { + + } + + @Override + public void onAuthenticationSucceeded(Fingerprint fp, int userId, + boolean isStrongBiometric) { + + } + + @Override + public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) { + + } + + @Override + public void onAuthenticationFailed() { + + } + + @Override + public void onError(int error, int vendorCode) { + + } + + @Override + public void onRemoved(Fingerprint fp, int remaining) { + + } + + @Override + public void onChallengeGenerated(int sensorId, long challenge) { + + } + }; + + BiometricTestSessionImpl(@NonNull Context context, int sensorId, + @NonNull FingerprintProvider provider, @NonNull Sensor sensor) { + mContext = context; + mSensorId = sensorId; + mProvider = provider; + mSensor = sensor; + mEnrollmentIds = new HashSet<>(); + mRandom = new Random(); + } + + @Override + public void setTestHalEnabled(boolean enabled) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mProvider.setTestHalEnabled(enabled); + mSensor.setTestHalEnabled(enabled); + } + + @Override + public void startEnroll(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, + mContext.getOpPackageName(), null /* surface */); + } + + @Override + public void finishEnroll(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + int nextRandomId = mRandom.nextInt(); + while (mEnrollmentIds.contains(nextRandomId)) { + nextRandomId = mRandom.nextInt(); + } + + mEnrollmentIds.add(nextRandomId); + mSensor.getSessionForUser(userId).mHalSessionCallback + .onEnrollmentProgress(nextRandomId, 0 /* remaining */); + } + + @Override + public void acceptAuthentication(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + // Fake authentication with any of the existing fingers + List<Fingerprint> fingerprints = FingerprintUtils.getInstance() + .getBiometricsForUser(mContext, userId); + if (fingerprints.isEmpty()) { + Slog.w(TAG, "No fingerprints, returning"); + return; + } + final int fid = fingerprints.get(0).getBiometricId(); + mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationSucceeded(fid, + HardwareAuthTokenUtils.toHardwareAuthToken(new byte[69])); + } + + @Override + public void rejectAuthentication(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed(); + } + + @Override + public void notifyAcquired(int userId, int acquireInfo) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mSensor.getSessionForUser(userId).mHalSessionCallback + .onAcquired((byte) acquireInfo, 0 /* vendorCode */); + } + + @Override + public void notifyError(int userId, int errorCode) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mSensor.getSessionForUser(userId).mHalSessionCallback.onError((byte) errorCode, + 0 /* vendorCode */); + } + + @Override + public void cleanupInternalState(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mProvider.scheduleInternalCleanup(mSensorId, userId); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java index fec3cff6d52f..2ad1fa306781 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java @@ -47,6 +47,11 @@ class FingerprintGetAuthenticatorIdClient extends ClientMonitor<ISession> { // Nothing to do here } + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + @Override protected void startHalOperation() { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index d713f981b451..4d07f583fdf5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -24,6 +24,7 @@ import android.app.IActivityTaskManager; import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; +import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.fingerprint.Fingerprint; @@ -38,6 +39,7 @@ import android.os.ServiceManager; import android.os.UserManager; import android.util.Slog; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import android.view.Surface; import com.android.server.biometrics.Utils; @@ -62,6 +64,8 @@ import java.util.List; @SuppressWarnings("deprecation") public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider { + private boolean mTestHalEnabled; + @NonNull private final Context mContext; @NonNull private final String mHalInstanceName; @NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports @@ -132,7 +136,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi prop.commonProps.maxEnrollmentsPerUser, prop.sensorType, true /* resetLockoutRequiresHardwareAuthToken */); - final Sensor sensor = new Sensor(getTag() + "/" + sensorId, mContext, mHandler, + final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler, internalProp, gestureAvailabilityDispatcher); mSensors.put(sensorId, sensor); @@ -146,6 +150,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Nullable private synchronized IFingerprint getHalInstance() { + if (mTestHalEnabled) { + // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables + // the test HAL for all sensors under that HAL. This can be updated in the future if + // necessary. + return new TestHal(); + } + if (mDaemon != null) { return mDaemon; } @@ -153,7 +164,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi Slog.d(getTag(), "Daemon was null, reconnecting"); mDaemon = IFingerprint.Stub.asInterface( - ServiceManager.waitForDeclaredService(mHalInstanceName)); + ServiceManager.waitForDeclaredService(IFingerprint.DESCRIPTOR + + "/" + mHalInstanceName)); if (mDaemon == null) { Slog.e(getTag(), "Unable to get daemon"); return null; @@ -561,7 +573,14 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public void dumpProto(int sensorId, @NonNull FileDescriptor fd) { + public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + if (mSensors.contains(sensorId)) { + mSensors.get(sensorId).dumpProtoState(sensorId, proto); + } + } + + @Override + public void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd) { } @@ -570,6 +589,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } + @NonNull + @Override + public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { + return mSensors.get(sensorId).createTestSession(); + } + @Override public void binderDied() { Slog.e(getTag(), "HAL died"); @@ -582,4 +607,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } }); } + + void setTestHalEnabled(boolean enabled) { + mTestHalEnabled = enabled; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index d4ce896d42ec..51c30b68e0c5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -19,7 +19,9 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.fingerprint.Error; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; @@ -31,11 +33,16 @@ import android.hardware.keymaster.HardwareAuthToken; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserManager; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.fingerprint.FingerprintServiceStateProto; +import com.android.server.biometrics.fingerprint.SensorStateProto; +import com.android.server.biometrics.fingerprint.UserStateProto; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BiometricScheduler; @@ -48,9 +55,7 @@ import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -59,7 +64,11 @@ import java.util.Map; */ @SuppressWarnings("deprecation") class Sensor implements IBinder.DeathRecipient { + + private boolean mTestHalEnabled; + @NonNull private final String mTag; + @NonNull private final FingerprintProvider mProvider; @NonNull private final Context mContext; @NonNull private final Handler mHandler; @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; @@ -91,320 +100,377 @@ class Sensor implements IBinder.DeathRecipient { }); } - private static class Session { + static class Session { @NonNull private final String mTag; @NonNull private final ISession mSession; private final int mUserId; - private final ISessionCallback mSessionCallback; + @NonNull final HalSessionCallback mHalSessionCallback; Session(@NonNull String tag, @NonNull ISession session, int userId, - @NonNull ISessionCallback sessionCallback) { + @NonNull HalSessionCallback halSessionCallback) { mTag = tag; mSession = session; mUserId = userId; - mSessionCallback = sessionCallback; + mHalSessionCallback = halSessionCallback; Slog.d(mTag, "New session created for user: " + userId); } } - Sensor(@NonNull String tag, @NonNull Context context, @NonNull Handler handler, - @NonNull FingerprintSensorPropertiesInternal sensorProperties, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - mTag = tag; - mContext = context; - mHandler = handler; - mSensorProperties = sensorProperties; - mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher); - mLockoutCache = new LockoutCache(); - mAuthenticatorIds = new HashMap<>(); - mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null; - } - - @NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() { - return mLazySession; - } - - @NonNull FingerprintSensorPropertiesInternal getSensorProperties() { - return mSensorProperties; - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean hasSessionForUser(int userId) { - return mCurrentSession != null && mCurrentSession.mUserId == userId; - } - - void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId) - throws RemoteException { - final ISessionCallback callback = new ISessionCallback.Stub() { - @Override - public void onStateChanged(int cookie, byte state) { - // TODO(b/162973174) - } - - @Override - public void onChallengeGenerated(long challenge) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGenerateChallengeClient)) { - Slog.e(mTag, "onChallengeGenerated for wrong client: " - + Utils.getClientName(client)); - return; - } - - final FingerprintGenerateChallengeClient generateChallengeClient = - (FingerprintGenerateChallengeClient) client; - generateChallengeClient.onChallengeGenerated(sensorId, userId, challenge); - }); - } + static class HalSessionCallback extends ISessionCallback.Stub { - @Override - public void onChallengeRevoked(long challenge) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintRevokeChallengeClient)) { - Slog.e(mTag, "onChallengeRevoked for wrong client: " - + Utils.getClientName(client)); - return; - } + /** + * Interface to sends results to the HalSessionCallback's owner. + */ + public interface Callback { + /** + * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. + */ + void onHardwareUnavailable(); + } - final FingerprintRevokeChallengeClient revokeChallengeClient = - (FingerprintRevokeChallengeClient) client; - revokeChallengeClient.onChallengeRevoked(sensorId, userId, challenge); - }); - } + @NonNull private final Context mContext; + @NonNull private final Handler mHandler; + @NonNull private final String mTag; + @NonNull private final BiometricScheduler mScheduler; + private final int mSensorId; + private final int mUserId; + @NonNull private final Callback mCallback; - @Override - public void onAcquired(byte info, int vendorCode) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof AcquisitionClient)) { - Slog.e(mTag, "onAcquired for non-acquisition client: " - + Utils.getClientName(client)); - return; - } + HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, + @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull Callback callback) { + mContext = context; + mHandler = handler; + mTag = tag; + mScheduler = scheduler; + mSensorId = sensorId; + mUserId = userId; + mCallback = callback; + } - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(info, vendorCode); - }); - } + @Override + public void onStateChanged(int cookie, byte state) { + // TODO(b/162973174) + } - @Override - public void onError(byte error, int vendorCode) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - Slog.d(mTag, "onError" - + ", client: " + Utils.getClientName(client) - + ", error: " + error - + ", vendorCode: " + vendorCode); - if (!(client instanceof Interruptable)) { - Slog.e(mTag, "onError for non-error consumer: " - + Utils.getClientName(client)); - return; - } + @Override + public void onChallengeGenerated(long challenge) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FingerprintGenerateChallengeClient)) { + Slog.e(mTag, "onChallengeGenerated for wrong client: " + + Utils.getClientName(client)); + return; + } + + final FingerprintGenerateChallengeClient generateChallengeClient = + (FingerprintGenerateChallengeClient) client; + generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); + }); + } - final Interruptable interruptable = (Interruptable) client; - interruptable.onError(error, vendorCode); + @Override + public void onChallengeRevoked(long challenge) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FingerprintRevokeChallengeClient)) { + Slog.e(mTag, "onChallengeRevoked for wrong client: " + + Utils.getClientName(client)); + return; + } + + final FingerprintRevokeChallengeClient revokeChallengeClient = + (FingerprintRevokeChallengeClient) client; + revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); + }); + } - if (error == Error.HW_UNAVAILABLE) { - Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); - mCurrentSession = null; - } - }); - } + @Override + public void onAcquired(byte info, int vendorCode) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof AcquisitionClient)) { + Slog.e(mTag, "onAcquired for non-acquisition client: " + + Utils.getClientName(client)); + return; + } + + final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; + acquisitionClient.onAcquired(info, vendorCode); + }); + } - @Override - public void onEnrollmentProgress(int enrollmentId, int remaining) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintEnrollClient)) { - Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " - + Utils.getClientName(client)); - return; - } + @Override + public void onError(byte error, int vendorCode) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + Slog.d(mTag, "onError" + + ", client: " + Utils.getClientName(client) + + ", error: " + error + + ", vendorCode: " + vendorCode); + if (!(client instanceof Interruptable)) { + Slog.e(mTag, "onError for non-error consumer: " + + Utils.getClientName(client)); + return; + } - final int currentUserId = client.getTargetUserId(); - final CharSequence name = FingerprintUtils.getInstance(sensorId) - .getUniqueName(mContext, currentUserId); - final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, sensorId); + final Interruptable interruptable = (Interruptable) client; + interruptable.onError(error, vendorCode); - final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; - enrollClient.onEnrollResult(fingerprint, remaining); - }); - } + if (error == Error.HW_UNAVAILABLE) { + mCallback.onHardwareUnavailable(); + } + }); + } - @Override - public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } + @Override + public void onEnrollmentProgress(int enrollmentId, int remaining) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FingerprintEnrollClient)) { + Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " + + Utils.getClientName(client)); + return; + } + + final int currentUserId = client.getTargetUserId(); + final CharSequence name = FingerprintUtils.getInstance(mSensorId) + .getUniqueName(mContext, currentUserId); + final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId); + + final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; + enrollClient.onEnrollResult(fingerprint, remaining); + }); + } - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", enrollmentId, sensorId); - final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); - final ArrayList<Byte> byteList = new ArrayList<>(); - for (byte b : byteArray) { - byteList.add(b); - } + @Override + public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof AuthenticationConsumer)) { + Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " + + Utils.getClientName(client)); + return; + } + + final AuthenticationConsumer authenticationConsumer = + (AuthenticationConsumer) client; + final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); + final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); + final ArrayList<Byte> byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + + authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList); + }); + } - authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList); - }); - } + @Override + public void onAuthenticationFailed() { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof AuthenticationConsumer)) { + Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " + + Utils.getClientName(client)); + return; + } + + final AuthenticationConsumer authenticationConsumer = + (AuthenticationConsumer) client; + final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); + authenticationConsumer + .onAuthenticated(fp, false /* authenticated */, null /* hat */); + }); + } - @Override - public void onAuthenticationFailed() { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof AuthenticationConsumer)) { - Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " - + Utils.getClientName(client)); - return; - } + @Override + public void onLockoutTimed(long durationMillis) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof LockoutConsumer)) { + Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " + + Utils.getClientName(client)); + return; + } + + final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; + lockoutConsumer.onLockoutTimed(durationMillis); + }); + } - final AuthenticationConsumer authenticationConsumer = - (AuthenticationConsumer) client; - final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, sensorId); - authenticationConsumer - .onAuthenticated(fp, false /* authenticated */, null /* hat */); - }); - } + @Override + public void onLockoutPermanent() { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof LockoutConsumer)) { + Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " + + Utils.getClientName(client)); + return; + } + + final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; + lockoutConsumer.onLockoutPermanent(); + }); + } - @Override - public void onLockoutTimed(long durationMillis) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " - + Utils.getClientName(client)); - return; - } + @Override + public void onLockoutCleared() { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FingerprintResetLockoutClient)) { + Slog.e(mTag, "onLockoutCleared for non-resetLockout client: " + + Utils.getClientName(client)); + return; + } + + final FingerprintResetLockoutClient resetLockoutClient = + (FingerprintResetLockoutClient) client; + resetLockoutClient.onLockoutCleared(); + }); + } - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutTimed(durationMillis); - }); - } + @Override + public void onInteractionDetected() { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FingerprintDetectClient)) { + Slog.e(mTag, "onInteractionDetected for non-detect client: " + + Utils.getClientName(client)); + return; + } + + final FingerprintDetectClient fingerprintDetectClient = + (FingerprintDetectClient) client; + fingerprintDetectClient.onInteractionDetected(); + }); + } - @Override - public void onLockoutPermanent() { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof LockoutConsumer)) { - Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " - + Utils.getClientName(client)); - return; + @Override + public void onEnrollmentsEnumerated(int[] enrollmentIds) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof EnumerateConsumer)) { + Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " + + Utils.getClientName(client)); + return; + } + + final EnumerateConsumer enumerateConsumer = + (EnumerateConsumer) client; + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1); } + } else { + enumerateConsumer.onEnumerationResult(null /* identifier */, 0); + } + }); + } - final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; - lockoutConsumer.onLockoutPermanent(); - }); - } - - @Override - public void onLockoutCleared() { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintResetLockoutClient)) { - Slog.e(mTag, "onLockoutCleared for non-resetLockout client: " - + Utils.getClientName(client)); - return; + @Override + public void onEnrollmentsRemoved(int[] enrollmentIds) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof RemovalConsumer)) { + Slog.e(mTag, "onRemoved for non-removal consumer: " + + Utils.getClientName(client)); + return; + } + + final RemovalConsumer removalConsumer = (RemovalConsumer) client; + if (enrollmentIds.length > 0) { + for (int i = 0; i < enrollmentIds.length; i++) { + final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); + removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1); } + } else { + removalConsumer.onRemoved(null, 0); + } + }); + } - final FingerprintResetLockoutClient resetLockoutClient = - (FingerprintResetLockoutClient) client; - resetLockoutClient.onLockoutCleared(); - }); - } + @Override + public void onAuthenticatorIdRetrieved(long authenticatorId) { + mHandler.post(() -> { + final ClientMonitor<?> client = mScheduler.getCurrentClient(); + if (!(client instanceof FingerprintGetAuthenticatorIdClient)) { + Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " + + Utils.getClientName(client)); + return; + } + + final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient = + (FingerprintGetAuthenticatorIdClient) client; + getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); + }); + } - @Override - public void onInteractionDetected() { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintDetectClient)) { - Slog.e(mTag, "onInteractionDetected for non-detect client: " - + Utils.getClientName(client)); - return; - } + @Override + public void onAuthenticatorIdInvalidated() { + // TODO(159667191) + } + } - final FingerprintDetectClient fingerprintDetectClient = - (FingerprintDetectClient) client; - fingerprintDetectClient.onInteractionDetected(); - }); + Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, + @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { + mTag = tag; + mProvider = provider; + mContext = context; + mHandler = handler; + mSensorProperties = sensorProperties; + mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher); + mLockoutCache = new LockoutCache(); + mAuthenticatorIds = new HashMap<>(); + mLazySession = () -> { + if (mTestHalEnabled) { + return new TestSession(mCurrentSession.mHalSessionCallback); + } else { + return mCurrentSession != null ? mCurrentSession.mSession : null; } + }; + } - @Override - public void onEnrollmentsEnumerated(int[] enrollmentIds) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof EnumerateConsumer)) { - Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " - + Utils.getClientName(client)); - return; - } + @NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() { + return mLazySession; + } - final EnumerateConsumer enumerateConsumer = - (EnumerateConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], sensorId); - enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1); - } - } else { - enumerateConsumer.onEnumerationResult(null /* identifier */, 0); - } - }); - } + @NonNull FingerprintSensorPropertiesInternal getSensorProperties() { + return mSensorProperties; + } - @Override - public void onEnrollmentsRemoved(int[] enrollmentIds) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof RemovalConsumer)) { - Slog.e(mTag, "onRemoved for non-removal consumer: " - + Utils.getClientName(client)); - return; - } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + boolean hasSessionForUser(int userId) { + return mCurrentSession != null && mCurrentSession.mUserId == userId; + } - final RemovalConsumer removalConsumer = (RemovalConsumer) client; - if (enrollmentIds.length > 0) { - for (int i = 0; i < enrollmentIds.length; i++) { - final Fingerprint fp = new Fingerprint("", enrollmentIds[i], sensorId); - removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1); - } - } else { - removalConsumer.onRemoved(null, 0); - } - }); - } + @Nullable Session getSessionForUser(int userId) { + if (mCurrentSession != null && mCurrentSession.mUserId == userId) { + return mCurrentSession; + } else { + return null; + } + } - @Override - public void onAuthenticatorIdRetrieved(long authenticatorId) { - mHandler.post(() -> { - final ClientMonitor<?> client = mScheduler.getCurrentClient(); - if (!(client instanceof FingerprintGetAuthenticatorIdClient)) { - Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " - + Utils.getClientName(client)); - return; - } + @NonNull ITestSession createTestSession() { + return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this); + } - final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient = - (FingerprintGetAuthenticatorIdClient) client; - getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); - }); - } + void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId) + throws RemoteException { - @Override - public void onAuthenticatorIdInvalidated() { - // TODO(159667191) - } + final HalSessionCallback.Callback callback = () -> { + Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); + mCurrentSession = null; }; + final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler, + mTag, mScheduler, sensorId, userId, callback); - final ISession newSession = daemon.createSession(sensorId, userId, callback); + final ISession newSession = daemon.createSession(sensorId, userId, resultController); newSession.asBinder().linkToDeath(this, 0 /* flags */); - mCurrentSession = new Session(mTag, newSession, userId, callback); + mCurrentSession = new Session(mTag, newSession, userId, resultController); } @NonNull BiometricScheduler getScheduler() { @@ -418,4 +484,27 @@ class Sensor implements IBinder.DeathRecipient { @NonNull Map<Integer, Long> getAuthenticatorIds() { return mAuthenticatorIds; } + + void setTestHalEnabled(boolean enabled) { + mTestHalEnabled = enabled; + } + + void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + final long sensorToken = proto.start(FingerprintServiceStateProto.SENSOR_STATES); + + proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId); + proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null); + + for (UserInfo user : UserManager.get(mContext).getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + final long userToken = proto.start(SensorStateProto.USER_STATES); + proto.write(UserStateProto.USER_ID, userId); + proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getInstance() + .getBiometricsForUser(mContext, userId).size()); + proto.end(userToken); + } + + proto.end(sensorToken); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java new file mode 100644 index 000000000000..8c9a269486bb --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.fingerprint.IFingerprint; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.hardware.biometrics.fingerprint.SensorProps; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.Binder; +import android.os.IBinder; + +/** + * Test HAL that provides only provides no-ops. + */ +public class TestHal extends IFingerprint.Stub { + @Override + public SensorProps[] getSensorProps() { + return new SensorProps[0]; + } + + @Override + public ISession createSession(int sensorId, int userId, ISessionCallback cb) { + return new ISession() { + @Override + public void generateChallenge(int cookie, int timeoutSec) { + + } + + @Override + public void revokeChallenge(int cookie, long challenge) { + + } + + @Override + public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) { + return null; + } + + @Override + public ICancellationSignal authenticate(int cookie, long operationId) { + return null; + } + + @Override + public ICancellationSignal detectInteraction(int cookie) { + return null; + } + + @Override + public void enumerateEnrollments(int cookie) { + + } + + @Override + public void removeEnrollments(int cookie, int[] enrollmentIds) { + + } + + @Override + public void getAuthenticatorId(int cookie) { + + } + + @Override + public void invalidateAuthenticatorId(int cookie, HardwareAuthToken hat) { + + } + + @Override + public void resetLockout(int cookie, HardwareAuthToken hat) { + + } + + @Override + public void onPointerDown(int pointerId, int x, int y, float minor, float major) { + + } + + @Override + public void onPointerUp(int pointerId) { + + } + + @Override + public void onUiReady() { + + } + + @Override + public IBinder asBinder() { + return new Binder(); + } + }; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java new file mode 100644 index 000000000000..d5afd0c68ad9 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +/** + * Test HAL that provides only provides mostly no-ops. + */ +class TestSession extends ISession.Stub { + + private static final String TAG = "TestSession"; + + @NonNull private final Sensor.HalSessionCallback mHalSessionCallback; + + TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) { + mHalSessionCallback = halSessionCallback; + } + + @Override + public void generateChallenge(int cookie, int timeoutSec) { + + } + + @Override + public void revokeChallenge(int cookie, long challenge) { + + } + + @Override + public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) { + Slog.d(TAG, "enroll"); + return null; + } + + @Override + public ICancellationSignal authenticate(int cookie, long operationId) { + Slog.d(TAG, "authenticate"); + return null; + } + + @Override + public ICancellationSignal detectInteraction(int cookie) { + return null; + } + + @Override + public void enumerateEnrollments(int cookie) { + Slog.d(TAG, "enumerate"); + } + + @Override + public void removeEnrollments(int cookie, int[] enrollmentIds) { + Slog.d(TAG, "remove"); + } + + @Override + public void getAuthenticatorId(int cookie) { + Slog.d(TAG, "getAuthenticatorId"); + // Immediately return a value so the framework can continue with subsequent requests. + mHalSessionCallback.onAuthenticatorIdRetrieved(0); + } + + @Override + public void invalidateAuthenticatorId(int cookie, HardwareAuthToken hat) { + + } + + @Override + public void resetLockout(int cookie, HardwareAuthToken hat) { + + } + + @Override + public void onPointerDown(int pointerId, int x, int y, float minor, float major) { + + } + + @Override + public void onPointerUp(int pointerId) { + + } + + @Override + public void onUiReady() { + + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java new file mode 100644 index 000000000000..e0ea99077d51 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import static android.Manifest.permission.TEST_BIOMETRIC; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.ITestSession; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.Binder; +import android.util.Slog; + +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * A test session implementation for the {@link Fingerprint21} provider. See + * {@link android.hardware.biometrics.BiometricTestSession}. + */ +public class BiometricTestSessionImpl extends ITestSession.Stub { + + private static final String TAG = "BiometricTestSessionImpl"; + + @NonNull private final Context mContext; + private final int mSensorId; + @NonNull private final Fingerprint21 mFingerprint21; + @NonNull private final Fingerprint21.HalResultController mHalResultController; + @NonNull private final Set<Integer> mEnrollmentIds; + @NonNull private final Random mRandom; + + /** + * Internal receiver currently only used for enroll. Results do not need to be forwarded to the + * test, since enrollment is a platform-only API. The authentication path is tested through + * the public FingerprintManager APIs and does not use this receiver. + */ + private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() { + @Override + public void onEnrollResult(Fingerprint fp, int remaining) { + + } + + @Override + public void onAcquired(int acquiredInfo, int vendorCode) { + + } + + @Override + public void onAuthenticationSucceeded(Fingerprint fp, int userId, + boolean isStrongBiometric) { + + } + + @Override + public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) { + + } + + @Override + public void onAuthenticationFailed() { + + } + + @Override + public void onError(int error, int vendorCode) { + + } + + @Override + public void onRemoved(Fingerprint fp, int remaining) { + + } + + @Override + public void onChallengeGenerated(int sensorId, long challenge) { + + } + }; + + BiometricTestSessionImpl(@NonNull Context context, int sensorId, + @NonNull Fingerprint21 fingerprint21, + @NonNull Fingerprint21.HalResultController halResultController) { + mContext = context; + mSensorId = sensorId; + mFingerprint21 = fingerprint21; + mHalResultController = halResultController; + mEnrollmentIds = new HashSet<>(); + mRandom = new Random(); + } + + @Override + public void setTestHalEnabled(boolean enabled) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mFingerprint21.setTestHalEnabled(enabled); + } + + @Override + public void startEnroll(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, + mContext.getOpPackageName(), null /* surface */); + } + + @Override + public void finishEnroll(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + int nextRandomId = mRandom.nextInt(); + while (mEnrollmentIds.contains(nextRandomId)) { + nextRandomId = mRandom.nextInt(); + } + + mEnrollmentIds.add(nextRandomId); + mHalResultController.onEnrollResult(0 /* deviceId */, + nextRandomId /* fingerId */, userId, 0); + } + + @Override + public void acceptAuthentication(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + // Fake authentication with any of the existing fingers + List<Fingerprint> fingerprints = FingerprintUtils.getInstance() + .getBiometricsForUser(mContext, userId); + if (fingerprints.isEmpty()) { + Slog.w(TAG, "No fingerprints, returning"); + return; + } + final int fid = fingerprints.get(0).getBiometricId(); + final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0)); + mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat); + } + + @Override + public void rejectAuthentication(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null); + } + + @Override + public void notifyAcquired(int userId, int acquireInfo) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */); + } + + @Override + public void notifyError(int userId, int errorCode) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */); + } + + @Override + public void cleanupInternalState(int userId) { + Utils.checkPermission(mContext, TEST_BIOMETRIC); + + mFingerprint21.scheduleInternalCleanup(mSensorId, userId); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index ab4427c5235c..241c911ce9ab 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -29,6 +29,7 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; @@ -51,8 +52,11 @@ import com.android.internal.R; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.Utils; import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto; +import com.android.server.biometrics.fingerprint.FingerprintServiceStateProto; import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto; import com.android.server.biometrics.fingerprint.PerformanceStatsProto; +import com.android.server.biometrics.fingerprint.SensorStateProto; +import com.android.server.biometrics.fingerprint.UserStateProto; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; @@ -91,6 +95,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private static final String TAG = "Fingerprint21"; private static final int ENROLL_TIMEOUT_SEC = 60; + private boolean mTestHalEnabled; + final Context mContext; private final IActivityTaskManager mActivityTaskManager; @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; @@ -391,6 +397,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } private synchronized IBiometricsFingerprint getDaemon() { + if (mTestHalEnabled) { + return new TestHal(); + } + if (mDaemon != null) { return mDaemon; } @@ -693,7 +703,27 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public void dumpProto(int sensorId, FileDescriptor fd) { + public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto) { + final long sensorToken = proto.start(FingerprintServiceStateProto.SENSOR_STATES); + + proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId); + proto.write(SensorStateProto.IS_BUSY, mScheduler.getCurrentClient() != null); + + for (UserInfo user : UserManager.get(mContext).getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + final long userToken = proto.start(SensorStateProto.USER_STATES); + proto.write(UserStateProto.USER_ID, userId); + proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getInstance() + .getBiometricsForUser(mContext, userId).size()); + proto.end(userToken); + } + + proto.end(sensorToken); + } + + @Override + public void dumpProtoMetrics(int sensorId, FileDescriptor fd) { PerformanceTracker tracker = PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId); @@ -771,4 +801,15 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount()); mScheduler.dump(pw); } + + void setTestHalEnabled(boolean enabled) { + mTestHalEnabled = enabled; + } + + @NonNull + @Override + public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { + return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, this, + mHalResultController); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java new file mode 100644 index 000000000000..86c0875af48c --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback; +import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint; +import android.os.RemoteException; + +/** + * Test HAL that provides only provides no-ops. + */ +public class TestHal extends IBiometricsFingerprint.Stub { + @Override + public boolean isUdfps(int sensorId) { + return false; + } + + @Override + public void onFingerDown(int x, int y, float minor, float major) { + + } + + @Override + public void onFingerUp() { + + } + + @Override + public long setNotify(IBiometricsFingerprintClientCallback clientCallback) { + return 0; + } + + @Override + public long preEnroll() { + return 0; + } + + @Override + public int enroll(byte[] hat, int gid, int timeoutSec) { + return 0; + } + + @Override + public int postEnroll() { + return 0; + } + + @Override + public long getAuthenticatorId() { + return 0; + } + + @Override + public int cancel() { + return 0; + } + + @Override + public int enumerate() { + return 0; + } + + @Override + public int remove(int gid, int fid) { + return 0; + } + + @Override + public int setActiveGroup(int gid, String storePath) { + return 0; + } + + @Override + public int authenticate(long operationId, int gid) { + return 0; + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java index 15f43a0481bd..fbd089c1f0ee 100644 --- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java +++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java @@ -52,7 +52,6 @@ public class DataConnectionStats extends BroadcastReceiver { private SignalStrength mSignalStrength; private ServiceState mServiceState; private int mDataState = TelephonyManager.DATA_DISCONNECTED; - private int mNrState = NetworkRegistrationInfo.NR_STATE_NONE; public DataConnectionStats(Context context, Handler listenerHandler) { mContext = context; @@ -100,7 +99,7 @@ public class DataConnectionStats extends BroadcastReceiver { : regInfo.getAccessNetworkTechnology(); // If the device is in NSA NR connection the networkType will report as LTE. // For cell dwell rate metrics, this should report NR instead. - if (mNrState == NetworkRegistrationInfo.NR_STATE_CONNECTED) { + if (regInfo != null && regInfo.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED) { networkType = TelephonyManager.NETWORK_TYPE_NR; } if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible", @@ -172,7 +171,6 @@ public class DataConnectionStats extends BroadcastReceiver { @Override public void onServiceStateChanged(ServiceState state) { mServiceState = state; - mNrState = state.getNrState(); notePhoneDataConnectionState(); } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index c686ba4c4b5c..6a4ca8da00d1 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -210,7 +210,7 @@ public class SyncManager { private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock"; - private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = false; + private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = true; private static final int SYNC_OP_STATE_VALID = 0; // "1" used to include errors 3, 4 and 5 but now it's split up. @@ -231,7 +231,7 @@ public class SyncManager { private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; - // TODO: add better locking around mRunningAccounts + private final Object mAccountsLock = new Object(); private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY; volatile private PowerManager.WakeLock mSyncManagerWakeLock; @@ -933,19 +933,21 @@ public class SyncManager { } AccountAndUser[] accounts = null; - if (requestedAccount != null) { - if (userId != UserHandle.USER_ALL) { - accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)}; - } else { - for (AccountAndUser runningAccount : mRunningAccounts) { - if (requestedAccount.equals(runningAccount.account)) { - accounts = ArrayUtils.appendElement(AccountAndUser.class, - accounts, runningAccount); + synchronized (mAccountsLock) { + if (requestedAccount != null) { + if (userId != UserHandle.USER_ALL) { + accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)}; + } else { + for (AccountAndUser runningAccount : mRunningAccounts) { + if (requestedAccount.equals(runningAccount.account)) { + accounts = ArrayUtils.appendElement(AccountAndUser.class, + accounts, runningAccount); + } } } + } else { + accounts = mRunningAccounts; } - } else { - accounts = mRunningAccounts; } if (ArrayUtils.isEmpty(accounts)) { @@ -3228,40 +3230,43 @@ public class SyncManager { } private void updateRunningAccountsH(EndPoint syncTargets) { - AccountAndUser[] oldAccounts = mRunningAccounts; - mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts(); - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "Accounts list: "); - for (AccountAndUser acc : mRunningAccounts) { - Slog.v(TAG, acc.toString()); + synchronized (mAccountsLock) { + AccountAndUser[] oldAccounts = mRunningAccounts; + mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "Accounts list: "); + for (AccountAndUser acc : mRunningAccounts) { + Slog.v(TAG, acc.toString()); + } } - } - if (mLogger.enabled()) { - mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts)); - } - removeStaleAccounts(); - - AccountAndUser[] accounts = mRunningAccounts; - for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { - if (!containsAccountAndUser(accounts, - currentSyncContext.mSyncOperation.target.account, - currentSyncContext.mSyncOperation.target.userId)) { - Log.d(TAG, "canceling sync since the account is no longer running"); - sendSyncFinishedOrCanceledMessage(currentSyncContext, - null /* no result since this is a cancel */); + if (mLogger.enabled()) { + mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts)); + } + removeStaleAccounts(); + + AccountAndUser[] accounts = mRunningAccounts; + for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { + if (!containsAccountAndUser(accounts, + currentSyncContext.mSyncOperation.target.account, + currentSyncContext.mSyncOperation.target.userId)) { + Log.d(TAG, "canceling sync since the account is no longer running"); + sendSyncFinishedOrCanceledMessage(currentSyncContext, + null /* no result since this is a cancel */); + } } - } - if (syncTargets != null) { - // On account add, check if there are any settings to be restored. - for (AccountAndUser aau : mRunningAccounts) { - if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Account " + aau.account - + " added, checking sync restore data"); + if (syncTargets != null) { + // On account add, check if there are any settings to be restored. + for (AccountAndUser aau : mRunningAccounts) { + if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Account " + aau.account + + " added, checking sync restore data"); + } + AccountSyncSettingsBackupHelper.accountAdded(mContext, + syncTargets.userId); + break; } - AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId); - break; } } } @@ -3442,13 +3447,15 @@ public class SyncManager { final EndPoint target = op.target; // Drop the sync if the account of this operation no longer exists. - AccountAndUser[] accounts = mRunningAccounts; - if (!containsAccountAndUser(accounts, target.account, target.userId)) { - if (isLoggable) { - Slog.v(TAG, " Dropping sync operation: account doesn't exist."); + synchronized (mAccountsLock) { + AccountAndUser[] accounts = mRunningAccounts; + if (!containsAccountAndUser(accounts, target.account, target.userId)) { + if (isLoggable) { + Slog.v(TAG, " Dropping sync operation: account doesn't exist."); + } + logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist."); + return SYNC_OP_STATE_INVALID_NO_ACCOUNT; } - logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist."); - return SYNC_OP_STATE_INVALID_NO_ACCOUNT; } // Drop this sync request if it isn't syncable. state = computeSyncable(target.account, target.userId, target.provider, true); diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index a9e8719890ef..8980de12a31f 100755 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -339,7 +339,8 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { // This is to manager CEC device separately in case they don't have address. if (mIsTvDevice) { - tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, + localDevice().mService.getHdmiCecNetwork().updateCecSwitchInfo(current.mLogicalAddress, + current.mDeviceType, current.mPhysicalAddress); } increaseProcessedDeviceCount(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 96679c3ab767..efe730231d36 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -32,7 +32,6 @@ import android.os.Looper; import android.os.RemoteException; import android.stats.hdmi.HdmiStatsEnums; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; @@ -97,7 +96,7 @@ final class HdmiCecController { private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() { @Override public boolean test(Integer address) { - return !isAllocatedLocalDeviceAddress(address); + return !mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); } }; @@ -118,9 +117,6 @@ final class HdmiCecController { private final HdmiControlService mService; - // Stores the local CEC devices in the system. Device type is used for key. - private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>(); - // Stores recent CEC messages and HDMI Hotplug event history for debugging purpose. private final ArrayBlockingQueue<Dumpable> mMessageHistory = new ArrayBlockingQueue<>(MAX_HDMI_MESSAGE_HISTORY); @@ -173,12 +169,6 @@ final class HdmiCecController { nativeWrapper.setCallback(new HdmiCecCallback()); } - @ServiceThreadOnly - void addLocalDevice(int deviceType, HdmiCecLocalDevice device) { - assertRunOnServiceThread(); - mLocalDevices.put(deviceType, device); - } - /** * Allocate a new logical address of the given device type. Allocated * address will be reported through {@link AllocateAddressCallback}. @@ -269,17 +259,6 @@ final class HdmiCecController { } /** - * Return the locally hosted logical device of a given type. - * - * @param deviceType logical device type - * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available; - * otherwise null. - */ - HdmiCecLocalDevice getLocalDevice(int deviceType) { - return mLocalDevices.get(deviceType); - } - - /** * Add a new logical address to the device. Device's HW should be notified * when a new logical address is assigned to a device, so that it can accept * a command having available destinations. @@ -307,18 +286,9 @@ final class HdmiCecController { @ServiceThreadOnly void clearLogicalAddress() { assertRunOnServiceThread(); - for (int i = 0; i < mLocalDevices.size(); ++i) { - mLocalDevices.valueAt(i).clearAddress(); - } mNativeWrapperImpl.nativeClearLogicalAddress(); } - @ServiceThreadOnly - void clearLocalDevices() { - assertRunOnServiceThread(); - mLocalDevices.clear(); - } - /** * Return the physical address of the device. * @@ -428,17 +398,6 @@ final class HdmiCecController { runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated); } - /** - * Return a list of all {@link HdmiCecLocalDevice}s. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - */ - @ServiceThreadOnly - List<HdmiCecLocalDevice> getLocalDeviceList() { - assertRunOnServiceThread(); - return HdmiUtils.sparseArrayToList(mLocalDevices); - } - private List<Integer> pickPollCandidates(int pickStrategy) { int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; Predicate<Integer> pickPredicate = null; @@ -475,17 +434,6 @@ final class HdmiCecController { } @ServiceThreadOnly - private boolean isAllocatedLocalDeviceAddress(int address) { - assertRunOnServiceThread(); - for (int i = 0; i < mLocalDevices.size(); ++i) { - if (mLocalDevices.valueAt(i).isAddressOf(address)) { - return true; - } - } - return false; - } - - @ServiceThreadOnly private void runDevicePolling(final int sourceAddress, final List<Integer> candidates, final int retryCount, final DevicePollingCallback callback, final List<Integer> allocated) { @@ -578,7 +526,7 @@ final class HdmiCecController { if (address == Constants.ADDR_BROADCAST) { return true; } - return isAllocatedLocalDeviceAddress(address); + return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address); } @ServiceThreadOnly @@ -682,7 +630,7 @@ final class HdmiCecController { private int incomingMessageDirection(int srcAddress, int dstAddress) { boolean sourceIsLocal = false; boolean destinationIsLocal = false; - for (HdmiCecLocalDevice localDevice : getLocalDeviceList()) { + for (HdmiCecLocalDevice localDevice : mService.getHdmiCecNetwork().getLocalDeviceList()) { int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress(); if (logicalAddress == srcAddress) { sourceIsLocal = true; @@ -731,24 +679,9 @@ final class HdmiCecController { } void dump(final IndentingPrintWriter pw) { - final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - for (int i = 0; i < mLocalDevices.size(); ++i) { - pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":"); - pw.increaseIndent(); - mLocalDevices.valueAt(i).dump(pw); - - pw.println("Active Source history:"); - pw.increaseIndent(); - for (Dumpable activeSourceEvent : mLocalDevices.valueAt(i).getActiveSourceHistory()) { - activeSourceEvent.dump(pw, sdf); - } - pw.decreaseIndent(); - pw.decreaseIndent(); - } - pw.println("CEC message history:"); pw.increaseIndent(); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (Dumpable record : mMessageHistory) { record.dump(pw, sdf); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 946fb0d00d60..62a67b6243d7 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import android.annotation.CallSuper; import android.annotation.Nullable; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -471,8 +472,27 @@ abstract class HdmiCecLocalDevice { return false; } + @CallSuper protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { - return false; + // <Report Physical Address> is also handled in HdmiCecNetwork to update the local network + // state + + int address = message.getSource(); + + // Ignore if [Device Discovery Action] is going on. + if (hasAction(DeviceDiscoveryAction.class)) { + Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); + return true; + } + + HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address); + // If no non-default display name is available for the device, request the devices OSD name. + if (cecDeviceInfo.getDisplayName().equals(HdmiUtils.getDefaultDeviceName(address))) { + mService.sendCecCommand( + HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); + } + + return true; } protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { @@ -708,7 +728,7 @@ abstract class HdmiCecLocalDevice { } protected boolean handleSetOsdName(HdmiCecMessage message) { - // The default behavior of <Set Osd Name> is doing nothing. + // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state return true; } @@ -724,7 +744,8 @@ abstract class HdmiCecLocalDevice { } protected boolean handleReportPowerStatus(HdmiCecMessage message) { - return false; + // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state + return true; } protected boolean handleTimerStatus(HdmiCecMessage message) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 29bdd6cb40c3..fe4fd3805994 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -37,7 +37,6 @@ import android.os.SystemProperties; import android.provider.Settings.Global; import android.sysprop.HdmiProperties; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -54,15 +53,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; - /** * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android * system. @@ -104,14 +100,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @GuardedBy("mLock") private final HashMap<String, HdmiDeviceInfo> mTvInputsToDeviceInfo = new HashMap<>(); - // Copy of mDeviceInfos to guarantee thread-safety. - @GuardedBy("mLock") - private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); - - // Map-like container of all cec devices. - // device id is used as key of container. - private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); - // Message buffer used to buffer selected messages to process later. <Active Source> // from a source device, for instance, needs to be buffered if the device is not // discovered yet. The buffered commands are taken out and when they are ready to @@ -187,135 +175,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { return info != null; } - /** - * Called when a device is newly added or a new device is detected or - * an existing device is updated. - * - * @param info device info of a new device. - */ - @ServiceThreadOnly - final void addCecDevice(HdmiDeviceInfo info) { - assertRunOnServiceThread(); - HdmiDeviceInfo old = addDeviceInfo(info); - if (info.getPhysicalAddress() == mService.getPhysicalAddress()) { - // The addition of the device itself should not be notified. - // Note that different logical address could still be the same local device. - return; - } - if (old == null) { - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); - } else if (!old.equals(info)) { - invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); - } - } - - /** - * Called when a device is removed or removal of device is detected. - * - * @param address a logical address of a device to be removed - */ - @ServiceThreadOnly - final void removeCecDevice(int address) { - assertRunOnServiceThread(); - HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); - - mCecMessageCache.flushMessagesFrom(address); - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); - } - - /** - * Called when a device is updated. - * - * @param info device info of the updating device. - */ - @ServiceThreadOnly - final void updateCecDevice(HdmiDeviceInfo info) { - assertRunOnServiceThread(); - HdmiDeviceInfo old = addDeviceInfo(info); - - if (old == null) { - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); - } else if (!old.equals(info)) { - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); - } - } - - /** - * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same - * logical address as new device info's. - * - * @param deviceInfo a new {@link HdmiDeviceInfo} to be added. - * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} - * that has the same logical address as new one has. - */ - @ServiceThreadOnly - @VisibleForTesting - protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { - assertRunOnServiceThread(); - mService.checkLogicalAddressConflictAndReallocate(deviceInfo.getLogicalAddress()); - HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); - if (oldDeviceInfo != null) { - removeDeviceInfo(deviceInfo.getId()); - } - mDeviceInfos.append(deviceInfo.getId(), deviceInfo); - updateSafeDeviceInfoList(); - return oldDeviceInfo; - } - - /** - * Remove a device info corresponding to the given {@code logicalAddress}. - * It returns removed {@link HdmiDeviceInfo} if exists. - * - * @param id id of device to be removed - * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} - */ - @ServiceThreadOnly - private HdmiDeviceInfo removeDeviceInfo(int id) { - assertRunOnServiceThread(); - HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); - if (deviceInfo != null) { - mDeviceInfos.remove(id); - } - updateSafeDeviceInfoList(); - return deviceInfo; - } - - /** - * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. - * - * @param logicalAddress logical address of the device to be retrieved - * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. - * Returns null if no logical address matched - */ - @ServiceThreadOnly - HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { - assertRunOnServiceThread(); - return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); - } - - @ServiceThreadOnly - private void updateSafeDeviceInfoList() { - assertRunOnServiceThread(); - List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); - synchronized (mLock) { - mSafeAllDeviceInfos = copiedDevices; - } - } - - @GuardedBy("mLock") - List<HdmiDeviceInfo> getSafeCecDevicesLocked() { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - infoList.add(info); - } - return infoList; - } - - private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { - mService.invokeDeviceEventListeners(info, status); - } - @Override @ServiceThreadOnly void onHotplug(int portId, boolean connected) { @@ -342,7 +201,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } // Update with TIF on the device removal. TIF callback will update // mPortIdToTvInputs and mPortIdToTvInputs. - removeCecDevice(info.getLogicalAddress()); + mService.getHdmiCecNetwork().removeCecDevice(this, info.getLogicalAddress()); } } @@ -399,7 +258,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { boolean lastSystemAudioControlStatus = SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); - clearDeviceInfoList(); + mService.getHdmiCecNetwork().clearDeviceList(); launchDeviceDiscovery(); startQueuedActions(); } @@ -458,7 +317,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // If the new Active Source is under the current device, check if the device info and the TV // input is ready to switch to the new Active Source. If not ready, buffer the cec command // to handle later when the device is ready. - HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress); if (info == null) { HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); mDelayedMessageBuffer.add(message); @@ -474,79 +333,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { - assertRunOnServiceThread(); - int path = HdmiUtils.twoBytesToInt(message.getParams()); - int address = message.getSource(); - int type = message.getParams()[2]; - - // Ignore if [Device Discovery Action] is going on. - if (hasAction(DeviceDiscoveryAction.class)) { - Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); - return true; - } - - // Update the device info with TIF, note that the same device info could have added in - // device discovery and we do not want to override it with default OSD name. Therefore we - // need the following check to skip redundant device info updating. - HdmiDeviceInfo oldDevice = getCecDeviceInfo(address); - if (oldDevice == null || oldDevice.getPhysicalAddress() != path) { - addCecDevice(new HdmiDeviceInfo( - address, path, mService.pathToPortId(path), type, - Constants.UNKNOWN_VENDOR_ID, "")); - // if we are adding a new device info, send out a give osd name command - // to update the name of the device in TIF - mService.sendCecCommand( - HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); - return true; - } - - Slog.w(TAG, "Device info exists. Not updating on Physical Address."); - return true; - } - - @Override - protected boolean handleReportPowerStatus(HdmiCecMessage command) { - int newStatus = command.getParams()[0] & 0xFF; - updateDevicePowerStatus(command.getSource(), newStatus); - return true; - } - - @Override - @ServiceThreadOnly - protected boolean handleSetOsdName(HdmiCecMessage message) { - int source = message.getSource(); - String osdName; - HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source); - // If the device is not in device list, ignore it. - if (deviceInfo == null) { - Slog.i(TAG, "No source device info for <Set Osd Name>." + message); - return true; - } - try { - osdName = new String(message.getParams(), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); - return true; - } - - if (deviceInfo.getDisplayName() != null - && deviceInfo.getDisplayName().equals(osdName)) { - Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); - return true; - } - - Slog.d(TAG, "Updating device OSD name from " - + deviceInfo.getDisplayName() - + " to " + osdName); - updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), - deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); - return true; - } - - @Override - @ServiceThreadOnly protected boolean handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement initiate arc handler @@ -864,14 +650,9 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) { return true; } - boolean isDeviceInCecDeviceList = false; - for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { - if (info.getPhysicalAddress() == sourcePhysicalAddress) { - isDeviceInCecDeviceList = true; - break; - } - } - if (!isDeviceInCecDeviceList) { + HdmiDeviceInfo safeDeviceInfoByPath = + mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress); + if (safeDeviceInfoByPath == null) { switchInputOnReceivingNewActivePath(sourcePhysicalAddress); } } @@ -1345,24 +1126,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { routeToInputFromPortId(getRoutingPort()); } - protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { - HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); - if (info == null) { - Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); - return; - } - - if (info.getDevicePowerStatus() == newPowerStatus) { - return; - } - - HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus); - // addDeviceInfo replaces old device info with new one if exists. - addDeviceInfo(newInfo); - - invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); - } - @ServiceThreadOnly private void launchDeviceDiscovery() { assertRunOnServiceThread(); @@ -1375,27 +1138,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { for (HdmiDeviceInfo info : deviceInfos) { - addCecDevice(info); + mService.getHdmiCecNetwork().addCecDevice(info); } } }); addAndStartAction(action); } - // Clear all device info. - @ServiceThreadOnly - private void clearDeviceInfoList() { - assertRunOnServiceThread(); - for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { - if (info.getPhysicalAddress() == mService.getPhysicalAddress()) { - continue; - } - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); - } - mDeviceInfos.clear(); - updateSafeDeviceInfoList(); - } - @Override protected void dump(IndentingPrintWriter pw) { pw.println("HdmiCecLocalDeviceAudioSystem:"); @@ -1409,7 +1158,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { pw.println("mLocalActivePort: " + getLocalActivePort()); HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs); HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo); - HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos); pw.decreaseIndent(); super.dump(pw); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 0325ad929849..93cdca2d0c83 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -42,9 +42,7 @@ import android.media.AudioSystem; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager.TvInputCallback; import android.provider.Settings.Global; -import android.util.ArraySet; import android.util.Slog; -import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; @@ -53,13 +51,8 @@ import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; /** @@ -95,37 +88,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @GuardedBy("mLock") private boolean mSystemAudioMute = false; - // Copy of mDeviceInfos to guarantee thread-safety. - @GuardedBy("mLock") - private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); - // All external cec input(source) devices. Does not include system audio device. - @GuardedBy("mLock") - private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList(); - - // Map-like container of all cec devices including local ones. - // device id is used as key of container. - // This is not thread-safe. For external purpose use mSafeDeviceInfos. - private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); - // If true, TV going to standby mode puts other devices also to standby. private boolean mAutoDeviceOff; // If true, TV wakes itself up when receiving <Text/Image View On>. private boolean mAutoWakeup; - // List of the logical address of local CEC devices. Unmodifiable, thread-safe. - private List<Integer> mLocalDeviceAddresses; - private final HdmiCecStandbyModeHandler mStandbyHandler; // If true, do not do routing control/send active source for internal source. // Set to true when the device was woken up by <Text/Image View On>. private boolean mSkipRoutingControl; - // Set of physical addresses of CEC switches on the CEC bus. Managed independently from - // other CEC devices since they might not have logical address. - private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>(); - // Message buffer used to buffer selected messages to process later. <Active Source> // from a source device, for instance, needs to be buffered if the device is not // discovered yet. The buffered commands are taken out and when they are ready to @@ -205,12 +179,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mAddress, mService.getPhysicalAddress(), mDeviceType)); mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( mAddress, mService.getVendorId())); - mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too. + mService.getHdmiCecNetwork().addCecSwitch( + mService.getHdmiCecNetwork().getPhysicalAddress()); // TV is a CEC switch too. mTvInputs.clear(); mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE); launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && reason != HdmiControlService.INITIATED_BY_BOOT_UP); - mLocalDeviceAddresses = initLocalDeviceAddresses(); resetSelectRequestBuffer(); launchDeviceDiscovery(); startQueuedActions(); @@ -220,17 +194,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @ServiceThreadOnly - private List<Integer> initLocalDeviceAddresses() { - assertRunOnServiceThread(); - List<Integer> addresses = new ArrayList<>(); - for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { - addresses.add(device.getDeviceInfo().getLogicalAddress()); - } - return Collections.unmodifiableList(addresses); - } - - - @ServiceThreadOnly public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) { assertRunOnServiceThread(); mSelectRequestBuffer = requestBuffer; @@ -272,7 +235,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly void deviceSelect(int id, IHdmiControlCallback callback) { assertRunOnServiceThread(); - HdmiDeviceInfo targetDevice = mDeviceInfos.get(id); + HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id); if (targetDevice == null) { invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); return; @@ -335,7 +298,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } setActiveSource(newActive, caller); int logicalAddress = newActive.logicalAddress; - if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) { + if (mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress) != null + && logicalAddress != mAddress) { if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) { setPrevPortId(getActivePortId()); } @@ -374,7 +338,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Show OSD port change banner if (notifyInputChange) { ActiveSource activeSource = getActiveSource(); - HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo( + activeSource.logicalAddress); if (info == null) { info = mService.getDeviceInfoByPort(getActivePortId()); if (info == null) { @@ -442,7 +407,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (getActiveSource().isValid()) { return getActiveSource().logicalAddress; } - HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath()); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getDeviceInfoByPath(getActivePath()); if (info != null) { return info.getLogicalAddress(); } @@ -455,7 +420,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress); if (info == null) { if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); @@ -463,7 +428,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } else if (isInputReady(info.getId()) || info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { - updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON); + mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress, + HdmiControlManager.POWER_STATUS_ON); ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); } else { @@ -490,7 +456,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (portId != Constants.INVALID_PORT_ID) { // TODO: Do this only if TV is not showing multiview like PIP/PAP. - HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource()); + HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo( + message.getSource()); if (inactiveSource == null) { return true; } @@ -546,42 +513,20 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - @ServiceThreadOnly protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { - assertRunOnServiceThread(); + super.handleReportPhysicalAddress(message); int path = HdmiUtils.twoBytesToInt(message.getParams()); int address = message.getSource(); int type = message.getParams()[2]; - if (updateCecSwitchInfo(address, type, path)) return true; - - // Ignore if [Device Discovery Action] is going on. - if (hasAction(DeviceDiscoveryAction.class)) { - Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); - return true; - } - - if (!isInDeviceList(address, path)) { + if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) { handleNewDeviceAtTheTailOfActivePath(path); } - - // Add the device ahead with default information to handle <Active Source> - // promptly, rather than waiting till the new device action is finished. - HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)); - addCecDevice(deviceInfo); startNewDeviceAction(ActiveSource.of(address, path), type); return true; } @Override - protected boolean handleReportPowerStatus(HdmiCecMessage command) { - int newStatus = command.getParams()[0] & 0xFF; - updateDevicePowerStatus(command.getSource(), newStatus); - return true; - } - - @Override protected boolean handleTimerStatus(HdmiCecMessage message) { // Do nothing. return true; @@ -593,19 +538,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return true; } - boolean updateCecSwitchInfo(int address, int type, int path) { - if (address == Constants.ADDR_UNREGISTERED - && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) { - mCecSwitches.add(path); - updateSafeDeviceInfoList(); - return true; // Pure switch does not need further processing. Return here. - } - if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { - mCecSwitches.add(path); - } - return false; - } - void startNewDeviceAction(ActiveSource activeSource, int deviceType) { for (NewDeviceAction action : getActions(NewDeviceAction.class)) { // If there is new device action which has the same logical address and path @@ -719,35 +651,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return handleTextViewOn(message); } - @Override - @ServiceThreadOnly - protected boolean handleSetOsdName(HdmiCecMessage message) { - int source = message.getSource(); - HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source); - // If the device is not in device list, ignore it. - if (deviceInfo == null) { - Slog.e(TAG, "No source device info for <Set Osd Name>." + message); - return true; - } - String osdName = null; - try { - osdName = new String(message.getParams(), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); - return true; - } - - if (deviceInfo.getDisplayName().equals(osdName)) { - Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); - return true; - } - - addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), - deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); - return true; - } - @ServiceThreadOnly private void launchDeviceDiscovery() { assertRunOnServiceThread(); @@ -757,14 +660,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { for (HdmiDeviceInfo info : deviceInfos) { - addCecDevice(info); + mService.getHdmiCecNetwork().addCecDevice(info); } // Since we removed all devices when it's start and // device discovery action does not poll local devices, // we should put device info of local device manually here for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { - addCecDevice(device.getDeviceInfo()); + mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo()); } mSelectRequestBuffer.process(); @@ -798,11 +701,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly private void clearDeviceInfoList() { assertRunOnServiceThread(); - for (HdmiDeviceInfo info : mSafeExternalInputs) { - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); - } - mDeviceInfos.clear(); - updateSafeDeviceInfoList(); + mService.getHdmiCecNetwork().clearDeviceList(); } @ServiceThreadOnly @@ -1224,170 +1123,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { && getAvrDeviceInfo() != null; } - /** - * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same - * logical address as new device info's. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - * - * @param deviceInfo a new {@link HdmiDeviceInfo} to be added. - * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} - * that has the same logical address as new one has. - */ - @ServiceThreadOnly - private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { - assertRunOnServiceThread(); - HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); - if (oldDeviceInfo != null) { - removeDeviceInfo(deviceInfo.getId()); - } - mDeviceInfos.append(deviceInfo.getId(), deviceInfo); - updateSafeDeviceInfoList(); - return oldDeviceInfo; - } - - /** - * Remove a device info corresponding to the given {@code logicalAddress}. - * It returns removed {@link HdmiDeviceInfo} if exists. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - * - * @param id id of device to be removed - * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} - */ - @ServiceThreadOnly - private HdmiDeviceInfo removeDeviceInfo(int id) { - assertRunOnServiceThread(); - HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); - if (deviceInfo != null) { - mDeviceInfos.remove(id); - } - updateSafeDeviceInfoList(); - return deviceInfo; - } - - /** - * Return a list of all {@link HdmiDeviceInfo}. - * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which - * does not include local device. - */ - @ServiceThreadOnly - List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { - assertRunOnServiceThread(); - if (includeLocalDevice) { - return HdmiUtils.sparseArrayToList(mDeviceInfos); - } else { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (int i = 0; i < mDeviceInfos.size(); ++i) { - HdmiDeviceInfo info = mDeviceInfos.valueAt(i); - if (!isLocalDeviceAddress(info.getLogicalAddress())) { - infoList.add(info); - } - } - return infoList; - } - } - - /** - * Return external input devices. - */ - @GuardedBy("mLock") - List<HdmiDeviceInfo> getSafeExternalInputsLocked() { - return mSafeExternalInputs; - } - - @ServiceThreadOnly - private void updateSafeDeviceInfoList() { - assertRunOnServiceThread(); - List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); - List<HdmiDeviceInfo> externalInputs = getInputDevices(); - synchronized (mLock) { - mSafeAllDeviceInfos = copiedDevices; - mSafeExternalInputs = externalInputs; - } - } - - /** - * Return a list of external cec input (source) devices. - * - * <p>Note that this effectively excludes non-source devices like system audio, - * secondary TV. - */ - private List<HdmiDeviceInfo> getInputDevices() { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (int i = 0; i < mDeviceInfos.size(); ++i) { - HdmiDeviceInfo info = mDeviceInfos.valueAt(i); - if (isLocalDeviceAddress(info.getLogicalAddress())) { - continue; - } - if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { - infoList.add(info); - } - } - return infoList; - } - - // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. - // Returns true if the policy is set to true, and the device to check does not have - // a parent CEC device (which should be the CEC-enabled switch) in the list. - private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) { - return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH - && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches); - } - - private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) { - for (int switchPath : switches) { - if (isParentPath(switchPath, path)) { - return true; - } - } - return false; - } - - private static boolean isParentPath(int parentPath, int childPath) { - // (A000, AB00) (AB00, ABC0), (ABC0, ABCD) - // If child's last non-zero nibble is removed, the result equals to the parent. - for (int i = 0; i <= 12; i += 4) { - int nibble = (childPath >> i) & 0xF; - if (nibble != 0) { - int parentNibble = (parentPath >> i) & 0xF; - return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4); - } - } - return false; - } - - private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { - if (!hideDevicesBehindLegacySwitch(info)) { - mService.invokeDeviceEventListeners(info, status); - } - } - - private boolean isLocalDeviceAddress(int address) { - return mLocalDeviceAddresses.contains(address); - } - @ServiceThreadOnly HdmiDeviceInfo getAvrDeviceInfo() { assertRunOnServiceThread(); - return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); - } - - /** - * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. - * - * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. - * - * @param logicalAddress logical address of the device to be retrieved - * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. - * Returns null if no logical address matched - */ - @ServiceThreadOnly - HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { - assertRunOnServiceThread(); - return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); + return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } boolean hasSystemAudioDevice() { @@ -1395,74 +1134,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } HdmiDeviceInfo getSafeAvrDeviceInfo() { - return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); - } - - /** - * Thread safe version of {@link #getCecDeviceInfo(int)}. - * - * @param logicalAddress logical address to be retrieved - * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. - * Returns null if no logical address matched - */ - HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { - synchronized (mLock) { - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { - return info; - } - } - return null; - } - } - - @GuardedBy("mLock") - List<HdmiDeviceInfo> getSafeCecDevicesLocked() { - ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - if (isLocalDeviceAddress(info.getLogicalAddress())) { - continue; - } - infoList.add(info); - } - return infoList; - } - - /** - * Called when a device is newly added or a new device is detected or - * existing device is updated. - * - * @param info device info of a new device. - */ - @ServiceThreadOnly - final void addCecDevice(HdmiDeviceInfo info) { - assertRunOnServiceThread(); - HdmiDeviceInfo old = addDeviceInfo(info); - if (info.getLogicalAddress() == mAddress) { - // The addition of TV device itself should not be notified. - return; - } - if (old == null) { - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); - } else if (!old.equals(info)) { - invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); - } + return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } - /** - * Called when a device is removed or removal of device is detected. - * - * @param address a logical address of a device to be removed - */ - @ServiceThreadOnly - final void removeCecDevice(int address) { - assertRunOnServiceThread(); - HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); - - mCecMessageCache.flushMessagesFrom(address); - invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); - } @ServiceThreadOnly void handleRemoveActiveRoutingPath(int path) { @@ -1501,72 +1175,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - /** - * Returns the {@link HdmiDeviceInfo} instance whose physical address matches - * the given routing path. CEC devices use routing path for its physical address to - * describe the hierarchy of the devices in the network. - * - * @param path routing path or physical address - * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null - */ - @ServiceThreadOnly - final HdmiDeviceInfo getDeviceInfoByPath(int path) { - assertRunOnServiceThread(); - for (HdmiDeviceInfo info : getDeviceInfoList(false)) { - if (info.getPhysicalAddress() == path) { - return info; - } - } - return null; - } - - /** - * Returns the {@link HdmiDeviceInfo} instance whose physical address matches - * the given routing path. This is the version accessible safely from threads - * other than service thread. - * - * @param path routing path or physical address - * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null - */ - HdmiDeviceInfo getSafeDeviceInfoByPath(int path) { - synchronized (mLock) { - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - if (info.getPhysicalAddress() == path) { - return info; - } - } - return null; - } - } - - /** - * Whether a device of the specified physical address and logical address exists - * in a device info list. However, both are minimal condition and it could - * be different device from the original one. - * - * @param logicalAddress logical address of a device to be searched - * @param physicalAddress physical address of a device to be searched - * @return true if exist; otherwise false - */ - @ServiceThreadOnly - boolean isInDeviceList(int logicalAddress, int physicalAddress) { - assertRunOnServiceThread(); - HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); - if (device == null) { - return false; - } - return device.getPhysicalAddress() == physicalAddress; - } - @Override @ServiceThreadOnly void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); - - if (!connected) { - removeCecSwitches(portId); - } - // Turning System Audio Mode off when the AVR is unlugged or standby. // When the device is not unplugged but reawaken from standby, we check if the System // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly. @@ -1588,16 +1200,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - private void removeCecSwitches(int portId) { - Iterator<Integer> it = mCecSwitches.iterator(); - while (!it.hasNext()) { - int path = it.next(); - if (pathToPortId(path) == portId) { - it.remove(); - } - } - } - @Override @ServiceThreadOnly void setAutoDeviceOff(boolean enabled) { @@ -1765,7 +1367,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } private boolean checkRecorder(int recorderAddress) { - HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress); + HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress); return (device != null) && (HdmiUtils.getTypeFromAddress(recorderAddress) == HdmiDeviceInfo.DEVICE_RECORDER); @@ -1871,24 +1473,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { }); } - void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { - HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); - if (info == null) { - Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); - return; - } - - if (info.getDevicePowerStatus() == newPowerStatus) { - return; - } - - HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus); - // addDeviceInfo replaces old device info with new one if exists. - addDeviceInfo(newInfo); - - invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); - } - @Override protected boolean handleMenuStatus(HdmiCecMessage message) { // Do nothing and just return true not to prevent from responding <Feature Abort>. @@ -1897,7 +1481,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override protected void sendStandby(int deviceId) { - HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId); + HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId); if (targetDevice == null) { return; } @@ -1934,11 +1518,5 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { pw.println("mAutoWakeup: " + mAutoWakeup); pw.println("mSkipRoutingControl: " + mSkipRoutingControl); pw.println("mPrevPortId: " + mPrevPortId); - pw.println("CEC devices:"); - pw.increaseIndent(); - for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { - pw.println(info); - } - pw.decreaseIndent(); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java new file mode 100644 index 000000000000..5d75a63d0c8d --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java @@ -0,0 +1,846 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.hdmi; + +import static com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; + +import android.annotation.Nullable; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.os.Handler; +import android.os.Looper; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * Holds information about the current state of the HDMI CEC network. It is the sole source of + * truth for device information in the CEC network. + * + * This information includes: + * - All local devices + * - All HDMI ports, their capabilities and status + * - All devices connected to the CEC bus + * + * This class receives all incoming CEC messages and passively listens to device updates to fill + * out the above information. + * This class should not take any active action in sending CEC messages. + * + * Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD + * names, power states can be outdated. + */ +class HdmiCecNetwork { + private static final String TAG = "HdmiCecNetwork"; + + protected final Object mLock; + private final HdmiControlService mHdmiControlService; + private final HdmiCecController mHdmiCecController; + private final HdmiMhlControllerStub mHdmiMhlController; + private final Handler mHandler; + // Stores the local CEC devices in the system. Device type is used for key. + private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>(); + + // Map-like container of all cec devices including local ones. + // device id is used as key of container. + // This is not thread-safe. For external purpose use mSafeDeviceInfos. + private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); + // Set of physical addresses of CEC switches on the CEC bus. Managed independently from + // other CEC devices since they might not have logical address. + private final ArraySet<Integer> mCecSwitches = new ArraySet<>(); + // Copy of mDeviceInfos to guarantee thread-safety. + @GuardedBy("mLock") + private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); + // All external cec input(source) devices. Does not include system audio device. + @GuardedBy("mLock") + private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList(); + // HDMI port information. Stored in the unmodifiable list to keep the static information + // from being modified. + @GuardedBy("mLock") + private List<HdmiPortInfo> mPortInfo = Collections.emptyList(); + + // Map from path(physical address) to port ID. + private UnmodifiableSparseIntArray mPortIdMap; + + // Map from port ID to HdmiPortInfo. + private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap; + + // Map from port ID to HdmiDeviceInfo. + private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap; + + HdmiCecNetwork(HdmiControlService hdmiControlService, + HdmiCecController hdmiCecController, + HdmiMhlControllerStub hdmiMhlController) { + mHdmiControlService = hdmiControlService; + mHdmiCecController = hdmiCecController; + mHdmiMhlController = hdmiMhlController; + mHandler = new Handler(mHdmiControlService.getServiceLooper()); + mLock = mHdmiControlService.getServiceLock(); + } + + private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) { + for (int switchPath : switches) { + if (isParentPath(switchPath, path)) { + return true; + } + } + return false; + } + + private static boolean isParentPath(int parentPath, int childPath) { + // (A000, AB00) (AB00, ABC0), (ABC0, ABCD) + // If child's last non-zero nibble is removed, the result equals to the parent. + for (int i = 0; i <= 12; i += 4) { + int nibble = (childPath >> i) & 0xF; + if (nibble != 0) { + int parentNibble = (parentPath >> i) & 0xF; + return parentNibble == 0 && (childPath >> i + 4) == (parentPath >> i + 4); + } + } + return false; + } + + public void addLocalDevice(int deviceType, HdmiCecLocalDevice device) { + mLocalDevices.put(deviceType, device); + } + + /** + * Return the locally hosted logical device of a given type. + * + * @param deviceType logical device type + * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available; + * otherwise null. + */ + HdmiCecLocalDevice getLocalDevice(int deviceType) { + return mLocalDevices.get(deviceType); + } + + /** + * Return a list of all {@link HdmiCecLocalDevice}s. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + */ + @ServiceThreadOnly + List<HdmiCecLocalDevice> getLocalDeviceList() { + assertRunOnServiceThread(); + return HdmiUtils.sparseArrayToList(mLocalDevices); + } + + @ServiceThreadOnly + boolean isAllocatedLocalDeviceAddress(int address) { + assertRunOnServiceThread(); + for (int i = 0; i < mLocalDevices.size(); ++i) { + if (mLocalDevices.valueAt(i).isAddressOf(address)) { + return true; + } + } + return false; + } + + /** + * Clear all logical addresses registered in the device. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + */ + @ServiceThreadOnly + void clearLogicalAddress() { + assertRunOnServiceThread(); + for (int i = 0; i < mLocalDevices.size(); ++i) { + mLocalDevices.valueAt(i).clearAddress(); + } + } + + @ServiceThreadOnly + void clearLocalDevices() { + assertRunOnServiceThread(); + mLocalDevices.clear(); + } + + public HdmiDeviceInfo getDeviceInfo(int id) { + return mDeviceInfos.get(id); + } + + /** + * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same + * logical address as new device info's. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + * + * @param deviceInfo a new {@link HdmiDeviceInfo} to be added. + * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} + * that has the same logical address as new one has. + */ + @ServiceThreadOnly + private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { + assertRunOnServiceThread(); + HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); + mHdmiControlService.checkLogicalAddressConflictAndReallocate( + deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress()); + if (oldDeviceInfo != null) { + removeDeviceInfo(deviceInfo.getId()); + } + mDeviceInfos.append(deviceInfo.getId(), deviceInfo); + updateSafeDeviceInfoList(); + return oldDeviceInfo; + } + + /** + * Remove a device info corresponding to the given {@code logicalAddress}. + * It returns removed {@link HdmiDeviceInfo} if exists. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + * + * @param id id of device to be removed + * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} + */ + @ServiceThreadOnly + private HdmiDeviceInfo removeDeviceInfo(int id) { + assertRunOnServiceThread(); + HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); + if (deviceInfo != null) { + mDeviceInfos.remove(id); + } + updateSafeDeviceInfoList(); + return deviceInfo; + } + + /** + * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. + * + * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. + * + * @param logicalAddress logical address of the device to be retrieved + * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. + * Returns null if no logical address matched + */ + @ServiceThreadOnly + @Nullable + HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { + assertRunOnServiceThread(); + return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); + } + + /** + * Called when a device is newly added or a new device is detected or + * existing device is updated. + * + * @param info device info of a new device. + */ + @ServiceThreadOnly + final void addCecDevice(HdmiDeviceInfo info) { + assertRunOnServiceThread(); + HdmiDeviceInfo old = addDeviceInfo(info); + if (isLocalDeviceAddress(info.getLogicalAddress())) { + // The addition of a local device should not notify listeners + return; + } + if (old == null) { + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } else if (!old.equals(info)) { + invokeDeviceEventListener(old, + HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + } + + private void invokeDeviceEventListener(HdmiDeviceInfo info, int event) { + if (!hideDevicesBehindLegacySwitch(info)) { + mHdmiControlService.invokeDeviceEventListeners(info, event); + } + } + + /** + * Called when a device is updated. + * + * @param info device info of the updating device. + */ + @ServiceThreadOnly + final void updateCecDevice(HdmiDeviceInfo info) { + assertRunOnServiceThread(); + HdmiDeviceInfo old = addDeviceInfo(info); + + if (old == null) { + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } else if (!old.equals(info)) { + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + } + + @ServiceThreadOnly + private void updateSafeDeviceInfoList() { + assertRunOnServiceThread(); + List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); + List<HdmiDeviceInfo> externalInputs = getInputDevices(); + mSafeAllDeviceInfos = copiedDevices; + mSafeExternalInputs = externalInputs; + } + + /** + * Return a list of all {@link HdmiDeviceInfo}. + * + * <p>Declared as package-private. accessed by {@link HdmiControlService} only. + * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which + * does not include local device. + */ + @ServiceThreadOnly + List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { + assertRunOnServiceThread(); + if (includeLocalDevice) { + return HdmiUtils.sparseArrayToList(mDeviceInfos); + } else { + ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); + for (int i = 0; i < mDeviceInfos.size(); ++i) { + HdmiDeviceInfo info = mDeviceInfos.valueAt(i); + if (!isLocalDeviceAddress(info.getLogicalAddress())) { + infoList.add(info); + } + } + return infoList; + } + } + + /** + * Return external input devices. + */ + @GuardedBy("mLock") + List<HdmiDeviceInfo> getSafeExternalInputsLocked() { + return mSafeExternalInputs; + } + + /** + * Return a list of external cec input (source) devices. + * + * <p>Note that this effectively excludes non-source devices like system audio, + * secondary TV. + */ + private List<HdmiDeviceInfo> getInputDevices() { + ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); + for (int i = 0; i < mDeviceInfos.size(); ++i) { + HdmiDeviceInfo info = mDeviceInfos.valueAt(i); + if (isLocalDeviceAddress(info.getLogicalAddress())) { + continue; + } + if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { + infoList.add(info); + } + } + return infoList; + } + + // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. + // This only applies to TV devices. + // Returns true if the policy is set to true, and the device to check does not have + // a parent CEC device (which should be the CEC-enabled switch) in the list. + private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) { + return isLocalDeviceAddress(Constants.ADDR_TV) + && HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH + && !isConnectedToCecSwitch(info.getPhysicalAddress(), getCecSwitches()); + } + + /** + * Called when a device is removed or removal of device is detected. + * + * @param address a logical address of a device to be removed + */ + @ServiceThreadOnly + final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) { + assertRunOnServiceThread(); + HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); + + localDevice.mCecMessageCache.flushMessagesFrom(address); + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + + public void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { + HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + if (info == null) { + Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); + return; + } + + if (info.getDevicePowerStatus() == newPowerStatus) { + return; + } + + updateCecDevice(HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus)); + } + + /** + * Whether a device of the specified physical address is connected to ARC enabled port. + */ + boolean isConnectedToArcPort(int physicalAddress) { + int portId = physicalAddressToPortId(physicalAddress); + if (portId != Constants.INVALID_PORT_ID) { + return mPortInfoMap.get(portId).isArcSupported(); + } + return false; + } + + + // Initialize HDMI port information. Combine the information from CEC and MHL HAL and + // keep them in one place. + @ServiceThreadOnly + @VisibleForTesting + public void initPortInfo() { + assertRunOnServiceThread(); + HdmiPortInfo[] cecPortInfo = null; + // CEC HAL provides majority of the info while MHL does only MHL support flag for + // each port. Return empty array if CEC HAL didn't provide the info. + if (mHdmiCecController != null) { + cecPortInfo = mHdmiCecController.getPortInfos(); + } + if (cecPortInfo == null) { + return; + } + + SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); + SparseIntArray portIdMap = new SparseIntArray(); + SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>(); + for (HdmiPortInfo info : cecPortInfo) { + portIdMap.put(info.getAddress(), info.getId()); + portInfoMap.put(info.getId(), info); + portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); + } + mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); + mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); + mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); + + if (mHdmiMhlController == null) { + return; + } + HdmiPortInfo[] mhlPortInfo = mHdmiMhlController.getPortInfos(); + ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); + for (HdmiPortInfo info : mhlPortInfo) { + if (info.isMhlSupported()) { + mhlSupportedPorts.add(info.getId()); + } + } + + // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use + // cec port info if we do not have have port that supports MHL. + if (mhlSupportedPorts.isEmpty()) { + setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo))); + return; + } + ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); + for (HdmiPortInfo info : cecPortInfo) { + if (mhlSupportedPorts.contains(info.getId())) { + result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), + info.isCecSupported(), true, info.isArcSupported())); + } else { + result.add(info); + } + } + setPortInfo(Collections.unmodifiableList(result)); + } + + HdmiDeviceInfo getDeviceForPortId(int portId) { + return mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE); + } + + /** + * Whether a device of the specified physical address and logical address exists + * in a device info list. However, both are minimal condition and it could + * be different device from the original one. + * + * @param logicalAddress logical address of a device to be searched + * @param physicalAddress physical address of a device to be searched + * @return true if exist; otherwise false + */ + @ServiceThreadOnly + boolean isInDeviceList(int logicalAddress, int physicalAddress) { + assertRunOnServiceThread(); + HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); + if (device == null) { + return false; + } + return device.getPhysicalAddress() == physicalAddress; + } + + /** + * Passively listen to incoming CEC messages. + * + * This shall not result in any CEC messages being sent. + */ + @ServiceThreadOnly + public void handleCecMessage(HdmiCecMessage message) { + assertRunOnServiceThread(); + // Add device by logical address if it's not already known + int sourceAddress = message.getSource(); + if (getCecDeviceInfo(sourceAddress) == null) { + HdmiDeviceInfo newDevice = new HdmiDeviceInfo(sourceAddress, + HdmiDeviceInfo.PATH_INVALID, HdmiDeviceInfo.PORT_INVALID, + HdmiDeviceInfo.DEVICE_RESERVED, Constants.UNKNOWN_VENDOR_ID, + HdmiUtils.getDefaultDeviceName(sourceAddress)); + addCecDevice(newDevice); + } + + switch (message.getOpcode()) { + case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: + handleReportPhysicalAddress(message); + break; + case Constants.MESSAGE_REPORT_POWER_STATUS: + handleReportPowerStatus(message); + break; + case Constants.MESSAGE_SET_OSD_NAME: + handleSetOsdName(message); + break; + case Constants.MESSAGE_DEVICE_VENDOR_ID: + handleDeviceVendorId(message); + break; + + } + } + + @ServiceThreadOnly + private void handleReportPhysicalAddress(HdmiCecMessage message) { + assertRunOnServiceThread(); + int logicalAddress = message.getSource(); + int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); + int type = message.getParams()[2]; + + if (updateCecSwitchInfo(logicalAddress, type, physicalAddress)) return; + + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); + if (deviceInfo == null) { + Slog.i(TAG, "Unknown source device info for <Report Physical Address> " + message); + } else { + HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), + physicalAddress, + physicalAddressToPortId(physicalAddress), type, deviceInfo.getVendorId(), + deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus()); + updateCecDevice(updatedDeviceInfo); + } + } + + @ServiceThreadOnly + private void handleReportPowerStatus(HdmiCecMessage message) { + assertRunOnServiceThread(); + // Update power status of device + int newStatus = message.getParams()[0] & 0xFF; + updateDevicePowerStatus(message.getSource(), newStatus); + } + + @ServiceThreadOnly + private void handleSetOsdName(HdmiCecMessage message) { + assertRunOnServiceThread(); + int logicalAddress = message.getSource(); + String osdName; + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); + // If the device is not in device list, ignore it. + if (deviceInfo == null) { + Slog.i(TAG, "No source device info for <Set Osd Name>." + message); + return; + } + try { + osdName = new String(message.getParams(), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); + return; + } + + if (deviceInfo.getDisplayName() != null + && deviceInfo.getDisplayName().equals(osdName)) { + Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); + return; + } + + Slog.d(TAG, "Updating device OSD name from " + + deviceInfo.getDisplayName() + + " to " + osdName); + updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), + deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), + deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName, + deviceInfo.getDevicePowerStatus())); + } + + @ServiceThreadOnly + private void handleDeviceVendorId(HdmiCecMessage message) { + assertRunOnServiceThread(); + int logicalAddress = message.getSource(); + int vendorId = HdmiUtils.threeBytesToInt(message.getParams()); + + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); + if (deviceInfo == null) { + Slog.i(TAG, "Unknown source device info for <Device Vendor ID> " + message); + } else { + HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), + deviceInfo.getPhysicalAddress(), + deviceInfo.getPortId(), deviceInfo.getDeviceType(), vendorId, + deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus()); + updateCecDevice(updatedDeviceInfo); + } + } + + void addCecSwitch(int physicalAddress) { + mCecSwitches.add(physicalAddress); + } + + public ArraySet<Integer> getCecSwitches() { + return mCecSwitches; + } + + void removeDevicesConnectedToPort(int portId) { + Iterator<Integer> it = mCecSwitches.iterator(); + while (it.hasNext()) { + int path = it.next(); + int devicePortId = physicalAddressToPortId(path); + if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) { + it.remove(); + } + } + List<Integer> toRemove = new ArrayList<>(); + for (int i = 0; i < mDeviceInfos.size(); i++) { + int key = mDeviceInfos.keyAt(i); + int physicalAddress = mDeviceInfos.get(key).getPhysicalAddress(); + int devicePortId = physicalAddressToPortId(physicalAddress); + if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) { + toRemove.add(key); + } + } + for (Integer key : toRemove) { + removeDeviceInfo(key); + } + } + + boolean updateCecSwitchInfo(int address, int type, int path) { + if (address == Constants.ADDR_UNREGISTERED + && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) { + mCecSwitches.add(path); + updateSafeDeviceInfoList(); + return true; // Pure switch does not need further processing. Return here. + } + if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { + mCecSwitches.add(path); + } + return false; + } + + @GuardedBy("mLock") + List<HdmiDeviceInfo> getSafeCecDevicesLocked() { + ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (isLocalDeviceAddress(info.getLogicalAddress())) { + continue; + } + infoList.add(info); + } + return infoList; + } + + /** + * Thread safe version of {@link #getCecDeviceInfo(int)}. + * + * @param logicalAddress logical address to be retrieved + * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. + * Returns null if no logical address matched + */ + HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { + return info; + } + } + return null; + } + + /** + * Returns the {@link HdmiDeviceInfo} instance whose physical address matches + * + * + * + * qq * the given routing path. CEC devices use routing path for its physical address to + * describe the hierarchy of the devices in the network. + * + * @param path routing path or physical address + * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null + */ + @ServiceThreadOnly + final HdmiDeviceInfo getDeviceInfoByPath(int path) { + assertRunOnServiceThread(); + for (HdmiDeviceInfo info : getDeviceInfoList(false)) { + if (info.getPhysicalAddress() == path) { + return info; + } + } + return null; + } + + /** + * Returns the {@link HdmiDeviceInfo} instance whose physical address matches + * the given routing path. This is the version accessible safely from threads + * other than service thread. + * + * @param path routing path or physical address + * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null + */ + HdmiDeviceInfo getSafeDeviceInfoByPath(int path) { + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (info.getPhysicalAddress() == path) { + return info; + } + } + return null; + } + + public int getPhysicalAddress() { + return mHdmiCecController.getPhysicalAddress(); + } + + @ServiceThreadOnly + public void clear() { + assertRunOnServiceThread(); + initPortInfo(); + clearDeviceList(); + clearLocalDevices(); + } + + @ServiceThreadOnly + public void clearDeviceList() { + assertRunOnServiceThread(); + for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { + if (info.getPhysicalAddress() == getPhysicalAddress()) { + continue; + } + invokeDeviceEventListener(info, + HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } + mDeviceInfos.clear(); + updateSafeDeviceInfoList(); + } + + /** + * Returns HDMI port information for the given port id. + * + * @param portId HDMI port id + * @return {@link HdmiPortInfo} for the given port + */ + HdmiPortInfo getPortInfo(int portId) { + return mPortInfoMap.get(portId, null); + } + + /** + * Returns the routing path (physical address) of the HDMI port for the given + * port id. + */ + int portIdToPath(int portId) { + HdmiPortInfo portInfo = getPortInfo(portId); + if (portInfo == null) { + Slog.e(TAG, "Cannot find the port info: " + portId); + return Constants.INVALID_PHYSICAL_ADDRESS; + } + return portInfo.getAddress(); + } + + /** + * Returns the id of HDMI port located at the current device that runs this method. + * + * For TV with physical address 0x0000, target device 0x1120, we want port physical address + * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address + * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id. + * + * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to. + * + * @param path the target device's physical address. + * @return the id of the port that the target device eventually connects to + * on the current device. + */ + int physicalAddressToPortId(int path) { + int mask = 0xF000; + int finalMask = 0xF000; + int physicalAddress; + physicalAddress = getPhysicalAddress(); + int maskedAddress = physicalAddress; + + while (maskedAddress != 0) { + maskedAddress = physicalAddress & mask; + finalMask |= mask; + mask >>= 4; + } + + int portAddress = path & finalMask; + return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); + } + + List<HdmiPortInfo> getPortInfo() { + return mPortInfo; + } + + void setPortInfo(List<HdmiPortInfo> portInfo) { + mPortInfo = portInfo; + } + + private boolean isLocalDeviceAddress(int address) { + for (int i = 0; i < mLocalDevices.size(); i++) { + int key = mLocalDevices.keyAt(i); + if (mLocalDevices.get(key).mAddress == address) { + return true; + } + } + return false; + } + + private void assertRunOnServiceThread() { + if (Looper.myLooper() != mHandler.getLooper()) { + throw new IllegalStateException("Should run on service thread."); + } + } + + protected void dump(IndentingPrintWriter pw) { + pw.println("HDMI CEC Network"); + pw.increaseIndent(); + HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo); + for (int i = 0; i < mLocalDevices.size(); ++i) { + pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":"); + pw.increaseIndent(); + mLocalDevices.valueAt(i).dump(pw); + + pw.println("Active Source history:"); + pw.increaseIndent(); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + ArrayBlockingQueue<HdmiCecController.Dumpable> activeSourceHistory = + mLocalDevices.valueAt(i).getActiveSourceHistory(); + for (HdmiCecController.Dumpable activeSourceEvent : activeSourceHistory) { + activeSourceEvent.dump(pw, sdf); + } + pw.decreaseIndent(); + pw.decreaseIndent(); + } + HdmiUtils.dumpIterable(pw, "mDeviceInfos:", mSafeAllDeviceInfos); + pw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 8bb89da5726f..ee86593916ae 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -71,10 +71,8 @@ import android.os.UserHandle; import android.provider.Settings.Global; import android.sysprop.HdmiProperties; import android.text.TextUtils; -import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -91,7 +89,6 @@ import libcore.util.EmptyArray; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -177,6 +174,8 @@ public class HdmiControlService extends SystemService { static final int STANDBY_SCREEN_OFF = 0; static final int STANDBY_SHUTDOWN = 1; + private HdmiCecNetwork mHdmiCecNetwork; + // Logical address of the active source. @GuardedBy("mLock") protected final ActiveSource mActiveSource = new ActiveSource(); @@ -333,21 +332,6 @@ public class HdmiControlService extends SystemService { @Nullable private HdmiCecController mCecController; - // HDMI port information. Stored in the unmodifiable list to keep the static information - // from being modified. - // This variable is null if the current device does not have hdmi input. - @GuardedBy("mLock") - private List<HdmiPortInfo> mPortInfo = null; - - // Map from path(physical address) to port ID. - private UnmodifiableSparseIntArray mPortIdMap; - - // Map from port ID to HdmiPortInfo. - private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap; - - // Map from port ID to HdmiDeviceInfo. - private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap; - private HdmiCecMessageValidator mMessageValidator; @ServiceThreadOnly @@ -389,10 +373,6 @@ public class HdmiControlService extends SystemService { @Nullable private Looper mIoLooper; - // Thread safe physical address - @GuardedBy("mLock") - private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; - // Last input port before switching to the MHL port. Should switch back to this port // when the mobile device sends the request one touch play with off. // Gets invalidated if we go to other port/input. @@ -507,6 +487,26 @@ public class HdmiControlService extends SystemService { @Override public void onStart() { + initService(); + publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); + + if (mCecController != null) { + // Register broadcast receiver for power state change. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SHUTDOWN); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); + + // Register ContentObserver to monitor the settings change. + registerContentObserver(); + } + mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); + } + + @VisibleForTesting + void initService() { if (mIoLooper == null) { mIoThread.start(); mIoLooper = mIoThread.getLooper(); @@ -521,13 +521,7 @@ public class HdmiControlService extends SystemService { if (mCecController == null) { mCecController = HdmiCecController.create(this, getAtomWriter()); } - if (mCecController != null) { - if (mHdmiControlEnabled) { - initializeCec(INITIATED_BY_BOOT_UP); - } else { - mCecController.setOption(OptionKey.ENABLE_CEC, false); - } - } else { + if (mCecController == null) { Slog.i(TAG, "Device does not support HDMI-CEC."); return; } @@ -537,27 +531,18 @@ public class HdmiControlService extends SystemService { if (!mMhlController.isReady()) { Slog.i(TAG, "Device does not support MHL-control."); } + mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController); + if (mHdmiControlEnabled) { + initializeCec(INITIATED_BY_BOOT_UP); + } else { + mCecController.setOption(OptionKey.ENABLE_CEC, false); + } mMhlDevices = Collections.emptyList(); - initPortInfo(); + mHdmiCecNetwork.initPortInfo(); if (mMessageValidator == null) { mMessageValidator = new HdmiCecMessageValidator(this); } - publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); - - if (mCecController != null) { - // Register broadcast receiver for power state change. - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SHUTDOWN); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); - - // Register ContentObserver to monitor the settings change. - registerContentObserver(); - } - mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); } private void bootCompleted() { @@ -588,6 +573,15 @@ public class HdmiControlService extends SystemService { } @VisibleForTesting + void setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork) { + mHdmiCecNetwork = hdmiCecNetwork; + } + + public HdmiCecNetwork getHdmiCecNetwork() { + return mHdmiCecNetwork; + } + + @VisibleForTesting void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) { mMhlController = hdmiMhlController; } @@ -705,7 +699,7 @@ public class HdmiControlService extends SystemService { break; case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED: for (int type : mLocalDevices) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); if (localDevice != null) { localDevice.setAutoDeviceOff(enabled); } @@ -800,7 +794,7 @@ public class HdmiControlService extends SystemService { // A container for [Device type, Local device info]. ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); for (int type : mLocalDevices) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); if (localDevice == null) { localDevice = HdmiCecLocalDevice.create(this, type); } @@ -832,43 +826,48 @@ public class HdmiControlService extends SystemService { for (final HdmiCecLocalDevice localDevice : allocatingDevices) { mCecController.allocateLogicalAddress(localDevice.getType(), localDevice.getPreferredAddress(), new AllocateAddressCallback() { - @Override - public void onAllocated(int deviceType, int logicalAddress) { - if (logicalAddress == Constants.ADDR_UNREGISTERED) { - Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); - } else { - // Set POWER_STATUS_ON to all local devices because they share lifetime - // with system. - HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType, - HdmiControlManager.POWER_STATUS_ON); - localDevice.setDeviceInfo(deviceInfo); - mCecController.addLocalDevice(deviceType, localDevice); - mCecController.addLogicalAddress(logicalAddress); - allocatedDevices.add(localDevice); - } + @Override + public void onAllocated(int deviceType, int logicalAddress) { + if (logicalAddress == Constants.ADDR_UNREGISTERED) { + Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + + "]"); + } else { + // Set POWER_STATUS_ON to all local devices because they share + // lifetime + // with system. + HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, + deviceType, + HdmiControlManager.POWER_STATUS_ON); + localDevice.setDeviceInfo(deviceInfo); + mHdmiCecNetwork.addLocalDevice(deviceType, localDevice); + mCecController.addLogicalAddress(logicalAddress); + allocatedDevices.add(localDevice); + } - // Address allocation completed for all devices. Notify each device. - if (allocatingDevices.size() == ++finished[0]) { - mAddressAllocated = true; - if (initiatedBy != INITIATED_BY_HOTPLUG) { - // In case of the hotplug we don't call onInitializeCecComplete() - // since we reallocate the logical address only. - onInitializeCecComplete(initiatedBy); - } - notifyAddressAllocated(allocatedDevices, initiatedBy); - // Reinvoke the saved display status callback once the local device is ready. - if (mDisplayStatusCallback != null) { - queryDisplayStatus(mDisplayStatusCallback); - mDisplayStatusCallback = null; - } - if (mOtpCallbackPendingAddressAllocation != null) { - oneTouchPlay(mOtpCallbackPendingAddressAllocation); - mOtpCallbackPendingAddressAllocation = null; + // Address allocation completed for all devices. Notify each device. + if (allocatingDevices.size() == ++finished[0]) { + mAddressAllocated = true; + if (initiatedBy != INITIATED_BY_HOTPLUG) { + // In case of the hotplug we don't call + // onInitializeCecComplete() + // since we reallocate the logical address only. + onInitializeCecComplete(initiatedBy); + } + notifyAddressAllocated(allocatedDevices, initiatedBy); + // Reinvoke the saved display status callback once the local + // device is ready. + if (mDisplayStatusCallback != null) { + queryDisplayStatus(mDisplayStatusCallback); + mDisplayStatusCallback = null; + } + if (mOtpCallbackPendingAddressAllocation != null) { + oneTouchPlay(mOtpCallbackPendingAddressAllocation); + mOtpCallbackPendingAddressAllocation = null; + } + mCecMessageBuffer.processMessages(); + } } - mCecMessageBuffer.processMessages(); - } - } - }); + }); } } @@ -888,88 +887,14 @@ public class HdmiControlService extends SystemService { return mAddressAllocated; } - // Initialize HDMI port information. Combine the information from CEC and MHL HAL and - // keep them in one place. - @ServiceThreadOnly - @VisibleForTesting - protected void initPortInfo() { - assertRunOnServiceThread(); - HdmiPortInfo[] cecPortInfo = null; - - synchronized (mLock) { - mPhysicalAddress = getPhysicalAddress(); - } - - // CEC HAL provides majority of the info while MHL does only MHL support flag for - // each port. Return empty array if CEC HAL didn't provide the info. - if (mCecController != null) { - cecPortInfo = mCecController.getPortInfos(); - } - if (cecPortInfo == null) { - return; - } - - SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); - SparseIntArray portIdMap = new SparseIntArray(); - SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>(); - for (HdmiPortInfo info : cecPortInfo) { - portIdMap.put(info.getAddress(), info.getId()); - portInfoMap.put(info.getId(), info); - portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); - } - mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); - mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); - mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); - - if (mMhlController == null) { - return; - } - HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); - ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); - for (HdmiPortInfo info : mhlPortInfo) { - if (info.isMhlSupported()) { - mhlSupportedPorts.add(info.getId()); - } - } - - // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use - // cec port info if we do not have have port that supports MHL. - if (mhlSupportedPorts.isEmpty()) { - setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo))); - return; - } - ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); - for (HdmiPortInfo info : cecPortInfo) { - if (mhlSupportedPorts.contains(info.getId())) { - result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), - info.isCecSupported(), true, info.isArcSupported())); - } else { - result.add(info); - } - } - setPortInfo(Collections.unmodifiableList(result)); - } - List<HdmiPortInfo> getPortInfo() { synchronized (mLock) { - return mPortInfo; - } - } - - void setPortInfo(List<HdmiPortInfo> portInfo) { - synchronized (mLock) { - mPortInfo = portInfo; + return mHdmiCecNetwork.getPortInfo(); } } - /** - * Returns HDMI port information for the given port id. - * - * @param portId HDMI port id - * @return {@link HdmiPortInfo} for the given port - */ HdmiPortInfo getPortInfo(int portId) { - return mPortInfoMap.get(portId, null); + return mHdmiCecNetwork.getPortInfo(portId); } /** @@ -977,12 +902,7 @@ public class HdmiControlService extends SystemService { * port id. */ int portIdToPath(int portId) { - HdmiPortInfo portInfo = getPortInfo(portId); - if (portInfo == null) { - Slog.e(TAG, "Cannot find the port info: " + portId); - return Constants.INVALID_PHYSICAL_ADDRESS; - } - return portInfo.getAddress(); + return mHdmiCecNetwork.portIdToPath(portId); } /** @@ -999,26 +919,11 @@ public class HdmiControlService extends SystemService { * on the current device. */ int pathToPortId(int path) { - int mask = 0xF000; - int finalMask = 0xF000; - int physicalAddress; - synchronized (mLock) { - physicalAddress = mPhysicalAddress; - } - int maskedAddress = physicalAddress; - - while (maskedAddress != 0) { - maskedAddress = physicalAddress & mask; - finalMask |= mask; - mask >>= 4; - } - - int portAddress = path & finalMask; - return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); + return mHdmiCecNetwork.physicalAddressToPortId(path); } boolean isValidPortId(int portId) { - return getPortInfo(portId) != null; + return mHdmiCecNetwork.getPortInfo(portId) != null; } /** @@ -1068,7 +973,7 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly HdmiDeviceInfo getDeviceInfo(int logicalAddress) { assertRunOnServiceThread(); - return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress); + return mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); } @ServiceThreadOnly @@ -1092,11 +997,7 @@ public class HdmiControlService extends SystemService { * Whether a device of the specified physical address is connected to ARC enabled port. */ boolean isConnectedToArcPort(int physicalAddress) { - int portId = pathToPortId(physicalAddress); - if (portId != Constants.INVALID_PORT_ID) { - return mPortInfoMap.get(portId).isArcSupported(); - } - return false; + return mHdmiCecNetwork.isConnectedToArcPort(physicalAddress); } @ServiceThreadOnly @@ -1168,7 +1069,7 @@ public class HdmiControlService extends SystemService { } return true; } - + getHdmiCecNetwork().handleCecMessage(message); if (dispatchMessageToLocalDevice(message)) { return true; } @@ -1183,7 +1084,7 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { assertRunOnServiceThread(); - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { if (device.dispatchMessage(message) && message.getDestination() != Constants.ADDR_BROADCAST) { return true; @@ -1209,12 +1110,12 @@ public class HdmiControlService extends SystemService { if (connected && !isTvDevice() && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { if (isSwitchDevice()) { - initPortInfo(); + mHdmiCecNetwork.initPortInfo(); HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx."); } ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); for (int type : mLocalDevices) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); if (localDevice == null) { localDevice = HdmiCecLocalDevice.create(this, type); localDevice.init(); @@ -1224,9 +1125,14 @@ public class HdmiControlService extends SystemService { allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); } - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { device.onHotplug(portId, connected); } + + if (!connected) { + mHdmiCecNetwork.removeDevicesConnectedToPort(portId); + } + announceHotplugEvent(portId, connected); } @@ -1262,7 +1168,7 @@ public class HdmiControlService extends SystemService { List<HdmiCecLocalDevice> getAllLocalDevices() { assertRunOnServiceThread(); - return mCecController.getLocalDeviceList(); + return mHdmiCecNetwork.getLocalDeviceList(); } /** @@ -1275,8 +1181,14 @@ public class HdmiControlService extends SystemService { * * @param logicalAddress logical address of the remote device that might have the same logical * address as the current device. + * @param physicalAddress physical address of the given device. */ - protected void checkLogicalAddressConflictAndReallocate(int logicalAddress) { + protected void checkLogicalAddressConflictAndReallocate(int logicalAddress, + int physicalAddress) { + // The given device is a local device. No logical address conflict. + if (physicalAddress == getPhysicalAddress()) { + return; + } for (HdmiCecLocalDevice device : getAllLocalDevices()) { if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) { HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo()); @@ -1616,8 +1528,7 @@ public class HdmiControlService extends SystemService { return null; } if (audioSystem() != null) { - HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); - for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) { + for (HdmiDeviceInfo info : mHdmiCecNetwork.getSafeCecDevicesLocked()) { if (info.getPhysicalAddress() == activeSource.physicalAddress) { return info; } @@ -1649,7 +1560,7 @@ public class HdmiControlService extends SystemService { } int activePath = tv.getActivePath(); if (activePath != HdmiDeviceInfo.PATH_INVALID) { - HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath); + HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath); return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId()); } return null; @@ -1752,7 +1663,7 @@ public class HdmiControlService extends SystemService { return; } if (mCecController != null) { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType); if (localDevice == null) { Slog.w(TAG, "Local device not available to send key event."); return; @@ -1774,7 +1685,7 @@ public class HdmiControlService extends SystemService { Slog.w(TAG, "CEC controller not available to send volume key event."); return; } - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType); if (localDevice == null) { Slog.w(TAG, "Local device " + deviceType + " not available to send volume key event."); @@ -1888,7 +1799,7 @@ public class HdmiControlService extends SystemService { public int getPhysicalAddress() { enforceAccessPermission(); synchronized (mLock) { - return mPhysicalAddress; + return mHdmiCecNetwork.getPhysicalAddress(); } } @@ -1934,13 +1845,8 @@ public class HdmiControlService extends SystemService { enforceAccessPermission(); // No need to hold the lock for obtaining TV device as the local device instance // is preserved while the HDMI control is enabled. - HdmiCecLocalDeviceTv tv = tv(); - synchronized (mLock) { - List<HdmiDeviceInfo> cecDevices = (tv == null) - ? Collections.<HdmiDeviceInfo>emptyList() - : tv.getSafeExternalInputsLocked(); - return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); - } + return HdmiUtils.mergeToUnmodifiableList(mHdmiCecNetwork.getSafeExternalInputsLocked(), + getMhlDevicesLocked()); } // Returns all the CEC devices on the bus including system audio, switch, @@ -1948,19 +1854,7 @@ public class HdmiControlService extends SystemService { @Override public List<HdmiDeviceInfo> getDeviceList() { enforceAccessPermission(); - HdmiCecLocalDeviceTv tv = tv(); - if (tv != null) { - synchronized (mLock) { - return tv.getSafeCecDevicesLocked(); - } - } else { - HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); - synchronized (mLock) { - return (audioSystem == null) - ? Collections.<HdmiDeviceInfo>emptyList() - : audioSystem.getSafeCecDevicesLocked(); - } - } + return mHdmiCecNetwork.getSafeCecDevicesLocked(); } @Override @@ -2089,7 +1983,7 @@ public class HdmiControlService extends SystemService { runOnServiceThread(new Runnable() { @Override public void run() { - HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); if (device == null) { Slog.w(TAG, "Local device not available"); return; @@ -2117,7 +2011,7 @@ public class HdmiControlService extends SystemService { mhlDevice.sendStandby(); return; } - HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); if (device == null) { device = audioSystem(); } @@ -2262,7 +2156,7 @@ public class HdmiControlService extends SystemService { runOnServiceThread(new Runnable() { @Override public void run() { - HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); + HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); if (device == null) { Slog.w(TAG, "Local device not available"); return; @@ -2339,8 +2233,7 @@ public class HdmiControlService extends SystemService { pw.increaseIndent(); mMhlController.dump(pw); pw.decreaseIndent(); - - HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo); + mHdmiCecNetwork.dump(pw); if (mCecController != null) { pw.println("mCecController: "); pw.increaseIndent(); @@ -2832,7 +2725,7 @@ public class HdmiControlService extends SystemService { } public HdmiCecLocalDeviceTv tv() { - return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); + return (HdmiCecLocalDeviceTv) mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); } boolean isTvDevice() { @@ -2857,11 +2750,11 @@ public class HdmiControlService extends SystemService { protected HdmiCecLocalDevicePlayback playback() { return (HdmiCecLocalDevicePlayback) - mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); + mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); } public HdmiCecLocalDeviceAudioSystem audioSystem() { - return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice( + return (HdmiCecLocalDeviceAudioSystem) mHdmiCecNetwork.getLocalDevice( HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); } @@ -2991,7 +2884,7 @@ public class HdmiControlService extends SystemService { } private boolean canGoToStandby() { - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { if (!device.canGoToStandby()) return false; } return true; @@ -3025,7 +2918,7 @@ public class HdmiControlService extends SystemService { private void disableDevices(PendingActionClearedCallback callback) { if (mCecController != null) { - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { device.disableDevice(mStandbyMessageReceived, callback); } } @@ -3039,7 +2932,8 @@ public class HdmiControlService extends SystemService { return; } mCecController.clearLogicalAddress(); - mCecController.clearLocalDevices(); + mHdmiCecNetwork.clearLogicalAddress(); + mHdmiCecNetwork.clearLocalDevices(); } @ServiceThreadOnly @@ -3051,7 +2945,7 @@ public class HdmiControlService extends SystemService { return; } mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { device.onStandby(mStandbyMessageReceived, standbyAction); } mStandbyMessageReceived = false; @@ -3411,7 +3305,7 @@ public class HdmiControlService extends SystemService { // input change listener should be the one describing the corresponding HDMI port. HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); HdmiDeviceInfo info = (device != null) ? device.getInfo() - : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE); + : mHdmiCecNetwork.getDeviceForPortId(portId); invokeInputChangeListener(info); } diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index 7670dccf9c0a..ece78bfa2769 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -148,7 +148,8 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { } private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) { - BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly); + BitSet currentInfos = infoListToBitSet( + localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false), audioOnly); BitSet polledResult = addressListToBitSet(ackedAddress); // At first, check removed devices. @@ -225,11 +226,11 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { mayCancelOneTouchRecord(removedAddress); mayDisableSystemAudioAndARC(removedAddress); - tv().removeCecDevice(removedAddress); + localDevice().mService.getHdmiCecNetwork().removeCecDevice(localDevice(), removedAddress); } private void mayChangeRoutingPath(int address) { - HdmiDeviceInfo info = tv().getCecDeviceInfo(address); + HdmiDeviceInfo info = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(address); if (info != null) { tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); } diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index 6753368911b9..edc7bd95a017 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -19,6 +19,7 @@ import android.hardware.hdmi.HdmiDeviceInfo; import android.util.Slog; import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; + import java.io.UnsupportedEncodingException; /** @@ -164,7 +165,8 @@ final class NewDeviceAction extends HdmiCecFeatureAction { private void addDeviceInfo() { // The device should be in the device list with default information. - if (!tv().isInDeviceList(mDeviceLogicalAddress, mDevicePhysicalAddress)) { + if (!localDevice().mService.getHdmiCecNetwork().isInDeviceList(mDeviceLogicalAddress, + mDevicePhysicalAddress)) { Slog.w(TAG, String.format("Device not found (%02x, %04x)", mDeviceLogicalAddress, mDevicePhysicalAddress)); return; @@ -176,7 +178,7 @@ final class NewDeviceAction extends HdmiCecFeatureAction { mDeviceLogicalAddress, mDevicePhysicalAddress, tv().getPortId(mDevicePhysicalAddress), mDeviceType, mVendorId, mDisplayName); - tv().addCecDevice(deviceInfo); + localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo); // Consume CEC messages we already got for this newly found device. tv().processDelayedMessages(mDeviceLogicalAddress); diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index e78a86c21453..53f9a109d08d 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -19,6 +19,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; import android.hardware.hdmi.IHdmiControlCallback; import android.os.RemoteException; +import android.provider.Settings.Global; import android.util.Slog; import java.util.ArrayList; @@ -54,6 +55,8 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { private int mPowerStatusCounter = 0; + private HdmiCecLocalDeviceSource mSource; + // Factory method. Ensures arguments are valid. static OneTouchPlayAction create(HdmiCecLocalDeviceSource source, int targetAddress, IHdmiControlCallback callback) { @@ -74,27 +77,33 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { @Override boolean start() { + // Because only source device can create this action, it's safe to cast. + mSource = source(); sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress)); broadcastActiveSource(); + // If the device is not an audio system itself, request the connected audio system to + // turn on. + if (shouldTurnOnConnectedAudioSystem()) { + sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(), + Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true)); + } queryDevicePowerStatus(); addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } private void broadcastActiveSource() { - // Because only source device can create this action, it's safe to cast. - HdmiCecLocalDeviceSource source = source(); - source.mService.setAndBroadcastActiveSourceFromOneDeviceType( + mSource.mService.setAndBroadcastActiveSourceFromOneDeviceType( mTargetAddress, getSourcePath(), "OneTouchPlayAction#broadcastActiveSource()"); // When OneTouchPlay is called, client side should be responsible to send out the intent // of which internal source, for example YouTube, it would like to switch to. // Here we only update the active port and the active source records in the local // device as well as claiming Active Source. - if (source.mService.audioSystem() != null) { - source = source.mService.audioSystem(); + if (mSource.mService.audioSystem() != null) { + mSource = mSource.mService.audioSystem(); } - source.setRoutingPort(Constants.CEC_SWITCH_HOME); - source.setLocalActivePort(Constants.CEC_SWITCH_HOME); + mSource.setRoutingPort(Constants.CEC_SWITCH_HOME); + mSource.setLocalActivePort(Constants.CEC_SWITCH_HOME); } private void queryDevicePowerStatus() { @@ -151,4 +160,14 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { Slog.e(TAG, "Callback failed:" + e); } } + + private boolean shouldTurnOnConnectedAudioSystem() { + HdmiControlService service = mSource.mService; + if (service.isAudioSystemDevice()) { + return false; + } + String sendStandbyOnSleep = service.readStringSetting( + Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, ""); + return sendStandbyOnSleep.equals(HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST); + } } diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java index a62d0b63221c..909fcda26c39 100644 --- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java +++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java @@ -20,7 +20,9 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.util.SparseIntArray; + import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + import java.util.List; /** @@ -111,7 +113,8 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { } private void queryPowerStatus() { - List<HdmiDeviceInfo> deviceInfos = tv().getDeviceInfoList(false); + List<HdmiDeviceInfo> deviceInfos = + localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false); resetPowerStatus(deviceInfos); for (HdmiDeviceInfo info : deviceInfos) { final int logicalAddress = info.getLogicalAddress(); @@ -137,7 +140,8 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction { } private void updatePowerStatus(int logicalAddress, int newStatus, boolean remove) { - tv().updateDevicePowerStatus(logicalAddress, newStatus); + localDevice().mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress, + newStatus); if (remove) { mPowerStatus.delete(logicalAddress); diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java index 6c8694ea74ad..6c147ed5e6d6 100644 --- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java +++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java @@ -17,8 +17,8 @@ package com.android.server.hdmi; import android.annotation.Nullable; -import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.RemoteException; import android.util.Slog; @@ -160,7 +160,9 @@ final class RoutingControlAction extends HdmiCecFeatureAction { } switch (timeoutState) { case STATE_WAIT_FOR_ROUTING_INFORMATION: - HdmiDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath); + HdmiDeviceInfo device = + localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath( + mCurrentRoutingPath); if (device != null && mQueryDevicePowerStatus) { int deviceLogicalAddress = device.getLogicalAddress(); queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index cdb73d8ad6a2..9ca4d35f4579 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -556,8 +556,8 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void registerLocationListener(String provider, LocationRequest request, - ILocationListener listener, String packageName, String attributionTag, - String listenerId) { + ILocationListener listener, String packageName, @Nullable String attributionTag, + @Nullable String listenerId) { CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, listenerId); int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), @@ -582,7 +582,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void registerLocationPendingIntent(String provider, LocationRequest request, - PendingIntent pendingIntent, String packageName, String attributionTag) { + PendingIntent pendingIntent, String packageName, @Nullable String attributionTag) { CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag, AppOpsManager.toReceiverId(pendingIntent)); int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(), diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java index 179fb7d2f723..b4a172393ba6 100644 --- a/services/core/java/com/android/server/location/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/LocationProviderManager.java @@ -77,7 +77,6 @@ import android.util.SparseBooleanArray; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; import com.android.internal.util.Preconditions; @@ -115,7 +114,6 @@ import java.util.Objects; class LocationProviderManager extends ListenerMultiplexer<Object, LocationProviderManager.LocationTransport, - LocationProviderManager.LocationListenerOperation, LocationProviderManager.Registration, ProviderRequest> implements AbstractLocationProvider.Listener { @@ -225,16 +223,8 @@ class LocationProviderManager extends } } - protected interface LocationListenerOperation extends ListenerOperation<LocationTransport> { - /** - * Must be implemented to return the location this operation intends to deliver. - */ - @Nullable - Location getLocation(); - } - protected abstract class Registration extends RemoteListenerRegistration<LocationRequest, - LocationTransport, LocationListenerOperation> { + LocationTransport> { private final @PermissionLevel int mPermissionLevel; @@ -310,7 +300,7 @@ class LocationProviderManager extends protected void onProviderListenerUnregister() {} @Override - protected final LocationListenerOperation onActive() { + protected final void onActive() { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); } @@ -319,11 +309,12 @@ class LocationProviderManager extends mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey()); } onHighPowerUsageChanged(); - return onProviderListenerActive(); + + onProviderListenerActive(); } @Override - protected final LocationListenerOperation onInactive() { + protected final void onInactive() { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); } @@ -332,24 +323,21 @@ class LocationProviderManager extends if (!getRequest().isHiddenFromAppOps()) { mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey()); } - return onProviderListenerInactive(); + + onProviderListenerInactive(); } /** * Subclasses may override this instead of {@link #onActive()}. */ @GuardedBy("mLock") - protected LocationListenerOperation onProviderListenerActive() { - return null; - } + protected void onProviderListenerActive() {} /** * Subclasses may override this instead of {@link #onInactive()} ()}. */ @GuardedBy("mLock") - protected LocationListenerOperation onProviderListenerInactive() { - return null; - } + protected void onProviderListenerInactive() {} @Override public final LocationRequest getRequest() { @@ -357,10 +345,8 @@ class LocationProviderManager extends } @GuardedBy("mLock") - final void initializeLastLocation(@Nullable Location location) { - if (mLastLocation == null) { - mLastLocation = location; - } + final void setLastDeliveredLocation(@Nullable Location location) { + mLastLocation = location; } @GuardedBy("mLock") @@ -541,16 +527,8 @@ class LocationProviderManager extends } @GuardedBy("mLock") - @Override - protected final LocationListenerOperation onExecuteOperation( - LocationListenerOperation operation) { - mLastLocation = operation.getLocation(); - return super.onExecuteOperation(operation); - } - - @GuardedBy("mLock") - @Nullable - abstract LocationListenerOperation acceptLocationChange(Location fineLocation); + abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange( + Location fineLocation); @Override public String toString() { @@ -656,7 +634,7 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override - protected final LocationListenerOperation onProviderListenerActive() { + protected final void onProviderListenerActive() { // a new registration may not get a location immediately, the provider request may be // delayed. therefore we deliver a historical location if available. since delivering an // older location could be considered a breaking change for some applications, we only @@ -679,12 +657,10 @@ class LocationProviderManager extends getRequest().isLocationSettingsIgnored(), maxLocationAgeMs); if (lastLocation != null) { - return acceptLocationChange(lastLocation); + executeOperation(acceptLocationChange(lastLocation)); } } } - - return null; } @Override @@ -703,9 +679,9 @@ class LocationProviderManager extends } @GuardedBy("mLock") - @Nullable @Override - LocationListenerOperation acceptLocationChange(Location fineLocation) { + @Nullable ListenerOperation<LocationTransport> acceptLocationChange( + Location fineLocation) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); } @@ -748,16 +724,20 @@ class LocationProviderManager extends return null; } - return new LocationListenerOperation() { - @Override - public Location getLocation() { - return location; - } + // deliver location + return new ListenerOperation<LocationTransport>() { + + private boolean mUseWakeLock; @Override public void onPreExecute() { + mUseWakeLock = !location.isFromMockProvider(); + + // update last delivered location + setLastDeliveredLocation(location); + // don't acquire a wakelock for mock locations to prevent abuse - if (!location.isFromMockProvider()) { + if (mUseWakeLock) { mWakeLock.acquire(WAKELOCK_TIMEOUT_MS); } } @@ -774,13 +754,13 @@ class LocationProviderManager extends } listener.deliverOnLocationChanged(deliveryLocation, - location.isFromMockProvider() ? null : mWakeLock::release); + mUseWakeLock ? mWakeLock::release : null); mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity()); } @Override public void onPostExecute(boolean success) { - if (!success && !location.isFromMockProvider()) { + if (!success && mUseWakeLock) { mWakeLock.release(); } @@ -852,7 +832,8 @@ class LocationProviderManager extends } @Override - public void onOperationFailure(LocationListenerOperation operation, Exception exception) { + public void onOperationFailure(ListenerOperation<LocationTransport> operation, + Exception exception) { onTransportFailure(exception); } @@ -913,7 +894,8 @@ class LocationProviderManager extends } @Override - public void onOperationFailure(LocationListenerOperation operation, Exception exception) { + public void onOperationFailure(ListenerOperation<LocationTransport> operation, + Exception exception) { onTransportFailure(exception); } @@ -988,28 +970,24 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override - protected LocationListenerOperation onProviderListenerActive() { + protected void onProviderListenerActive() { Location lastLocation = getLastLocationUnsafe( getIdentity().getUserId(), getPermissionLevel(), getRequest().isLocationSettingsIgnored(), MAX_CURRENT_LOCATION_AGE_MS); if (lastLocation != null) { - return acceptLocationChange(lastLocation); + executeOperation(acceptLocationChange(lastLocation)); } - - return null; } @GuardedBy("mLock") @Override - protected LocationListenerOperation onProviderListenerInactive() { + protected void onProviderListenerInactive() { if (!getRequest().isLocationSettingsIgnored()) { // if we go inactive for any reason, fail immediately - return acceptLocationChange(null); + executeOperation(acceptLocationChange(null)); } - - return null; } void deliverNull() { @@ -1035,9 +1013,9 @@ class LocationProviderManager extends } @GuardedBy("mLock") - @Nullable @Override - LocationListenerOperation acceptLocationChange(@Nullable Location fineLocation) { + @Nullable ListenerOperation<LocationTransport> acceptLocationChange( + @Nullable Location fineLocation) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); } @@ -1059,36 +1037,21 @@ class LocationProviderManager extends Location location = getPermittedLocation(fineLocation, getPermissionLevel()); - return new LocationListenerOperation() { - @Override - public Location getLocation() { - return location; + // deliver location + return listener -> { + // if delivering to the same process, make a copy of the location first (since + // location is mutable) + Location deliveryLocation = location; + if (getIdentity().getPid() == Process.myPid() && location != null) { + deliveryLocation = new Location(location); } - @Override - public void operate(LocationTransport listener) { - // if delivering to the same process, make a copy of the location first (since - // location is mutable) - Location deliveryLocation = location; - if (getIdentity().getPid() == Process.myPid() && location != null) { - deliveryLocation = new Location(location); - } - - // we currently don't hold a wakelock for getCurrentLocation deliveries - try { - listener.deliverOnLocationChanged(deliveryLocation, null); - mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity()); - } catch (Exception exception) { - if (exception instanceof RemoteException) { - Log.w(TAG, "registration " + this + " failed", exception); - } else { - throw new AssertionError(exception); - } - } + // we currently don't hold a wakelock for getCurrentLocation deliveries + listener.deliverOnLocationChanged(deliveryLocation, null); + mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity()); - synchronized (mLock) { - remove(); - } + synchronized (mLock) { + remove(); } }; } @@ -1114,7 +1077,7 @@ class LocationProviderManager extends protected final Object mLock = new Object(); protected final String mName; - @Nullable private final PassiveLocationProviderManager mPassiveManager; + private final @Nullable PassiveLocationProviderManager mPassiveManager; protected final Context mContext; @@ -1178,7 +1141,7 @@ class LocationProviderManager extends protected final MockableLocationProvider mProvider; @GuardedBy("mLock") - @Nullable private OnAlarmListener mDelayedRegister; + private @Nullable OnAlarmListener mDelayedRegister; LocationProviderManager(Context context, Injector injector, String name, @Nullable PassiveLocationProviderManager passiveManager) { @@ -1254,13 +1217,11 @@ class LocationProviderManager extends return mName; } - @Nullable - public CallerIdentity getIdentity() { + public @Nullable CallerIdentity getIdentity() { return mProvider.getState().identity; } - @Nullable - public ProviderProperties getProperties() { + public @Nullable ProviderProperties getProperties() { return mProvider.getState().properties; } @@ -1381,9 +1342,8 @@ class LocationProviderManager extends } } - @Nullable - public Location getLastLocation(CallerIdentity identity, @PermissionLevel int permissionLevel, - boolean ignoreLocationSettings) { + public @Nullable Location getLastLocation(CallerIdentity identity, + @PermissionLevel int permissionLevel, boolean ignoreLocationSettings) { if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(), identity.getPackageName())) { return null; @@ -1426,9 +1386,9 @@ class LocationProviderManager extends * location, even if the permissionLevel is coarse. You are responsible for coarsening the * location if necessary. */ - @Nullable - public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel, - boolean ignoreLocationSettings, long maximumAgeMs) { + public @Nullable Location getLastLocationUnsafe(int userId, + @PermissionLevel int permissionLevel, boolean ignoreLocationSettings, + long maximumAgeMs) { if (userId == UserHandle.USER_ALL) { // find the most recent location across all users Location lastLocation = null; @@ -1500,8 +1460,7 @@ class LocationProviderManager extends } } - @Nullable - public ICancellationSignal getCurrentLocation(LocationRequest request, + public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request, CallerIdentity identity, int permissionLevel, ILocationCallback callback) { if (request.getDurationMillis() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) { request = new LocationRequest.Builder(request) @@ -1519,7 +1478,7 @@ class LocationProviderManager extends synchronized (mLock) { final long ident = Binder.clearCallingIdentity(); try { - addRegistration(callback.asBinder(), registration); + putRegistration(callback.asBinder(), registration); if (!registration.isActive()) { // if the registration never activated, fail it immediately registration.deliverNull(); @@ -1560,7 +1519,7 @@ class LocationProviderManager extends synchronized (mLock) { final long ident = Binder.clearCallingIdentity(); try { - addRegistration(listener.asBinder(), registration); + putRegistration(listener.asBinder(), registration); } finally { Binder.restoreCallingIdentity(ident); } @@ -1578,7 +1537,7 @@ class LocationProviderManager extends synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { - addRegistration(pendingIntent, registration); + putRegistration(pendingIntent, registration); } finally { Binder.restoreCallingIdentity(identity); } @@ -1673,7 +1632,7 @@ class LocationProviderManager extends Registration newRegistration) { // by saving the last delivered location state we are able to potentially delay the // resulting provider request longer and save additional power - newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation()); + newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation()); super.onRegistrationReplaced(key, oldRegistration, newRegistration); } @@ -2056,7 +2015,9 @@ class LocationProviderManager extends setLastLocation(location, UserHandle.USER_ALL); // attempt listener delivery - deliverToListeners(registration -> registration.acceptLocationChange(location)); + deliverToListeners(registration -> { + return registration.acceptLocationChange(location); + }); // notify passive provider if (mPassiveManager != null) { @@ -2194,8 +2155,7 @@ class LocationProviderManager extends updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - @Nullable - private Location getPermittedLocation(@Nullable Location fineLocation, + private @Nullable Location getPermittedLocation(@Nullable Location fineLocation, @PermissionLevel int permissionLevel) { switch (permissionLevel) { case PERMISSION_FINE: @@ -2250,10 +2210,10 @@ class LocationProviderManager extends private static class LastLocation { - @Nullable private Location mFineLocation; - @Nullable private Location mCoarseLocation; - @Nullable private Location mFineBypassLocation; - @Nullable private Location mCoarseBypassLocation; + private @Nullable Location mFineLocation; + private @Nullable Location mCoarseLocation; + private @Nullable Location mFineBypassLocation; + private @Nullable Location mCoarseBypassLocation; public void clearMock() { if (mFineLocation != null && mFineLocation.isFromMockProvider()) { @@ -2275,8 +2235,8 @@ class LocationProviderManager extends mCoarseLocation = null; } - @Nullable - public Location get(@PermissionLevel int permissionLevel, boolean ignoreLocationSettings) { + public @Nullable Location get(@PermissionLevel int permissionLevel, + boolean ignoreLocationSettings) { switch (permissionLevel) { case PERMISSION_FINE: if (ignoreLocationSettings) { @@ -2337,13 +2297,12 @@ class LocationProviderManager extends private static class SingleUseCallback extends IRemoteCallback.Stub implements Runnable, CancellationSignal.OnCancelListener { - @Nullable - public static SingleUseCallback wrap(@Nullable Runnable callback) { + public static @Nullable SingleUseCallback wrap(@Nullable Runnable callback) { return callback == null ? null : new SingleUseCallback(callback); } @GuardedBy("this") - @Nullable private Runnable mCallback; + private @Nullable Runnable mCallback; private SingleUseCallback(Runnable callback) { mCallback = Objects.requireNonNull(callback); diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java index c91ee824ff61..7a59cba02dd9 100644 --- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java +++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java @@ -41,7 +41,6 @@ import android.stats.location.LocationStatsEnums; import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; import com.android.server.PendingIntentUtils; import com.android.server.location.LocationPermissions; import com.android.server.location.listeners.ListenerMultiplexer; @@ -60,8 +59,8 @@ import java.util.Objects; * Manages all geofences. */ public class GeofenceManager extends - ListenerMultiplexer<GeofenceKey, PendingIntent, ListenerOperation<PendingIntent>, - GeofenceManager.GeofenceRegistration, LocationRequest> implements + ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration, + LocationRequest> implements LocationListener { private static final String TAG = "GeofenceManager"; @@ -121,12 +120,10 @@ public class GeofenceManager extends } @Override - protected ListenerOperation<PendingIntent> onActive() { + protected void onActive() { Location location = getLastLocation(); if (location != null) { - return onLocationChanged(location); - } else { - return null; + executeOperation(onLocationChanged(location)); } } @@ -304,7 +301,7 @@ public class GeofenceManager extends final long identity = Binder.clearCallingIdentity(); try { - addRegistration(new GeofenceKey(pendingIntent, geofence), + putRegistration(new GeofenceKey(pendingIntent, geofence), new GeofenceRegistration(geofence, callerIdentity, pendingIntent)); } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index ec48d4cbcecd..7592d22a3a78 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -32,7 +32,6 @@ import android.os.IInterface; import android.os.Process; import android.util.ArraySet; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.location.listeners.BinderListenerRegistration; @@ -60,7 +59,7 @@ import java.util.Objects; */ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInterface, TMergedRegistration> extends - ListenerMultiplexer<IBinder, TListener, ListenerOperation<TListener>, + ListenerMultiplexer<IBinder, TListener, GnssListenerMultiplexer<TRequest, TListener, TMergedRegistration> .GnssListenerRegistration, TMergedRegistration> { @@ -231,7 +230,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter TListener listener) { final long identity = Binder.clearCallingIdentity(); try { - addRegistration(listener.asBinder(), + putRegistration(listener.asBinder(), createRegistration(request, callerIdentity, listener)); } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 74284f357f61..4a3f94f9b73a 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -63,18 +63,16 @@ public final class GnssMeasurementsProvider extends @Nullable @Override - protected ListenerOperation<IGnssMeasurementsListener> onActive() { + protected void onActive() { mLocationAttributionHelper.reportHighPowerLocationStart( getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); - return null; } @Nullable @Override - protected ListenerOperation<IGnssMeasurementsListener> onInactive() { + protected void onInactive() { mLocationAttributionHelper.reportHighPowerLocationStop( getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); - return null; } } diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java index bc675ceda970..d6b179bab5a2 100644 --- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java @@ -23,8 +23,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; - /** * A registration that works with IBinder keys, and registers a DeathListener to automatically * remove the registration if the binder dies. The key for this registration must either be an @@ -34,7 +32,7 @@ import com.android.internal.listeners.ListenerExecutor.ListenerOperation; * @param <TListener> listener type */ public abstract class BinderListenerRegistration<TRequest, TListener> extends - RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements + RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient { /** diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java index 0318ffb519ba..6b936169c82f 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java @@ -75,13 +75,11 @@ import java.util.function.Predicate; * * @param <TKey> key type * @param <TListener> listener type - * @param <TListenerOperation> listener operation type * @param <TRegistration> registration type * @param <TMergedRegistration> merged registration type */ public abstract class ListenerMultiplexer<TKey, TListener, - TListenerOperation extends ListenerOperation<TListener>, - TRegistration extends ListenerRegistration<TListener, TListenerOperation>, + TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> { @GuardedBy("mRegistrations") @@ -218,10 +216,26 @@ public abstract class ListenerMultiplexer<TKey, TListener, protected void onInactive() {} /** - * Adds a new registration with the given key. This method cannot be called to add a - * registration re-entrantly. + * Puts a new registration with the given key, replacing any previous registration under the + * same key. This method cannot be called to put a registration re-entrantly. + */ + protected final void putRegistration(@NonNull TKey key, @NonNull TRegistration registration) { + replaceRegistration(key, key, registration); + } + + /** + * Atomically removes the registration with the old key and adds a new registration with the + * given key. If there was a registration for the old key, + * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be + * invoked for the new registration and key instead of + * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share + * the same key. The old key may be the same value as the new key, in which case this function + * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot + * be called to add a registration re-entrantly. */ - protected final void addRegistration(@NonNull TKey key, @NonNull TRegistration registration) { + protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key, + @NonNull TRegistration registration) { + Objects.requireNonNull(oldKey); Objects.requireNonNull(key); Objects.requireNonNull(registration); @@ -229,6 +243,9 @@ public abstract class ListenerMultiplexer<TKey, TListener, // adding listeners reentrantly is not supported Preconditions.checkState(!mReentrancyGuard.isReentrant()); + // new key may only have a prior registration if the oldKey is the same as the key + Preconditions.checkArgument(oldKey == key || !mRegistrations.containsKey(key)); + // since adding a registration can invoke a variety of callbacks, we need to ensure // those callbacks themselves do not re-enter, as this could lead to out-of-order // callbacks. further, we buffer service updates since adding a registration may @@ -241,9 +258,11 @@ public abstract class ListenerMultiplexer<TKey, TListener, boolean wasEmpty = mRegistrations.isEmpty(); TRegistration oldRegistration = null; - int index = mRegistrations.indexOfKey(key); + int index = mRegistrations.indexOfKey(oldKey); if (index >= 0) { - oldRegistration = removeRegistration(index, false); + oldRegistration = removeRegistration(index, oldKey != key); + } + if (oldKey == key && index >= 0) { mRegistrations.setValueAt(index, registration); } else { mRegistrations.put(key, registration); @@ -316,7 +335,7 @@ public abstract class ListenerMultiplexer<TKey, TListener, * re-entrancy, and may be called to remove a registration re-entrantly. */ protected final void removeRegistration(@NonNull Object key, - @NonNull ListenerRegistration<?, ?> registration) { + @NonNull ListenerRegistration<?> registration) { synchronized (mRegistrations) { int index = mRegistrations.indexOfKey(key); if (index < 0) { @@ -478,15 +497,9 @@ public abstract class ListenerMultiplexer<TKey, TListener, if (++mActiveRegistrationsCount == 1) { onActive(); } - TListenerOperation operation = registration.onActive(); - if (operation != null) { - execute(registration, operation); - } + registration.onActive(); } else { - TListenerOperation operation = registration.onInactive(); - if (operation != null) { - execute(registration, operation); - } + registration.onInactive(); if (--mActiveRegistrationsCount == 0) { onInactive(); } @@ -502,16 +515,16 @@ public abstract class ListenerMultiplexer<TKey, TListener, * change the active state of the registration. */ protected final void deliverToListeners( - @NonNull Function<TRegistration, TListenerOperation> function) { + @NonNull Function<TRegistration, ListenerOperation<TListener>> function) { synchronized (mRegistrations) { try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) { final int size = mRegistrations.size(); for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { - TListenerOperation operation = function.apply(registration); + ListenerOperation<TListener> operation = function.apply(registration); if (operation != null) { - execute(registration, operation); + registration.executeOperation(operation); } } } @@ -526,14 +539,14 @@ public abstract class ListenerMultiplexer<TKey, TListener, * deliverToListeners(registration -> operation); * </pre> */ - protected final void deliverToListeners(@NonNull TListenerOperation operation) { + protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) { synchronized (mRegistrations) { try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) { final int size = mRegistrations.size(); for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { - execute(registration, operation); + registration.executeOperation(operation); } } } @@ -545,10 +558,6 @@ public abstract class ListenerMultiplexer<TKey, TListener, onRegistrationActiveChanged(registration); } - private void execute(TRegistration registration, TListenerOperation operation) { - registration.executeInternal(operation); - } - /** * Dumps debug information. */ @@ -606,7 +615,7 @@ public abstract class ListenerMultiplexer<TKey, TListener, @GuardedBy("mRegistrations") private int mGuardCount; @GuardedBy("mRegistrations") - private @Nullable ArraySet<Entry<Object, ListenerRegistration<?, ?>>> mScheduledRemovals; + private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals; ReentrancyGuard() { mGuardCount = 0; @@ -622,7 +631,7 @@ public abstract class ListenerMultiplexer<TKey, TListener, } @GuardedBy("mRegistrations") - void markForRemoval(Object key, ListenerRegistration<?, ?> registration) { + void markForRemoval(Object key, ListenerRegistration<?> registration) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mRegistrations)); } @@ -641,7 +650,7 @@ public abstract class ListenerMultiplexer<TKey, TListener, @Override public void close() { - ArraySet<Entry<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null; + ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null; Preconditions.checkState(mGuardCount > 0); if (--mGuardCount == 0) { @@ -656,7 +665,7 @@ public abstract class ListenerMultiplexer<TKey, TListener, try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) { final int size = scheduledRemovals.size(); for (int i = 0; i < size; i++) { - Entry<Object, ListenerRegistration<?, ?>> entry = scheduledRemovals.valueAt(i); + Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i); removeRegistration(entry.getKey(), entry.getValue()); } } diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java index d7ecbcb7cfdf..fa21b3a8e369 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java @@ -17,11 +17,9 @@ package com.android.server.location.listeners; -import android.annotation.NonNull; import android.annotation.Nullable; import com.android.internal.listeners.ListenerExecutor; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; import java.util.Objects; import java.util.concurrent.Executor; @@ -31,11 +29,8 @@ import java.util.concurrent.Executor; * request, and an executor responsible for listener invocations. * * @param <TListener> listener type - * @param <TListenerOperation> listener operation type */ -public class ListenerRegistration<TListener, - TListenerOperation extends ListenerOperation<TListener>> implements - ListenerExecutor { +public class ListenerRegistration<TListener> implements ListenerExecutor { private final Executor mExecutor; @@ -70,18 +65,14 @@ public class ListenerRegistration<TListener, * returns a non-null operation, that operation will be invoked for the listener. Invoked * while holding the owning multiplexer's internal lock. */ - protected @Nullable TListenerOperation onActive() { - return null; - } + protected void onActive() {} /** * May be overridden by subclasses. Invoked when registration becomes inactive. If this returns * a non-null operation, that operation will be invoked for the listener. Invoked while holding * the owning multiplexer's internal lock. */ - protected @Nullable TListenerOperation onInactive() { - return null; - } + protected void onInactive() {} public final boolean isActive() { return mActive; @@ -114,27 +105,20 @@ public class ListenerRegistration<TListener, protected void onListenerUnregister() {} /** - * May be overridden by subclasses, however should rarely be needed. Invoked whenever a listener - * operation is submitted for execution, and allows the registration a chance to replace the - * listener operation or perform related bookkeeping. There is no guarantee a listener operation - * submitted or returned here will ever be invoked. Will always be invoked on the calling - * thread. - */ - protected TListenerOperation onExecuteOperation(@NonNull TListenerOperation operation) { - return operation; - } - - /** * May be overridden by subclasses to handle listener operation failures. The default behavior * is to further propagate any exceptions. Will always be invoked on the executor thread. */ - protected void onOperationFailure(TListenerOperation operation, Exception exception) { - throw new AssertionError(exception); + protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) { + throw new AssertionError(e); } - final void executeInternal(@NonNull TListenerOperation operation) { - executeSafely(mExecutor, () -> mListener, - onExecuteOperation(Objects.requireNonNull(operation)), this::onOperationFailure); + /** + * Executes the given listener operation, invoking + * {@link #onOperationFailure(ListenerOperation, Exception)} in case the listener operation + * fails. + */ + protected final void executeOperation(@Nullable ListenerOperation<TListener> operation) { + executeSafely(mExecutor, () -> mListener, operation, this::onOperationFailure); } @Override diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java index e57b5322de8d..0aafb2929d56 100644 --- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java @@ -21,8 +21,6 @@ import android.app.PendingIntent; import android.location.util.identity.CallerIdentity; import android.util.Log; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; - /** * A registration that works with PendingIntent keys, and registers a CancelListener to * automatically remove the registration if the PendingIntent is canceled. The key for this @@ -32,8 +30,7 @@ import com.android.internal.listeners.ListenerExecutor.ListenerOperation; * @param <TListener> listener type */ public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends - RemoteListenerRegistration<TRequest, TListener, ListenerOperation<TListener>> implements - PendingIntent.CancelListener { + RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener { /** * Interface to allowed pending intent retrieval when keys are not themselves PendingIntents. @@ -73,7 +70,7 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext protected void onPendingIntentListenerUnregister() {} @Override - public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) { + protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) { if (e instanceof PendingIntent.CanceledException) { Log.w(getOwner().getTag(), "registration " + this + " removed", e); remove(); diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java index 242bf323f6cd..4eca577dcf4f 100644 --- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java @@ -24,7 +24,6 @@ import android.location.util.identity.CallerIdentity; import android.os.Process; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; import com.android.server.FgThread; import java.util.Objects; @@ -38,11 +37,9 @@ import java.util.concurrent.Executor; * * @param <TRequest> request type * @param <TListener> listener type - * @param <TListenerOperation> listener operation type */ -public abstract class RemoteListenerRegistration<TRequest, TListener, - TListenerOperation extends ListenerOperation<TListener>> extends - RemovableListenerRegistration<TRequest, TListener, TListenerOperation> { +public abstract class RemoteListenerRegistration<TRequest, TListener> extends + RemovableListenerRegistration<TRequest, TListener> { @VisibleForTesting public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor(); diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java index d3b5f6696167..618ff24b873b 100644 --- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java @@ -18,8 +18,6 @@ package com.android.server.location.listeners; import android.annotation.Nullable; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; - import java.util.Objects; import java.util.concurrent.Executor; @@ -29,11 +27,9 @@ import java.util.concurrent.Executor; * * @param <TRequest> request type * @param <TListener> listener type - * @param <TListenerOperation> listener operation type */ -public abstract class RemovableListenerRegistration<TRequest, TListener, - TListenerOperation extends ListenerOperation<TListener>> extends - RequestListenerRegistration<TRequest, TListener, TListenerOperation> { +public abstract class RemovableListenerRegistration<TRequest, TListener> extends + RequestListenerRegistration<TRequest, TListener> { private volatile @Nullable Object mKey; @@ -47,8 +43,7 @@ public abstract class RemovableListenerRegistration<TRequest, TListener, * with. Often this is easiest to accomplish by defining registration subclasses as non-static * inner classes of the multiplexer they are to be used with. */ - protected abstract ListenerMultiplexer<?, ? super TListener, ? - super TListenerOperation, ?, ?> getOwner(); + protected abstract ListenerMultiplexer<?, ? super TListener, ?, ?> getOwner(); /** * Returns the key associated with this registration. May not be invoked before diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java index d97abae59dd3..0c2fc9142d92 100644 --- a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java @@ -16,8 +16,6 @@ package com.android.server.location.listeners; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; - import java.util.concurrent.Executor; /** @@ -25,11 +23,9 @@ import java.util.concurrent.Executor; * * @param <TRequest> request type * @param <TListener> listener type - * @param <TListenerOperation> listener operation type */ -public class RequestListenerRegistration<TRequest, TListener, - TListenerOperation extends ListenerOperation<TListener>> extends - ListenerRegistration<TListener, TListenerOperation> { +public class RequestListenerRegistration<TRequest, TListener> extends + ListenerRegistration<TListener> { private final TRequest mRequest; diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java index 26ace47776be..72803ac35a3f 100644 --- a/services/core/java/com/android/server/pm/IncrementalStates.java +++ b/services/core/java/com/android/server/pm/IncrementalStates.java @@ -119,6 +119,33 @@ public final class IncrementalStates { } } + /** + * Change the startable state if the app has crashed or ANR'd during loading. + * If the app is not loading (i.e., fully loaded), this event doesn't change startable state. + */ + public void onCrashOrAnr() { + if (DEBUG) { + Slog.i(TAG, "received package crash or ANR event"); + } + final boolean startableStateChanged; + synchronized (mLock) { + if (mStartableState.isStartable() && mLoadingState.isLoading()) { + // Changing from startable -> unstartable only if app is still loading. + mStartableState.adoptNewStartableStateLocked(false); + startableStateChanged = true; + } else { + // If the app is fully loaded, the crash or ANR is caused by the app itself, so + // we do not change the startable state. + startableStateChanged = false; + } + } + if (startableStateChanged) { + mHandler.post(PooledLambda.obtainRunnable( + IncrementalStates::reportStartableState, + IncrementalStates.this).recycleOnUse()); + } + } + private void reportStartableState() { final Callback callback; final boolean startable; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 410fbe57bf26..1896c9f08f83 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -336,6 +336,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.os.Zygote; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; @@ -12687,12 +12688,17 @@ public class PackageManagerService extends IPackageManager.Stub mPermissionManager.addAllPermissionGroups(pkg, chatty); } + // If a permission has had its defining app changed, or it has had its protection + // upgraded, we need to revoke apps that hold it + final List<String> permissionsWithChangedDefinition; // Don't allow ephemeral applications to define new permissions. if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { + permissionsWithChangedDefinition = null; Slog.w(TAG, "Permissions from package " + pkg.getPackageName() + " ignored: instant apps cannot define new permissions."); } else { - mPermissionManager.addAllPermissions(pkg, chatty); + permissionsWithChangedDefinition = + mPermissionManager.addAllPermissions(pkg, chatty); } int collectionSize = ArrayUtils.size(pkg.getInstrumentations()); @@ -12721,7 +12727,10 @@ public class PackageManagerService extends IPackageManager.Stub } } - if (oldPkg != null) { + boolean hasOldPkg = oldPkg != null; + boolean hasPermissionDefinitionChanges = + !CollectionUtils.isEmpty(permissionsWithChangedDefinition); + if (hasOldPkg || hasPermissionDefinitionChanges) { // We need to call revokeRuntimePermissionsIfGroupChanged async as permission // revoke callbacks from this method might need to kill apps which need the // mPackages lock on a different thread. This would dead lock. @@ -12732,9 +12741,16 @@ public class PackageManagerService extends IPackageManager.Stub // won't be granted yet, hence new packages are no problem. final ArrayList<String> allPackageNames = new ArrayList<>(mPackages.keySet()); - AsyncTask.execute(() -> + AsyncTask.execute(() -> { + if (hasOldPkg) { mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, - allPackageNames)); + allPackageNames); + } + if (hasPermissionDefinitionChanges) { + mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged( + permissionsWithChangedDefinition, allPackageNames); + } + }); } } @@ -25843,6 +25859,20 @@ public class PackageManagerService extends IPackageManager.Stub } return ps.getIncrementalStates(); } + + @Override + public void notifyPackageCrashOrAnr(@NonNull String packageName) { + final PackageSetting ps; + synchronized (mLock) { + ps = mSettings.mPackages.get(packageName); + if (ps == null) { + Slog.w(TAG, "Failed notifyPackageCrash. Package " + packageName + + " is not installed"); + return; + } + } + ps.setStatesOnCrashOrAnr(); + } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index be7c7c6ff1d6..ac76cf71ef67 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -772,6 +772,14 @@ public abstract class PackageSettingBase extends SettingBase { } /** + * Called to indicate that the running app has crashed or ANR'd. This might change the startable + * state of the package, depending on whether the package is fully loaded. + */ + public void setStatesOnCrashOrAnr() { + incrementalStates.onCrashOrAnr(); + } + + /** * Called to set the callback to listen for startable state changes. */ public void setIncrementalStatesCallback(IncrementalStates.Callback callback) { diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index 6ffc5983417a..155d71673e06 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -74,6 +74,8 @@ public final class BasePermission { private static final String ATTR_PACKAGE = "package"; private static final String TAG_ITEM = "item"; + private boolean mPermissionDefinitionChanged; + @NonNull private PermissionInfo mPermissionInfo; @@ -122,6 +124,10 @@ public final class BasePermission { return mPermissionInfo.packageName; } + public boolean isPermissionDefinitionChanged() { + return mPermissionDefinitionChanged; + } + public int getType() { return mType; } @@ -148,6 +154,10 @@ public final class BasePermission { mReconciled = permissionInfo != null; } + public void setPermissionDefinitionChanged(boolean shouldOverride) { + mPermissionDefinitionChanged = shouldOverride; + } + public boolean hasGids() { return mGids.length != 0; } @@ -364,6 +374,7 @@ public final class BasePermission { @NonNull AndroidPackage pkg, Collection<BasePermission> permissionTrees, boolean chatty) { // Allow system apps to redefine non-system permissions + boolean ownerChanged = false; if (bp != null && !Objects.equals(bp.mPermissionInfo.packageName, p.packageName)) { final boolean currentOwnerIsSystem; if (!bp.mReconciled) { @@ -389,6 +400,7 @@ public final class BasePermission { String msg = "New decl " + pkg + " of permission " + p.name + " is system; overriding " + bp.mPermissionInfo.packageName; PackageManagerService.reportSettingsProblem(Log.WARN, msg); + ownerChanged = true; bp = null; } } @@ -396,6 +408,7 @@ public final class BasePermission { if (bp == null) { bp = new BasePermission(p.name, p.packageName, TYPE_MANIFEST); } + boolean wasNormal = bp.isNormal(); StringBuilder r = null; if (!bp.mReconciled) { if (bp.mPermissionInfo.packageName == null @@ -435,6 +448,11 @@ public final class BasePermission { r.append("DUP:"); r.append(p.name); } + if (bp.isRuntime() && (ownerChanged || wasNormal)) { + // If this is a runtime permission and the owner has changed, or this was a normal + // permission, then permission state should be cleaned up + bp.mPermissionDefinitionChanged = true; + } if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) { Log.d(TAG, " Permissions: " + r); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 4d43969578cd..da4ef63d6945 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2327,8 +2327,74 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - private void addAllPermissions(AndroidPackage pkg, boolean chatty) { + /** + * If permissions are upgraded to runtime, or their owner changes to the system, then any + * granted permissions must be revoked. + * + * @param permissionsToRevoke A list of permission names to revoke + * @param allPackageNames All package names + * @param permissionCallback Callback for permission changed + */ + private void revokeRuntimePermissionsIfPermissionDefinitionChanged( + @NonNull List<String> permissionsToRevoke, + @NonNull ArrayList<String> allPackageNames, + @NonNull PermissionCallback permissionCallback) { + + final int[] userIds = mUserManagerInt.getUserIds(); + final int numPermissions = permissionsToRevoke.size(); + final int numUserIds = userIds.length; + final int numPackages = allPackageNames.size(); + final int callingUid = Binder.getCallingUid(); + + for (int permNum = 0; permNum < numPermissions; permNum++) { + String permName = permissionsToRevoke.get(permNum); + BasePermission bp = mSettings.getPermission(permName); + if (bp == null || !bp.isRuntime()) { + continue; + } + for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) { + final int userId = userIds[userIdNum]; + for (int packageNum = 0; packageNum < numPackages; packageNum++) { + final String packageName = allPackageNames.get(packageNum); + final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); + if (uid < Process.FIRST_APPLICATION_UID) { + // do not revoke from system apps + continue; + } + final int permissionState = checkPermissionImpl(permName, packageName, + userId); + final int flags = getPermissionFlags(permName, packageName, userId); + final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED + | FLAG_PERMISSION_GRANTED_BY_DEFAULT + | FLAG_PERMISSION_GRANTED_BY_ROLE; + if (permissionState == PackageManager.PERMISSION_GRANTED + && (flags & flagMask) == 0) { + EventLog.writeEvent(0x534e4554, "154505240", uid, + "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + EventLog.writeEvent(0x534e4554, "168319670", uid, + "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + Slog.e(TAG, "Revoking permission " + permName + " from package " + + packageName + " due to definition change"); + try { + revokeRuntimePermissionInternal(permName, packageName, + false, callingUid, userId, null, permissionCallback); + } catch (Exception e) { + Slog.e(TAG, "Could not revoke " + permName + " from " + + packageName, e); + } + } + } + } + bp.setPermissionDefinitionChanged(false); + } + } + + private List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) { final int N = ArrayUtils.size(pkg.getPermissions()); + ArrayList<String> definitionChangedPermissions = new ArrayList<>(); for (int i=0; i<N; i++) { ParsedPermission p = pkg.getPermissions().get(i); @@ -2369,8 +2435,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (bp.isInstalled()) { p.setFlags(p.getFlags() | PermissionInfo.FLAG_INSTALLED); } + if (bp.isPermissionDefinitionChanged()) { + definitionChangedPermissions.add(p.getName()); + } } } + return definitionChangedPermissions; } private void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) { @@ -4750,9 +4820,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage, oldPackage, allPackageNames, mDefaultPermissionCallback); } + + @Override + public void revokeRuntimePermissionsIfPermissionDefinitionChanged( + @NonNull List<String> permissionsToRevoke, + @NonNull ArrayList<String> allPackageNames) { + PermissionManagerService.this.revokeRuntimePermissionsIfPermissionDefinitionChanged( + permissionsToRevoke, allPackageNames, mDefaultPermissionCallback); + } + @Override - public void addAllPermissions(AndroidPackage pkg, boolean chatty) { - PermissionManagerService.this.addAllPermissions(pkg, chatty); + public List<String> addAllPermissions(AndroidPackage pkg, boolean chatty) { + return PermissionManagerService.this.addAllPermissions(pkg, chatty); } @Override public void addAllPermissionGroups(AndroidPackage pkg, boolean chatty) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 5ea3458fcbfa..20e9c5dcb521 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -256,12 +256,26 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager @NonNull ArrayList<String> allPackageNames); /** + * Some permissions might have been owned by a non-system package, and the system then defined + * said permission. Some other permissions may one have been install permissions, but are now + * runtime or higher. These permissions should be revoked. + * + * @param permissionsToRevoke A list of permission names to revoke + * @param allPackageNames All packages + */ + public abstract void revokeRuntimePermissionsIfPermissionDefinitionChanged( + @NonNull List<String> permissionsToRevoke, + @NonNull ArrayList<String> allPackageNames); + + /** * Add all permissions in the given package. * <p> * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to * the permission settings. + * + * @return A list of BasePermissions that were updated, and need to be revoked from packages */ - public abstract void addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty); + public abstract List<String> addAllPermissions(@NonNull AndroidPackage pkg, boolean chatty); public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty); public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d01bc815c28d..0a7f08bfbe8c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1362,6 +1362,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mLetterbox != null) { mLetterbox.hide(); } + task.maybeUpdateLetterboxBounds(this, getLetterboxParams(w)); } void updateLetterboxSurface(WindowState winHint) { @@ -1375,6 +1376,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @Nullable + private Rect getLetterboxParams(WindowState w) { + boolean isLetterboxed = w.isLetterboxedAppWindow() && fillsParent(); + return isLetterboxed ? getBounds() : null; + } + Rect getLetterboxInsets() { if (mLetterbox != null) { return mLetterbox.getInsets(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 8e5add916620..c582e6c8cb29 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -39,6 +39,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ApplicationInfo.FLAG_FACTORY_TEST; import static android.content.pm.ConfigurationInfo.GL_ES_VERSION_UNDEFINED; import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; @@ -1295,6 +1296,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; + a.resizeMode = RESIZE_MODE_UNRESIZEABLE; final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8da6c813f591..7641de5ab7b9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5018,6 +5018,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp || windowingMode == WINDOWING_MODE_MULTI_WINDOW); } + static boolean canReuseExistingTask(int windowingMode, int activityType) { + // Existing Tasks can be reused if a new stack will be created anyway, or for the Dream - + // because there can only ever be one DreamActivity. + return alwaysCreateStack(windowingMode, activityType) + || activityType == ACTIVITY_TYPE_DREAM; + } + @Nullable Task getFocusedStack() { return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedStack); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 132029f36096..6ffd9a28c10a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -528,6 +528,11 @@ class Task extends WindowContainer<WindowContainer> { // {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag of the root activity. boolean mSupportsPictureInPicture; + // Activity bounds if this task or its top activity is presented in letterbox mode and + // {@code null} otherwise. + @Nullable + private Rect mLetterboxActivityBounds; + // Whether the task is currently being drag-resized private boolean mDragResizing; private int mDragResizeMode; @@ -790,6 +795,10 @@ class Task extends WindowContainer<WindowContainer> { */ boolean mTaskAppearedSent; + // If the sending of the task appear signal should be deferred until this flag is set back to + // false. + private boolean mDeferTaskAppear; + /** * This task was created by the task organizer which has the following implementations. * <ul> @@ -802,14 +811,20 @@ class Task extends WindowContainer<WindowContainer> { @VisibleForTesting boolean mCreatedByOrganizer; + // Tracking cookie for the creation of this task. + IBinder mLaunchCookie; + /** * Don't use constructor directly. Use {@link TaskDisplayArea#createStackUnchecked()} instead. */ - Task(ActivityTaskManagerService atmService, int id, int activityType, - ActivityInfo info, Intent intent, boolean createdByOrganizer) { + Task(ActivityTaskManagerService atmService, int id, int activityType, ActivityInfo info, + Intent intent, boolean createdByOrganizer, boolean deferTaskAppear, + IBinder launchCookie) { this(atmService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, null /*taskDescription*/, null /*stack*/); mCreatedByOrganizer = createdByOrganizer; + mLaunchCookie = launchCookie; + mDeferTaskAppear = deferTaskAppear; setActivityType(activityType); } @@ -2821,16 +2836,25 @@ class Task extends WindowContainer<WindowContainer> { int windowingMode = getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); + final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode(); // Resolve override windowing mode to fullscreen for home task (even on freeform // display), or split-screen if in split-screen mode. if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) { - final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode(); windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode) ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN; getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); } + // Do not allow non-resizable non-pinned tasks to be in a multi-window mode - they should + // use their parent's windowing mode, or fullscreen. + if (!isResizeable() && windowingMode != WINDOWING_MODE_PINNED + && WindowConfiguration.inMultiWindowMode(windowingMode)) { + windowingMode = WindowConfiguration.inMultiWindowMode(parentWindowingMode) + ? WINDOWING_MODE_FULLSCREEN : parentWindowingMode; + getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); + } + if (isLeafTask()) { resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */); } @@ -2921,13 +2945,27 @@ class Task extends WindowContainer<WindowContainer> { final int parentWidth = parentBounds.width(); final int parentHeight = parentBounds.height(); - final float aspect = ((float) parentHeight) / parentWidth; + float aspect = Math.max(parentWidth, parentHeight) + / (float) Math.min(parentWidth, parentHeight); + + // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the + // extra available space. + if (refActivity != null) { + final float maxAspectRatio = refActivity.info.maxAspectRatio; + final float minAspectRatio = refActivity.info.minAspectRatio; + if (aspect > maxAspectRatio && maxAspectRatio != 0) { + aspect = maxAspectRatio; + } else if (aspect < minAspectRatio) { + aspect = minAspectRatio; + } + } + if (forcedOrientation == ORIENTATION_LANDSCAPE) { - final int height = (int) (parentWidth / aspect); + final int height = (int) Math.rint(parentWidth / aspect); final int top = parentBounds.centerY() - height / 2; outBounds.set(parentBounds.left, top, parentBounds.right, top + height); } else { - final int width = (int) (parentHeight * aspect); + final int width = (int) Math.rint(parentHeight / aspect); final int left = parentBounds.centerX() - width / 2; outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); } @@ -3333,7 +3371,9 @@ class Task extends WindowContainer<WindowContainer> { } boolean isResizeable(boolean checkSupportsPip) { - return (mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode) + final boolean forceResizable = mAtmService.mForceResizableActivities + && getActivityType() == ACTIVITY_TYPE_STANDARD; + return (forceResizable || ActivityInfo.isResizeableMode(mResizeMode) || (checkSupportsPip && mSupportsPictureInPicture)); } @@ -4058,11 +4098,18 @@ class Task extends WindowContainer<WindowContainer> { info.resizeMode = top != null ? top.mResizeMode : mResizeMode; info.topActivityType = top.getActivityType(); info.isResizeable = isResizeable(); + // Don't query getTopNonFinishingActivity().getBounds() directly because when fillTaskInfo + // is triggered for the first time after activities change, getBounds() may return non final + // bounds, e.g. fullscreen bounds instead of letterboxed bounds. To work around this, + // assigning bounds from ActivityRecord#layoutLetterbox when they are ready. + info.letterboxActivityBounds = Rect.copyOrNull(mLetterboxActivityBounds); + info.positionInParent = getRelativePosition(); info.pictureInPictureParams = getPictureInPictureParams(); info.topActivityInfo = mReuseActivitiesReport.top != null ? mReuseActivitiesReport.top.info : null; + info.addLaunchCookie(mLaunchCookie); forAllActivities(r -> { info.addLaunchCookie(r.mLaunchCookie); }); @@ -4076,6 +4123,21 @@ class Task extends WindowContainer<WindowContainer> { ? null : rootActivity.pictureInPictureArgs; } + void maybeUpdateLetterboxBounds( + ActivityRecord activityRecord, @Nullable Rect letterboxActivityBounds) { + if (isOrganized() + && mReuseActivitiesReport.top == activityRecord + // Want to force update only if letterbox bounds have changed. + && !Objects.equals( + mLetterboxActivityBounds, + letterboxActivityBounds)) { + mLetterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds); + // Forcing update to reduce visual jank during the transition. + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, /* force= */ true); + } + } + /** * Returns a {@link TaskInfo} with information from this task. */ @@ -4817,6 +4879,13 @@ class Task extends WindowContainer<WindowContainer> { return mHasBeenVisible; } + void setDeferTaskAppear(boolean deferTaskAppear) { + mDeferTaskAppear = deferTaskAppear; + if (!mDeferTaskAppear) { + sendTaskAppeared(); + } + } + /** In the case that these conditions are true, we want to send the Task to the organizer: * 1. An organizer has been set * 2. The Task was created by the organizer @@ -4831,6 +4900,10 @@ class Task extends WindowContainer<WindowContainer> { return false; } + if (mDeferTaskAppear) { + return false; + } + if (mCreatedByOrganizer) { return true; } @@ -5264,14 +5337,12 @@ class Task extends WindowContainer<WindowContainer> { taskDisplayArea.moveHomeStackToFront(reason + " returnToHome"); } - if (isRootTask()) { - taskDisplayArea.positionChildAt(POSITION_TOP, this, false /* includingParents */, - reason); - } + final Task lastFocusedTask = isRootTask() ? taskDisplayArea.getFocusedStack() : null; if (task == null) { task = this; } task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */); + taskDisplayArea.updateLastFocusedRootTask(lastFocusedTask, reason); } /** @@ -5294,8 +5365,9 @@ class Task extends WindowContainer<WindowContainer> { if (parentTask != null) { parentTask.moveToBack(reason, this); } else { - displayArea.positionChildAt(POSITION_BOTTOM, this, false /*includingParents*/, - reason); + final Task lastFocusedTask = displayArea.getFocusedStack(); + displayArea.positionChildAt(POSITION_BOTTOM, this, false /*includingParents*/); + displayArea.updateLastFocusedRootTask(lastFocusedTask, reason); } if (task != null && task != this) { positionChildAtBottom(task); @@ -5886,6 +5958,8 @@ class Task extends WindowContainer<WindowContainer> { if (mResumedActivity == next && next.isState(RESUMED) && taskDisplayArea.getWindowingMode() != WINDOWING_MODE_FREEFORM && taskDisplayArea.allResumedActivitiesComplete()) { + // The activity may be waiting for stop, but that is no longer appropriate for it. + mStackSupervisor.mStoppingActivities.remove(next); // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. executeAppTransition(options); @@ -6804,13 +6878,10 @@ class Task extends WindowContainer<WindowContainer> { // get calculated incorrectly. mDisplayContent.deferUpdateImeTarget(); - // Shift all activities with this task up to the top - // of the stack, keeping them in the same internal order. - positionChildAtTop(tr); - // Don't refocus if invisible to current user final ActivityRecord top = tr.getTopNonFinishingActivity(); if (top == null || !top.okToShowLocked()) { + positionChildAtTop(tr); if (top != null) { mStackSupervisor.mRecentTasks.add(top.getTask()); } @@ -6818,20 +6889,15 @@ class Task extends WindowContainer<WindowContainer> { return; } - // Set focus to the top running activity of this stack. - final ActivityRecord r = topRunningActivity(); - if (r != null) { - r.moveFocusableActivityToTop(reason); - } + // Set focus to the top running activity of this task and move all its parents to top. + top.moveFocusableActivityToTop(reason); if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr); if (noAnimation) { mDisplayContent.prepareAppTransitionOld(TRANSIT_OLD_NONE, false /* alwaysKeepCurrent */); mDisplayContent.prepareAppTransition(TRANSIT_NONE); - if (r != null) { - mStackSupervisor.mNoAnimActivities.add(r); - } + mStackSupervisor.mNoAnimActivities.add(top); ActivityOptions.abort(options); } else { updateTransitLocked(TRANSIT_OLD_TASK_TO_FRONT, TRANSIT_TO_FRONT, @@ -7170,7 +7236,7 @@ class Task extends WindowContainer<WindowContainer> { ActivityRecord source, ActivityOptions options) { Task task; - if (DisplayContent.alwaysCreateStack(getWindowingMode(), getActivityType())) { + if (DisplayContent.canReuseExistingTask(getWindowingMode(), getActivityType())) { // This stack will only contain one task, so just return itself since all stacks ara now // tasks and all tasks are now stacks. task = reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity); @@ -7435,6 +7501,12 @@ class Task extends WindowContainer<WindowContainer> { outPos.y -= outset; } + private Point getRelativePosition() { + Point position = new Point(); + getRelativePosition(position); + return position; + } + boolean shouldIgnoreInput() { if (inSplitScreenPrimaryWindowingMode() && !isFocusable()) { return true; diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 9392666fbf54..e7213192dfd3 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -50,6 +50,7 @@ import android.app.WindowConfiguration; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.os.IBinder; import android.os.UserHandle; import android.util.IntArray; import android.util.Slog; @@ -334,29 +335,6 @@ final class TaskDisplayArea extends DisplayArea<Task> { return true; } - void positionChildAt(int position, Task child, boolean includingParents, - String updateLastFocusedTaskReason) { - final Task prevFocusedTask = updateLastFocusedTaskReason != null ? getFocusedStack() : null; - - positionChildAt(position, child, includingParents); - - if (updateLastFocusedTaskReason == null) { - return; - } - - final Task currentFocusedStack = getFocusedStack(); - if (currentFocusedStack == prevFocusedTask) { - return; - } - - mLastFocusedStack = prevFocusedTask; - EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser, - mDisplayContent.mDisplayId, - currentFocusedStack == null ? -1 : currentFocusedStack.getRootTaskId(), - mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(), - updateLastFocusedTaskReason); - } - @Override void positionChildAt(int position, Task child, boolean includingParents) { final boolean moveToTop = position >= getChildCount() - 1; @@ -996,6 +974,13 @@ final class TaskDisplayArea extends DisplayArea<Task> { false /* createdByOrganizer */); } + Task createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info, + Intent intent, boolean createdByOrganizer) { + return createStack(windowingMode, activityType, onTop, null /* info */, null /* intent */, + false /* createdByOrganizer */ , false /* deferTaskAppear */, + null /* launchCookie */); + } + /** * Creates a stack matching the input windowing mode and activity type on this display. * @@ -1013,10 +998,14 @@ final class TaskDisplayArea extends DisplayArea<Task> { * @param intent The intent that started this task. * @param createdByOrganizer @{code true} if this is created by task organizer, @{code false} * otherwise. + * @param deferTaskAppear @{code true} if the task appeared signal should be deferred. + * @param launchCookie Launch cookie used for tracking/association of the task we are + * creating. * @return The newly created stack. */ Task createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info, - Intent intent, boolean createdByOrganizer) { + Intent intent, boolean createdByOrganizer, boolean deferTaskAppear, + IBinder launchCookie) { if (activityType == ACTIVITY_TYPE_UNDEFINED && !createdByOrganizer) { // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants // anything else should be passing it in anyways...except for the task organizer. @@ -1048,7 +1037,7 @@ final class TaskDisplayArea extends DisplayArea<Task> { final int stackId = getNextStackId(); return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent, - createdByOrganizer); + createdByOrganizer, deferTaskAppear, launchCookie); } /** @return the root task to create the next task in. */ @@ -1078,8 +1067,9 @@ final class TaskDisplayArea extends DisplayArea<Task> { } @VisibleForTesting - Task createStackUnchecked(int windowingMode, int activityType, int stackId, - boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) { + Task createStackUnchecked(int windowingMode, int activityType, int stackId, boolean onTop, + ActivityInfo info, Intent intent, boolean createdByOrganizer, boolean deferTaskAppear, + IBinder launchCookie) { if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) { throw new IllegalArgumentException("Stack with windowing mode cannot with non standard " + "activity type."); @@ -1097,7 +1087,7 @@ final class TaskDisplayArea extends DisplayArea<Task> { } final Task stack = new Task(mAtmService, stackId, activityType, - info, intent, createdByOrganizer); + info, intent, createdByOrganizer, deferTaskAppear, launchCookie); if (launchRootTask != null) { launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); if (onTop) { @@ -1189,6 +1179,24 @@ final class TaskDisplayArea extends DisplayArea<Task> { return mLastFocusedStack; } + void updateLastFocusedRootTask(Task prevFocusedTask, String updateLastFocusedTaskReason) { + if (updateLastFocusedTaskReason == null) { + return; + } + + final Task currentFocusedTask = getFocusedStack(); + if (currentFocusedTask == prevFocusedTask) { + return; + } + + mLastFocusedStack = prevFocusedTask; + EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser, + mDisplayContent.mDisplayId, + currentFocusedTask == null ? -1 : currentFocusedTask.getRootTaskId(), + mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(), + updateLastFocusedTaskReason); + } + boolean allResumedActivitiesComplete() { for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityRecord r = getStackAt(stackNdx).getResumedActivity(); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 6504f00905e7..a70efbcf5500 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -53,6 +53,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Consumer; @@ -155,9 +156,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) { - if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) { - // Skip if the task has not yet received taskAppeared(), except for tasks created - // by the organizer that don't receive that signal + if (!task.mTaskAppearedSent) { + // Skip if the task has not yet received taskAppeared(). return; } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task info changed taskId=%d", task.mTaskId); @@ -178,9 +178,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { void onBackPressedOnTaskRoot(Task task) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task back pressed on root taskId=%d", task.mTaskId); - if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) { - // Skip if the task has not yet received taskAppeared(), except for tasks created - // by the organizer that don't receive that signal + if (!task.mTaskAppearedSent) { + // Skip if the task has not yet received taskAppeared(). return; } if (!task.isOrganized()) { @@ -401,30 +400,39 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public RunningTaskInfo createRootTask(int displayId, int windowingMode) { + public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) { enforceStackPermission("createRootTask()"); final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId); if (display == null) { - return null; + ProtoLog.e(WM_DEBUG_WINDOW_ORGANIZER, + "createRootTask unknown displayId=%d", displayId); + return; } - ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d", - displayId, windowingMode); - final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode, - ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(), - true /* createdByOrganizer */); - RunningTaskInfo out = task.getTaskInfo(); - mLastSentTaskInfos.put(task, out); - return out; + createRootTask(display, windowingMode, launchCookie); } } finally { Binder.restoreCallingIdentity(origId); } } + @VisibleForTesting + Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie) { + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d", + display.mDisplayId, windowingMode); + // We want to defer the task appear signal until the task is fully created and attached to + // to the hierarchy so that the complete starting configuration is in the task info we send + // over to the organizer. + final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode, + ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(), + true /* createdByOrganizer */, true /* deferTaskAppear */, launchCookie); + task.setDeferTaskAppear(false /* deferTaskAppear */); + return task; + } + @Override public boolean deleteRootTask(WindowContainerToken token) { enforceStackPermission("deleteRootTask()"); @@ -475,6 +483,12 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { boolean changed = lastInfo == null || mTmpTaskInfo.topActivityType != lastInfo.topActivityType || mTmpTaskInfo.isResizeable != lastInfo.isResizeable + || !Objects.equals( + mTmpTaskInfo.letterboxActivityBounds, + lastInfo.letterboxActivityBounds) + || !Objects.equals( + mTmpTaskInfo.positionInParent, + lastInfo.positionInParent) || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams || mTmpTaskInfo.getConfiguration().windowConfiguration.getWindowingMode() != lastInfo.getConfiguration().windowConfiguration.getWindowingMode() diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ddda392ea951..411d8d60b7c4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1649,6 +1649,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** + * Creates a new {@link CallerIdentity} object to represent the caller's identity. + * If an {@code adminComponent} is specified, then the caller must be an admin and + * the provided component name must match the caller's UID. + * + * If a package name is provided, then the caller doesn't have to be an admin, and the + * provided package must belong to the caller's UID. + * + * If neither is provided, the caller identity is returned as-is. + * + * Note: this method should only be called when the caller may not be an admin. If the caller + * is not an admin, the ComponentName in the returned identity will be null. + */ + private CallerIdentity getNonPrivilegedOrAdminCallerIdentity( + @Nullable ComponentName adminComponent, + @Nullable String callerPackage) { + if (adminComponent != null) { + return getCallerIdentity(adminComponent); + } + + return getNonPrivilegedOrAdminCallerIdentityUsingPackage(callerPackage); + } + + /** * Retrieves the active admin of the caller. This method should not be called directly and * should only be called by {@link #getAdminCallerIdentity}, * {@link #getNonPrivilegedOrAdminCallerIdentity}, {@link #getAdminCallerIdentityUsingPackage} @@ -2315,14 +2338,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean isDeviceOwner = isDeviceOwner(admin.info.getComponent(), userId); final boolean isProfileOwner = isProfileOwner(admin.info.getComponent(), userId); - if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { - throw new SecurityException("Admin " + admin.info.getComponent() - + " does not own the device"); - } - if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { - throw new SecurityException("Admin " + admin.info.getComponent() - + " does not own the profile"); - } if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) { throw new SecurityException("Admin " + admin.info.getComponent() + " is not a device owner or profile owner, so may not use policy: " @@ -2428,20 +2443,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ensureLocked(); final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId); final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId); - final boolean ownsProfileOnOrganizationOwnedDevice = - isProfileOwnerOfOrganizationOwnedDevice(admin.info.getComponent(), userId); - - if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { - return ownsDevice; - } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { - // DO always has the PO power. - return ownsDevice || ownsProfileOnOrganizationOwnedDevice || ownsProfile; - } else { - boolean allowedToUsePolicy = ownsDevice || ownsProfile - || !DA_DISALLOWED_POLICIES.contains(reqPolicy) - || getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.Q; - return allowedToUsePolicy && admin.info.usesPolicy(reqPolicy); - } + + boolean allowedToUsePolicy = ownsDevice || ownsProfile + || !DA_DISALLOWED_POLICIES.contains(reqPolicy) + || getTargetSdk(admin.info.getPackageName(), userId) < Build.VERSION_CODES.Q; + return allowedToUsePolicy && admin.info.usesPolicy(reqPolicy); } void sendAdminCommandLocked(ActiveAdmin admin, String action) { @@ -5488,22 +5494,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<String> getDelegatedScopes(ComponentName who, String delegatePackage) throws SecurityException { Objects.requireNonNull(delegatePackage, "Delegate package is null"); + final CallerIdentity caller = getNonPrivilegedOrAdminCallerIdentity(who, delegatePackage); - // Retrieve the user ID of the calling process. - final int callingUid = mInjector.binderGetCallingUid(); - final int userId = UserHandle.getUserId(callingUid); + // Ensure the caller may call this method: + // * Either it's an admin + // * Or it's an app identified by its calling package name (the + // getNonPrivilegedOrAdminCallerIdentity method validated the UID and package match). + Preconditions.checkCallAuthorization( + (caller.hasAdminComponent() && (isProfileOwner(caller) || isDeviceOwner(caller))) + || delegatePackage != null); synchronized (getLockObject()) { - // Ensure calling process is device/profile owner. - if (who != null) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - // Or ensure calling process is delegatePackage itself. - } else { - if (!isCallingFromPackage(delegatePackage, callingUid)) { - throw new SecurityException("Caller with uid " + callingUid + " is not " - + delegatePackage); - } - } - final DevicePolicyData policy = getUserData(userId); + final DevicePolicyData policy = getUserData(caller.getUserId()); // Retrieve the scopes assigned to delegatePackage, or null if no scope was given. final List<String> scopes = policy.mDelegationMap.get(delegatePackage); return scopes == null ? Collections.EMPTY_LIST : scopes; @@ -7893,7 +7894,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Current user has a managed-profile, but current user is not managed, so // rather than moving to finalized state, go back to unmanaged once // profile provisioning is complete. - if (newState == DevicePolicyManager.STATE_USER_PROFILE_FINALIZED) { + if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) { return; } break; @@ -8362,37 +8363,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void enforceDeviceOwnerOrManageUsers() { - synchronized (getLockObject()) { - if (getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, - mInjector.binderGetCallingUid()) != null) { - return; - } + final CallerIdentity caller = getCallerIdentity(); + if (isDeviceOwner(caller)) { + return; } - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(caller)); } private void enforceProfileOwnerOrSystemUser() { - synchronized (getLockObject()) { - if (getActiveAdminWithPolicyForUidLocked(null, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid()) - != null) { - return; - } + final CallerIdentity caller = getCallerIdentity(); + if (isDeviceOwner(caller) || isProfileOwner(caller)) { + return; } - Preconditions.checkState(isCallerWithSystemUid(), + Preconditions.checkState(isSystemUid(caller), "Only profile owner, device owner and system may call this method."); } private void enforceProfileOwnerOrFullCrossUsersPermission(CallerIdentity caller, int userId) { - if (userId == caller.getUserId()) { - synchronized (getLockObject()) { - if (getActiveAdminWithPolicyForUidLocked(null, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, caller.getUid()) != null) { - // Device Owner/Profile Owner may access the user it runs on. - return; - } - } + if ((userId == caller.getUserId()) && (isProfileOwner(caller) || isDeviceOwner(caller))) { + // Device Owner/Profile Owner may access the user it runs on. + return; } Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId)); } @@ -9285,10 +9276,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalArgumentException("profileOwner " + profileOwner + " and admin " + admin + " are not in the same package"); } + final CallerIdentity caller = getCallerIdentity(admin); // Only allow the system user to use this method - if (!mInjector.binderGetCallingUserHandle().isSystem()) { - throw new SecurityException("createAndManageUser was called from non-system user"); - } + Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(), + "createAndManageUser was called from non-system user"); + Preconditions.checkCallAuthorization(isDeviceOwner(caller)); final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0; final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0 && UserManager.isDeviceInDemoMode(mContext); @@ -9298,8 +9290,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Create user. UserHandle user = null; synchronized (getLockObject()) { - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - final int callingUid = mInjector.binderGetCallingUid(); final long id = mInjector.binderClearCallingIdentity(); try { @@ -11161,25 +11151,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isActiveDeviceOwner(int uid) { - synchronized (getLockObject()) { - return getActiveAdminWithPolicyForUidLocked( - null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, uid) != null; - } + return isDeviceOwner(new CallerIdentity(uid, null, null)); } @Override public boolean isActiveProfileOwner(int uid) { - synchronized (getLockObject()) { - return getActiveAdminWithPolicyForUidLocked( - null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid) != null; - } + return isProfileOwner(new CallerIdentity(uid, null, null)); } @Override public boolean isActiveSupervisionApp(int uid) { + if (!isProfileOwner(new CallerIdentity(uid, null, null))) { + return false; + } synchronized (getLockObject()) { - final ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked( - null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid); + final ActiveAdmin admin = getProfileOwnerAdminLocked(UserHandle.getUserId(uid)); if (admin == null) { return false; } @@ -11705,6 +11691,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .getPackageName(); try { String[] pkgs = mInjector.getIPackageManager().getPackagesForUid(appUid); + if (pkgs == null) { + return false; + } + for (String pkg : pkgs) { if (deviceOwnerPackageName.equals(pkg)) { return true; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 975e2265b60c..e116a353c723 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -76,14 +76,18 @@ import android.provider.Settings; import android.server.ServerProtoEnums; import android.sysprop.VoldProperties; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; +import android.util.TimeUtils; import android.view.contentcapture.ContentCaptureManager; import com.android.i18n.timezone.ZoneInfoDb; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; @@ -188,14 +192,20 @@ import dalvik.system.VMRuntime; import com.google.android.startop.iorap.IorapForwardingService; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; import java.util.LinkedList; import java.util.Locale; import java.util.Timer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; -public final class SystemServer { +/** + * Entry point to {@code system_server}. + */ +public final class SystemServer implements Dumpable { private static final String TAG = "SystemServer"; @@ -384,6 +394,9 @@ public final class SystemServer { private Future<?> mZygotePreload; private Future<?> mBlobStoreServiceStart; + private final SystemServerDumper mDumper = new SystemServerDumper(); + + /** * The pending WTF to be logged into dropbox. */ @@ -446,6 +459,75 @@ public final class SystemServer { mRuntimeRestart = "1".equals(SystemProperties.get("sys.boot_completed")); } + @Override + public void dump(IndentingPrintWriter pw, String[] args) { + pw.printf("Runtime restart: %b\n", mRuntimeRestart); + pw.printf("Start count: %d\n", mStartCount); + pw.print("Runtime start-up time: "); + TimeUtils.formatDuration(mRuntimeStartUptime, pw); pw.println(); + pw.print("Runtime start-elapsed time: "); + TimeUtils.formatDuration(mRuntimeStartElapsedTime, pw); pw.println(); + } + + private final class SystemServerDumper extends Binder { + + @GuardedBy("mDumpables") + private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>(4); + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + final boolean hasArgs = args != null && args.length > 0; + + synchronized (mDumpables) { + if (hasArgs && "--list".equals(args[0])) { + final int dumpablesSize = mDumpables.size(); + for (int i = 0; i < dumpablesSize; i++) { + pw.println(mDumpables.keyAt(i)); + } + return; + } + + if (hasArgs && "--name".equals(args[0])) { + if (args.length < 2) { + pw.println("Must pass at least one argument to --name"); + return; + } + final String name = args[1]; + final Dumpable dumpable = mDumpables.get(name); + if (dumpable == null) { + pw.printf("No dummpable named %s\n", name); + return; + } + + try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { + // Strip --name DUMPABLE from args + final String[] actualArgs = Arrays.copyOfRange(args, 2, args.length); + dumpable.dump(ipw, actualArgs); + } + return; + } + + final int dumpablesSize = mDumpables.size(); + try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { + for (int i = 0; i < dumpablesSize; i++) { + final Dumpable dumpable = mDumpables.valueAt(i); + ipw.printf("%s:\n", dumpable.getDumpableName()); + ipw.increaseIndent(); + dumpable.dump(ipw, args); + ipw.decreaseIndent(); + ipw.println(); + } + } + } + } + + private void addDumpable(@NonNull Dumpable dumpable) { + synchronized (mDumpables) { + mDumpables.put(dumpable.getDumpableName(), dumpable); + } + } + } + private void run() { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); try { @@ -572,13 +654,21 @@ public final class SystemServer { // Call per-process mainline module initialization. ActivityThread.initializeMainlineModules(); + // Sets the dumper service + ServiceManager.addService("system_server_dumper", mDumper); + mDumper.addDumpable(this); + // Create the system service manager. mSystemServiceManager = new SystemServiceManager(mSystemContext); mSystemServiceManager.setStartInfo(mRuntimeRestart, mRuntimeStartElapsedTime, mRuntimeStartUptime); + mDumper.addDumpable(mSystemServiceManager); + LocalServices.addService(SystemServiceManager.class, mSystemServiceManager); // Prepare the thread pool for init tasks that can be parallelized - SystemServerInitThreadPool.start(); + SystemServerInitThreadPool tp = SystemServerInitThreadPool.start(); + mDumper.addDumpable(tp); + // Attach JVMTI agent if this is a debuggable build and the system property is set. if (Build.IS_DEBUGGABLE) { // Property is of the form "library_path=parameters". @@ -2321,7 +2411,11 @@ public final class SystemServer { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { t.traceBegin("StartCarServiceHelperService"); - mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS); + final SystemService cshs = mSystemServiceManager + .startService(CAR_SERVICE_HELPER_SERVICE_CLASS); + if (cshs instanceof Dumpable) { + mDumper.addDumpable((Dumpable) cshs); + } t.traceEnd(); } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java index 29d3f29ad7e1..d7fef604d25b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java @@ -34,7 +34,6 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.listeners.ListenerExecutor.ListenerOperation; import com.android.server.location.listeners.ListenerMultiplexer.UpdateServiceLock; import org.junit.Before; @@ -59,6 +58,9 @@ public class ListenerMultiplexerTest { void onRegistrationAdded(Consumer<TestListenerRegistration> consumer, TestListenerRegistration registration); + void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer, + TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration); + void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer, TestListenerRegistration registration); @@ -90,8 +92,39 @@ public class ListenerMultiplexerTest { assertThat(mMultiplexer.mRegistered).isTrue(); assertThat(mMultiplexer.mMergedRequest).isEqualTo(0); + mMultiplexer.addListener(1, consumer); + mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer), + any(TestListenerRegistration.class)); + mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer), + any(TestListenerRegistration.class), any(TestListenerRegistration.class)); + assertThat(mMultiplexer.mRegistered).isTrue(); + assertThat(mMultiplexer.mMergedRequest).isEqualTo(1); + + mMultiplexer.notifyListeners(); + verify(consumer).accept(any(TestListenerRegistration.class)); + } + + @Test + public void testReplace() { + Consumer<TestListenerRegistration> oldConsumer = mock(Consumer.class); + Consumer<TestListenerRegistration> consumer = mock(Consumer.class); + + mMultiplexer.addListener(0, oldConsumer); + mInOrder.verify(mCallbacks).onRegister(); + mInOrder.verify(mCallbacks).onRegistrationAdded(eq(oldConsumer), + any(TestListenerRegistration.class)); + mInOrder.verify(mCallbacks).onActive(); + mMultiplexer.replaceListener(1, oldConsumer, consumer); + mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer), + any(TestListenerRegistration.class)); + mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer), + any(TestListenerRegistration.class), any(TestListenerRegistration.class)); + assertThat(mMultiplexer.mRegistered).isTrue(); + assertThat(mMultiplexer.mMergedRequest).isEqualTo(1); + mMultiplexer.notifyListeners(); verify(consumer).accept(any(TestListenerRegistration.class)); + verify(oldConsumer, never()).accept(any(TestListenerRegistration.class)); } @Test @@ -319,8 +352,7 @@ public class ListenerMultiplexerTest { } private static class TestListenerRegistration extends - RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>, - ListenerOperation<Consumer<TestListenerRegistration>>> { + RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> { boolean mActive = true; @@ -332,9 +364,7 @@ public class ListenerMultiplexerTest { private static class TestMultiplexer extends ListenerMultiplexer<Consumer<TestListenerRegistration>, - Consumer<TestListenerRegistration>, - ListenerOperation<Consumer<TestListenerRegistration>>, TestListenerRegistration, - Integer> { + Consumer<TestListenerRegistration>, TestListenerRegistration, Integer> { boolean mRegistered; int mMergedRequest; @@ -351,7 +381,13 @@ public class ListenerMultiplexerTest { } public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) { - addRegistration(consumer, new TestListenerRegistration(request, consumer)); + putRegistration(consumer, new TestListenerRegistration(request, consumer)); + } + + public void replaceListener(Integer request, Consumer<TestListenerRegistration> oldConsumer, + Consumer<TestListenerRegistration> consumer) { + replaceRegistration(oldConsumer, consumer, + new TestListenerRegistration(request, consumer)); } public void removeListener(Consumer<TestListenerRegistration> consumer) { @@ -422,6 +458,13 @@ public class ListenerMultiplexerTest { } @Override + protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer, + TestListenerRegistration oldRegistration, + TestListenerRegistration newRegistration) { + mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration); + } + + @Override protected void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer, TestListenerRegistration registration) { mCallbacks.onRegistrationRemoved(consumer, registration); diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index d2d85c860cd5..e76c5a476c48 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -46,7 +46,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; @@ -158,16 +157,16 @@ public class GestureLauncherServiceTest { } @Test - public void testIsPanicButtonGestureEnabled_settingDisabled() { - withPanicGestureEnabledSettingValue(false); - assertFalse(mGestureLauncherService.isPanicButtonGestureEnabled( + public void testIsEmergencyGestureEnabled_settingDisabled() { + withEmergencyGestureEnabledSettingValue(false); + assertFalse(mGestureLauncherService.isEmergencyGestureEnabled( mContext, FAKE_USER_ID)); } @Test - public void testIsPanicButtonGestureEnabled_settingEnabled() { - withPanicGestureEnabledSettingValue(true); - assertTrue(mGestureLauncherService.isPanicButtonGestureEnabled( + public void testIsEmergencyGestureEnabled_settingEnabled() { + withEmergencyGestureEnabledSettingValue(true); + assertTrue(mGestureLauncherService.isEmergencyGestureEnabled( mContext, FAKE_USER_ID)); } @@ -181,10 +180,10 @@ public class GestureLauncherServiceTest { } @Test - public void testHandlePanicGesture_userSetupComplete() { + public void testHandleEmergencyGesture_userSetupComplete() { withUserSetupCompleteValue(true); - assertTrue(mGestureLauncherService.handlePanicButtonGesture()); + assertTrue(mGestureLauncherService.handleEmergencyGesture()); } @Test @@ -196,10 +195,10 @@ public class GestureLauncherServiceTest { } @Test - public void testHandlePanicGesture_userSetupNotComplete() { + public void testHandleEmergencyGesture_userSetupNotComplete() { withUserSetupCompleteValue(false); - assertFalse(mGestureLauncherService.handlePanicButtonGesture()); + assertFalse(mGestureLauncherService.handleEmergencyGesture()); } @Test @@ -223,9 +222,9 @@ public class GestureLauncherServiceTest { } @Test - public void testInterceptPowerKeyDown_firstPowerDown_panicGestureNotLaunched() { - withPanicGestureEnabledSettingValue(true); - mGestureLauncherService.updatePanicButtonGestureEnabled(); + public void testInterceptPowerKeyDown_firstPowerDown_emergencyGestureNotLaunched() { + withEmergencyGestureEnabledSettingValue(true); + mGestureLauncherService.updateEmergencyGestureEnabled(); long eventTime = INITIAL_EVENT_TIME_MILLIS + GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS - 1; @@ -425,12 +424,12 @@ public class GestureLauncherServiceTest { @Test public void - testInterceptPowerKeyDown_fiveInboundPresses_cameraAndPanicEnabled_bothLaunch() { + testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() { withCameraDoubleTapPowerEnableConfigValue(true); withCameraDoubleTapPowerDisableSettingValue(0); - withPanicGestureEnabledSettingValue(true); + withEmergencyGestureEnabledSettingValue(true); mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); - mGestureLauncherService.updatePanicButtonGestureEnabled(); + mGestureLauncherService.updateEmergencyGestureEnabled(); withUserSetupCompleteValue(true); // First button press does nothing @@ -476,7 +475,7 @@ public class GestureLauncherServiceTest { assertEquals(1, tapCounts.get(0).intValue()); assertEquals(2, tapCounts.get(1).intValue()); - // Continue the button presses for the panic gesture. + // Continue the button presses for the emergency gesture. // Presses 3 and 4 should not trigger any gesture for (int i = 0; i < 2; i++) { @@ -490,7 +489,7 @@ public class GestureLauncherServiceTest { assertFalse(outLaunched.value); } - // Fifth button press should trigger the panic flow + // Fifth button press should trigger the emergency flow eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); @@ -513,9 +512,9 @@ public class GestureLauncherServiceTest { @Test public void - testInterceptPowerKeyDown_fiveInboundPresses_panicGestureEnabled_launchesPanicFlow() { - withPanicGestureEnabledSettingValue(true); - mGestureLauncherService.updatePanicButtonGestureEnabled(); + testInterceptPowerKeyDown_fiveInboundPresses_emergencyGestureEnabled_launchesFlow() { + withEmergencyGestureEnabledSettingValue(true); + mGestureLauncherService.updateEmergencyGestureEnabled(); withUserSetupCompleteValue(true); // First button press does nothing @@ -542,7 +541,7 @@ public class GestureLauncherServiceTest { assertFalse(outLaunched.value); } - // Fifth button press should trigger the panic flow + // Fifth button press should trigger the emergency flow eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); @@ -565,9 +564,9 @@ public class GestureLauncherServiceTest { @Test public void - testInterceptPowerKeyDown_tenInboundPresses_panicGestureEnabled_pressesIntercepted() { - withPanicGestureEnabledSettingValue(true); - mGestureLauncherService.updatePanicButtonGestureEnabled(); + testInterceptPowerKeyDown_tenInboundPresses_emergencyGestureEnabled_keyIntercepted() { + withEmergencyGestureEnabledSettingValue(true); + mGestureLauncherService.updateEmergencyGestureEnabled(); withUserSetupCompleteValue(true); // First button press does nothing @@ -594,7 +593,7 @@ public class GestureLauncherServiceTest { assertFalse(outLaunched.value); } - // Fifth button press should trigger the panic flow + // Fifth button press should trigger the emergency flow eventTime += interval; keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT); @@ -1128,10 +1127,10 @@ public class GestureLauncherServiceTest { UserHandle.USER_CURRENT); } - private void withPanicGestureEnabledSettingValue(boolean enable) { + private void withEmergencyGestureEnabledSettingValue(boolean enable) { Settings.Secure.putIntForUser( mContentResolver, - Settings.Secure.PANIC_GESTURE_ENABLED, + Settings.Secure.EMERGENCY_GESTURE_ENABLED, enable ? 1 : 0, UserHandle.USER_CURRENT); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index c4f7b9547277..30b1b3e78ad3 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2814,7 +2814,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { exerciseUserProvisioningTransitions(CALLER_USER_HANDLE, DevicePolicyManager.STATE_USER_PROFILE_COMPLETE, - DevicePolicyManager.STATE_USER_PROFILE_FINALIZED); + DevicePolicyManager.STATE_USER_UNMANAGED); } public void testSetUserProvisioningState_managedProfileFromSetupWizard_managedProfile() diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index 2a9c3942211c..8af7332e24ed 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -114,7 +114,7 @@ public class ActiveSourceActionTest { mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); mPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPhysicalAddress); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 1385376b740d..37a75e3822aa 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -123,7 +123,7 @@ public class ArcInitiationActionFromAvrTest { hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); - hdmiControlService.initPortInfo(); + hdmiControlService.initService(); mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 169f885a7253..6027c3e4eeab 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -117,7 +117,7 @@ public class ArcTerminationActionFromAvrTest { hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); - hdmiControlService.initPortInfo(); + hdmiControlService.initService(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index 2c42791fabce..bb57a69d6f51 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -30,6 +30,7 @@ import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** Fake {@link NativeWrapper} useful for testing. */ final class FakeNativeWrapper implements NativeWrapper { @@ -55,6 +56,7 @@ final class FakeNativeWrapper implements NativeWrapper { }; private final List<HdmiCecMessage> mResultMessages = new ArrayList<>(); + private final Map<Integer, Boolean> mPortConnectionStatus = new HashMap<>(); private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>(); private int mMyPhysicalAddress = 0; private HdmiPortInfo[] mHdmiPortInfo = null; @@ -125,7 +127,12 @@ final class FakeNativeWrapper implements NativeWrapper { @Override public boolean nativeIsConnected(int port) { - return false; + Boolean isConnected = mPortConnectionStatus.get(port); + return isConnected == null ? false : isConnected; + } + + public void setPortConnectionStatus(int port, boolean connected) { + mPortConnectionStatus.put(port, connected); } public void onCecMessage(HdmiCecMessage hdmiCecMessage) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 74fd6830de61..433f6e7938e2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -15,13 +15,9 @@ */ package com.android.server.hdmi; -import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; -import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE; - import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; -import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS; @@ -33,6 +29,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioManager; import android.os.Handler; import android.os.IPowerManager; @@ -226,7 +223,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { new HdmiPortInfo( 4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); // No TV device interacts with AVR so system audio control won't be turned on here mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); @@ -654,75 +651,6 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Test - public void updateCecDevice_deviceNotExists_addDevice() { - assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE); - HdmiDeviceInfo newDevice = new HdmiDeviceInfo( - ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); - - mHdmiCecLocalDeviceAudioSystem.updateCecDevice(newDevice); - assertThat(mDeviceInfo).isEqualTo(newDevice); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getCecDeviceInfo(newDevice.getLogicalAddress())).isEqualTo(newDevice); - assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE); - } - - @Test - public void updateCecDevice_deviceExists_doNothing() { - mInvokeDeviceEventState = 0; - HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( - ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); - mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); - - mHdmiCecLocalDeviceAudioSystem.updateCecDevice(oldDevice); - assertThat(mInvokeDeviceEventState).isEqualTo(0); - } - - @Test - public void updateCecDevice_deviceInfoDifferent_updateDevice() { - assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_UPDATE_DEVICE); - HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( - ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); - mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); - - HdmiDeviceInfo differentDevice = new HdmiDeviceInfo( - ADDR_PLAYBACK_1, 0x2300, 4, HdmiDeviceInfo.DEVICE_PLAYBACK, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); - - mHdmiCecLocalDeviceAudioSystem.updateCecDevice(differentDevice); - assertThat(mDeviceInfo).isEqualTo(differentDevice); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice); - assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_UPDATE_DEVICE); - } - - @Test - @Ignore("b/120845532") - public void handleReportPhysicalAddress_differentPath_addDevice() { - assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE); - HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( - ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1)); - mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice); - - HdmiDeviceInfo differentDevice = new HdmiDeviceInfo( - ADDR_PLAYBACK_2, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK, - Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_2)); - HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder - .buildReportPhysicalAddressCommand( - ADDR_PLAYBACK_2, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK); - mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress); - - mTestLooper.dispatchAll(); - assertThat(mDeviceInfo).isEqualTo(differentDevice); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice); - assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE); - } - - @Test public void doNotWakeUpOnHotPlug_PlugIn() { mWokenUp = false; mHdmiCecLocalDeviceAudioSystem.onHotplug(0, true); @@ -907,4 +835,42 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse(); } + + @Test + @Ignore("b/151150320") + public void oneTouchPlay() { + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + } + }); + mTestLooper.dispatchAll(); + + HdmiCecMessage textViewOn_fromPlayback = HdmiCecMessageBuilder.buildTextViewOn( + mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(), ADDR_TV); + HdmiCecMessage activeSource_fromPlayback = HdmiCecMessageBuilder.buildActiveSource( + mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(), + SELF_PHYSICAL_ADDRESS); + HdmiCecMessage systemAudioModeRequest_fromPlayback = + HdmiCecMessageBuilder.buildSystemAudioModeRequest( + mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(), + ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true); + HdmiCecMessage textViewOn_fromAudioSystem = HdmiCecMessageBuilder.buildTextViewOn( + mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(), ADDR_TV); + HdmiCecMessage activeSource_fromAudioSystem = HdmiCecMessageBuilder.buildActiveSource( + mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(), + SELF_PHYSICAL_ADDRESS); + HdmiCecMessage systemAudioModeRequest_fromAudioSystem = + HdmiCecMessageBuilder.buildSystemAudioModeRequest( + mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(), + ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true); + assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn_fromPlayback); + assertThat(mNativeWrapper.getResultMessages()).contains(activeSource_fromPlayback); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain( + systemAudioModeRequest_fromPlayback); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn_fromAudioSystem); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource_fromAudioSystem); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain( + systemAudioModeRequest_fromAudioSystem); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index ef98b98ba7e1..440befcb3368 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -25,6 +25,8 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiControlCallback; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; @@ -125,7 +127,12 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDevicePlayback); - mHdmiControlService.initPortInfo(); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mNativeWrapper.setPortConnectionStatus(1, true); + mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mPlaybackPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress); @@ -892,6 +899,7 @@ public class HdmiCecLocalDevicePlaybackTest { public void handleSetStreamPath_afterHotplug_broadcastsActiveSource() { mNativeWrapper.onHotplugEvent(1, false); mNativeWrapper.onHotplugEvent(1, true); + mTestLooper.dispatchAll(); HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress); @@ -967,4 +975,73 @@ public class HdmiCecLocalDevicePlaybackTest { assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isFalse(); } + + @Test + public void oneTouchPlay_SendStandbyOnSleepToTv() { + mHdmiCecLocalDevicePlayback.mService.writeStringSetting( + Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, + HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + } + }); + mTestLooper.dispatchAll(); + + HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress, + ADDR_TV); + HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( + mPlaybackLogicalAddress, mPlaybackPhysicalAddress); + HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest( + mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true); + assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); + assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest); + } + + @Test + public void oneTouchPlay_SendStandbyOnSleepBroadcast() { + mHdmiCecLocalDevicePlayback.mService.writeStringSetting( + Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, + HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + } + }); + mTestLooper.dispatchAll(); + + HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress, + ADDR_TV); + HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( + mPlaybackLogicalAddress, mPlaybackPhysicalAddress); + HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest( + mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true); + assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); + assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); + assertThat(mNativeWrapper.getResultMessages()).contains(systemAudioModeRequest); + } + + @Test + public void oneTouchPlay_SendStandbyOnSleepNone() { + mHdmiCecLocalDevicePlayback.mService.writeStringSetting( + Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP, + HdmiControlManager.SEND_STANDBY_ON_SLEEP_NONE); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + } + }); + mTestLooper.dispatchAll(); + + HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress, + ADDR_TV); + HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( + mPlaybackLogicalAddress, mPlaybackPhysicalAddress); + HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest( + mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM, mPlaybackPhysicalAddress, true); + assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn); + assertThat(mNativeWrapper.getResultMessages()).contains(activeSource); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index ce1cdf369076..bf4851b927b1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Handler; import android.os.IPowerManager; @@ -103,7 +104,11 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); - mHdmiControlService.initPortInfo(); + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTvPhysicalAddress = 0x0000; mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress); @@ -119,8 +124,9 @@ public class HdmiCecLocalDeviceTvTest { @Test public void onAddressAllocated_invokesDeviceDiscovery() { + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); - mHdmiCecLocalDeviceTv.onAddressAllocated(0, HdmiControlService.INITIATED_BY_BOOT_UP); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java new file mode 100644 index 000000000000..080b52bbbc6a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 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. + */ + +package com.android.server.hdmi; + + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.os.Looper; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link HdmiCecNetwork} class. + */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class HdmiCecNetworkTest { + + private HdmiCecNetwork mHdmiCecNetwork; + + private Context mContext; + + private HdmiControlService mHdmiControlService; + private HdmiMhlControllerStub mHdmiMhlControllerStub; + + private HdmiCecController mHdmiCecController; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private HdmiPortInfo[] mHdmiPortInfo; + private List<Integer> mDeviceEventListenerStatuses = new ArrayList<>(); + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mHdmiControlService = new HdmiControlService(mContext) { + @Override + void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { + mDeviceEventListenerStatuses.add(status); + } + }; + + mMyLooper = mTestLooper.getLooper(); + mHdmiControlService.setIoLooper(mMyLooper); + + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = HdmiCecController.createWithNativeWrapper(mHdmiControlService, + mNativeWrapper, mHdmiControlService.getAtomWriter()); + mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + + mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService, + mHdmiCecController, mHdmiMhlControllerStub); + + mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork); + + mHdmiPortInfo = new HdmiPortInfo[5]; + mHdmiPortInfo[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); + mHdmiPortInfo[1] = + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false); + mHdmiPortInfo[2] = + new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false); + mHdmiPortInfo[3] = + new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); + mHdmiPortInfo[4] = + new HdmiPortInfo(5, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false); + mNativeWrapper.setPortInfo(mHdmiPortInfo); + mHdmiCecNetwork.initPortInfo(); + } + + @Test + public void initializeNetwork_verifyPortInfo() { + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.getPortInfo()).hasSize(mHdmiPortInfo.length); + } + + @Test + public void physicalAddressToPort_pathExists_weAreNonTv() { + mNativeWrapper.setPhysicalAddress(0x2000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(1); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2234)).isEqualTo(2); + } + + @Test + public void physicalAddressToPort_pathExists_weAreSourceDevice() { + mNativeWrapper.setPhysicalAddress(0x2000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x0000)).isEqualTo(5); + } + + @Test + public void physicalAddressToPort_pathExists_weAreTv() { + mNativeWrapper.setPhysicalAddress(0x0000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x2120)).isEqualTo(3); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x3234)).isEqualTo(4); + } + + @Test + public void physicalAddressToPort_pathInvalid() { + mNativeWrapper.setPhysicalAddress(0x2000); + mHdmiCecNetwork.initPortInfo(); + assertThat(mHdmiCecNetwork.physicalAddressToPortId(0x1000)).isEqualTo( + Constants.INVALID_PORT_ID); + } + + @Test + public void localDevices_verifyOne_tv() { + mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV, + new HdmiCecLocalDeviceTv(mHdmiControlService)); + + assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1); + assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf( + HdmiCecLocalDeviceTv.class); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNotNull(); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNull(); + } + + @Test + public void localDevices_verifyOne_playback() { + mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK, + new HdmiCecLocalDevicePlayback(mHdmiControlService)); + + assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1); + assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf( + HdmiCecLocalDevicePlayback.class); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK)).isNotNull(); + assertThat(mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV)).isNull(); + } + + @Test + public void cecDevices_tracking_logicalAddressOnly() throws Exception { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + + @Test + public void cecDevices_tracking_logicalAddressOnly_doesntNotifyAgain() throws Exception { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + + @Test + public void cecDevices_tracking_reportPhysicalAddress() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int physicalAddress = 0x1000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + physicalAddress); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + } + + @Test + public void cecDevices_tracking_updateDeviceInfo_sameDoesntNotify() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int physicalAddress = 0x1000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + + + // ADD for logical address first detected + // UPDATE for updating device with physical address + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE, + HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + + @Test + public void cecDevices_tracking_reportPowerStatus() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int powerStatus = HdmiControlManager.POWER_STATUS_ON; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, powerStatus)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); + } + + @Test + public void cecDevices_tracking_reportOsdName() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + String osdName = "Test Device"; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, osdName)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + } + + @Test + public void cecDevices_tracking_reportVendorId() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int vendorId = 1234; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(vendorId); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + } + + @Test + public void cecDevices_tracking_updatesDeviceInfo() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int physicalAddress = 0x1000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + int powerStatus = HdmiControlManager.POWER_STATUS_ON; + String osdName = "Test Device"; + int vendorId = 1234; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + physicalAddress, type)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, powerStatus)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, osdName)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(physicalAddress); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(vendorId); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); + } + + @Test + public void cecDevices_tracking_updatesPhysicalAddress() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int initialPhysicalAddress = 0x1000; + int updatedPhysicalAddress = 0x2000; + int type = HdmiDeviceInfo.DEVICE_PLAYBACK; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + initialPhysicalAddress, type)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(logicalAddress, + updatedPhysicalAddress, type)); + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo(updatedPhysicalAddress); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); + + // ADD for logical address first detected + // UPDATE for updating device with physical address + // UPDATE for updating device with new physical address + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE, + HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE, + HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); + } + + @Test + public void cecDevices_tracking_updatesPowerStatus() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int powerStatus = HdmiControlManager.POWER_STATUS_ON; + int updatedPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, powerStatus)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildReportPowerStatus(logicalAddress, + Constants.ADDR_BROADCAST, updatedPowerStatus)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(updatedPowerStatus); + } + + @Test + public void cecDevices_tracking_updatesOsdName() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + String osdName = "Test Device"; + String updatedOsdName = "Different"; + + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, osdName)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildSetOsdNameCommand(logicalAddress, + Constants.ADDR_BROADCAST, updatedOsdName)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(updatedOsdName); + } + + @Test + public void cecDevices_tracking_updatesVendorId() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int vendorId = 1234; + int updatedVendorId = 12345; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, vendorId)); + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildDeviceVendorIdCommand(logicalAddress, updatedVendorId)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); + assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( + Constants.INVALID_PHYSICAL_ADDRESS); + assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); + assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( + HdmiUtils.getDefaultDeviceName(logicalAddress)); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(updatedVendorId); + assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_UNKNOWN); + } + + @Test + public void cecDevices_tracking_clearDevices() { + int logicalAddress = Constants.ADDR_PLAYBACK_1; + mHdmiCecNetwork.handleCecMessage( + HdmiCecMessageBuilder.buildActiveSource(logicalAddress, 0x1000)); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + + mHdmiCecNetwork.clearDeviceList(); + + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).isEmpty(); + + assertThat(mDeviceEventListenerStatuses).containsExactly( + HdmiControlManager.DEVICE_EVENT_ADD_DEVICE, + HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java index c4068d34c00d..74a00521d7f4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java @@ -21,11 +21,13 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; +import android.content.Context; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.Looper; import android.os.test.TestLooper; +import android.provider.Settings; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -44,6 +46,8 @@ import java.util.ArrayList; @RunWith(JUnit4.class) public class HdmiControlServiceBinderAPITest { + private Context mContext; + private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice { private boolean mCanGoToStandby; @@ -111,8 +115,12 @@ public class HdmiControlServiceBinderAPITest { @Before public void SetUp() { + mContext = InstrumentationRegistry.getTargetContext(); + // Some tests expect no logical addresses being allocated at the beginning of the test. + setHdmiControlEnabled(false); + mHdmiControlService = - new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + new HdmiControlService(mContext) { @Override void sendCecCommand(HdmiCecMessage command) { switch (command.getOpcode()) { @@ -164,7 +172,7 @@ public class HdmiControlServiceBinderAPITest { mHdmiPortInfo[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); mResult = -1; mPowerStatus = HdmiControlManager.POWER_STATUS_ON; @@ -183,6 +191,7 @@ public class HdmiControlServiceBinderAPITest { assertEquals(mResult, -1); assertThat(mPlaybackDevice.isActiveSource()).isFalse(); + setHdmiControlEnabled(true); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); @@ -192,6 +201,8 @@ public class HdmiControlServiceBinderAPITest { @Test public void oneTouchPlay_addressAllocated() { + setHdmiControlEnabled(true); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); @@ -204,4 +215,10 @@ public class HdmiControlServiceBinderAPITest { assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); assertThat(mPlaybackDevice.isActiveSource()).isTrue(); } + + private void setHdmiControlEnabled(boolean enabled) { + int value = enabled ? 1 : 0; + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED, + value); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 2f48b5ee4c70..8f631a307f15 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -176,7 +176,7 @@ public class HdmiControlServiceTest { mHdmiPortInfo[3] = new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.initPortInfo(); + mHdmiControlService.initService(); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); @@ -206,29 +206,6 @@ public class HdmiControlServiceTest { } @Test - public void pathToPort_pathExists_weAreNonTv() { - mNativeWrapper.setPhysicalAddress(0x2000); - mHdmiControlService.initPortInfo(); - assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1); - assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2); - } - - @Test - public void pathToPort_pathExists_weAreTv() { - mNativeWrapper.setPhysicalAddress(0x0000); - mHdmiControlService.initPortInfo(); - assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3); - assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4); - } - - @Test - public void pathToPort_pathInvalid() { - mNativeWrapper.setPhysicalAddress(0x2000); - mHdmiControlService.initPortInfo(); - assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID); - } - - @Test public void initialPowerStatus_normalBoot_isTransientToStandby() { assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index 6be28d9a13be..0e4bfab0d94c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -90,8 +90,6 @@ public class SystemAudioInitiationActionFromAvrTest { break; case Constants.MESSAGE_INITIATE_ARC: break; - default: - throw new IllegalArgumentException("Unexpected message"); } } @@ -159,6 +157,14 @@ public class SystemAudioInitiationActionFromAvrTest { return -1; } }; + + Looper looper = mTestLooper.getLooper(); + hdmiControlService.setIoLooper(looper); + HdmiCecController.NativeWrapper nativeWrapper = new FakeNativeWrapper(); + HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper( + hdmiControlService, nativeWrapper, hdmiControlService.getAtomWriter()); + hdmiControlService.setCecController(hdmiCecController); + hdmiControlService.initService(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override @@ -181,8 +187,6 @@ public class SystemAudioInitiationActionFromAvrTest { } }; mHdmiCecLocalDeviceAudioSystem.init(); - Looper looper = mTestLooper.getLooper(); - hdmiControlService.setIoLooper(looper); } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java index 86758f18a407..40d959d9793d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java @@ -222,4 +222,18 @@ public class IncrementalStatesTest { assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS)); assertFalse(mIncrementalStates.isLoading()); } + + /** + * Test startability transitions if app crashes or anrs + */ + @Test + public void testStartableTransition_AppCrashOrAnr() { + mIncrementalStates.onCrashOrAnr(); + assertFalse(mIncrementalStates.isStartable()); + mIncrementalStates.setProgress(1.0f); + assertTrue(mIncrementalStates.isStartable()); + mIncrementalStates.onCrashOrAnr(); + // Test that if fully loaded, app remains startable even if it has crashed + assertTrue(mIncrementalStates.isStartable()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index caf8a720e26c..b6323313dd27 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -245,9 +245,8 @@ public class ActivityStackTests extends WindowTestsBase { .setStack(rootHomeTask) .setCreateTask(true) .build(); - final Task secondaryStack = (Task) WindowContainer.fromBinder( - mAtm.mTaskOrganizerController.createRootTask(rootHomeTask.getDisplayId(), - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token.asBinder()); + final Task secondaryStack = mAtm.mTaskOrganizerController.createRootTask( + rootHomeTask.getDisplayContent(), WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); rootHomeTask.reparent(secondaryStack, POSITION_TOP); assertEquals(secondaryStack, rootHomeTask.getParent()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index e47881917b2c..3720e520b1b9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1022,7 +1022,7 @@ public class ActivityStarterTests extends WindowTestsBase { assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse(); // Move activity to split-screen-primary stack and make sure it has the focus. - TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayId()); + TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent()); top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM); top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent"); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index cc37e2bf94ca..d68dde5734ca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -750,17 +750,15 @@ public class SizeCompatTests extends WindowTestsBase { final Rect taskBounds = mTask.getBounds(); final Rect newActivityBounds = newActivity.getBounds(); - // Task bounds should be 700x1400 with the ratio as the display. + // Task bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. assertTrue(mTask.isTaskLetterboxed()); assertEquals(displayBounds.height(), taskBounds.height()); - assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), + assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio), taskBounds.width()); - // App bounds should be 700x(710 x 1.3 = 910) + // App bounds should be fullscreen in Task bounds. assertFalse(newActivity.inSizeCompatMode()); - assertEquals(taskBounds.width(), newActivityBounds.width()); - assertEquals((long) Math.rint(taskBounds.width() * newActivity.info.maxAspectRatio), - newActivityBounds.height()); + assertEquals(taskBounds, newActivityBounds); } private static WindowState addWindowToActivity(ActivityRecord activity) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7a1f65a3b62c..b4480aea3ce4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -479,21 +479,22 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testCreateDeleteRootTasks() { - RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - Display.DEFAULT_DISPLAY, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); + + Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + dc, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + RunningTaskInfo info1 = task1.getTaskInfo(); assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, info1.configuration.windowConfiguration.getWindowingMode()); assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); - RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - Display.DEFAULT_DISPLAY, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + dc, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + RunningTaskInfo info2 = task2.getTaskInfo(); assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, info2.configuration.windowConfiguration.getWindowingMode()); assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType); - DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); List<Task> infos = getTasksCreatedByOrganizer(dc); assertEquals(2, infos.size()); @@ -521,8 +522,9 @@ public class WindowOrganizerTests extends WindowTestsBase { } }; mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); - RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + RunningTaskInfo info1 = task.getTaskInfo(); final Task stack = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); @@ -579,8 +581,9 @@ public class WindowOrganizerTests extends WindowTestsBase { } }; mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); - RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + RunningTaskInfo info1 = task.getTaskInfo(); lastReportedTiles.clear(); called[0] = false; @@ -640,10 +643,13 @@ public class WindowOrganizerTests extends WindowTestsBase { } }; mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); - RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + + Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + RunningTaskInfo info1 = task1.getTaskInfo(); + Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + RunningTaskInfo info2 = task2.getTaskInfo(); final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks( mDisplayContent.mDisplayId, null /* activityTypes */).size(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 1eb7cbed02c8..62f04a1c830a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -973,8 +973,9 @@ class WindowTestsBase extends SystemServiceTestsBase { final int taskId = mTaskId >= 0 ? mTaskId : mTaskDisplayArea.getNextStackId(); if (mParentTask == null) { task = mTaskDisplayArea.createStackUnchecked( - mWindowingMode, mActivityType, taskId, mOnTop, mActivityInfo, - mIntent, false /* createdByOrganizer */); + mWindowingMode, mActivityType, taskId, mOnTop, mActivityInfo, mIntent, + false /* createdByOrganizer */, false /* deferTaskAppear */, + null /* launchCookie */); } else { task = new Task(mSupervisor.mService, taskId, mActivityInfo, mIntent /*intent*/, mVoiceSession, null /*_voiceInteractor*/, @@ -1016,20 +1017,17 @@ class WindowTestsBase extends SystemServiceTestsBase { // moves everything to secondary. Most tests expect this since sysui usually does it. boolean mMoveToSecondaryOnEnter = true; int mDisplayId; - TestSplitOrganizer(ActivityTaskManagerService service, int displayId) { + TestSplitOrganizer(ActivityTaskManagerService service, DisplayContent display) { mService = service; - mDisplayId = displayId; + mDisplayId = display.mDisplayId; mService.mTaskOrganizerController.registerTaskOrganizer(this); - WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask( - displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token; - mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask(); - WindowContainerToken secondary = mService.mTaskOrganizerController.createRootTask( - displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token; - mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask(); + mPrimary = mService.mTaskOrganizerController.createRootTask( + display, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + mSecondary = mService.mTaskOrganizerController.createRootTask( + display, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);; } TestSplitOrganizer(ActivityTaskManagerService service) { - this(service, - service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay().mDisplayId); + this(service, service.mStackSupervisor.mRootWindowContainer.getDefaultDisplay()); } public void setMoveToSecondaryOnEnter(boolean move) { mMoveToSecondaryOnEnter = move; diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt index 63e0e5a36a26..a58b6d84cbd1 100644 --- a/telephony/api/system-current.txt +++ b/telephony/api/system-current.txt @@ -747,6 +747,7 @@ package android.telephony { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int); + method public boolean isNrDualConnectivityEnabled(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String); @@ -780,6 +781,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean); + method public int setNrDualConnectivityState(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean); @@ -819,6 +821,11 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; // 0x2 + field public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; // 0x0 field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE"; @@ -851,6 +858,9 @@ package android.telephony { field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L + field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2 + field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3 + field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1 field public static final int RADIO_POWER_OFF = 0; // 0x0 field public static final int RADIO_POWER_ON = 1; // 0x1 field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2 diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 92238420fd32..f8a200a5f8d3 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -369,7 +369,6 @@ public final class NetworkRegistrationInfo implements Parcelable { * Get the 5G NR connection state. * * @return the 5G NR connection state. - * @hide */ public @NRState int getNrState() { return mNrState; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 20eeb1da0d99..aa68dcf5a88e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13672,6 +13672,149 @@ public class TelephonyManager { } } + /** + * No error. Operation succeeded. + * @hide + */ + @SystemApi + public static final int ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS = 0; + + /** + * NR Dual connectivity enablement is not supported. + * @hide + */ + @SystemApi + public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; + + /** + * Radio is not available. + * @hide + */ + @SystemApi + public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE = 2; + + /** + * Internal Radio error. + * @hide + */ + @SystemApi + public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; + + /** + * Currently in invalid state. Not able to process the request. + * @hide + */ + @SystemApi + public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ENABLE_NR_DUAL_CONNECTIVITY"}, value = { + ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS, + ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED, + ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE, + ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE, + ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR}) + public @interface EnableNrDualConnectivityResult {} + + /** + * Enable NR dual connectivity. Enabled state does not mean dual connectivity + * is active. It means device is allowed to connect to both primary and secondary. + * + * @hide + */ + @SystemApi + public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; + + /** + * Disable NR dual connectivity. Disabled state does not mean the secondary cell is released. + * Modem will release it only if the current bearer is released to avoid radio link failure. + * @hide + */ + @SystemApi + public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; + + /** + * Disable NR dual connectivity and force the secondary cell to be released if dual connectivity + * was active. This will result in radio link failure. + * @hide + */ + @SystemApi + public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; + + /** + * @hide + */ + @IntDef(prefix = { "NR_DUAL_CONNECTIVITY_" }, value = { + NR_DUAL_CONNECTIVITY_ENABLE, + NR_DUAL_CONNECTIVITY_DISABLE, + NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NrDualConnectivityState { + } + + /** + * Enable/Disable E-UTRA-NR Dual Connectivity. + * + * @param nrDualConnectivityState expected NR dual connectivity state + * This can be passed following states + * <ol> + * <li>Enable NR dual connectivity {@link #NR_DUAL_CONNECTIVITY_ENABLE} + * <li>Disable NR dual connectivity {@link #NR_DUAL_CONNECTIVITY_DISABLE} + * <li>Disable NR dual connectivity and force secondary cell to be released + * {@link #NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE} + * </ol> + * @return operation result. + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * @throws IllegalStateException if the Telephony process is not currently available. + * @hide + */ + @SystemApi + public @EnableNrDualConnectivityResult int setNrDualConnectivityState( + @NrDualConnectivityState int nrDualConnectivityState) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.setNrDualConnectivityState(getSubId(), nrDualConnectivityState); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "setNrDualConnectivityState RemoteException", ex); + ex.rethrowFromSystemServer(); + } + + return ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE; + } + + /** + * Is E-UTRA-NR Dual Connectivity enabled. + * @return true if dual connectivity is enabled else false. Enabled state does not mean dual + * connectivity is active. It means the device is allowed to connect to both primary and + * secondary cell. + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * @throws IllegalStateException if the Telephony process is not currently available. + * @hide + */ + @SystemApi + public boolean isNrDualConnectivityEnabled() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isNrDualConnectivityEnabled(getSubId()); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "isNRDualConnectivityEnabled RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return false; + } + private static class DeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 0d8351d1ff12..d33835f27053 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2216,4 +2216,20 @@ interface ITelephony { * does not exist on the SIM card. */ List<String> getEquivalentHomePlmns(int subId, String callingPackage, String callingFeatureId); + + /** + * Enable/Disable E-UTRA-NR Dual Connectivity + * @return operation result. See TelephonyManager.EnableNrDualConnectivityResult for + * details + * @param subId the id of the subscription + * @param enable enable/disable dual connectivity + */ + int setNrDualConnectivityState(int subId, int nrDualConnectivityState); + + /** + * Is E-UTRA-NR Dual Connectivity enabled + * @param subId the id of the subscription + * @return true if dual connectivity is enabled else false + */ + boolean isNrDualConnectivityEnabled(int subId); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index d524299d7ede..953a2924dc44 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -494,6 +494,8 @@ public interface RILConstants { int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210; int RIL_REQUEST_GET_BARRING_INFO = 211; int RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION = 212; + int RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY = 213; + int RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED = 214; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 992493143179..6cc2e2236127 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -100,10 +100,8 @@ class CloseImeAutoOpenWindowToHomeTest( Surface.ROTATION_0, bugId = 140855415) statusBarLayerRotatesScales(configuration.startRotation, Surface.ROTATION_0) - navBarLayerIsAlwaysVisible(configuration.startRotation != - Surface.ROTATION_0) - statusBarLayerIsAlwaysVisible(configuration.startRotation != - Surface.ROTATION_0) + navBarLayerIsAlwaysVisible(enabled = false) + statusBarLayerIsAlwaysVisible(enabled = false) imeLayerBecomesInvisible(bugId = 141458352) imeAppLayerBecomesInvisible(testApp, bugId = 153739621) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index 46f584b51e8c..8d9881ec2063 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -106,10 +106,8 @@ class CloseImeWindowToHomeTest( Surface.ROTATION_0, bugId = 140855415) statusBarLayerRotatesScales(configuration.startRotation, Surface.ROTATION_0) - navBarLayerIsAlwaysVisible(configuration.startRotation != - Surface.ROTATION_0) - statusBarLayerIsAlwaysVisible(configuration.startRotation != - Surface.ROTATION_0) + navBarLayerIsAlwaysVisible(enabled = false) + statusBarLayerIsAlwaysVisible(enabled = false) imeLayerBecomesInvisible(bugId = 153739621) imeAppLayerBecomesInvisible(testApp, bugId = 153739621) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index 1194933ee315..ad23d9f48568 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -100,10 +100,8 @@ class OpenAppColdTest( configuration.endRotation) statusBarLayerRotatesScales(Surface.ROTATION_0, configuration.endRotation) - navBarLayerIsAlwaysVisible(Surface.ROTATION_0 != - configuration.endRotation) - statusBarLayerIsAlwaysVisible(Surface.ROTATION_0 != - configuration.endRotation) + navBarLayerIsAlwaysVisible(enabled = false) + statusBarLayerIsAlwaysVisible(enabled = false) wallpaperLayerBecomesInvisible(testApp) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index 136be29e421d..5886a61eed34 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -104,10 +104,8 @@ class OpenAppWarmTest( configuration.endRotation) statusBarLayerRotatesScales(Surface.ROTATION_0, configuration.endRotation) - navBarLayerIsAlwaysVisible(Surface.ROTATION_0 != - configuration.endRotation) - statusBarLayerIsAlwaysVisible(Surface.ROTATION_0 != - configuration.endRotation) + navBarLayerIsAlwaysVisible(enabled = false) + statusBarLayerIsAlwaysVisible(enabled = false) wallpaperLayerBecomesInvisible(testApp) } diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py index da644021e30e..e4b96b1d4f5f 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ b/tools/hiddenapi/generate_hiddenapi_lists.py @@ -15,7 +15,7 @@ # limitations under the License. """Generate API lists for non-SDK API enforcement.""" import argparse -from collections import defaultdict +from collections import defaultdict, namedtuple import functools import os import re @@ -54,16 +54,21 @@ ALL_FLAGS = FLAGS_API_LIST + [ FLAGS_API_LIST_SET = set(FLAGS_API_LIST) ALL_FLAGS_SET = set(ALL_FLAGS) -# Suffix used in command line args to express that only known and -# otherwise unassigned entries should be assign the given flag. +# Option specified after one of FLAGS_API_LIST to indicate that +# only known and otherwise unassigned entries should be assign the +# given flag. # For example, the max-target-P list is checked in as it was in P, # but signatures have changes since then. The flag instructs this # script to skip any entries which do not exist any more. -FLAG_IGNORE_CONFLICTS_SUFFIX = "-ignore-conflicts" +FLAG_IGNORE_CONFLICTS = "ignore-conflicts" -# Suffix used in command line args to express that all apis within a given set -# of packages should be assign the given flag. -FLAG_PACKAGES_SUFFIX = "-packages" +# Option specified after one of FLAGS_API_LIST to express that all +# apis within a given set of packages should be assign the given flag. +FLAG_PACKAGES = "packages" + +# Option specified after one of FLAGS_API_LIST to indicate an extra +# tag that should be added to the matching APIs. +FLAG_TAG = "tag" # Regex patterns of fields/methods used in serialization. These are # considered public API despite being hidden. @@ -86,6 +91,17 @@ HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersectio IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api) +class StoreOrderedOptions(argparse.Action): + """An argparse action that stores a number of option arguments in the order that + they were specified. + """ + def __call__(self, parser, args, values, option_string = None): + items = getattr(args, self.dest, None) + if items is None: + items = [] + items.append([option_string.lstrip('-'), values]) + setattr(args, self.dest, items) + def get_args(): """Parses command line arguments. @@ -98,17 +114,19 @@ def get_args(): help='CSV files to be merged into output') for flag in ALL_FLAGS: - ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX - packages_flag = flag + FLAG_PACKAGES_SUFFIX - parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE', - help='lists of entries with flag "' + flag + '"') - parser.add_argument('--' + ignore_conflicts_flag, dest=ignore_conflicts_flag, nargs='*', - default=[], metavar='TXT_FILE', - help='lists of entries with flag "' + flag + - '". skip entry if missing or flag conflict.') - parser.add_argument('--' + packages_flag, dest=packages_flag, nargs='*', - default=[], metavar='TXT_FILE', - help='lists of packages to be added to ' + flag + ' list') + parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE', + action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"') + parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0, + action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned ' + 'entries should be assign the given flag. Must follow a list of entries and applies ' + 'to the preceding such list.') + parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0, + action=StoreOrderedOptions, help='Indicates that the previous list of entries ' + 'is a list of packages. All members in those packages will be given the flag. ' + 'Must follow a list of entries and applies to the preceding such list.') + parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1, + action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. ' + 'Must follow a list of entries and applies to the preceding such list.') return parser.parse_args() @@ -258,7 +276,7 @@ class FlagsDict: flags.append(FLAG_SDK) self._dict[csv[0]].update(flags) - def assign_flag(self, flag, apis, source="<unknown>"): + def assign_flag(self, flag, apis, source="<unknown>", tag = None): """Assigns a flag to given subset of entries. Args: @@ -278,11 +296,44 @@ class FlagsDict: # Iterate over the API subset, find each entry in dict and assign the flag to it. for api in apis: self._dict[api].add(flag) + if tag: + self._dict[api].add(tag) + + +FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag')) + +def parse_ordered_flags(ordered_flags): + r = [] + currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None + for flag_value in ordered_flags: + flag, value = flag_value[0], flag_value[1] + if flag in ALL_FLAGS_SET: + if currentflag: + r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag)) + ignore_conflicts, packages, tag = False, False, None + currentflag = flag + file = value + else: + if currentflag is None: + raise argparse.ArgumentError('--%s is only allowed after one of %s' % ( + flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET]))) + if flag == FLAG_IGNORE_CONFLICTS: + ignore_conflicts = True + elif flag == FLAG_PACKAGES: + packages = True + elif flag == FLAG_TAG: + tag = value[0] + + + if currentflag: + r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag)) + return r def main(argv): # Parse arguments. args = vars(get_args()) + flagfiles = parse_ordered_flags(args['ordered_flags']) # Initialize API->flags dictionary. flags = FlagsDict() @@ -300,28 +351,28 @@ def main(argv): flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION)) # (2) Merge text files with a known flag into the dictionary. - for flag in ALL_FLAGS: - for filename in args[flag]: - flags.assign_flag(flag, read_lines(filename), filename) + for info in flagfiles: + if (not info.ignore_conflicts) and (not info.packages): + flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag) # Merge text files where conflicts should be ignored. # This will only assign the given flag if: # (a) the entry exists, and # (b) it has not been assigned any other flag. # Because of (b), this must run after all strict assignments have been performed. - for flag in ALL_FLAGS: - for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]: - valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename)) - flags.assign_flag(flag, valid_entries, filename) + for info in flagfiles: + if info.ignore_conflicts: + valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file)) + flags.assign_flag(info.flag, valid_entries, filename, info.tag) # All members in the specified packages will be assigned the appropriate flag. - for flag in ALL_FLAGS: - for filename in args[flag + FLAG_PACKAGES_SUFFIX]: - packages_needing_list = set(read_lines(filename)) + for info in flagfiles: + if info.packages: + packages_needing_list = set(read_lines(info.file)) should_add_signature_to_list = lambda sig,lists: extract_package( sig) in packages_needing_list and not lists valid_entries = flags.filter_apis(should_add_signature_to_list) - flags.assign_flag(flag, valid_entries) + flags.assign_flag(info.flag, valid_entries, info.file, info.tag) # Mark all remaining entries as blocked. flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED)) |