diff --git a/core/api/current.txt b/core/api/current.txt index 731d1a91610c2..4b67ec2138e69 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -45795,6 +45795,8 @@ package android.util { method public void clear(); method public android.util.SparseArray clone(); method public boolean contains(int); + method public boolean contentEquals(@Nullable android.util.SparseArray); + method public int contentHashCode(); method public void delete(int); method public E get(int); method public E get(int, E); diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index 86120d1e650ce..6718e93f908c4 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -16,6 +16,7 @@ package android.util; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import com.android.internal.util.ArrayUtils; @@ -23,6 +24,8 @@ import com.android.internal.util.GrowingArrayUtils; import libcore.util.EmptyArray; +import java.util.Objects; + /** * SparseArray maps integers to Objects and, unlike a normal array of Objects, * its indices can contain gaps. SparseArray is intended to be more memory-efficient @@ -505,4 +508,44 @@ public class SparseArray implements Cloneable { buffer.append('}'); return buffer.toString(); } + + /** + * For backwards compatibility reasons, {@link Object#equals(Object)} cannot be implemented, + * so this serves as a manually invoked alternative. + */ + public boolean contentEquals(@Nullable SparseArray other) { + if (other == null) { + return false; + } + + int size = size(); + if (size != other.size()) { + return false; + } + + for (int index = 0; index < size; index++) { + int key = keyAt(index); + if (!Objects.equals(valueAt(index), other.get(key))) { + return false; + } + } + + return true; + } + + /** + * For backwards compatibility, {@link Object#hashCode()} cannot be implemented, so this serves + * as a manually invoked alternative. + */ + public int contentHashCode() { + int hash = 0; + int size = size(); + for (int index = 0; index < size; index++) { + int key = keyAt(index); + E value = valueAt(index); + hash = 31 * hash + Objects.hashCode(key); + hash = 31 * hash + Objects.hashCode(value); + } + return hash; + } } diff --git a/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java new file mode 100644 index 0000000000000..1dc55494cae5a --- /dev/null +++ b/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.domain.verify.models; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.domain.verify.DomainVerificationManager; +import android.content.pm.domain.verify.DomainVerificationState; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.util.DataClass; + +import java.util.Objects; +import java.util.UUID; + +/** + * State for a single package for the domain verification APIs. Stores the state of each individual + * domain declared by the package, including its verification state and user selection state. + */ +@DataClass(genToString = true, genEqualsHashCode = true) +public class DomainVerificationPkgState { + + @NonNull + private final String mPackageName; + + @NonNull + private UUID mId; + + /** + * Whether or not the package declares any autoVerify domains. This is separate from an empty + * check on the map itself, because an empty map means no response recorded, not necessarily no + * domains declared. When this is false, {@link #mStateMap} will be empty, but + * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to + * allow this package to open, which may or may not be marked autoVerify. + */ + private final boolean mHasAutoVerifyDomains; + + /** + * Map of domains to state integers. Only domains that are not set to the default value of + * {@link DomainVerificationState#STATE_NO_RESPONSE} are included. + * + * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, + * such as storing no state when the package is marked as a linked app in SystemConfig. + */ + @NonNull + private final ArrayMap mStateMap; + + @NonNull + private final SparseArray mUserSelectionStates; + + public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id, + boolean hasAutoVerifyDomains) { + this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0)); + } + + @Nullable + public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) { + return mUserSelectionStates.get(userId); + } + + @Nullable + public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) { + DomainVerificationUserState userState = mUserSelectionStates.get(userId); + if (userState == null) { + userState = new DomainVerificationUserState(userId); + mUserSelectionStates.put(userId, userState); + } + return userState; + } + + public void setId(@NonNull UUID id) { + mId = id; + } + + public void removeUser(@UserIdInt int userId) { + mUserSelectionStates.remove(userId); + } + + public void removeAllUsers() { + mUserSelectionStates.clear(); + } + + private int userSelectionStatesHashCode() { + return mUserSelectionStates.contentHashCode(); + } + + private boolean userSelectionStatesEquals( + @NonNull SparseArray other) { + return mUserSelectionStates.contentEquals(other); + } + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new DomainVerificationPkgState. + * + * @param stateMap + * Map of domains to state integers. Only domains that are not set to the default value of + * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included. + * + * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, + * such as storing no state when the package is marked as a linked app in SystemConfig. + */ + @DataClass.Generated.Member + public DomainVerificationPkgState( + @NonNull String packageName, + @NonNull UUID id, + boolean hasAutoVerifyDomains, + @NonNull ArrayMap stateMap, + @NonNull SparseArray userSelectionStates) { + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mId = id; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mId); + this.mHasAutoVerifyDomains = hasAutoVerifyDomains; + this.mStateMap = stateMap; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStateMap); + this.mUserSelectionStates = userSelectionStates; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mUserSelectionStates); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull String getPackageName() { + return mPackageName; + } + + @DataClass.Generated.Member + public @NonNull UUID getId() { + return mId; + } + + @DataClass.Generated.Member + public boolean isHasAutoVerifyDomains() { + return mHasAutoVerifyDomains; + } + + /** + * Map of domains to state integers. Only domains that are not set to the default value of + * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included. + * + * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations, + * such as storing no state when the package is marked as a linked app in SystemConfig. + */ + @DataClass.Generated.Member + public @NonNull ArrayMap getStateMap() { + return mStateMap; + } + + @DataClass.Generated.Member + public @NonNull SparseArray getUserSelectionStates() { + return mUserSelectionStates; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "DomainVerificationPkgState { " + + "packageName = " + mPackageName + ", " + + "id = " + mId + ", " + + "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " + + "stateMap = " + mStateMap + ", " + + "userSelectionStates = " + mUserSelectionStates + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(DomainVerificationPkgState other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + DomainVerificationPkgState that = (DomainVerificationPkgState) o; + //noinspection PointlessBooleanExpression + return true + && Objects.equals(mPackageName, that.mPackageName) + && Objects.equals(mId, that.mId) + && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains + && Objects.equals(mStateMap, that.mStateMap) + && userSelectionStatesEquals(that.mUserSelectionStates); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + Objects.hashCode(mPackageName); + _hash = 31 * _hash + Objects.hashCode(mId); + _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains); + _hash = 31 * _hash + Objects.hashCode(mStateMap); + _hash = 31 * _hash + userSelectionStatesHashCode(); + return _hash; + } + + @DataClass.Generated( + time = 1608234185474L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java", + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.domain.verify.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.domain.verify.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userSelectionStatesHashCode()\nprivate boolean userSelectionStatesEquals(android.util.SparseArray)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationStateMap.java b/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationStateMap.java new file mode 100644 index 0000000000000..ece1ce8281ac5 --- /dev/null +++ b/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationStateMap.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.domain.verify.models; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +/** + * A feature specific implementation of a multi-key map, since lookups by both a {@link String} + * package name and {@link UUID} domain set ID should be supported. + * + * @param stored object type + */ +public class DomainVerificationStateMap { + + private static final String TAG = "DomainVerificationStateMap"; + + @NonNull + private final ArrayMap mPackageNameMap = new ArrayMap<>(); + + @NonNull + private final ArrayMap mDomainSetIdMap = new ArrayMap<>(); + + public int size() { + return mPackageNameMap.size(); + } + + @NonNull + public ValueType valueAt(@IntRange(from = 0) int index) { + return mPackageNameMap.valueAt(index); + } + + @Nullable + public ValueType get(@NonNull String packageName) { + return mPackageNameMap.get(packageName); + } + + @Nullable + public ValueType get(@NonNull UUID domainSetId) { + return mDomainSetIdMap.get(domainSetId); + } + + public void put(@NonNull String packageName, @NonNull UUID domainSetId, + @NonNull ValueType valueType) { + if (mPackageNameMap.containsKey(packageName)) { + remove(packageName); + } + + mPackageNameMap.put(packageName, valueType); + mDomainSetIdMap.put(domainSetId, valueType); + } + + @Nullable + public ValueType remove(@NonNull String packageName) { + ValueType valueRemoved = mPackageNameMap.remove(packageName); + if (valueRemoved != null) { + int index = mDomainSetIdMap.indexOfValue(valueRemoved); + if (index >= 0) { + mDomainSetIdMap.removeAt(index); + } + } + return valueRemoved; + } + + @Nullable + public ValueType remove(@NonNull UUID domainSetId) { + ValueType valueRemoved = mDomainSetIdMap.remove(domainSetId); + if (valueRemoved != null) { + int index = mPackageNameMap.indexOfValue(valueRemoved); + if (index >= 0) { + mPackageNameMap.removeAt(index); + } + } + return valueRemoved; + } + + @NonNull + public List getPackageNames() { + return new ArrayList<>(mPackageNameMap.keySet()); + } + + /** + * Exposes the backing values collection of the one of the internal maps. Should only be used + * for test assertions. + */ + @VisibleForTesting + public Collection values() { + return new ArrayList<>(mPackageNameMap.values()); + } + + @Override + public String toString() { + return "DomainVerificationStateMap{" + + "packageNameMap=" + mPackageNameMap + + ", domainSetIdMap=" + mDomainSetIdMap + + '}'; + } +} diff --git a/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java new file mode 100644 index 0000000000000..43595408d17b0 --- /dev/null +++ b/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.domain.verify.models; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.util.ArraySet; + +import com.android.internal.util.DataClass; + +import java.util.Set; + +/** + * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open + * that domain when a web URL Intent is sent ft. + */ +@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true) +public class DomainVerificationUserState { + + @UserIdInt + private final int mUserId; + + /** List of domains which have been enabled by the user. **/ + @NonNull + private final ArraySet mEnabledHosts; + + /** Whether to disallow this package from automatically opening links by auto verification. */ + private boolean mDisallowLinkHandling; + + public DomainVerificationUserState(@UserIdInt int userId) { + mUserId = userId; + mEnabledHosts = new ArraySet<>(); + } + + public DomainVerificationUserState addHosts(@NonNull ArraySet newHosts) { + mEnabledHosts.addAll(newHosts); + return this; + } + + public DomainVerificationUserState addHosts(@NonNull Set newHosts) { + mEnabledHosts.addAll(newHosts); + return this; + } + + public DomainVerificationUserState removeHosts(@NonNull ArraySet newHosts) { + mEnabledHosts.removeAll(newHosts); + return this; + } + + public DomainVerificationUserState removeHosts(@NonNull Set newHosts) { + mEnabledHosts.removeAll(newHosts); + return this; + } + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new DomainVerificationUserState. + * + * @param enabledHosts + * List of domains which have been enabled by the user. * + */ + @DataClass.Generated.Member + public DomainVerificationUserState( + @UserIdInt int userId, + @NonNull ArraySet enabledHosts, + boolean disallowLinkHandling) { + this.mUserId = userId; + com.android.internal.util.AnnotationValidations.validate( + UserIdInt.class, null, mUserId); + this.mEnabledHosts = enabledHosts; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEnabledHosts); + this.mDisallowLinkHandling = disallowLinkHandling; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @UserIdInt int getUserId() { + return mUserId; + } + + /** + * List of domains which have been enabled by the user. * + */ + @DataClass.Generated.Member + public @NonNull ArraySet getEnabledHosts() { + return mEnabledHosts; + } + + @DataClass.Generated.Member + public boolean isDisallowLinkHandling() { + return mDisallowLinkHandling; + } + + @DataClass.Generated.Member + public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) { + mDisallowLinkHandling = value; + return this; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "DomainVerificationUserState { " + + "userId = " + mUserId + ", " + + "enabledHosts = " + mEnabledHosts + ", " + + "disallowLinkHandling = " + mDisallowLinkHandling + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(DomainVerificationUserState other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + DomainVerificationUserState that = (DomainVerificationUserState) o; + //noinspection PointlessBooleanExpression + return true + && mUserId == that.mUserId + && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts) + && mDisallowLinkHandling == that.mDisallowLinkHandling; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mUserId; + _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts); + _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling); + return _hash; + } + + @DataClass.Generated( + time = 1608234273324L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java", + inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet mEnabledHosts\nprivate boolean mDisallowLinkHandling\npublic com.android.server.pm.domain.verify.models.DomainVerificationUserState addHosts(android.util.ArraySet)\npublic com.android.server.pm.domain.verify.models.DomainVerificationUserState addHosts(java.util.Set)\npublic com.android.server.pm.domain.verify.models.DomainVerificationUserState removeHosts(android.util.ArraySet)\npublic com.android.server.pm.domain.verify.models.DomainVerificationUserState removeHosts(java.util.Set)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +}