Merge "Do not intercept LANGUAGE_SWITCH in the framework layer"
diff --git a/core/api/current.txt b/core/api/current.txt
index 74ff672..c70fa37 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -50935,6 +50935,7 @@
method public int getScaledDoubleTapSlop();
method public int getScaledEdgeSlop();
method public int getScaledFadingEdgeLength();
+ method public int getScaledHandwritingGestureLineMargin();
method public int getScaledHandwritingSlop();
method public float getScaledHorizontalScrollFactor();
method public int getScaledHoverSlop();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7c1c8ba..c170f74 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -401,6 +401,7 @@
public static final class R.dimen {
field public static final int config_restrictedIconSize = 17104903; // 0x1050007
+ field public static final int config_viewConfigurationHandwritingGestureLineMargin;
}
public static final class R.drawable {
@@ -7076,6 +7077,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);
method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
method public int removeOutputPid(@IntRange(from=0) int);
+ method public int requestFrontendById(int);
method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
method public int setLnaEnabled(boolean);
method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
@@ -11979,7 +11981,7 @@
public final class HotwordAudioStream implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.media.AudioFormat getAudioFormat();
- method @NonNull public android.os.ParcelFileDescriptor getAudioStream();
+ method @NonNull public android.os.ParcelFileDescriptor getAudioStreamParcelFileDescriptor();
method @NonNull public android.os.PersistableBundle getMetadata();
method @Nullable public android.media.AudioTimestamp getTimestamp();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11990,7 +11992,7 @@
ctor public HotwordAudioStream.Builder(@NonNull android.media.AudioFormat, @NonNull android.os.ParcelFileDescriptor);
method @NonNull public android.service.voice.HotwordAudioStream build();
method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioFormat(@NonNull android.media.AudioFormat);
- method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioStream(@NonNull android.os.ParcelFileDescriptor);
+ method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioStreamParcelFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
method @NonNull public android.service.voice.HotwordAudioStream.Builder setMetadata(@NonNull android.os.PersistableBundle);
method @NonNull public android.service.voice.HotwordAudioStream.Builder setTimestamp(@NonNull android.media.AudioTimestamp);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7a9f3c1..67d4416 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -658,7 +658,8 @@
"Received config update for non-existing activity");
}
activity.mMainThread.handleActivityConfigurationChanged(
- ActivityClientRecord.this, overrideConfig, newDisplayId);
+ ActivityClientRecord.this, overrideConfig, newDisplayId,
+ false /* alwaysReportChange */);
}
@Override
@@ -5859,13 +5860,13 @@
* @return {@link Configuration} instance sent to client, null if not sent.
*/
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
- Configuration newBaseConfig, int displayId) {
+ Configuration newBaseConfig, int displayId, boolean alwaysReportChange) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
- r.tmpConfig, r.overrideConfig, displayId);
+ r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
return reportedConfig;
}
@@ -5881,7 +5882,8 @@
* @return Configuration sent to client, null if no changes and not moved to different display.
*/
private Configuration performActivityConfigurationChanged(Activity activity,
- Configuration newConfig, Configuration amOverrideConfig, int displayId) {
+ Configuration newConfig, Configuration amOverrideConfig, int displayId,
+ boolean alwaysReportChange) {
final IBinder activityToken = activity.getActivityToken();
// WindowConfiguration differences aren't considered as public, check it separately.
@@ -5900,9 +5902,9 @@
final boolean shouldUpdateResources = hasPublicResConfigChange
|| shouldUpdateResources(activityToken, currentResConfig, newConfig,
amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
- final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
- r != null ? r.mSizeConfigurations : null,
- activity.mActivityInfo.getRealConfigChanged());
+ final boolean shouldReportChange = shouldReportChange(
+ activity.mCurrentConfig, newConfig, r != null ? r.mSizeConfigurations : null,
+ activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
// Nothing significant, don't proceed with updating and reporting.
if (!shouldUpdateResources && !shouldReportChange) {
return null;
@@ -5962,12 +5964,18 @@
@VisibleForTesting
public static boolean shouldReportChange(@Nullable Configuration currentConfig,
@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
- int handledConfigChanges) {
+ int handledConfigChanges, boolean alwaysReportChange) {
final int publicDiff = currentConfig.diffPublicOnly(newConfig);
// Don't report the change if there's no public diff between current and new config.
if (publicDiff == 0) {
return false;
}
+
+ // Report the change regardless if the changes across size-config-buckets.
+ if (alwaysReportChange) {
+ return true;
+ }
+
final int diffWithBucket = SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig,
newConfig, sizeBuckets);
// Compare to the diff which filter the change without crossing size buckets with
@@ -6094,6 +6102,18 @@
}
}
+ @Override
+ public void handleActivityConfigurationChanged(ActivityClientRecord r,
+ @NonNull Configuration overrideConfig, int displayId) {
+ handleActivityConfigurationChanged(r, overrideConfig, displayId,
+ // This is the only place that uses alwaysReportChange=true. The entry point should
+ // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
+ // has confirmed the activity should handle the configuration instead of relaunch.
+ // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
+ // something wrong from server side.
+ true /* alwaysReportChange */);
+ }
+
/**
* Handle new activity configuration and/or move to a different display. This method is a noop
* if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been
@@ -6104,9 +6124,8 @@
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
*/
- @Override
- public void handleActivityConfigurationChanged(ActivityClientRecord r,
- @NonNull Configuration overrideConfig, int displayId) {
+ void handleActivityConfigurationChanged(ActivityClientRecord r,
+ @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
synchronized (mPendingOverrideConfigs) {
final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);
if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {
@@ -6150,7 +6169,8 @@
}
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
mConfigurationController.getCompatConfiguration(),
- movedToDifferentDisplay ? displayId : r.activity.getDisplayId());
+ movedToDifferentDisplay ? displayId : r.activity.getDisplayId(),
+ alwaysReportChange);
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
// update to make sure that resources are updated before updating itself.
if (viewRoot != null) {
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index a8d8c75..5517c57 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -1196,7 +1196,8 @@
return sb.toString();
}
- private static String reasonCodeToString(@Reason int reason) {
+ /** @hide */
+ public static String reasonCodeToString(@Reason int reason) {
switch (reason) {
case REASON_EXIT_SELF:
return "EXIT_SELF";
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index 794c694..be53a62 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -127,6 +127,7 @@
* <p>This API can be used by an app's installer
* (per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName}) to retrieve
* the app's locales.
+ * <p>This API can be used by the current input method to retrieve locales of another packages.
* All other cases require {@code android.Manifest.permission#READ_APP_SPECIFIC_LOCALES}.
* Apps should generally retrieve their own locales via their in-process LocaleLists,
* or by calling {@link #getApplicationLocales()}.
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 18e6ba4..38bd4e5 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -41,7 +41,7 @@
"android.credentials.ui.extra.PROVIDER_DATA_LIST";
@NonNull
- private final String mPackageName;
+ private final String mProviderId;
@NonNull
private final List<Entry> mCredentialEntries;
@NonNull
@@ -50,11 +50,11 @@
private final Entry mAuthenticationEntry;
public ProviderData(
- @NonNull String packageName,
+ @NonNull String providerId,
@NonNull List<Entry> credentialEntries,
@NonNull List<Entry> actionChips,
@Nullable Entry authenticationEntry) {
- mPackageName = packageName;
+ mProviderId = providerId;
mCredentialEntries = credentialEntries;
mActionChips = actionChips;
mAuthenticationEntry = authenticationEntry;
@@ -62,8 +62,8 @@
/** Returns the provider package name. */
@NonNull
- public String getPackageName() {
- return mPackageName;
+ public String getProviderId() {
+ return mProviderId;
}
@NonNull
@@ -82,9 +82,9 @@
}
protected ProviderData(@NonNull Parcel in) {
- String packageName = in.readString8();
- mPackageName = packageName;
- AnnotationValidations.validate(NonNull.class, null, mPackageName);
+ String providerId = in.readString8();
+ mProviderId = providerId;
+ AnnotationValidations.validate(NonNull.class, null, mProviderId);
List<Entry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, Entry.CREATOR);
@@ -102,7 +102,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString8(mPackageName);
+ dest.writeString8(mProviderId);
dest.writeTypedList(mCredentialEntries);
dest.writeTypedList(mActionChips);
dest.writeTypedObject(mAuthenticationEntry, flags);
diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java
index 0927fb8..2ac5593 100644
--- a/core/java/android/credentials/ui/UserSelectionResult.java
+++ b/core/java/android/credentials/ui/UserSelectionResult.java
@@ -43,11 +43,16 @@
@NonNull
private final IBinder mRequestToken;
+ @NonNull
+ private final String mProviderId;
+
// TODO: consider switching to string or other types, depending on the service implementation.
private final int mEntryId;
- public UserSelectionResult(@NonNull IBinder requestToken, int entryId) {
+ public UserSelectionResult(@NonNull IBinder requestToken, @NonNull String providerId,
+ int entryId) {
mRequestToken = requestToken;
+ mProviderId = providerId;
mEntryId = entryId;
}
@@ -57,23 +62,33 @@
return mRequestToken;
}
+ /** Returns provider package name whose entry was selected by the user. */
+ @NonNull
+ public String getProviderId() {
+ return mProviderId;
+ }
+
/** Returns the id of the visual entry that the user selected. */
- public int geEntryId() {
+ public int getEntryId() {
return mEntryId;
}
protected UserSelectionResult(@NonNull Parcel in) {
IBinder requestToken = in.readStrongBinder();
+ String providerId = in.readString8();
int entryId = in.readInt();
mRequestToken = requestToken;
AnnotationValidations.validate(NonNull.class, null, mRequestToken);
+ mProviderId = providerId;
+ AnnotationValidations.validate(NonNull.class, null, mProviderId);
mEntryId = entryId;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mRequestToken);
+ dest.writeString8(mProviderId);
dest.writeInt(mEntryId);
}
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
index 18375ad..1c4d14c91 100644
--- a/core/java/android/service/voice/HotwordAudioStream.java
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -57,10 +57,10 @@
* the audio until the stream is shutdown by the {@link HotwordDetectionService}.
*/
@NonNull
- private final ParcelFileDescriptor mAudioStream;
+ private final ParcelFileDescriptor mAudioStreamParcelFileDescriptor;
/**
- * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform.
+ * The timestamp when the audio stream was captured by the Audio platform.
*
* <p>
* The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
@@ -74,6 +74,8 @@
* {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
* timestamps.
* </p>
+ *
+ * @see #getAudioStreamParcelFileDescriptor()
*/
@Nullable
private final AudioTimestamp mTimestamp;
@@ -143,15 +145,15 @@
@DataClass.Generated.Member
/* package-private */ HotwordAudioStream(
@NonNull AudioFormat audioFormat,
- @NonNull ParcelFileDescriptor audioStream,
+ @NonNull ParcelFileDescriptor audioStreamParcelFileDescriptor,
@Nullable AudioTimestamp timestamp,
@NonNull PersistableBundle metadata) {
this.mAudioFormat = audioFormat;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mAudioFormat);
- this.mAudioStream = audioStream;
+ this.mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mAudioStream);
+ NonNull.class, null, mAudioStreamParcelFileDescriptor);
this.mTimestamp = timestamp;
this.mMetadata = metadata;
com.android.internal.util.AnnotationValidations.validate(
@@ -173,12 +175,12 @@
* the audio until the stream is shutdown by the {@link HotwordDetectionService}.
*/
@DataClass.Generated.Member
- public @NonNull ParcelFileDescriptor getAudioStream() {
- return mAudioStream;
+ public @NonNull ParcelFileDescriptor getAudioStreamParcelFileDescriptor() {
+ return mAudioStreamParcelFileDescriptor;
}
/**
- * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform.
+ * The timestamp when the audio stream was captured by the Audio platform.
*
* <p>
* The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
@@ -192,6 +194,8 @@
* {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
* timestamps.
* </p>
+ *
+ * @see #getAudioStreamParcelFileDescriptor()
*/
@DataClass.Generated.Member
public @Nullable AudioTimestamp getTimestamp() {
@@ -214,7 +218,7 @@
return "HotwordAudioStream { " +
"audioFormat = " + mAudioFormat + ", " +
- "audioStream = " + mAudioStream + ", " +
+ "audioStreamParcelFileDescriptor = " + mAudioStreamParcelFileDescriptor + ", " +
"timestamp = " + timestampToString() + ", " +
"metadata = " + mMetadata +
" }";
@@ -234,7 +238,7 @@
//noinspection PointlessBooleanExpression
return true
&& Objects.equals(mAudioFormat, that.mAudioFormat)
- && Objects.equals(mAudioStream, that.mAudioStream)
+ && Objects.equals(mAudioStreamParcelFileDescriptor, that.mAudioStreamParcelFileDescriptor)
&& Objects.equals(mTimestamp, that.mTimestamp)
&& Objects.equals(mMetadata, that.mMetadata);
}
@@ -247,7 +251,7 @@
int _hash = 1;
_hash = 31 * _hash + Objects.hashCode(mAudioFormat);
- _hash = 31 * _hash + Objects.hashCode(mAudioStream);
+ _hash = 31 * _hash + Objects.hashCode(mAudioStreamParcelFileDescriptor);
_hash = 31 * _hash + Objects.hashCode(mTimestamp);
_hash = 31 * _hash + Objects.hashCode(mMetadata);
return _hash;
@@ -263,7 +267,7 @@
if (mTimestamp != null) flg |= 0x4;
dest.writeByte(flg);
dest.writeTypedObject(mAudioFormat, flags);
- dest.writeTypedObject(mAudioStream, flags);
+ dest.writeTypedObject(mAudioStreamParcelFileDescriptor, flags);
parcelTimestamp(dest, flags);
dest.writeTypedObject(mMetadata, flags);
}
@@ -281,16 +285,16 @@
byte flg = in.readByte();
AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR);
- ParcelFileDescriptor audioStream = (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR);
+ ParcelFileDescriptor audioStreamParcelFileDescriptor = (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR);
AudioTimestamp timestamp = unparcelTimestamp(in);
PersistableBundle metadata = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
this.mAudioFormat = audioFormat;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mAudioFormat);
- this.mAudioStream = audioStream;
+ this.mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mAudioStream);
+ NonNull.class, null, mAudioStreamParcelFileDescriptor);
this.mTimestamp = timestamp;
this.mMetadata = metadata;
com.android.internal.util.AnnotationValidations.validate(
@@ -321,7 +325,7 @@
public static final class Builder {
private @NonNull AudioFormat mAudioFormat;
- private @NonNull ParcelFileDescriptor mAudioStream;
+ private @NonNull ParcelFileDescriptor mAudioStreamParcelFileDescriptor;
private @Nullable AudioTimestamp mTimestamp;
private @NonNull PersistableBundle mMetadata;
@@ -332,19 +336,19 @@
*
* @param audioFormat
* The {@link AudioFormat} of the audio stream.
- * @param audioStream
+ * @param audioStreamParcelFileDescriptor
* This stream starts with the audio bytes used for hotword detection, but continues streaming
* the audio until the stream is shutdown by the {@link HotwordDetectionService}.
*/
public Builder(
@NonNull AudioFormat audioFormat,
- @NonNull ParcelFileDescriptor audioStream) {
+ @NonNull ParcelFileDescriptor audioStreamParcelFileDescriptor) {
mAudioFormat = audioFormat;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mAudioFormat);
- mAudioStream = audioStream;
+ mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mAudioStream);
+ NonNull.class, null, mAudioStreamParcelFileDescriptor);
}
/**
@@ -363,15 +367,15 @@
* the audio until the stream is shutdown by the {@link HotwordDetectionService}.
*/
@DataClass.Generated.Member
- public @NonNull Builder setAudioStream(@NonNull ParcelFileDescriptor value) {
+ public @NonNull Builder setAudioStreamParcelFileDescriptor(@NonNull ParcelFileDescriptor value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
- mAudioStream = value;
+ mAudioStreamParcelFileDescriptor = value;
return this;
}
/**
- * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform.
+ * The timestamp when the audio stream was captured by the Audio platform.
*
* <p>
* The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
@@ -385,6 +389,8 @@
* {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
* timestamps.
* </p>
+ *
+ * @see #getAudioStreamParcelFileDescriptor()
*/
@DataClass.Generated.Member
public @NonNull Builder setTimestamp(@NonNull AudioTimestamp value) {
@@ -418,7 +424,7 @@
}
HotwordAudioStream o = new HotwordAudioStream(
mAudioFormat,
- mAudioStream,
+ mAudioStreamParcelFileDescriptor,
mTimestamp,
mMetadata);
return o;
@@ -433,10 +439,10 @@
}
@DataClass.Generated(
- time = 1665463434564L,
+ time = 1665976240224L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java",
- inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStream\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static android.media.AudioTimestamp defaultTimestamp()\nprivate static android.os.PersistableBundle defaultMetadata()\nprivate java.lang.String timestampToString()\nprivate void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+ inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static android.media.AudioTimestamp defaultTimestamp()\nprivate static android.os.PersistableBundle defaultMetadata()\nprivate java.lang.String timestampToString()\nprivate void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index 6255d00..e22bbd8 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -32,6 +32,7 @@
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -203,7 +204,7 @@
@NonNull
private final List<HotwordAudioStream> mAudioStreams;
private static List<HotwordAudioStream> defaultAudioStreams() {
- return new ArrayList<>();
+ return Collections.emptyList();
}
/**
@@ -364,9 +365,27 @@
}
}
+ /**
+ * The list of the audio streams containing audio bytes that were used for hotword detection.
+ */
+ public @NonNull List<HotwordAudioStream> getAudioStreams() {
+ return List.copyOf(mAudioStreams);
+ }
+
@DataClass.Suppress("addAudioStreams")
abstract static class BaseBuilder {
-
+ /**
+ * The list of the audio streams containing audio bytes that were used for hotword
+ * detection.
+ */
+ public @NonNull Builder setAudioStreams(@NonNull List<HotwordAudioStream> value) {
+ Objects.requireNonNull(value, "value should not be null");
+ final Builder builder = (Builder) this;
+ // If the code gen flag in build() is changed, we must update the flag e.g. 0x200 here.
+ builder.mBuilderFieldsSet |= 0x200;
+ builder.mAudioStreams = List.copyOf(value);
+ return builder;
+ }
}
@@ -555,14 +574,6 @@
}
/**
- * The list of the audio streams containing audio bytes that were used for hotword detection.
- */
- @DataClass.Generated.Member
- public @NonNull List<HotwordAudioStream> getAudioStreams() {
- return mAudioStreams;
- }
-
- /**
* App-specific extras to support trigger.
*
* <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -881,17 +892,6 @@
}
/**
- * The list of the audio streams containing audio bytes that were used for hotword detection.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setAudioStreams(@NonNull List<HotwordAudioStream> value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x200;
- mAudioStreams = value;
- return this;
- }
-
- /**
* App-specific extras to support trigger.
*
* <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -984,10 +984,10 @@
}
@DataClass.Generated(
- time = 1664876310951L,
+ time = 1665995595979L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
- inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index e5a535b..f51d9ba 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -218,6 +218,11 @@
private static final int WINDOW_TOUCH_SLOP = 16;
/**
+ * Margin in dips around text line bounds where stylus handwriting gestures should be supported.
+ */
+ private static final int HANDWRITING_GESTURE_LINE_MARGIN = 16;
+
+ /**
* Minimum velocity to initiate a fling, as measured in dips per second
*/
private static final int MINIMUM_FLING_VELOCITY = 50;
@@ -338,6 +343,7 @@
private final int mPagingTouchSlop;
private final int mDoubleTapSlop;
private final int mWindowTouchSlop;
+ private final int mHandwritingGestureLineMargin;
private final float mAmbiguousGestureMultiplier;
private final int mMaximumDrawingCacheSize;
private final int mOverscrollDistance;
@@ -381,6 +387,7 @@
mPagingTouchSlop = PAGING_TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
+ mHandwritingGestureLineMargin = HANDWRITING_GESTURE_LINE_MARGIN;
mAmbiguousGestureMultiplier = AMBIGUOUS_GESTURE_MULTIPLIER;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -490,6 +497,9 @@
mDoubleTapTouchSlop = mTouchSlop;
+ mHandwritingGestureLineMargin = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
+
mMinimumFlingVelocity = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_viewMinFlingVelocity);
mMaximumFlingVelocity = res.getDimensionPixelSize(
@@ -796,6 +806,14 @@
}
/**
+ * @return margin in pixels around text line bounds where stylus handwriting gestures should be
+ * supported.
+ */
+ public int getScaledHandwritingGestureLineMargin() {
+ return mHandwritingGestureLineMargin;
+ }
+
+ /**
* Interval for dispatching a recurring accessibility event in milliseconds.
* This interval guarantees that a recurring event will be send at most once
* during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ce5365a..a2e9faa 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9391,12 +9391,8 @@
/** @hide */
public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
- int line = mLayout.getLineForVertical((int) point.y);
- if (point.y < mLayout.getLineTop(line)
- || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
- return handleGestureFailure(gesture);
- }
- if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
+ int line = getLineForHandwritingGesture(point);
+ if (line == -1) {
return handleGestureFailure(gesture);
}
int offset = mLayout.getOffsetForHorizontal(line, point.x);
@@ -9412,27 +9408,17 @@
PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
- // The operation should be applied to the first line of text touched by the line joining
- // the points.
- int yMin = (int) Math.min(startPoint.y, endPoint.y);
- int yMax = (int) Math.max(startPoint.y, endPoint.y);
- int line = mLayout.getLineForVertical(yMin);
- if (yMax < mLayout.getLineTop(line)) {
- // Both points are above the top of the first line.
- return handleGestureFailure(gesture);
- }
- if (yMin > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
- if (line == mLayout.getLineCount() - 1 || yMax < mLayout.getLineTop(line + 1)) {
- // The points are below the last line, or they are between two lines.
+ // The operation should be applied to the first line of text containing one of the points.
+ int startPointLine = getLineForHandwritingGesture(startPoint);
+ int endPointLine = getLineForHandwritingGesture(endPoint);
+ int line;
+ if (startPointLine == -1) {
+ if (endPointLine == -1) {
return handleGestureFailure(gesture);
- } else {
- // Apply the operation to the next line.
- line++;
}
- }
- if (Math.max(startPoint.x, endPoint.x) < mLayout.getLineLeft(line)
- || Math.min(startPoint.x, endPoint.x) > mLayout.getLineRight(line)) {
- return handleGestureFailure(gesture);
+ line = endPointLine;
+ } else {
+ line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine);
}
// The operation should be applied to all characters touched by the line joining the points.
@@ -9479,12 +9465,8 @@
public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
- int line = mLayout.getLineForVertical((int) point.y);
- if (point.y < mLayout.getLineTop(line)
- || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
- return handleGestureFailure(gesture);
- }
- if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
+ int line = getLineForHandwritingGesture(point);
+ if (line == -1) {
return handleGestureFailure(gesture);
}
@@ -9529,6 +9511,37 @@
return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
}
+ /**
+ * Returns the closest line such that the point is either inside the line bounds or within
+ * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns
+ * -1 if the point is not within the margin of any line bounds.
+ */
+ private int getLineForHandwritingGesture(PointF point) {
+ int line = mLayout.getLineForVertical((int) point.y);
+ int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin();
+ if (line < mLayout.getLineCount() - 1
+ && point.y > mLayout.getLineBottom(line) - lineMargin
+ && point.y
+ > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) {
+ // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical
+ // returns i. If the point is within lineMargin of line (i + 1), and closer to line
+ // (i + 1) than line i, then the gesture operation should be applied to line (i + 1).
+ line++;
+ } else if (point.y < mLayout.getLineTop(line) - lineMargin
+ || point.y
+ > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)
+ + lineMargin) {
+ // The point is not within lineMargin of a line.
+ return -1;
+ }
+ if (point.x < mLayout.getLineLeft(line) - lineMargin
+ || point.x > mLayout.getLineRight(line) + lineMargin) {
+ // The point is not within lineMargin of a line.
+ return -1;
+ }
+ return line;
+ }
+
@Nullable
private Range<Integer> getRangeForRect(@NonNull RectF area, int granularity) {
SegmentFinder segmentFinder;
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
new file mode 100644
index 0000000..65e8c64
--- /dev/null
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 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.internal.app;
+
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.LocaleList;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/** The Locale data collector for per-app language. */
+class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+ private static final String TAG = AppLocaleCollector.class.getSimpleName();
+ private final Context mContext;
+ private final String mAppPackageName;
+ private final LocaleStore.LocaleInfo mAppCurrentLocale;
+
+ AppLocaleCollector(Context context, String appPackageName) {
+ mContext = context;
+ mAppPackageName = appPackageName;
+ mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo(
+ mContext, mAppPackageName);
+ }
+
+ @Override
+ public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+ HashSet<String> langTagsToIgnore = new HashSet<>();
+
+ LocaleList systemLangList = LocaleList.getDefault();
+ for(int i = 0; i < systemLangList.size(); i++) {
+ langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
+ }
+
+ if (mAppCurrentLocale != null) {
+ langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
+ }
+ return langTagsToIgnore;
+ }
+
+ @Override
+ public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, boolean isForCountryMode) {
+ AppLocaleStore.AppLocaleResult result =
+ AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+ Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+ Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>();
+ Set<LocaleStore.LocaleInfo> systemLocaleList;
+ boolean shouldShowList =
+ result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
+ || result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+
+ // Get system supported locale list
+ if (isForCountryMode) {
+ systemLocaleList = LocaleStore.getLevelLocales(mContext,
+ langTagsToIgnore, parent, translatedOnly);
+ } else {
+ systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+ null /* no parent */, translatedOnly);
+ }
+
+ // Add current app locale
+ if (mAppCurrentLocale != null && !isForCountryMode) {
+ appLocaleList.add(mAppCurrentLocale);
+ }
+
+ // Add current system language into suggestion list
+ for(LocaleStore.LocaleInfo localeInfo:
+ LocaleStore.getSystemCurrentLocaleInfo()) {
+ boolean isNotCurrentLocale = mAppCurrentLocale == null
+ || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
+ if (!isForCountryMode && isNotCurrentLocale) {
+ appLocaleList.add(localeInfo);
+ }
+ }
+
+ // Add the languages that included in system supported locale
+ if (shouldShowList) {
+ appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale(
+ systemLocaleList, result.mAppSupportedLocales));
+ }
+
+ // Add "system language" option
+ if (!isForCountryMode && shouldShowList) {
+ appLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(
+ mAppCurrentLocale == null));
+ }
+
+ if (Build.isDebuggable()) {
+ Log.d(TAG, "App locale list: " + appLocaleList);
+ }
+
+ return appLocaleList;
+ }
+
+ @Override
+ public boolean hasSpecificPackageName() {
+ return true;
+ }
+
+ private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale(
+ Set<LocaleStore.LocaleInfo> systemLocaleList,
+ HashSet<Locale> appSupportedLocales) {
+ Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
+
+ for(LocaleStore.LocaleInfo li: systemLocaleList) {
+ if (appSupportedLocales.contains(li.getLocale())) {
+ filteredList.add(li);
+ } else {
+ for(Locale l: appSupportedLocales) {
+ if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
+ filteredList.add(li);
+ break;
+ }
+ }
+ }
+ }
+ return filteredList;
+ }
+}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 965895f..3efd279 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -16,16 +16,12 @@
package com.android.internal.app;
-import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus;
-
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.content.Context;
import android.os.Bundle;
-import android.os.LocaleList;
import android.text.TextUtils;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -36,7 +32,6 @@
import com.android.internal.R;
-import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
@@ -54,6 +49,7 @@
private SuggestedLocaleAdapter mAdapter;
private LocaleSelectedListener mListener;
+ private LocaleCollectorBase mLocalePickerCollector;
private Set<LocaleStore.LocaleInfo> mLocaleList;
private LocaleStore.LocaleInfo mParentLocale;
private boolean mTranslatedOnly = false;
@@ -62,7 +58,6 @@
private boolean mPreviousSearchHadFocus = false;
private int mFirstVisiblePosition = 0;
private int mTopDistance = 0;
- private String mAppPackageName;
private CharSequence mTitle = null;
private OnActionExpandListener mOnActionExpandListener;
@@ -79,31 +74,50 @@
void onLocaleSelected(LocaleStore.LocaleInfo locale);
}
- private static LocalePickerWithRegion createCountryPicker(Context context,
+ /**
+ * The interface which provides the locale list.
+ */
+ interface LocaleCollectorBase {
+ /** Gets the ignored locale list. */
+ HashSet<String> getIgnoredLocaleList(boolean translatedOnly);
+
+ /** Gets the supported locale list. */
+ Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, boolean isForCountryMode);
+
+ /** Indicates if the class work for specific package. */
+ boolean hasSpecificPackageName();
+ }
+
+ private static LocalePickerWithRegion createCountryPicker(
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
- boolean translatedOnly, String appPackageName,
- OnActionExpandListener onActionExpandListener) {
+ boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+ LocaleCollectorBase localePickerCollector) {
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
localePicker.setOnActionExpandListener(onActionExpandListener);
- boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
- translatedOnly, appPackageName);
+ boolean shouldShowTheList = localePicker.setListener(listener, parent,
+ translatedOnly, localePickerCollector);
return shouldShowTheList ? localePicker : null;
}
public static LocalePickerWithRegion createLanguagePicker(Context context,
LocaleSelectedListener listener, boolean translatedOnly) {
- LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
- localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null);
- return localePicker;
+ return createLanguagePicker(context, listener, translatedOnly, null, null);
}
public static LocalePickerWithRegion createLanguagePicker(Context context,
LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
OnActionExpandListener onActionExpandListener) {
+ LocaleCollectorBase localePickerController;
+ if (TextUtils.isEmpty(appPackageName)) {
+ localePickerController = new SystemLocaleCollector(context);
+ } else {
+ localePickerController = new AppLocaleCollector(context, appPackageName);
+ }
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
localePicker.setOnActionExpandListener(onActionExpandListener);
- localePicker.setListener(
- context, listener, /* parent */ null, translatedOnly, appPackageName);
+ localePicker.setListener(listener, /* parent */ null, translatedOnly,
+ localePickerController);
return localePicker;
}
@@ -120,109 +134,23 @@
* In this case we don't even show the list, we call the listener with that locale,
* "pretending" it was selected, and return false.</p>
*/
- private boolean setListener(Context context, LocaleSelectedListener listener,
- LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) {
+ private boolean setListener(LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, LocaleCollectorBase localePickerController) {
this.mParentLocale = parent;
this.mListener = listener;
this.mTranslatedOnly = translatedOnly;
- this.mAppPackageName = appPackageName;
+ this.mLocalePickerCollector = localePickerController;
setRetainInstance(true);
- final HashSet<String> langTagsToIgnore = new HashSet<>();
- LocaleStore.LocaleInfo appCurrentLocale =
- LocaleStore.getAppCurrentLocaleInfo(context, appPackageName);
- boolean isForCountryMode = parent != null;
+ mLocaleList = localePickerController.getSupportedLocaleList(
+ parent, translatedOnly, parent != null);
- if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) {
- // Filter current system locale to add them into suggestion
- LocaleList systemLangList = LocaleList.getDefault();
- for(int i = 0; i < systemLangList.size(); i++) {
- langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
- }
-
- if (appCurrentLocale != null) {
- Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag());
- langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag());
- } else {
- Log.d(TAG, "appCurrentLocale is null");
- }
- } else if (!translatedOnly) {
- final LocaleList userLocales = LocalePicker.getLocales();
- final String[] langTags = userLocales.toLanguageTags().split(",");
- Collections.addAll(langTagsToIgnore, langTags);
- }
-
- if (isForCountryMode) {
- mLocaleList = LocaleStore.getLevelLocales(context,
- langTagsToIgnore, parent, translatedOnly);
- if (mLocaleList.size() <= 1) {
- if (listener != null && (mLocaleList.size() == 1)) {
- listener.onLocaleSelected(mLocaleList.iterator().next());
- }
- return false;
- }
+ if (parent != null && listener != null && mLocaleList.size() == 1) {
+ listener.onLocaleSelected(mLocaleList.iterator().next());
+ return false;
} else {
- mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
- null /* no parent */, translatedOnly);
+ return true;
}
- Log.d(TAG, "mLocaleList size: " + mLocaleList.size());
-
- // Adding current locale and system default option into suggestion list
- if(!TextUtils.isEmpty(appPackageName)) {
- if (appCurrentLocale != null && !isForCountryMode) {
- mLocaleList.add(appCurrentLocale);
- }
-
- AppLocaleStore.AppLocaleResult result =
- AppLocaleStore.getAppSupportedLocales(context, appPackageName);
- boolean shouldShowList =
- result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
- || result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
-
- // Add current system language into suggestion list
- for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) {
- boolean isNotCurrentLocale = appCurrentLocale == null
- || !localeInfo.getLocale().equals(appCurrentLocale.getLocale());
- if (!isForCountryMode && isNotCurrentLocale) {
- mLocaleList.add(localeInfo);
- }
- }
-
- // Filter the language not support in app
- mLocaleList = filterTheLanguagesNotSupportedInApp(
- shouldShowList, result.mAppSupportedLocales);
-
- Log.d(TAG, "mLocaleList after app-supported filter: " + mLocaleList.size());
-
- // Add "system language"
- if (!isForCountryMode && shouldShowList) {
- mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(appCurrentLocale == null));
- }
- }
- return true;
- }
-
- private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotSupportedInApp(
- boolean shouldShowList, HashSet<Locale> supportedLocales) {
- Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
- if (!shouldShowList) {
- return filteredList;
- }
-
- for(LocaleStore.LocaleInfo li: mLocaleList) {
- if (supportedLocales.contains(li.getLocale())) {
- filteredList.add(li);
- } else {
- for(Locale l: supportedLocales) {
- if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
- filteredList.add(li);
- break;
- }
- }
- }
- }
-
- return filteredList;
}
private void returnToParentFrame() {
@@ -246,7 +174,9 @@
mTitle = getActivity().getTitle();
final boolean countryMode = mParentLocale != null;
final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault();
- mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName);
+ final boolean hasSpecificPackageName =
+ mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
+ mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
final LocaleHelper.LocaleInfoComparator comp =
new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
mAdapter.sort(comp);
@@ -321,8 +251,8 @@
returnToParentFrame();
} else {
LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
- getContext(), mListener, locale, mTranslatedOnly /* translate only */,
- mAppPackageName, mOnActionExpandListener);
+ mListener, locale, mTranslatedOnly /* translate only */,
+ mOnActionExpandListener, this.mLocalePickerCollector);
if (selector != null) {
getFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
@@ -340,7 +270,8 @@
inflater.inflate(R.menu.language_selection_list, menu);
final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
- if (!TextUtils.isEmpty(mAppPackageName) && mOnActionExpandListener != null) {
+ if (mLocalePickerCollector.hasSpecificPackageName()
+ && mOnActionExpandListener != null) {
searchMenuItem.setOnActionExpandListener(mOnActionExpandListener);
}
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 8f6bc43..a61a6d7 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -69,17 +69,17 @@
protected Locale mDisplayLocale = null;
// used to potentially cache a modified Context that uses mDisplayLocale
protected Context mContextOverride = null;
- private String mAppPackageName;
+ private boolean mHasSpecificAppPackageName;
public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
- this(localeOptions, countryMode, null);
+ this(localeOptions, countryMode, false);
}
public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode,
- String appPackageName) {
+ boolean hasSpecificAppPackageName) {
mCountryMode = countryMode;
mLocaleOptions = new ArrayList<>(localeOptions.size());
- mAppPackageName = appPackageName;
+ mHasSpecificAppPackageName = hasSpecificAppPackageName;
for (LocaleStore.LocaleInfo li : localeOptions) {
if (li.isSuggested()) {
@@ -136,7 +136,7 @@
@Override
public int getViewTypeCount() {
- if (!TextUtils.isEmpty(mAppPackageName) && showHeaders()) {
+ if (mHasSpecificAppPackageName && showHeaders()) {
// Two headers, 1 "System language", 1 current locale
return APP_LANGUAGE_PICKER_TYPE_COUNT;
} else if (showHeaders()) {
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
new file mode 100644
index 0000000..9a6d4c1
--- /dev/null
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.internal.app;
+
+import android.content.Context;
+import android.os.LocaleList;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** The Locale data collector for System language. */
+class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+ private final Context mContext;
+
+ SystemLocaleCollector(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+ HashSet<String> ignoreList = new HashSet<>();
+ if (!translatedOnly) {
+ final LocaleList userLocales = LocalePicker.getLocales();
+ final String[] langTags = userLocales.toLanguageTags().split(",");
+ Collections.addAll(ignoreList, langTags);
+ }
+ return ignoreList;
+ }
+
+ @Override
+ public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, boolean isForCountryMode) {
+ Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+ Set<LocaleStore.LocaleInfo> localeList;
+
+ if (isForCountryMode) {
+ localeList = LocaleStore.getLevelLocales(mContext,
+ langTagsToIgnore, parent, translatedOnly);
+ } else {
+ localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+ null /* no parent */, translatedOnly);
+ }
+ return localeList;
+ }
+
+
+ @Override
+ public boolean hasSpecificPackageName() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index f725b37..d5f7ba5 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -20,10 +20,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
+import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -39,6 +43,7 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
/**
* Base class representing a remote service.
@@ -66,6 +71,7 @@
@Deprecated
public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I>,
I extends IInterface> implements DeathRecipient {
+ private static final int SERVICE_NOT_EXIST = -1;
private static final int MSG_BIND = 1;
private static final int MSG_UNBIND = 2;
@@ -96,6 +102,9 @@
// Used just for debugging purposes (on dump)
private long mNextUnbind;
+ // Used just for debugging purposes (on dump)
+ private int mServiceExitReason;
+ private int mServiceExitSubReason;
/** Requests that have been scheduled, but that are not finished yet */
private final ArrayList<BasePendingRequest<S, I>> mUnfinishedRequests = new ArrayList<>();
@@ -126,6 +135,8 @@
mUserId = userId;
mHandler = new Handler(handler.getLooper());
mBindingFlags = bindingFlags;
+ mServiceExitReason = SERVICE_NOT_EXIST;
+ mServiceExitSubReason = SERVICE_NOT_EXIST;
}
/**
@@ -229,6 +240,7 @@
if (mService != null) {
mService.asBinder().unlinkToDeath(this, 0);
}
+ updateServicelicationExitInfo(mComponentName, mUserId);
mConnecting = true;
mService = null;
mServiceDied = true;
@@ -239,6 +251,33 @@
handleBindFailure();
}
+ private void updateServicelicationExitInfo(ComponentName componentName, int userId) {
+ IActivityManager am = ActivityManager.getService();
+ String packageName = componentName.getPackageName();
+ ParceledListSlice<ApplicationExitInfo> plistSlice = null;
+ try {
+ plistSlice = am.getHistoricalProcessExitReasons(packageName, 0, 1, userId);
+ } catch (RemoteException e) {
+ // do nothing. The local binder so it can not throw it.
+ }
+ if (plistSlice == null) {
+ return;
+ }
+ List<ApplicationExitInfo> list = plistSlice.getList();
+ if (list.isEmpty()) {
+ return;
+ }
+ ApplicationExitInfo info = list.get(0);
+ mServiceExitReason = info.getReason();
+ mServiceExitSubReason = info.getSubReason();
+ if (mVerbose) {
+ Slog.v(mTag, "updateServicelicationExitInfo: exitReason="
+ + ApplicationExitInfo.reasonCodeToString(mServiceExitReason)
+ + " exitSubReason= " + ApplicationExitInfo.subreasonToString(
+ mServiceExitSubReason));
+ }
+ }
+
// Note: we are dumping without a lock held so this is a bit racy but
// adding a lock to a class that offloads to a handler thread would
// mean adding a lock adding overhead to normal runtime operation.
@@ -272,6 +311,16 @@
}
}
pw.println();
+ if (mServiceExitReason != SERVICE_NOT_EXIST) {
+ pw.append(prefix).append(tab).append("serviceExistReason=")
+ .append(ApplicationExitInfo.reasonCodeToString(mServiceExitReason));
+ pw.println();
+ }
+ if (mServiceExitSubReason != SERVICE_NOT_EXIST) {
+ pw.append(prefix).append(tab).append("serviceExistSubReason=")
+ .append(ApplicationExitInfo.subreasonToString(mServiceExitSubReason));
+ pw.println();
+ }
pw.append(prefix).append("mBindingFlags=").println(mBindingFlags);
pw.append(prefix).append("idleTimeout=")
.append(Long.toString(idleTimeout / 1000)).append("s\n");
@@ -498,6 +547,8 @@
return;
}
mService = getServiceInterface(service);
+ mServiceExitReason = SERVICE_NOT_EXIST;
+ mServiceExitSubReason = SERVICE_NOT_EXIST;
handleOnConnectedStateChangedInternal(true);
mServiceDied = false;
}
diff --git a/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java
index a03bac4..90ad34d 100644
--- a/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java
+++ b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java
@@ -87,6 +87,10 @@
if (isTestOnly) {
return true;
}
+ // If system server is being profiled, it's OK to create class loaders anytime.
+ if (ZygoteInit.shouldProfileSystemServer()) {
+ return true;
+ }
return false;
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 73fb7fe..076e4e1 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -238,6 +238,21 @@
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
}
+ private static boolean isExperimentEnabled(String experiment) {
+ boolean defaultValue = SystemProperties.getBoolean(
+ "dalvik.vm." + experiment,
+ /*def=*/false);
+ // Can't use device_config since we are the zygote, and it's not initialized at this point.
+ return SystemProperties.getBoolean(
+ "persist.device_config." + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT
+ + "." + experiment,
+ defaultValue);
+ }
+
+ /* package-private */ static boolean shouldProfileSystemServer() {
+ return isExperimentEnabled("profilesystemserver");
+ }
+
/**
* Performs Zygote process initialization. Loads and initializes commonly used classes.
*
@@ -341,14 +356,7 @@
// If we are profiling the boot image, reset the Jit counters after preloading the
// classes. We want to preload for performance, and we can use method counters to
// infer what clases are used after calling resetJitCounters, for profile purposes.
- // Can't use device_config since we are the zygote.
- String prop = SystemProperties.get(
- "persist.device_config.runtime_native_boot.profilebootclasspath", "");
- // Might be empty if the property is unset since the default is "".
- if (prop.length() == 0) {
- prop = SystemProperties.get("dalvik.vm.profilebootclasspath", "");
- }
- if ("true".equals(prop)) {
+ if (isExperimentEnabled("profilebootclasspath")) {
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ResetJitCounters");
VMRuntime.resetJitCounters();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
@@ -489,16 +497,6 @@
ZygoteHooks.gcAndFinalize();
}
- private static boolean shouldProfileSystemServer() {
- boolean defaultValue = SystemProperties.getBoolean("dalvik.vm.profilesystemserver",
- /*default=*/ false);
- // Can't use DeviceConfig since it's not initialized at this point.
- return SystemProperties.getBoolean(
- "persist.device_config." + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT
- + ".profilesystemserver",
- defaultValue);
- }
-
/**
* Finish remaining work for the newly forked system server process.
*/
@@ -585,6 +583,13 @@
* in the forked system server process in the zygote SELinux domain.
*/
private static void prefetchStandaloneSystemServerJars() {
+ if (shouldProfileSystemServer()) {
+ // We don't prefetch AOT artifacts if we are profiling system server, as we are going to
+ // JIT it.
+ // This method only gets called from native and should already be skipped if we profile
+ // system server. Still, be robust and check it again.
+ return;
+ }
String envStr = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
if (TextUtils.isEmpty(envStr)) {
return;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 550259f..664e964 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -343,6 +343,7 @@
// Must match values in com.android.internal.os.Zygote.
enum RuntimeFlags : uint32_t {
DEBUG_ENABLE_JDWP = 1,
+ PROFILE_SYSTEM_SERVER = 1 << 14,
PROFILE_FROM_SHELL = 1 << 15,
MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20),
MEMORY_TAG_LEVEL_TBI = 1 << 19,
@@ -1821,9 +1822,11 @@
instruction_set.value().c_str());
}
- if (is_system_server) {
+ if (is_system_server && !(runtime_flags & RuntimeFlags::PROFILE_SYSTEM_SERVER)) {
// Prefetch the classloader for the system server. This is done early to
// allow a tie-down of the proper system server selinux domain.
+ // We don't prefetch when the system server is being profiled to avoid
+ // loading AOT code.
env->CallStaticObjectMethod(gZygoteInitClass, gGetOrCreateSystemServerClassLoader);
if (env->ExceptionCheck()) {
// Be robust here. The Java code will attempt to create the classloader
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8fde9d5..b5cdcff 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2612,6 +2612,9 @@
movement threshold under which hover is considered "stationary". -->
<dimen name="config_viewConfigurationHoverSlop">4dp</dimen>
+ <!-- Margin around text line bounds where stylus handwriting gestures should be supported. -->
+ <dimen name="config_viewConfigurationHandwritingGestureLineMargin">16dp</dimen>
+
<!-- Multiplier for gesture thresholds when a MotionEvent classification is ambiguous. -->
<item name="config_ambiguousGestureMultiplier" format="float" type="dimen">2.0</item>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 89741ef..d03d206 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -131,6 +131,8 @@
</staging-public-group>
<staging-public-group type="dimen" first-id="0x01ca0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_viewConfigurationHandwritingGestureLineMargin" />
</staging-public-group>
<staging-public-group type="color" first-id="0x01c90000">
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 613eddd..88b2de7 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -207,7 +207,7 @@
assertFalse("Must not report change if no public diff",
shouldReportChange(currentConfig, newConfig, null /* sizeBuckets */,
- 0 /* handledConfigChanges */));
+ 0 /* handledConfigChanges */, false /* alwaysReportChange */));
final int[] verticalThresholds = {100, 400};
final SizeConfigurationBuckets buckets = new SizeConfigurationBuckets(
@@ -221,24 +221,33 @@
assertFalse("Must not report changes if the diff is small and not handled",
shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_FONT_SCALE /* handledConfigChanges */));
+ CONFIG_FONT_SCALE /* handledConfigChanges */,
+ false /* alwaysReportChange */));
assertTrue("Must report changes if the small diff is handled",
shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_SCREEN_SIZE /* handledConfigChanges */));
+ CONFIG_SCREEN_SIZE /* handledConfigChanges */,
+ false /* alwaysReportChange */));
+
+ assertTrue("Must report changes if it should, even it is small and not handled",
+ shouldReportChange(currentConfig, newConfig, buckets,
+ CONFIG_FONT_SCALE /* handledConfigChanges */,
+ true /* alwaysReportChange */));
currentConfig.fontScale = 0.8f;
newConfig.fontScale = 1.2f;
assertTrue("Must report handled changes regardless of small unhandled change",
shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_FONT_SCALE /* handledConfigChanges */));
+ CONFIG_FONT_SCALE /* handledConfigChanges */,
+ false /* alwaysReportChange */));
newConfig.screenHeightDp = 500;
assertFalse("Must not report changes if there's unhandled big changes",
shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_FONT_SCALE /* handledConfigChanges */));
+ CONFIG_FONT_SCALE /* handledConfigChanges */,
+ false /* alwaysReportChange */));
}
private void recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index b1c9730..51b976b 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -289,6 +289,7 @@
private Integer mFrontendHandle;
private Tuner mFeOwnerTuner = null;
private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
+ private Integer mDesiredFrontendId = null;
private int mUserId;
private Lnb mLnb;
private Integer mLnbHandle;
@@ -1326,10 +1327,18 @@
private boolean requestFrontend() {
int[] feHandle = new int[1];
- TunerFrontendRequest request = new TunerFrontendRequest();
- request.clientId = mClientId;
- request.frontendType = mFrontendType;
- boolean granted = mTunerResourceManager.requestFrontend(request, feHandle);
+ boolean granted = false;
+ try {
+ TunerFrontendRequest request = new TunerFrontendRequest();
+ request.clientId = mClientId;
+ request.frontendType = mFrontendType;
+ request.desiredId = mDesiredFrontendId == null
+ ? TunerFrontendRequest.DEFAULT_DESIRED_ID
+ : mDesiredFrontendId;
+ granted = mTunerResourceManager.requestFrontend(request, feHandle);
+ } finally {
+ mDesiredFrontendId = null;
+ }
if (granted) {
mFrontendHandle = feHandle[0];
mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
@@ -2367,6 +2376,50 @@
}
/**
+ * Request a frontend by frontend id.
+ *
+ * <p> This API is used if the applications want to select a desired frontend before
+ * {@link tune} to use a specific satellite or sending SatCR DiSEqC command for {@link tune}.
+ *
+ * @param desiredId the desired fronted Id. It can be retrieved by
+ * {@link getAvailableFrontendInfos}
+ *
+ * @return result status of open operation.
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ */
+ @Result
+ public int requestFrontendById(int desiredId) {
+ mFrontendLock.lock();
+ try {
+ if (mFeOwnerTuner != null) {
+ Log.e(TAG, "Operation connot be done by sharee of tuner");
+ return RESULT_INVALID_STATE;
+ }
+ if (mFrontendHandle != null) {
+ Log.e(TAG, "A frontend has been opened before");
+ return RESULT_INVALID_STATE;
+ }
+ FrontendInfo frontendInfo = getFrontendInfoById(desiredId);
+ if (frontendInfo == null) {
+ Log.e(TAG, "Failed to get a FrontendInfo by frontend id: " + desiredId);
+ return RESULT_UNAVAILABLE;
+ }
+ int frontendType = frontendInfo.getType();
+ if (DEBUG) {
+ Log.d(TAG, "Opening frontend with type " + frontendType + ", id " + desiredId);
+ }
+ mFrontendType = frontendType;
+ mDesiredFrontendId = desiredId;
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+ return RESULT_UNAVAILABLE;
+ }
+ return RESULT_SUCCESS;
+ } finally {
+ mFrontendLock.unlock();
+ }
+ }
+
+ /**
* Open a shared filter instance.
*
* @param context the context of the caller.
@@ -2445,7 +2498,7 @@
return granted;
}
- private boolean checkResource(int resourceType, ReentrantLock localLock) {
+ private boolean checkResource(int resourceType, ReentrantLock localLock) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
if (mFrontendHandle == null && !requestResource(resourceType, localLock)) {
@@ -2483,7 +2536,7 @@
// 3) if no, then first release the held lock and grab the TRMS lock to avoid deadlock
// 4) grab the local lock again and release the TRMS lock
// If localLock is null, we'll assume the caller does not want the lock related operations
- private boolean requestResource(int resourceType, ReentrantLock localLock) {
+ private boolean requestResource(int resourceType, ReentrantLock localLock) {
boolean enableLockOperations = localLock != null;
// release the local lock first to avoid deadlock
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 144b98c..e0af76d 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -140,19 +140,29 @@
void setLnbInfoList(in int[] lnbIds);
/*
- * This API is used by the Tuner framework to request an available frontend from the TunerHAL.
+ * This API is used by the Tuner framework to request a frontend from the TunerHAL.
*
- * <p>There are three possible scenarios:
+ * <p>There are two cases:
* <ul>
- * <li>If there is frontend available, the API would send the id back.
- *
- * <li>If no Frontend is available but the current request info can show higher priority than
- * other uses of Frontend, the API will send
+ * <li>If the desiredId is not {@link TunerFrontendRequest#DEFAULT_DESIRED_ID}
+ * <li><li>If the desired frontend with the given frontendType is available, the API would send
+ * the id back.
+ * <li><li>If the desired frontend with the given frontendType is in use but the current request
+ * info can show higher priority than other uses of Frontend, the API will send
* {@link IResourcesReclaimListener#onReclaimResources()} to the {@link Tuner}. Tuner would
* handle the resource reclaim on the holder of lower priority and notify the holder of its
* resource loss.
+ * <li><li>If no frontend can be granted, the API would return false.
+ * <ul>
*
- * <li>If no frontend can be granted, the API would return false.
+ * <li>If the desiredId is {@link TunerFrontendRequest#DEFAULT_DESIRED_ID}
+ * <li><li>If there is frontend available, the API would send the id back.
+ * <li><li>If no Frontend is available but the current request info can show higher priority
+ * than other uses of Frontend, the API will send
+ * {@link IResourcesReclaimListener#onReclaimResources()} to the {@link Tuner}. Tuner would
+ * handle the resource reclaim on the holder of lower priority and notify the holder of its
+ * resource loss.
+ * <li><li>If no frontend can be granted, the API would return false.
* <ul>
*
* <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl
index 4d98222..c4598a4 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerFrontendRequest.aidl
@@ -22,7 +22,11 @@
* @hide
*/
parcelable TunerFrontendRequest {
+ const int DEFAULT_DESIRED_ID = 0xFFFFFFFF;
+
int clientId;
int frontendType;
-}
\ No newline at end of file
+
+ int desiredId = DEFAULT_DESIRED_ID;
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index c575fbc..489cc27 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager
+import android.app.Activity
import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.content.Context
@@ -23,8 +24,11 @@
import android.credentials.ui.Entry
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
+import android.credentials.ui.UserSelectionResult
import android.graphics.drawable.Icon
import android.os.Binder
+import android.os.Bundle
+import android.os.ResultReceiver
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.getflow.GetCredentialUiState
@@ -37,6 +41,8 @@
) {
private val requestInfo: RequestInfo
private val providerList: List<ProviderData>
+ // TODO: require non-null.
+ val resultReceiver: ResultReceiver?
init {
requestInfo = intent.extras?.getParcelable(
@@ -52,6 +58,29 @@
ProviderData.EXTRA_PROVIDER_DATA_LIST,
ProviderData::class.java
) ?: testProviderList()
+
+ resultReceiver = intent.getParcelableExtra(
+ RequestInfo.EXTRA_RESULT_RECEIVER,
+ ResultReceiver::class.java
+ )
+ }
+
+ fun onCancel() {
+ resultReceiver?.send(Activity.RESULT_CANCELED, null)
+ }
+
+ fun onOptionSelected(providerPackageName: String, entryId: Int) {
+ val userSelectionResult = UserSelectionResult(
+ requestInfo.token,
+ providerPackageName,
+ entryId
+ )
+ val resultData = Bundle()
+ resultData.putParcelable(
+ UserSelectionResult.EXTRA_USER_SELECTION_RESULT,
+ userSelectionResult
+ )
+ resultReceiver?.send(Activity.RESULT_OK, resultData)
}
fun getCredentialInitialUiState(): GetCredentialUiState {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index b538ae7..78edaa9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -23,9 +23,15 @@
import androidx.activity.compose.setContent
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
+import androidx.lifecycle.Observer
+import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.common.DialogType
+import com.android.credentialmanager.common.DialogResult
+import com.android.credentialmanager.common.ResultState
import com.android.credentialmanager.createflow.CreatePasskeyScreen
+import com.android.credentialmanager.createflow.CreatePasskeyViewModel
import com.android.credentialmanager.getflow.GetCredentialScreen
+import com.android.credentialmanager.getflow.GetCredentialViewModel
import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
@ExperimentalMaterialApi
@@ -57,10 +63,20 @@
val dialogType = DialogType.toDialogType(operationType)
when (dialogType) {
DialogType.CREATE_PASSKEY -> {
- CreatePasskeyScreen(cancelActivity = onCancel)
+ val viewModel: CreatePasskeyViewModel = viewModel()
+ viewModel.observeDialogResult().observe(
+ this@CredentialSelectorActivity,
+ onCancel
+ )
+ CreatePasskeyScreen(viewModel = viewModel)
}
DialogType.GET_CREDENTIALS -> {
- GetCredentialScreen(cancelActivity = onCancel)
+ val viewModel: GetCredentialViewModel = viewModel()
+ viewModel.observeDialogResult().observe(
+ this@CredentialSelectorActivity,
+ onCancel
+ )
+ GetCredentialScreen(viewModel = viewModel)
}
else -> {
Log.w("AccountSelector", "Unknown type, not rendering any UI")
@@ -69,7 +85,9 @@
}
}
- private val onCancel = {
- this@CredentialSelectorActivity.finish()
+ private val onCancel = Observer<DialogResult> {
+ if (it.resultState == ResultState.COMPLETE || it.resultState == ResultState.CANCELED) {
+ this@CredentialSelectorActivity.finish()
+ }
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index af27ce5..6b503ff 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -35,7 +35,7 @@
ProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = it.packageName,
+ name = it.providerId,
appDomainName = "tribank.us",
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
@@ -78,7 +78,7 @@
com.android.credentialmanager.createflow.ProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
- name = it.packageName,
+ name = it.providerId,
appDomainName = "tribank.us",
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
createOptions = toCreationOptionInfoList(it.credentialEntries, context),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt
new file mode 100644
index 0000000..b751663
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.common
+
+enum class ResultState {
+ COMPLETE,
+ CANCELED,
+}
+
+data class DialogResult(
+ val resultState: ResultState,
+)
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 6489d73..f4d60b5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -38,7 +38,6 @@
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.ui.theme.Grey100
import com.android.credentialmanager.ui.theme.Shapes
@@ -50,8 +49,7 @@
@ExperimentalMaterialApi
@Composable
fun CreatePasskeyScreen(
- viewModel: CreatePasskeyViewModel = viewModel(),
- cancelActivity: () -> Unit,
+ viewModel: CreatePasskeyViewModel,
) {
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
@@ -64,17 +62,17 @@
when (uiState.currentScreenState) {
CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
onConfirm = {viewModel.onConfirmIntro()},
- onCancel = cancelActivity,
+ onCancel = {viewModel.onCancel()},
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
providerList = uiState.providers,
- onCancel = cancelActivity,
+ onCancel = {viewModel.onCancel()},
onProviderSelected = {viewModel.onProviderSelected(it)}
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
providerInfo = uiState.selectedProvider!!,
onOptionSelected = {viewModel.onCreateOptionSelected(it)},
- onCancel = cancelActivity,
+ onCancel = {viewModel.onCancel()},
multiProvider = uiState.providers.size > 1,
onMoreOptionsSelected = {viewModel.onMoreOptionsSelected(it)}
)
@@ -93,7 +91,7 @@
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
- cancelActivity()
+ viewModel.onCancel()
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 85fe31d..3cf81da 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -20,8 +20,12 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.CredentialManagerRepo
+import com.android.credentialmanager.common.DialogResult
+import com.android.credentialmanager.common.ResultState
data class CreatePasskeyUiState(
val providers: List<ProviderInfo>,
@@ -36,6 +40,14 @@
var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())
private set
+ val dialogResult: MutableLiveData<DialogResult> by lazy {
+ MutableLiveData<DialogResult>()
+ }
+
+ fun observeDialogResult(): LiveData<DialogResult> {
+ return dialogResult
+ }
+
fun onConfirmIntro() {
if (uiState.providers.size > 1) {
uiState = uiState.copy(
@@ -60,6 +72,13 @@
fun onCreateOptionSelected(createOptionId: Int) {
Log.d("Account Selector", "Option selected for creation: $createOptionId")
+ CredentialManagerRepo.getInstance().onOptionSelected(
+ uiState.selectedProvider!!.name,
+ createOptionId
+ )
+ dialogResult.value = DialogResult(
+ ResultState.COMPLETE,
+ )
}
fun getProviderInfoByName(providerName: String): ProviderInfo {
@@ -88,4 +107,9 @@
selectedProvider = getProviderInfoByName(providerName)
)
}
+
+ fun onCancel() {
+ CredentialManagerRepo.getInstance().onCancel()
+ dialogResult.value = DialogResult(ResultState.CANCELED)
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 0b18822..48c67bb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -44,7 +44,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.createflow.CancelButton
import com.android.credentialmanager.ui.theme.Grey100
@@ -55,8 +54,7 @@
@ExperimentalMaterialApi
@Composable
fun GetCredentialScreen(
- viewModel: GetCredentialViewModel = viewModel(),
- cancelActivity: () -> Unit,
+ viewModel: GetCredentialViewModel,
) {
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
@@ -69,7 +67,7 @@
when (uiState.currentScreenState) {
GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(
providerInfo = uiState.selectedProvider!!,
- onCancel = cancelActivity,
+ onCancel = {viewModel.onCancel()},
onOptionSelected = {viewModel.onCredentailSelected(it)},
multiProvider = uiState.providers.size > 1,
onMoreOptionSelected = {viewModel.onMoreOptionSelected()},
@@ -81,7 +79,7 @@
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
- cancelActivity()
+ viewModel.onCancel()
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 0fdd8ec..33858f5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -20,8 +20,12 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.CredentialManagerRepo
+import com.android.credentialmanager.common.DialogResult
+import com.android.credentialmanager.common.ResultState
data class GetCredentialUiState(
val providers: List<ProviderInfo>,
@@ -36,11 +40,31 @@
var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState())
private set
+ val dialogResult: MutableLiveData<DialogResult> by lazy {
+ MutableLiveData<DialogResult>()
+ }
+
+ fun observeDialogResult(): LiveData<DialogResult> {
+ return dialogResult
+ }
+
fun onCredentailSelected(credentialId: Int) {
Log.d("Account Selector", "credential selected: $credentialId")
+ CredentialManagerRepo.getInstance().onOptionSelected(
+ uiState.selectedProvider!!.name,
+ credentialId
+ )
+ dialogResult.value = DialogResult(
+ ResultState.COMPLETE,
+ )
}
fun onMoreOptionSelected() {
Log.d("Account Selector", "More Option selected")
}
+
+ fun onCancel() {
+ CredentialManagerRepo.getInstance().onCancel()
+ dialogResult.value = DialogResult(ResultState.CANCELED)
+ }
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 0f95bf6..7567c6d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -33,8 +33,8 @@
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.SettingsSlider
-import com.android.settingslib.spa.widget.SettingsSliderModel
+import com.android.settingslib.spa.widget.preference.SliderPreference
+import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -51,7 +51,7 @@
SettingsEntryBuilder.create("Simple Slider", owner)
.setIsAllowSearch(true)
.setUiLayoutFn {
- SettingsSlider(object : SettingsSliderModel {
+ SliderPreference(object : SliderPreferenceModel {
override val title = "Simple Slider"
override val initValue = 40
})
@@ -61,7 +61,7 @@
SettingsEntryBuilder.create("Slider with icon", owner)
.setIsAllowSearch(true)
.setUiLayoutFn {
- SettingsSlider(object : SettingsSliderModel {
+ SliderPreference(object : SliderPreferenceModel {
override val title = "Slider with icon"
override val initValue = 30
override val onValueChangeFinished = {
@@ -78,7 +78,7 @@
val initValue = 0
var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
var sliderPosition by remember { mutableStateOf(initValue) }
- SettingsSlider(object : SettingsSliderModel {
+ SliderPreference(object : SliderPreferenceModel {
override val title = "Slider with changeable icon"
override val initValue = initValue
override val onValueChange = { it: Int ->
@@ -96,7 +96,7 @@
SettingsEntryBuilder.create("Slider with steps", owner)
.setIsAllowSearch(true)
.setUiLayoutFn {
- SettingsSlider(object : SettingsSliderModel {
+ SliderPreference(object : SliderPreferenceModel {
override val title = "Slider with steps"
override val initValue = 2
override val valueRange = 1..5
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index a3aeda6..8616b9f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -17,15 +17,11 @@
package com.android.settingslib.spa.framework.common
import android.os.Bundle
-import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidedValue
import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.platform.LocalContext
import com.android.settingslib.spa.framework.compose.LocalNavController
const val INJECT_ENTRY_NAME = "INJECT"
@@ -126,17 +122,6 @@
@Composable
fun UiLayout(runtimeArguments: Bundle? = null) {
- val context = LocalContext.current
- val controller = LocalNavController.current
- val highlight = rememberSaveable {
- mutableStateOf(controller.highlightEntryId == id)
- }
- if (highlight.value) {
- highlight.value = false
- // TODO: Add highlight entry logic
- Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show()
- }
-
CompositionLocalProvider(provideLocalEntryData()) {
uiLayoutImpl(fullArgument(runtimeArguments))
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt
new file mode 100644
index 0000000..b23f4e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+@file:OptIn(ExperimentalTime::class)
+
+package com.android.settingslib.spa.framework.compose
+
+import android.util.Log
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import kotlin.time.ExperimentalTime
+import kotlin.time.TimeSource
+
+const val ENABLE_MEASURE_TIME = false
+
+interface TimeMeasurer {
+ fun log(msg: String) {}
+ fun logFirst(msg: String) {}
+
+ companion object {
+ private object EmptyTimeMeasurer : TimeMeasurer
+
+ @Composable
+ fun rememberTimeMeasurer(tag: String): TimeMeasurer = remember {
+ if (ENABLE_MEASURE_TIME) TimeMeasurerImpl(tag) else EmptyTimeMeasurer
+ }
+ }
+}
+
+private class TimeMeasurerImpl(private val tag: String) : TimeMeasurer {
+ private val mark = TimeSource.Monotonic.markNow()
+ private val msgLogged = mutableSetOf<String>()
+
+ override fun log(msg: String) {
+ Log.d(tag, "Timer $msg: ${mark.elapsedNow()}")
+ }
+
+ override fun logFirst(msg: String) {
+ if (msgLogged.add(msg)) {
+ Log.d(tag, "Timer $msg: ${mark.elapsedNow()}")
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
new file mode 100644
index 0000000..8e24ce0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
+
+@Composable
+internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
+ val entryData = LocalEntryDataProvider.current
+ val isHighlighted = rememberSaveable { entryData.isHighlighted }
+ val backgroundColor =
+ if (isHighlighted) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent
+ Box(modifier = Modifier.background(color = backgroundColor)) {
+ UiLayoutFn()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index f2fe7ad7..3e04b16 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -28,26 +28,29 @@
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.EntryHighlight
@Composable
fun MainSwitchPreference(model: SwitchPreferenceModel) {
- Surface(
- modifier = Modifier.padding(SettingsDimension.itemPaddingEnd),
- color = when (model.checked.value) {
- true -> MaterialTheme.colorScheme.primaryContainer
- else -> MaterialTheme.colorScheme.secondaryContainer
- },
- shape = SettingsShape.CornerLarge,
- ) {
- InternalSwitchPreference(
- title = model.title,
- checked = model.checked,
- changeable = model.changeable,
- onCheckedChange = model.onCheckedChange,
- paddingStart = 20.dp,
- paddingEnd = 20.dp,
- paddingVertical = 18.dp,
- )
+ EntryHighlight {
+ Surface(
+ modifier = Modifier.padding(SettingsDimension.itemPaddingEnd),
+ color = when (model.checked.value) {
+ true -> MaterialTheme.colorScheme.primaryContainer
+ else -> MaterialTheme.colorScheme.secondaryContainer
+ },
+ shape = SettingsShape.CornerLarge,
+ ) {
+ InternalSwitchPreference(
+ title = model.title,
+ checked = model.checked,
+ changeable = model.changeable,
+ onCheckedChange = model.onCheckedChange,
+ paddingStart = 20.dp,
+ paddingEnd = 20.dp,
+ paddingVertical = 18.dp,
+ )
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index d1021e2..7c0116a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -27,6 +27,7 @@
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.WrapOnClickWithLog
+import com.android.settingslib.spa.framework.util.EntryHighlight
import com.android.settingslib.spa.widget.ui.createSettingsIcon
data class SimplePreferenceMacro(
@@ -115,12 +116,14 @@
)
} else Modifier
}
- BasePreference(
- title = model.title,
- summary = model.summary,
- singleLineSummary = singleLineSummary,
- modifier = modifier,
- icon = model.icon,
- enabled = model.enabled,
- )
+ EntryHighlight {
+ BasePreference(
+ title = model.title,
+ summary = model.summary,
+ singleLineSummary = singleLineSummary,
+ modifier = modifier,
+ icon = model.icon,
+ enabled = model.enabled,
+ )
+ }
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
similarity index 68%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
index 4f77a89..7bca38f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/SettingsSlider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SliderPreference.kt
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccessAlarm
import androidx.compose.material.icons.outlined.MusicNote
import androidx.compose.material.icons.outlined.MusicOff
import androidx.compose.material3.Icon
-import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -32,32 +31,32 @@
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.preference.BaseLayout
-import kotlin.math.roundToInt
+import com.android.settingslib.spa.framework.util.EntryHighlight
+import com.android.settingslib.spa.widget.ui.SettingsSlider
/**
- * The widget model for [SettingsSlider] widget.
+ * The widget model for [SliderPreference] widget.
*/
-interface SettingsSliderModel {
+interface SliderPreferenceModel {
/**
- * The title of this [SettingsSlider].
+ * The title of this [SliderPreference].
*/
val title: String
/**
- * The initial position of the [SettingsSlider].
+ * The initial position of the [SliderPreference].
*/
val initValue: Int
/**
- * The value range for this [SettingsSlider].
+ * The value range for this [SliderPreference].
*/
val valueRange: IntRange
get() = 0..100
/**
* The lambda to be invoked during the value change by dragging or a click. This callback is
- * used to get the real time value of the [SettingsSlider].
+ * used to get the real time value of the [SliderPreference].
*/
val onValueChange: ((value: Int) -> Unit)?
get() = null
@@ -70,7 +69,7 @@
get() = null
/**
- * The icon image for [SettingsSlider]. If not specified, the slider hides the icon by default.
+ * The icon image for [SliderPreference]. If not specified, the slider hides the icon by default.
*/
val icon: ImageVector?
get() = null
@@ -89,46 +88,44 @@
/**
* Settings slider widget.
*
- * Data is provided through [SettingsSliderModel].
+ * Data is provided through [SliderPreferenceModel].
*/
@Composable
-fun SettingsSlider(model: SettingsSliderModel) {
- SettingsSlider(
- title = model.title,
- initValue = model.initValue,
- valueRange = model.valueRange,
- onValueChange = model.onValueChange,
- onValueChangeFinished = model.onValueChangeFinished,
- icon = model.icon,
- showSteps = model.showSteps,
- )
+fun SliderPreference(model: SliderPreferenceModel) {
+ EntryHighlight {
+ SliderPreference(
+ title = model.title,
+ initValue = model.initValue,
+ valueRange = model.valueRange,
+ onValueChange = model.onValueChange,
+ onValueChangeFinished = model.onValueChangeFinished,
+ icon = model.icon,
+ showSteps = model.showSteps,
+ )
+ }
}
@Composable
-internal fun SettingsSlider(
+internal fun SliderPreference(
title: String,
initValue: Int,
+ modifier: Modifier = Modifier,
valueRange: IntRange = 0..100,
onValueChange: ((value: Int) -> Unit)? = null,
onValueChangeFinished: (() -> Unit)? = null,
icon: ImageVector? = null,
showSteps: Boolean = false,
- modifier: Modifier = Modifier,
) {
- var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }
BaseLayout(
title = title,
subTitle = {
- Slider(
- value = sliderPosition,
- onValueChange = {
- sliderPosition = it
- onValueChange?.invoke(sliderPosition.roundToInt())
- },
- modifier = modifier,
- valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
- steps = if (showSteps) (valueRange.count() - 2) else 0,
- onValueChangeFinished = onValueChangeFinished,
+ SettingsSlider(
+ initValue,
+ modifier,
+ valueRange,
+ onValueChange,
+ onValueChangeFinished,
+ showSteps
)
},
icon = if (icon != null) ({
@@ -139,11 +136,11 @@
@Preview
@Composable
-private fun SettingsSliderPreview() {
+private fun SliderPreferencePreview() {
SettingsTheme {
val initValue = 30
var sliderPosition by rememberSaveable { mutableStateOf(initValue) }
- SettingsSlider(
+ SliderPreference(
title = "Alarm Volume",
initValue = 30,
onValueChange = { sliderPosition = it },
@@ -157,10 +154,10 @@
@Preview
@Composable
-private fun SettingsSliderIconChangePreview() {
+private fun SliderPreferenceIconChangePreview() {
SettingsTheme {
var icon by remember { mutableStateOf(Icons.Outlined.MusicNote) }
- SettingsSlider(
+ SliderPreference(
title = "Media Volume",
initValue = 40,
onValueChange = { it: Int ->
@@ -173,9 +170,9 @@
@Preview
@Composable
-private fun SettingsSliderStepsPreview() {
+private fun SliderPreferenceStepsPreview() {
SettingsTheme {
- SettingsSlider(
+ SliderPreference(
title = "Display Text",
initValue = 2,
valueRange = 1..5,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index 992ce9e..592a99f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -32,6 +32,7 @@
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.util.WrapOnSwitchWithLog
+import com.android.settingslib.spa.framework.util.EntryHighlight
import com.android.settingslib.spa.widget.ui.SettingsSwitch
/**
@@ -79,13 +80,15 @@
*/
@Composable
fun SwitchPreference(model: SwitchPreferenceModel) {
- InternalSwitchPreference(
- title = model.title,
- summary = model.summary,
- checked = model.checked,
- changeable = model.changeable,
- onCheckedChange = model.onCheckedChange,
- )
+ EntryHighlight {
+ InternalSwitchPreference(
+ title = model.title,
+ summary = model.summary,
+ checked = model.checked,
+ changeable = model.changeable,
+ onCheckedChange = model.onCheckedChange,
+ )
+ }
}
@Composable
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index f1541b7..63de2c8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.util.EntryHighlight
import com.android.settingslib.spa.widget.ui.SettingsSwitch
@Composable
@@ -25,16 +26,18 @@
icon: @Composable (() -> Unit)? = null,
onClick: () -> Unit,
) {
- TwoTargetPreference(
- title = model.title,
- summary = model.summary,
- onClick = onClick,
- icon = icon,
- ) {
- SettingsSwitch(
- checked = model.checked,
- changeable = model.changeable,
- onCheckedChange = model.onCheckedChange,
- )
+ EntryHighlight {
+ TwoTargetPreference(
+ title = model.title,
+ summary = model.summary,
+ onClick = onClick,
+ icon = icon,
+ ) {
+ SettingsSwitch(
+ checked = model.checked,
+ changeable = model.changeable,
+ onCheckedChange = model.onCheckedChange,
+ )
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
new file mode 100644
index 0000000..d8455e4
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.material3.Slider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import kotlin.math.roundToInt
+
+@Composable
+fun SettingsSlider(
+ initValue: Int,
+ modifier: Modifier = Modifier,
+ valueRange: IntRange = 0..100,
+ onValueChange: ((value: Int) -> Unit)? = null,
+ onValueChangeFinished: (() -> Unit)? = null,
+ showSteps: Boolean = false,
+) {
+ var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }
+ Slider(
+ value = sliderPosition,
+ onValueChange = {
+ sliderPosition = it
+ onValueChange?.invoke(sliderPosition.roundToInt())
+ },
+ modifier = modifier,
+ valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
+ steps = if (showSteps) (valueRange.count() - 2) else 0,
+ onValueChangeFinished = onValueChangeFinished,
+ )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt
similarity index 85%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt
index 1d95e33..7ae1175 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/SettingsSliderTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.preference
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
@@ -25,14 +25,14 @@
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class SettingsSliderTest {
+class SliderPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
- SettingsSlider(object : SettingsSliderModel {
+ SliderPreference(object : SliderPreferenceModel {
override val title = "Slider"
override val initValue = 40
})
@@ -41,5 +41,5 @@
composeTestRule.onNodeWithText("Slider").assertIsDisplayed()
}
- // TODO: Add more unit tests for SettingsSlider widget.
+ // TODO: Add more unit tests for SliderPreference widget.
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 6318b4e..c5ad181 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -28,6 +28,7 @@
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.LogCompositions
+import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
@@ -66,7 +67,9 @@
listModel: AppListModel<T>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
+ val timeMeasurer = rememberTimeMeasurer(TAG)
appListData.value?.let { (list, option) ->
+ timeMeasurer.logFirst("app list first loaded")
if (list.isEmpty()) {
PlaceholderTitle(stringResource(R.string.no_applications))
return
diff --git a/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java b/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java
index 4870d45..4063b93 100644
--- a/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java
+++ b/packages/SettingsLib/search/stub-src/com/android/settingslib/search/SearchIndexableResourcesBase.java
@@ -24,11 +24,11 @@
public class SearchIndexableResourcesBase implements SearchIndexableResources {
@Override
- public Collection<Class> getProviderValues() {
+ public Collection<SearchIndexableData> getProviderValues() {
throw new RuntimeException("STUB!");
}
- public void addIndex(Class indexClass) {
+ public void addIndex(SearchIndexableData indexClass) {
throw new RuntimeException("STUB!");
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 5662ce6..6bc1160 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -356,7 +356,7 @@
* @return {@code true}, if the device should pair automatically; Otherwise, return
* {@code false}.
*/
- public synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
+ private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;
int bondState = device.getBondState();
if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE
@@ -365,13 +365,47 @@
+ " , device.getBondState: " + bondState);
return false;
}
-
- Log.d(TAG, "Bond " + device.getName() + " by CSIP");
- mOngoingSetMemberPair = device;
return true;
}
/**
+ * Called when we found a set member of a group. The function will check the {@code groupId} if
+ * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair
+ * , and then pair the device automatically.
+ *
+ * @param device The found device
+ * @param groupId The group id of the found device
+ */
+ public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) {
+ if (!shouldPairByCsip(device, groupId)) {
+ return;
+ }
+ Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
+ mOngoingSetMemberPair = device;
+ syncConfigFromMainDevice(device, groupId);
+ device.createBond(BluetoothDevice.TRANSPORT_LE);
+ }
+
+ private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
+ if (!isOngoingPairByCsip(device)) {
+ return;
+ }
+ CachedBluetoothDevice memberDevice = findDevice(device);
+ CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice);
+ if (mainDevice == null) {
+ mainDevice = mCsipDeviceManager.getCachedDevice(groupId);
+ }
+
+ if (mainDevice == null || mainDevice.equals(memberDevice)) {
+ Log.d(TAG, "no mainDevice");
+ return;
+ }
+
+ // The memberDevice set PhonebookAccessPermission
+ device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission());
+ }
+
+ /**
* Called when the bond state change. If the bond state change is related with the
* ongoing set member pair, the cachedBluetoothDevice will be created but the UI
* would not be updated. For the other case, return {@code false} to go through the normal
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index d5de3f0..20a6cd8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -101,7 +101,14 @@
return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
}
- private CachedBluetoothDevice getCachedDevice(int groupId) {
+ /**
+ * To find the device with {@code groupId}.
+ *
+ * @param groupId The group id
+ * @return if we could find a device with this {@code groupId} return this device. Otherwise,
+ * return null.
+ */
+ public CachedBluetoothDevice getCachedDevice(int groupId) {
log("getCachedDevice: groupId: " + groupId);
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 62552f91..61802a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -582,4 +582,24 @@
assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
}
+
+ @Test
+ public void pairDeviceByCsip_device2AndCapGroup1_device2StartsPairing() {
+ doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
+ when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mDevice1.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_ALLOWED);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ assertThat(cachedDevice1).isNotNull();
+ when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+ assertThat(cachedDevice2).isNotNull();
+
+ int groupId = CAP_GROUP1.keySet().stream().findFirst().orElse(
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ assertThat(groupId).isNotEqualTo(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ mCachedDeviceManager.pairDeviceByCsip(mDevice2, groupId);
+
+ verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE);
+ }
}
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 6d61fd8..77ddc6e 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -83,7 +83,6 @@
stwu@google.com
syeonlee@google.com
sunnygoyal@google.com
-susikp@google.com
thiruram@google.com
tracyzhou@google.com
tsuji@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index a3351e1..5d52056 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -86,30 +86,38 @@
onUpdate()
}
- fun onDisplayChanged(newDisplayUniqueId: String?) {
+ fun updateConfiguration(newDisplayUniqueId: String?) {
+ val info = DisplayInfo()
+ context.display?.getDisplayInfo(info)
val oldMode: Display.Mode? = displayMode
- val display: Display? = context.display
- displayMode = display?.mode
+ displayMode = info.mode
- if (displayUniqueId != display?.uniqueId) {
- displayUniqueId = display?.uniqueId
- shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
- context.resources, displayUniqueId
- )
- }
+ updateDisplayUniqueId(info.uniqueId)
// Skip if display mode or cutout hasn't changed.
if (!displayModeChanged(oldMode, displayMode) &&
- display?.cutout == displayInfo.displayCutout) {
+ displayInfo.displayCutout == info.displayCutout &&
+ displayRotation == info.rotation) {
return
}
- if (newDisplayUniqueId == display?.uniqueId) {
+ if (newDisplayUniqueId == info.uniqueId) {
+ displayRotation = info.rotation
updateCutout()
updateProtectionBoundingPath()
onUpdate()
}
}
+ open fun updateDisplayUniqueId(newDisplayUniqueId: String?) {
+ if (displayUniqueId != newDisplayUniqueId) {
+ displayUniqueId = newDisplayUniqueId
+ shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
+ context.resources, displayUniqueId
+ )
+ invalidate()
+ }
+ }
+
open fun updateRotation(rotation: Int) {
displayRotation = rotation
updateCutout()
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index b5f42a1..11d579d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -456,7 +456,6 @@
}
}
- boolean needToUpdateProviderViews = false;
final String newUniqueId = mDisplayInfo.uniqueId;
if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
mDisplayUniqueId = newUniqueId;
@@ -474,37 +473,6 @@
setupDecorations();
return;
}
-
- if (mScreenDecorHwcLayer != null) {
- updateHwLayerRoundedCornerDrawable();
- updateHwLayerRoundedCornerExistAndSize();
- }
- needToUpdateProviderViews = true;
- }
-
- final float newRatio = getPhysicalPixelDisplaySizeRatio();
- if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) {
- mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio);
- if (mScreenDecorHwcLayer != null) {
- updateHwLayerRoundedCornerExistAndSize();
- }
- needToUpdateProviderViews = true;
- }
-
- if (needToUpdateProviderViews) {
- updateOverlayProviderViews(null);
- } else {
- updateOverlayProviderViews(new Integer[] {
- mFaceScanningViewId,
- R.id.display_cutout,
- R.id.display_cutout_left,
- R.id.display_cutout_right,
- R.id.display_cutout_bottom,
- });
- }
-
- if (mScreenDecorHwcLayer != null) {
- mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
}
}
};
@@ -1070,9 +1038,11 @@
&& (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
mRotation = newRotation;
mDisplayMode = newMod;
+ mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
+ getPhysicalPixelDisplaySizeRatio());
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = false;
- mScreenDecorHwcLayer.updateRotation(mRotation);
+ mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId);
updateHwLayerRoundedCornerExistAndSize();
updateHwLayerRoundedCornerDrawable();
}
@@ -1111,7 +1081,8 @@
context.getResources(), context.getDisplay().getUniqueId());
}
- private void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
+ @VisibleForTesting
+ void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
if (mOverlays == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
index 991b54e..ded0fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
@@ -59,7 +59,7 @@
(view as? DisplayCutoutView)?.let { cutoutView ->
cutoutView.setColor(tintColor)
cutoutView.updateRotation(rotation)
- cutoutView.onDisplayChanged(displayUniqueId)
+ cutoutView.updateConfiguration(displayUniqueId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index ec0013b..5fdd198 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -124,7 +124,7 @@
view.layoutParams = it
(view as? FaceScanningOverlay)?.let { overlay ->
overlay.setColor(tintColor)
- overlay.onDisplayChanged(displayUniqueId)
+ overlay.updateConfiguration(displayUniqueId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index a252864..8b4aeef 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -78,23 +78,18 @@
reloadMeasures()
}
- private fun reloadAll(newReloadToken: Int) {
- if (reloadToken == newReloadToken) {
- return
- }
- reloadToken = newReloadToken
- reloadRes()
- reloadMeasures()
- }
-
fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
if (displayUniqueId != newDisplayUniqueId) {
displayUniqueId = newDisplayUniqueId
newReloadToken ?.let { reloadToken = it }
reloadRes()
reloadMeasures()
- } else {
- newReloadToken?.let { reloadAll(it) }
+ } else if (newReloadToken != null) {
+ if (reloadToken == newReloadToken) {
+ return
+ }
+ reloadToken = newReloadToken
+ reloadMeasures()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 2319f43..181839a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -36,6 +36,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -255,6 +256,7 @@
});
mScreenDecorations.mDisplayInfo = mDisplayInfo;
doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
+ doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());
reset(mTunerService);
try {
@@ -1005,18 +1007,13 @@
assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());
- setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
- /* roundedTopDrawable */,
- getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
- /* roundedBottomDrawable */,
- 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
+ doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
mDisplayInfo.rotation = Surface.ROTATION_270;
mScreenDecorations.onConfigurationChanged(null);
- assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize());
- assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize());
+ assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
+ assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
}
@Test
@@ -1293,51 +1290,6 @@
}
@Test
- public void testOnDisplayChanged_hwcLayer() {
- setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
- 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
- final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
- decorationSupport.format = PixelFormat.R_8;
- doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
-
- // top cutout
- mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
- mScreenDecorations.start();
-
- final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer;
- spyOn(hwcLayer);
- doReturn(mDisplay).when(hwcLayer).getDisplay();
-
- mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
- verify(hwcLayer, times(1)).onDisplayChanged(any());
- }
-
- @Test
- public void testOnDisplayChanged_nonHwcLayer() {
- setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
- 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
-
- // top cutout
- mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
- mScreenDecorations.start();
-
- final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView)
- mScreenDecorations.getOverlayView(R.id.display_cutout);
- assertNotNull(cutoutView);
- spyOn(cutoutView);
- doReturn(mDisplay).when(cutoutView).getDisplay();
-
- mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
- verify(cutoutView, times(1)).onDisplayChanged(any());
- }
-
- @Test
public void testHasSameProvidersWithNullOverlays() {
setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index f933361..93a1868 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -24,12 +24,11 @@
import androidx.test.filters.SmallTest
import com.android.internal.R as InternalR
import com.android.systemui.R as SystemUIR
-import com.android.systemui.tests.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.tests.R
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
-
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@@ -102,14 +101,11 @@
assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
- setupResources(radius = 100,
- roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
- roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px))
-
+ roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f
roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
- assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
- assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
+ assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
+ assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)
}
@Test
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 3aed167..553146d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -156,6 +156,8 @@
"android.hardware.health-translate-java",
"android.hardware.light-V1-java",
"android.hardware.tv.cec-V1.1-java",
+ "android.hardware.tv.cec-V1-java",
+ "android.hardware.tv.hdmi-V1-java",
"android.hardware.weaver-V1.0-java",
"android.hardware.biometrics.face-V1.0-java",
"android.hardware.biometrics.fingerprint-V2.3-java",
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 5aa3fa4..5c1b33c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -19,20 +19,25 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.tv.cec.V1_0.CecMessage;
+import android.hardware.tv.cec.CecMessage;
+import android.hardware.tv.cec.IHdmiCec;
+import android.hardware.tv.cec.IHdmiCecCallback;
import android.hardware.tv.cec.V1_0.HotplugEvent;
-import android.hardware.tv.cec.V1_0.IHdmiCec;
import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback;
-import android.hardware.tv.cec.V1_0.IHdmiCecCallback;
+import android.hardware.tv.cec.V1_0.OptionKey;
import android.hardware.tv.cec.V1_0.Result;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.hardware.tv.hdmi.IHdmi;
+import android.hardware.tv.hdmi.IHdmiCallback;
import android.icu.util.IllformedLocaleException;
import android.icu.util.ULocale;
import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IHwBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.stats.hdmi.HdmiStatsEnums;
import android.util.Slog;
@@ -170,8 +175,14 @@
* returns {@code null}.
*/
static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
- HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(),
- atomWriter);
+ HdmiCecController controller =
+ createWithNativeWrapper(service, new NativeWrapperImplAidl(), atomWriter);
+ if (controller != null) {
+ return controller;
+ }
+ HdmiLogger.warning("Unable to use CEC and HDMI AIDL HALs");
+
+ controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), atomWriter);
if (controller != null) {
return controller;
}
@@ -362,16 +373,43 @@
}
/**
- * Set an option to CEC HAL.
+ * Configures the TV panel device wakeup behaviour in standby mode when it receives an OTP
+ * (One Touch Play) from a source device.
*
- * @param flag key of option
- * @param enabled whether to enable/disable the given option.
+ * @param value If true, the TV device will wake up when OTP is received and if false, the TV
+ * device will not wake up for an OTP.
*/
@ServiceThreadOnly
- void setOption(int flag, boolean enabled) {
+ void enableWakeupByOtp(boolean enabled) {
assertRunOnServiceThread();
- HdmiLogger.debug("setOption: [flag:%d, enabled:%b]", flag, enabled);
- mNativeWrapperImpl.nativeSetOption(flag, enabled);
+ HdmiLogger.debug("enableWakeupByOtp: %b", enabled);
+ mNativeWrapperImpl.enableWakeupByOtp(enabled);
+ }
+
+ /**
+ * Switch to enable or disable CEC on the device.
+ *
+ * @param value If true, the device will have all CEC functionalities and if false, the device
+ * will not perform any CEC functions.
+ */
+ @ServiceThreadOnly
+ void enableCec(boolean enabled) {
+ assertRunOnServiceThread();
+ HdmiLogger.debug("enableCec: %b", enabled);
+ mNativeWrapperImpl.enableCec(enabled);
+ }
+
+ /**
+ * Configures the module that processes CEC messages - the Android framework or the HAL.
+ *
+ * @param value If true, the Android framework will actively process CEC messages and if false,
+ * only the HAL will process the CEC messages.
+ */
+ @ServiceThreadOnly
+ void enableSystemCecControl(boolean enabled) {
+ assertRunOnServiceThread();
+ HdmiLogger.debug("enableSystemCecControl: %b", enabled);
+ mNativeWrapperImpl.enableSystemCecControl(enabled);
}
/**
@@ -829,12 +867,233 @@
int nativeGetVersion();
int nativeGetVendorId();
HdmiPortInfo[] nativeGetPortInfos();
- void nativeSetOption(int flag, boolean enabled);
+
+ void enableWakeupByOtp(boolean enabled);
+
+ void enableCec(boolean enabled);
+
+ void enableSystemCecControl(boolean enabled);
+
void nativeSetLanguage(String language);
void nativeEnableAudioReturnChannel(int port, boolean flag);
boolean nativeIsConnected(int port);
}
+ private static final class NativeWrapperImplAidl
+ implements NativeWrapper, IBinder.DeathRecipient {
+ private IHdmiCec mHdmiCec;
+ private IHdmi mHdmi;
+ @Nullable private HdmiCecCallback mCallback;
+
+ private final Object mLock = new Object();
+
+ @Override
+ public String nativeInit() {
+ return connectToHal() ? mHdmiCec.toString() + " " + mHdmi.toString() : null;
+ }
+
+ boolean connectToHal() {
+ mHdmiCec =
+ IHdmiCec.Stub.asInterface(
+ ServiceManager.getService(IHdmiCec.DESCRIPTOR + "/default"));
+ if (mHdmiCec == null) {
+ HdmiLogger.error("Could not initialize HDMI CEC AIDL HAL");
+ return false;
+ }
+ try {
+ mHdmiCec.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't link to death : ", e);
+ }
+
+ mHdmi =
+ IHdmi.Stub.asInterface(
+ ServiceManager.getService(IHdmi.DESCRIPTOR + "/default"));
+ if (mHdmi == null) {
+ HdmiLogger.error("Could not initialize HDMI AIDL HAL");
+ return false;
+ }
+ try {
+ mHdmi.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't link to death : ", e);
+ }
+ return true;
+ }
+
+ @Override
+ public void binderDied() {
+ // One of the services died, try to reconnect to both.
+ mHdmiCec.asBinder().unlinkToDeath(this, 0);
+ mHdmi.asBinder().unlinkToDeath(this, 0);
+ HdmiLogger.error("HDMI or CEC service died, reconnecting");
+ connectToHal();
+ // Reconnect the callback
+ if (mCallback != null) {
+ setCallback(mCallback);
+ }
+ }
+
+ @Override
+ public void setCallback(HdmiCecCallback callback) {
+ mCallback = callback;
+ try {
+ // Create an AIDL callback that can callback onCecMessage
+ mHdmiCec.setCallback(new HdmiCecCallbackAidl(callback));
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
+ }
+ try {
+ // Create an AIDL callback that can callback onHotplugEvent
+ mHdmi.setCallback(new HdmiCallbackAidl(callback));
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e);
+ }
+ }
+
+ @Override
+ public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
+ CecMessage message = new CecMessage();
+ message.initiator = (byte) (srcAddress & 0xF);
+ message.destination = (byte) (dstAddress & 0xF);
+ message.body = body;
+ try {
+ return mHdmiCec.sendMessage(message);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to send CEC message : ", e);
+ return SendMessageResult.FAIL;
+ }
+ }
+
+ @Override
+ public int nativeAddLogicalAddress(int logicalAddress) {
+ try {
+ return mHdmiCec.addLogicalAddress((byte) logicalAddress);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to add a logical address : ", e);
+ return Result.FAILURE_INVALID_ARGS;
+ }
+ }
+
+ @Override
+ public void nativeClearLogicalAddress() {
+ try {
+ mHdmiCec.clearLogicalAddress();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to clear logical address : ", e);
+ }
+ }
+
+ @Override
+ public int nativeGetPhysicalAddress() {
+ try {
+ return mHdmiCec.getPhysicalAddress();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get physical address : ", e);
+ return INVALID_PHYSICAL_ADDRESS;
+ }
+ }
+
+ @Override
+ public int nativeGetVersion() {
+ try {
+ return mHdmiCec.getCecVersion();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get cec version : ", e);
+ return Result.FAILURE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int nativeGetVendorId() {
+ try {
+ return mHdmiCec.getVendorId();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get vendor id : ", e);
+ return Result.FAILURE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public void enableWakeupByOtp(boolean enabled) {
+ try {
+ mHdmiCec.enableWakeupByOtp(enabled);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed call to enableWakeupByOtp : ", e);
+ }
+ }
+
+ @Override
+ public void enableCec(boolean enabled) {
+ try {
+ mHdmiCec.enableCec(enabled);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed call to enableCec : ", e);
+ }
+ }
+
+ @Override
+ public void enableSystemCecControl(boolean enabled) {
+ try {
+ mHdmiCec.enableSystemCecControl(enabled);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed call to enableSystemCecControl : ", e);
+ }
+ }
+
+ @Override
+ public void nativeSetLanguage(String language) {
+ try {
+ mHdmiCec.setLanguage(language);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to set language : ", e);
+ }
+ }
+
+ @Override
+ public void nativeEnableAudioReturnChannel(int port, boolean flag) {
+ try {
+ mHdmiCec.enableAudioReturnChannel(port, flag);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to enable/disable ARC : ", e);
+ }
+ }
+
+ @Override
+ public HdmiPortInfo[] nativeGetPortInfos() {
+ try {
+ android.hardware.tv.hdmi.HdmiPortInfo[] hdmiPortInfos = mHdmi.getPortInfo();
+ HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.length];
+ int i = 0;
+ for (android.hardware.tv.hdmi.HdmiPortInfo portInfo : hdmiPortInfos) {
+ hdmiPortInfo[i] =
+ new HdmiPortInfo(
+ portInfo.portId,
+ portInfo.type,
+ portInfo.physicalAddress,
+ portInfo.cecSupported,
+ false,
+ portInfo.arcSupported);
+ i++;
+ }
+ return hdmiPortInfo;
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get port information : ", e);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean nativeIsConnected(int port) {
+ try {
+ return mHdmi.isConnected(port);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get connection info : ", e);
+ return false;
+ }
+ }
+ }
+
private static final class NativeWrapperImpl11 implements NativeWrapper,
IHwBinder.DeathRecipient, getPhysicalAddressCallback {
private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec;
@@ -985,8 +1244,7 @@
}
}
- @Override
- public void nativeSetOption(int flag, boolean enabled) {
+ private void nativeSetOption(int flag, boolean enabled) {
try {
mHdmiCec.setOption(flag, enabled);
} catch (RemoteException e) {
@@ -995,6 +1253,21 @@
}
@Override
+ public void enableWakeupByOtp(boolean enabled) {
+ nativeSetOption(OptionKey.WAKEUP, enabled);
+ }
+
+ @Override
+ public void enableCec(boolean enabled) {
+ nativeSetOption(OptionKey.ENABLE_CEC, enabled);
+ }
+
+ @Override
+ public void enableSystemCecControl(boolean enabled) {
+ nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled);
+ }
+
+ @Override
public void nativeSetLanguage(String language) {
try {
mHdmiCec.setLanguage(language);
@@ -1038,7 +1311,7 @@
boolean connectToHal() {
try {
- mHdmiCec = IHdmiCec.getService(true);
+ mHdmiCec = android.hardware.tv.cec.V1_0.IHdmiCec.getService(true);
try {
mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
} catch (RemoteException e) {
@@ -1063,7 +1336,8 @@
@Override
public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
- CecMessage message = new CecMessage();
+ android.hardware.tv.cec.V1_0.CecMessage message =
+ new android.hardware.tv.cec.V1_0.CecMessage();
message.initiator = srcAddress;
message.destination = dstAddress;
message.body = new ArrayList<>(body.length);
@@ -1151,8 +1425,7 @@
}
}
- @Override
- public void nativeSetOption(int flag, boolean enabled) {
+ private void nativeSetOption(int flag, boolean enabled) {
try {
mHdmiCec.setOption(flag, enabled);
} catch (RemoteException e) {
@@ -1161,6 +1434,21 @@
}
@Override
+ public void enableWakeupByOtp(boolean enabled) {
+ nativeSetOption(OptionKey.WAKEUP, enabled);
+ }
+
+ @Override
+ public void enableCec(boolean enabled) {
+ nativeSetOption(OptionKey.ENABLE_CEC, enabled);
+ }
+
+ @Override
+ public void enableSystemCecControl(boolean enabled) {
+ nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled);
+ }
+
+ @Override
public void nativeSetLanguage(String language) {
try {
mHdmiCec.setLanguage(language);
@@ -1221,7 +1509,8 @@
}
}
- private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub {
+ private static final class HdmiCecCallback10
+ extends android.hardware.tv.cec.V1_0.IHdmiCecCallback.Stub {
private final HdmiCecCallback mHdmiCecCallback;
HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) {
@@ -1229,7 +1518,8 @@
}
@Override
- public void onCecMessage(CecMessage message) throws RemoteException {
+ public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)
+ throws RemoteException {
byte[] body = new byte[message.body.size()];
for (int i = 0; i < message.body.size(); i++) {
body[i] = message.body.get(i);
@@ -1262,7 +1552,8 @@
}
@Override
- public void onCecMessage(CecMessage message) throws RemoteException {
+ public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)
+ throws RemoteException {
byte[] body = new byte[message.body.size()];
for (int i = 0; i < message.body.size(); i++) {
body[i] = message.body.get(i);
@@ -1276,6 +1567,52 @@
}
}
+ private static final class HdmiCecCallbackAidl extends IHdmiCecCallback.Stub {
+ private final HdmiCecCallback mHdmiCecCallback;
+
+ HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback) {
+ mHdmiCecCallback = hdmiCecCallback;
+ }
+
+ @Override
+ public void onCecMessage(CecMessage message) throws RemoteException {
+ mHdmiCecCallback.onCecMessage(message.initiator, message.destination, message.body);
+ }
+
+ @Override
+ public synchronized String getInterfaceHash() throws android.os.RemoteException {
+ return IHdmiCecCallback.Stub.HASH;
+ }
+
+ @Override
+ public int getInterfaceVersion() throws android.os.RemoteException {
+ return IHdmiCecCallback.Stub.VERSION;
+ }
+ }
+
+ private static final class HdmiCallbackAidl extends IHdmiCallback.Stub {
+ private final HdmiCecCallback mHdmiCecCallback;
+
+ HdmiCallbackAidl(HdmiCecCallback hdmiCecCallback) {
+ mHdmiCecCallback = hdmiCecCallback;
+ }
+
+ @Override
+ public void onHotplugEvent(boolean connected, int portId) throws RemoteException {
+ mHdmiCecCallback.onHotplugEvent(portId, connected);
+ }
+
+ @Override
+ public synchronized String getInterfaceHash() throws android.os.RemoteException {
+ return IHdmiCallback.Stub.HASH;
+ }
+
+ @Override
+ public int getInterfaceVersion() throws android.os.RemoteException {
+ return IHdmiCallback.Stub.VERSION;
+ }
+ }
+
public abstract static class Dumpable {
protected final long mTime;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3ee3503..1ae1b5b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -56,7 +56,6 @@
import android.hardware.hdmi.IHdmiRecordListener;
import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
-import android.hardware.tv.cec.V1_0.OptionKey;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
@@ -656,7 +655,7 @@
if (mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
initializeCec(INITIATED_BY_BOOT_UP);
} else {
- mCecController.setOption(OptionKey.ENABLE_CEC, false);
+ mCecController.enableCec(false);
}
mMhlDevices = Collections.emptyList();
@@ -730,10 +729,11 @@
@Override
public void onChange(String setting) {
if (isTvDeviceEnabled()) {
- setCecOption(OptionKey.WAKEUP, tv().getAutoWakeup());
+ mCecController.enableWakeupByOtp(tv().getAutoWakeup());
}
}
- }, mServiceThreadExecutor);
+ },
+ mServiceThreadExecutor);
}
/** Returns true if the device screen is off */
@@ -854,7 +854,7 @@
mWakeUpMessageReceived = false;
if (isTvDeviceEnabled()) {
- mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
+ mCecController.enableWakeupByOtp(tv().getAutoWakeup());
}
int reason = -1;
switch (initiatedBy) {
@@ -988,7 +988,7 @@
mCecVersion = Math.max(HdmiControlManager.HDMI_CEC_VERSION_1_4_B,
Math.min(settingsCecVersion, supportedCecVersion));
- mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
+ mCecController.enableSystemCecControl(true);
mCecController.setLanguage(mMenuLanguage);
initializeLocalDevices(initiatedBy);
}
@@ -3424,7 +3424,7 @@
device.onStandby(mStandbyMessageReceived, standbyAction);
}
if (!isAudioSystemDevice()) {
- mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
+ mCecController.enableSystemCecControl(false);
mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
}
}
@@ -3573,12 +3573,6 @@
}
@ServiceThreadOnly
- void setCecOption(int key, boolean value) {
- assertRunOnServiceThread();
- mCecController.setOption(key, value);
- }
-
- @ServiceThreadOnly
void setControlEnabled(@HdmiControlManager.HdmiCecControl int enabled) {
assertRunOnServiceThread();
@@ -3612,8 +3606,8 @@
@ServiceThreadOnly
private void enableHdmiControlService() {
- mCecController.setOption(OptionKey.ENABLE_CEC, true);
- mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
+ mCecController.enableCec(true);
+ mCecController.enableSystemCecControl(true);
mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
initializeCec(INITIATED_BY_ENABLE_CEC);
@@ -3621,21 +3615,23 @@
@ServiceThreadOnly
private void disableHdmiControlService() {
- disableDevices(new PendingActionClearedCallback() {
- @Override
- public void onCleared(HdmiCecLocalDevice device) {
- assertRunOnServiceThread();
- mCecController.flush(new Runnable() {
+ disableDevices(
+ new PendingActionClearedCallback() {
@Override
- public void run() {
- mCecController.setOption(OptionKey.ENABLE_CEC, false);
- mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
- mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
- clearLocalDevices();
+ public void onCleared(HdmiCecLocalDevice device) {
+ assertRunOnServiceThread();
+ mCecController.flush(
+ new Runnable() {
+ @Override
+ public void run() {
+ mCecController.enableCec(false);
+ mCecController.enableSystemCecControl(false);
+ mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
+ clearLocalDevices();
+ }
+ });
}
});
- }
- });
}
@ServiceThreadOnly
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index fc7be7f..364f6db 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -25,6 +25,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ILocaleManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -38,6 +39,8 @@
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -357,17 +360,20 @@
false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
"getApplicationLocales", /* callerPackage= */ null);
- // This function handles three types of query operations:
+ // This function handles four types of query operations:
// 1.) A normal, non-privileged app querying its own locale.
- // 2.) The installer of the given app querying locales of a package installed
- // by said installer.
- // 3.) A privileged system service querying locales of another package.
- // The least privileged case is a normal app performing a query, so check that first and
- // get locales if the package name is owned by the app. Next check if the calling app
- // is the installer of the given app and get locales. If neither conditions matched,
- // check if the caller has the necessary permission and fetch locales.
+ // 2.) The installer of the given app querying locales of a package installed by said
+ // installer.
+ // 3.) The current input method querying locales of another package.
+ // 4.) A privileged system service querying locales of another package.
+ // The least privileged case is a normal app performing a query, so check that first and get
+ // locales if the package name is owned by the app. Next check if the calling app is the
+ // installer of the given app and get locales. Finally check if the calling app is the
+ // current input method. If neither conditions matched, check if the caller has the
+ // necessary permission and fetch locales.
if (!isPackageOwnedByCaller(appPackageName, userId)
- && !isCallerInstaller(appPackageName, userId)) {
+ && !isCallerInstaller(appPackageName, userId)
+ && !isCallerFromCurrentInputMethod(userId)) {
enforceReadAppSpecificLocalesPermission();
}
final long token = Binder.clearCallingIdentity();
@@ -412,6 +418,26 @@
return false;
}
+ /**
+ * Checks if the calling app is the current input method.
+ */
+ private boolean isCallerFromCurrentInputMethod(int userId) {
+ String currentInputMethod = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD,
+ userId);
+ if (!TextUtils.isEmpty(currentInputMethod)) {
+ String inputMethodPkgName = ComponentName
+ .unflattenFromString(currentInputMethod)
+ .getPackageName();
+ int inputMethodUid = getPackageUid(inputMethodPkgName, userId);
+ return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(),
+ inputMethodUid);
+ }
+
+ return false;
+ }
+
private void enforceReadAppSpecificLocalesPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_APP_SPECIFIC_LOCALES,
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 07e9fe6..6c4c829 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -115,6 +115,16 @@
if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
return true;
}
+ // The HAL doc does not specify if consecutive start() calls will be allowed.
+ // Some vendors may ignore the 2nd start() call if stop() is not called.
+ // Thus, here we always call stop() before calling start() to avoid being ignored.
+ if (mGnssNative.stopMeasurementCollection()) {
+ if (D) {
+ Log.d(TAG, "stopping gnss measurements");
+ }
+ } else {
+ Log.e(TAG, "error stopping gnss measurements");
+ }
if (mGnssNative.startMeasurementCollection(request.isFullTracking(),
request.isCorrelationVectorOutputsEnabled(),
request.getIntervalMillis())) {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index edd1ef3..ad1ff72 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -946,8 +946,12 @@
int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ // If the desired frontend id was specified, we only need to check the frontend.
+ boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
for (FrontendResource fr : getFrontendResources().values()) {
- if (fr.getType() == request.frontendType) {
+ int frontendId = getResourceIdFromHandle(fr.getHandle());
+ if (fr.getType() == request.frontendType
+ && (!hasDesiredFrontend || frontendId == request.desiredId)) {
if (!fr.isInUse()) {
// Unused resource cannot be acquired if the max is already reached, but
// TRM still has to look for the reclaim candidate
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 559a2c0..29eccd4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -118,7 +118,13 @@
}
@Override
- public void nativeSetOption(int flag, boolean enabled) {}
+ public void enableWakeupByOtp(boolean enabled) {}
+
+ @Override
+ public void enableCec(boolean enabled) {}
+
+ @Override
+ public void enableSystemCecControl(boolean enabled) {}
@Override
public void nativeSetLanguage(String language) {}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 1dcdbac..dbcd38c 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.locales;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
@@ -35,13 +37,16 @@
import android.Manifest;
import android.app.ActivityManagerInternal;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.LocaleList;
+import android.provider.Settings;
+import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
@@ -111,6 +116,8 @@
doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
.handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
anyString(), anyString());
+ doReturn(InstrumentationRegistry.getContext().getContentResolver())
+ .when(mMockContext).getContentResolver();
mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
@@ -299,6 +306,25 @@
assertEquals(DEFAULT_LOCALES, locales);
}
+ @Test
+ public void testGetApplicationLocales_callerIsCurrentInputMethod_returnsLocales()
+ throws Exception {
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
+ String imPkgName = getCurrentInputMethodPackageName();
+ doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(imPkgName), any(), anyInt());
+
+ LocaleList locales =
+ mLocaleManagerService.getApplicationLocales(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ verify(mMockContext, never()).enforceCallingOrSelfPermission(any(), any());
+ assertEquals(DEFAULT_LOCALES, locales);
+ }
+
private static void assertNoLocalesStored(LocaleList locales) {
assertNull(locales);
}
@@ -311,4 +337,13 @@
private void setUpPassingPermissionCheckFor(String permission) {
doNothing().when(mMockContext).enforceCallingOrSelfPermission(eq(permission), any());
}
+
+ private String getCurrentInputMethodPackageName() {
+ String im = Settings.Secure.getString(
+ InstrumentationRegistry.getContext().getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ ComponentName cn = ComponentName.unflattenFromString(im);
+ assertThat(cn).isNotNull();
+ return cn.getPackageName();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index 808b74e..853eea1 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -47,6 +47,8 @@
import android.util.TypedXmlPullParser;
import android.util.Xml;
+import androidx.test.InstrumentationRegistry;
+
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.XmlUtils;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -124,6 +126,8 @@
doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+ doReturn(InstrumentationRegistry.getContext().getContentResolver())
+ .when(mMockContext).getContentResolver();
mStoragefile = new AtomicFile(new File(
Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index 7abe369..d535677 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -88,7 +88,7 @@
@After
public void tearDown() {
- mPositioner = null;
+ TaskPositioner.setFactory(null);
}
@Test
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index 35859fe..29d87a2 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -35,6 +36,7 @@
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public class MultiUserRollbackTest extends BaseHostJUnit4Test {
+ private boolean mSupportMultiUsers;
// The user that was running originally when the test starts.
private int mOriginalUserId;
private int mSecondaryUserId = -1;
@@ -46,14 +48,20 @@
@After
public void tearDown() throws Exception {
- removeSecondaryUserIfNecessary();
- runPhaseForUsers("cleanUp", mOriginalUserId);
- uninstallPackage("com.android.cts.install.lib.testapp.A");
- uninstallPackage("com.android.cts.install.lib.testapp.B");
+ if (mSupportMultiUsers) {
+ removeSecondaryUserIfNecessary();
+ runPhaseForUsers("cleanUp", mOriginalUserId);
+ uninstallPackage("com.android.cts.install.lib.testapp.A");
+ uninstallPackage("com.android.cts.install.lib.testapp.B");
+ }
}
@Before
public void setup() throws Exception {
+ assumeTrue("Device does not support multiple users",
+ getDevice().isMultiUserSupported());
+
+ mSupportMultiUsers = true;
mOriginalUserId = getDevice().getCurrentUser();
createAndStartSecondaryUser();
installPackage("RollbackTest.apk", "--user all");