diff options
81 files changed, 4966 insertions, 984 deletions
diff --git a/Android.bp b/Android.bp index dd46108b8576..ae34005ddd02 100644 --- a/Android.bp +++ b/Android.bp @@ -74,6 +74,14 @@ filegroup { } filegroup { + name: "framework-identity-sources", + srcs: [ + "identity/java/**/*.java", + ], + path: "identity/java", +} + +filegroup { name: "framework-keystore-sources", srcs: [ "keystore/java/**/*.java", @@ -210,6 +218,7 @@ filegroup { ":framework-graphics-sources", ":framework-jobscheduler-sources", // jobscheduler is not a module for R ":framework-keystore-sources", + ":framework-identity-sources", ":framework-location-sources", ":framework-lowpan-sources", ":framework-mca-effect-sources", @@ -232,6 +241,7 @@ filegroup { ":platform-compat-native-aidl", // AIDL sources from external directories + ":credstore_aidl", ":dumpstate_aidl", ":framework_native_aidl", ":gatekeeper_aidl", @@ -295,6 +305,7 @@ java_defaults { "core/java", "drm/java", "graphics/java", + "identity/java", "keystore/java", "location/java", "lowpan/java", diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java index f32bf9a4b9e6..c62aad622f25 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java @@ -20,6 +20,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import android.app.Activity; import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; import android.perftests.utils.BenchmarkState; @@ -149,7 +150,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, mOutContentInsets, mOutVisibleInsets, mOutStableInsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState); + mOutSurfaceControl, mOutInsetsState, new Point()); } } } diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java new file mode 100644 index 000000000000..773db9346625 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; + +import java.util.Collections; +import java.util.Map; + +/** + * Provides access to multiple results from a batch operation accepting multiple inputs. + * + * @param <KeyType> The type of the keys for {@link #getResults} and {@link #getFailures}. + * @param <ValueType> The type of result objects associated with the keys. + * @hide + */ +public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable { + @NonNull private final Map<KeyType, ValueType> mResults; + @NonNull private final Map<KeyType, Throwable> mFailures; + + private AppSearchBatchResult( + @NonNull Map<KeyType, ValueType> results, @NonNull Map<KeyType, Throwable> failures) { + mResults = results; + mFailures = failures; + } + + private AppSearchBatchResult(@NonNull Parcel in) { + mResults = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null)); + mFailures = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null)); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeMap(mResults); + dest.writeMap(mFailures); + } + + /** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */ + public boolean isSuccess() { + return mFailures.isEmpty(); + } + + /** + * Returns a {@link Map} of all successful keys mapped to the results they produced. + * + * <p>The values of the {@link Map} may be {@code null}. + */ + @NonNull + public Map<KeyType, ValueType> getResults() { + return mResults; + } + + /** + * Returns a {@link Map} of all failed keys mapped to a {@link Throwable} representing the cause + * of failure. + * + * <p>The values of the {@link Map} may be {@code null}. + */ + @NonNull + public Map<KeyType, Throwable> getFailures() { + return mFailures; + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<AppSearchBatchResult> CREATOR = + new Creator<AppSearchBatchResult>() { + @NonNull + @Override + public AppSearchBatchResult createFromParcel(@NonNull Parcel in) { + return new AppSearchBatchResult(in); + } + + @NonNull + @Override + public AppSearchBatchResult[] newArray(int size) { + return new AppSearchBatchResult[size]; + } + }; + + /** + * Creates a new {@link Builder} for this {@link AppSearchBatchResult}. + * @hide + */ + @NonNull + public static <KeyType, ValueType> Builder<KeyType, ValueType> newBuilder() { + return new Builder<>(); + } + + /** + * Builder for {@link AppSearchBatchResult} objects. + * + * @param <KeyType> The type of keys. + * @param <ValueType> The type of result objects associated with the keys. + * @hide + */ + public static final class Builder<KeyType, ValueType> { + @NonNull private final Map<KeyType, ValueType> mResults = new ArrayMap<>(); + @NonNull private final Map<KeyType, Throwable> mFailures = new ArrayMap<>(); + + private Builder() {} + + /** + * Registers that the {@code key} was processed successfully and associates it with + * {@code value}. Any previous mapping for a key, whether success or failure, is deleted. + */ + public Builder setSuccess(@NonNull KeyType key, @Nullable ValueType value) { + mResults.put(key, value); + mFailures.remove(key); + return this; + } + + /** + * Registers that the {@code key} failed and associates it with {@code throwable}. Any + * previous mapping for a key, whether success or failure, is deleted. + */ + public Builder setFailure(@NonNull KeyType key, @Nullable Throwable throwable) { + mFailures.put(key, throwable); + mResults.remove(key); + return this; + } + + /** Builds an {@link AppSearchBatchResult} from the contents of this {@link Builder}. */ + @NonNull + public AppSearchBatchResult<KeyType, ValueType> build() { + return new AppSearchBatchResult<>(mResults, mFailures); + } + } +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 66cba52ae03a..e3f6b3de433a 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -29,12 +29,12 @@ import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.StatusProto; import com.google.android.icing.protobuf.InvalidProtocolBufferException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.BiConsumer; -import java.util.function.Consumer; /** * This class provides access to the centralized AppSearch index maintained by the system. @@ -158,26 +158,25 @@ public class AppSearchManager { * name of a schema type previously registered via the {@link #setSchema} method. * * @param documents {@link Document Documents} that need to be indexed. - * @param executor Executor on which to invoke the callback. - * @param callback Callback to receive errors. On success, it will be called with {@code null}. - * On failure, it will be called with a {@link Throwable} describing the failure. + * @return An {@link AppSearchBatchResult} mapping the document URIs to {@link Void} if they + * were successfully indexed, or a {@link Throwable} describing the failure if they could + * not be indexed. + * @hide */ - public void putDocuments( - @NonNull List<Document> documents, - @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<? super Throwable> callback) { - AndroidFuture<Void> future = new AndroidFuture<>(); + public AppSearchBatchResult<String, Void> putDocuments(@NonNull List<Document> documents) { + // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in + // one big list. + List<byte[]> documentsBytes = new ArrayList<>(documents.size()); for (Document document : documents) { - // TODO(b/146386470) batching Document protos - try { - mService.putDocument(document.getProto().toByteArray(), future); - } catch (RemoteException e) { - future.completeExceptionally(e); - break; - } + documentsBytes.add(document.getProto().toByteArray()); + } + AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); + try { + mService.putDocuments(documentsBytes, future); + } catch (RemoteException e) { + future.completeExceptionally(e); } - // TODO(b/147614371) Fix error report for multiple documents. - future.whenCompleteAsync((noop, err) -> callback.accept(err), executor); + return getFutureOrThrow(future); } /** diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index c9c5d7fbfda4..20c8af985c21 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -17,6 +17,8 @@ package android.app.appsearch; import com.android.internal.infra.AndroidFuture; +parcelable AppSearchBatchResult; + /** {@hide} */ interface IAppSearchManager { /** @@ -32,14 +34,17 @@ interface IAppSearchManager { void setSchema(in byte[] schemaBytes, boolean forceOverride, in AndroidFuture callback); /** - * Inserts a document into the index. + * Inserts documents into the index. * - * @param documentBytes serialized DocumentProto - * @param callback {@link AndroidFuture}<{@link Void}>. Will be completed with - * {@code null} upon successful completion of the put call, or completed exceptionally if - * put fails. + * @param documentsBytes {@link List}<byte[]> of serialized DocumentProtos. + * @param callback + * {@link AndroidFuture}<{@link AppSearchBatchResult}<{@link String}, {@link Void}>>. + * If the call fails to start, {@code callback} will be completed exceptionally. Otherwise, + * {@code callback} will be completed with an + * {@link AppSearchBatchResult}<{@link String}, {@link Void}> + * where the keys are document URIs, and the values are {@code null}. */ - void putDocument(in byte[] documentBytes, in AndroidFuture callback); + void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback); /** * Searches a document based on a given query string. diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index 6929202445e9..d2d9cf9fdf17 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -16,6 +16,7 @@ package com.android.server.appsearch; import android.annotation.NonNull; +import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.IAppSearchManager; import android.content.Context; import android.os.Binder; @@ -34,6 +35,8 @@ import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.protobuf.InvalidProtocolBufferException; +import java.util.List; + /** * TODO(b/142567528): add comments when implement this class */ @@ -72,17 +75,28 @@ public class AppSearchManagerService extends SystemService { } @Override - public void putDocument(byte[] documentBytes, AndroidFuture callback) { - Preconditions.checkNotNull(documentBytes); + public void putDocuments( + List documentsBytes, AndroidFuture<AppSearchBatchResult> callback) { + Preconditions.checkNotNull(documentsBytes); Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); long callingIdentity = Binder.clearCallingIdentity(); try { - DocumentProto document = DocumentProto.parseFrom(documentBytes); AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); - impl.putDocument(callingUid, document); - callback.complete(null); + AppSearchBatchResult.Builder<String, Void> resultBuilder = + AppSearchBatchResult.newBuilder(); + for (int i = 0; i < documentsBytes.size(); i++) { + byte[] documentBytes = (byte[]) documentsBytes.get(i); + DocumentProto document = DocumentProto.parseFrom(documentBytes); + try { + impl.putDocument(callingUid, document); + resultBuilder.setSuccess(document.getUri(), /*value=*/ null); + } catch (Throwable t) { + resultBuilder.setFailure(document.getUri(), t); + } + } + callback.complete(resultBuilder.build()); } catch (Throwable t) { callback.completeExceptionally(t); } finally { diff --git a/api/current.txt b/api/current.txt index 430e8407a4d8..636ac72aa544 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16980,7 +16980,9 @@ package android.hardware.biometrics { ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature); ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher); ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac); + ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential); method public javax.crypto.Cipher getCipher(); + method @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); method public javax.crypto.Mac getMac(); method public java.security.Signature getSignature(); } @@ -17941,7 +17943,9 @@ package android.hardware.fingerprint { ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature); ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher); ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac); + ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull android.security.identity.IdentityCredential); method @Deprecated public javax.crypto.Cipher getCipher(); + method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); method @Deprecated public javax.crypto.Mac getMac(); method @Deprecated public java.security.Signature getSignature(); } @@ -25775,10 +25779,12 @@ package android.media { field public static final int COLOR_TRANSFER_LINEAR = 1; // 0x1 field public static final int COLOR_TRANSFER_SDR_VIDEO = 3; // 0x3 field public static final int COLOR_TRANSFER_ST2084 = 6; // 0x6 + field public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode"; field public static final String KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-cut-level"; field public static final String KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-level"; field public static final String KEY_AAC_DRC_EFFECT_TYPE = "aac-drc-effect-type"; field public static final String KEY_AAC_DRC_HEAVY_COMPRESSION = "aac-drc-heavy-compression"; + field public static final String KEY_AAC_DRC_OUTPUT_LOUDNESS = "aac-drc-output-loudness"; field public static final String KEY_AAC_DRC_TARGET_REFERENCE_LEVEL = "aac-target-ref-level"; field public static final String KEY_AAC_ENCODED_TARGET_LEVEL = "aac-encoded-target-level"; field public static final String KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT = "aac-max-output-channel_count"; @@ -42058,6 +42064,139 @@ package android.security { } +package android.security.identity { + + public class AccessControlProfile { + } + + public static final class AccessControlProfile.Builder { + ctor public AccessControlProfile.Builder(@NonNull android.security.identity.AccessControlProfileId); + method @NonNull public android.security.identity.AccessControlProfile build(); + method @NonNull public android.security.identity.AccessControlProfile.Builder setReaderCertificate(@NonNull java.security.cert.X509Certificate); + method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationRequired(boolean); + method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationTimeout(long); + } + + public class AccessControlProfileId { + ctor public AccessControlProfileId(int); + method public int getId(); + } + + public class AlreadyPersonalizedException extends android.security.identity.IdentityCredentialException { + ctor public AlreadyPersonalizedException(@NonNull String); + ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable); + } + + public class CipherSuiteNotSupportedException extends android.security.identity.IdentityCredentialException { + ctor public CipherSuiteNotSupportedException(@NonNull String); + ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable); + } + + public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException { + ctor public DocTypeNotSupportedException(@NonNull String); + ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable); + } + + public class EphemeralPublicKeyNotFoundException extends android.security.identity.IdentityCredentialException { + ctor public EphemeralPublicKeyNotFoundException(@NonNull String); + ctor public EphemeralPublicKeyNotFoundException(@NonNull String, @NonNull Throwable); + } + + public abstract class IdentityCredential { + method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair(); + method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException; + method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]); + method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification(); + method @NonNull public abstract int[] getAuthenticationDataUsageCount(); + method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(); + method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException; + method public abstract void setAllowUsingExhaustedKeys(boolean); + method public abstract void setAvailableAuthenticationKeys(int, int); + method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException; + method public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException; + } + + public class IdentityCredentialException extends java.lang.Exception { + ctor public IdentityCredentialException(@NonNull String); + ctor public IdentityCredentialException(@NonNull String, @NonNull Throwable); + } + + public abstract class IdentityCredentialStore { + method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException; + method @Nullable public abstract byte[] deleteCredentialByName(@NonNull String); + method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException; + method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context); + method @Nullable public static android.security.identity.IdentityCredentialStore getInstance(@NonNull android.content.Context); + method @NonNull public abstract String[] getSupportedDocTypes(); + field public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; // 0x1 + } + + public class InvalidReaderSignatureException extends android.security.identity.IdentityCredentialException { + ctor public InvalidReaderSignatureException(@NonNull String); + ctor public InvalidReaderSignatureException(@NonNull String, @NonNull Throwable); + } + + public class InvalidRequestMessageException extends android.security.identity.IdentityCredentialException { + ctor public InvalidRequestMessageException(@NonNull String); + ctor public InvalidRequestMessageException(@NonNull String, @NonNull Throwable); + } + + public class MessageDecryptionException extends android.security.identity.IdentityCredentialException { + ctor public MessageDecryptionException(@NonNull String); + ctor public MessageDecryptionException(@NonNull String, @NonNull Throwable); + } + + public class NoAuthenticationKeyAvailableException extends android.security.identity.IdentityCredentialException { + ctor public NoAuthenticationKeyAvailableException(@NonNull String); + ctor public NoAuthenticationKeyAvailableException(@NonNull String, @NonNull Throwable); + } + + public class PersonalizationData { + } + + public static final class PersonalizationData.Builder { + ctor public PersonalizationData.Builder(); + method @NonNull public android.security.identity.PersonalizationData.Builder addAccessControlProfile(@NonNull android.security.identity.AccessControlProfile); + method @NonNull public android.security.identity.PersonalizationData build(); + method @NonNull public android.security.identity.PersonalizationData.Builder setEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]); + } + + public abstract class ResultData { + method @NonNull public abstract byte[] getAuthenticatedData(); + method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String); + method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String); + method @Nullable public abstract byte[] getMessageAuthenticationCode(); + method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaceNames(); + method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String); + method @NonNull public abstract byte[] getStaticAuthenticationData(); + method public abstract int getStatus(@NonNull String, @NonNull String); + field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3 + field public static final int STATUS_NOT_REQUESTED = 2; // 0x2 + field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6 + field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1 + field public static final int STATUS_OK = 0; // 0x0 + field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5 + field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4 + } + + public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException { + ctor public SessionTranscriptMismatchException(@NonNull String); + ctor public SessionTranscriptMismatchException(@NonNull String, @NonNull Throwable); + } + + public class UnknownAuthenticationKeyException extends android.security.identity.IdentityCredentialException { + ctor public UnknownAuthenticationKeyException(@NonNull String); + ctor public UnknownAuthenticationKeyException(@NonNull String, @NonNull Throwable); + } + + public abstract class WritableIdentityCredential { + ctor public WritableIdentityCredential(); + method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[]); + method @NonNull public abstract byte[] personalize(@NonNull android.security.identity.PersonalizationData); + } + +} + package android.security.keystore { public class KeyExpiredException extends java.security.InvalidKeyException { @@ -53988,8 +54127,10 @@ package android.view { public interface WindowInsetsController { method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener); + method public int getSystemBarsAppearance(); + method public int getSystemBarsBehavior(); method public default void hideInputMethod(); - method public void setSystemBarsAppearance(int); + method public void setSystemBarsAppearance(int, int); method public void setSystemBarsBehavior(int); method public default void showInputMethod(); field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10 diff --git a/api/system-current.txt b/api/system-current.txt index d1696c8256c4..c4709c6fb036 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2120,6 +2120,10 @@ package android.content.pm { public class PackageInstaller { method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); + field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2 + field public static final int DATA_LOADER_TYPE_NONE = 0; // 0x0 + field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1 + field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE"; } public static class PackageInstaller.Session implements java.io.Closeable { @@ -5457,8 +5461,11 @@ package android.net { method public int getLegacyType(); method @NonNull public String getLegacyTypeName(); method @Nullable public String getSubscriberId(); + method public boolean isExplicitlySelected(); method public boolean isNat64DetectionEnabled(); + method public boolean isPartialConnectivityAcceptable(); method public boolean isProvisioningNotificationEnabled(); + method public boolean isUnvalidatedConnectivityAcceptable(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; } @@ -5468,9 +5475,12 @@ package android.net { method @NonNull public android.net.NetworkAgentConfig build(); method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection(); method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification(); + method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); + method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); } public final class NetworkCapabilities implements android.os.Parcelable { diff --git a/api/test-current.txt b/api/test-current.txt index b1003d61495e..e4304e757a30 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4456,7 +4456,7 @@ package android.view { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40 field public CharSequence accessibilityTitle; - field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="FIT_INSETS_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="ONLY_DRAW_BOTTOM_BAR_BACKGROUND")}) public int privateFlags; + field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="APPEARANCE_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="BEHAVIOR_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x10000000, equals=0x10000000, name="FIT_INSETS_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x20000000, equals=0x20000000, name="ONLY_DRAW_BOTTOM_BAR_BACKGROUND")}) public int privateFlags; } } diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index 6954b312f91c..33bd839a53f6 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import android.os.Binder; import android.os.Process; @@ -67,22 +68,30 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class PermissionChecker { - /** Permission result: The permission is granted. */ + /** The permission is granted. */ public static final int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED; - /** Permission result: The permission is denied. */ - public static final int PERMISSION_DENIED = PackageManager.PERMISSION_DENIED; + /** Returned when: + * <ul> + * <li>For non app op permissions, returned when the permission is denied.</li> + * <li>For app op permissions, returned when the app op is denied or app op is + * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li> + * </ul> + * + */ + public static final int PERMISSION_HARD_DENIED = PackageManager.PERMISSION_DENIED; - /** Permission result: The permission is denied because the app op is not allowed. */ - public static final int PERMISSION_DENIED_APP_OP = PackageManager.PERMISSION_DENIED - 1; + /** Only for runtime permissions, its returned when the runtime permission + * is granted, but the corresponding app op is denied. */ + public static final int PERMISSION_SOFT_DENIED = PackageManager.PERMISSION_DENIED - 1; /** Constant when the PID for which we check permissions is unknown. */ public static final int PID_UNKNOWN = -1; /** @hide */ @IntDef({PERMISSION_GRANTED, - PERMISSION_DENIED, - PERMISSION_DENIED_APP_OP}) + PERMISSION_SOFT_DENIED, + PERMISSION_HARD_DENIED}) @Retention(RetentionPolicy.SOURCE) public @interface PermissionResult {} @@ -116,7 +125,7 @@ public final class PermissionChecker { * the first package for the calling UID will be used. * @param featureId Feature in the package * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param message A message describing the reason the permission was checked * * @see #checkPermissionForPreflight(Context, String, int, int, String) @@ -155,7 +164,7 @@ public final class PermissionChecker { * @param packageName The package name for which to check. If null the * the first package for the calling UID will be used. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String) */ @@ -189,7 +198,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param message A message describing the reason the permission was checked * * @see #checkSelfPermissionForPreflight(Context, String) @@ -225,7 +234,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkSelfPermissionForDataDelivery(Context, String, String) */ @@ -259,7 +268,7 @@ public final class PermissionChecker { * the first package for the calling UID will be used. * @param featureId The feature inside of the app * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param message A message describing the reason the permission was checked * * @see #checkCallingPermissionForPreflight(Context, String, String) @@ -269,7 +278,7 @@ public final class PermissionChecker { @NonNull String permission, @Nullable String packageName, @Nullable String featureId, @Nullable String message) { if (Binder.getCallingPid() == Process.myPid()) { - return PERMISSION_DENIED; + return PERMISSION_HARD_DENIED; } return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(), Binder.getCallingUid(), packageName, featureId, message); @@ -299,7 +308,7 @@ public final class PermissionChecker { * @param packageName The package name making the IPC. If null the * the first package for the calling UID will be used. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkCallingPermissionForDataDelivery(Context, String, String, String) */ @@ -307,7 +316,7 @@ public final class PermissionChecker { public static int checkCallingPermissionForPreflight(@NonNull Context context, @NonNull String permission, @Nullable String packageName) { if (Binder.getCallingPid() == Process.myPid()) { - return PERMISSION_DENIED; + return PERMISSION_HARD_DENIED; } return checkPermissionForPreflight(context, permission, Binder.getCallingPid(), Binder.getCallingUid(), packageName); @@ -333,7 +342,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param featureId feature Id of caller (if not self) * @param message A message describing the reason the permission was checked * @@ -372,7 +381,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkCallingOrSelfPermissionForDataDelivery(Context, String, String, String) */ @@ -385,39 +394,85 @@ public final class PermissionChecker { Binder.getCallingUid(), packageName); } - private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission, + static int checkPermissionCommon(@NonNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName, @Nullable String featureId, @Nullable String message, boolean forDataDelivery) { - if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { - return PERMISSION_DENIED; - } - - AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - String op = appOpsManager.permissionToOp(permission); - if (op == null) { - return PERMISSION_GRANTED; + final PermissionInfo permissionInfo; + try { + // TODO(b/147869157): Cache platform defined app op and runtime permissions to avoid + // calling into the package manager every time. + permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0); + } catch (PackageManager.NameNotFoundException ignored) { + return PERMISSION_HARD_DENIED; } if (packageName == null) { String[] packageNames = context.getPackageManager().getPackagesForUid(uid); - if (packageNames == null || packageNames.length <= 0) { - return PERMISSION_DENIED; + if (packageNames != null && packageNames.length > 0) { + packageName = packageNames[0]; } - packageName = packageNames[0]; } - if (forDataDelivery) { - if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message) - != AppOpsManager.MODE_ALLOWED) { - return PERMISSION_DENIED_APP_OP; + if (permissionInfo.isAppOp()) { + return checkAppOpPermission(context, permission, pid, uid, packageName, featureId, + message, forDataDelivery); + } + if (permissionInfo.isRuntime()) { + return checkRuntimePermission(context, permission, pid, uid, packageName, featureId, + message, forDataDelivery); + } + return context.checkPermission(permission, pid, uid); + } + + private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission, + int pid, int uid, @Nullable String packageName, @Nullable String featureId, + @Nullable String message, boolean forDataDelivery) { + final String op = AppOpsManager.permissionToOp(permission); + if (op == null || packageName == null) { + return PERMISSION_HARD_DENIED; + } + + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + final int opMode = (forDataDelivery) + ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message) + : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); + + switch (opMode) { + case AppOpsManager.MODE_ALLOWED: { + return PERMISSION_GRANTED; } - } else { - final int mode = appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); - if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_FOREGROUND) { - return PERMISSION_DENIED_APP_OP; + case AppOpsManager.MODE_DEFAULT: { + return context.checkPermission(permission, pid, uid) + == PackageManager.PERMISSION_GRANTED + ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + } + default: { + return PERMISSION_HARD_DENIED; } } + } + + private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission, + int pid, int uid, @Nullable String packageName, @Nullable String featureId, + @Nullable String message, boolean forDataDelivery) { + if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { + return PERMISSION_HARD_DENIED; + } - return PERMISSION_GRANTED; + final String op = AppOpsManager.permissionToOp(permission); + if (op == null || packageName == null) { + return PERMISSION_GRANTED; + } + + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + final int opMode = (forDataDelivery) + ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message) + : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); + + if (opMode == AppOpsManager.MODE_ALLOWED) { + return PERMISSION_GRANTED; + } else { + return PERMISSION_SOFT_DENIED; + } } } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 3c6f602e93e1..f264adbbb592 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -222,6 +222,19 @@ public class PackageInstaller { public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK"; /** + * Type of DataLoader for this session. Will be one of + * {@link #DATA_LOADER_TYPE_NONE}, {@link #DATA_LOADER_TYPE_STREAMING}, + * {@link #DATA_LOADER_TYPE_INCREMENTAL}. + * <p> + * See the individual types documentation for details. + * + * @see Intent#getIntExtra(String, int) + * {@hide} + */ + @SystemApi + public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE"; + + /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. * @@ -325,6 +338,33 @@ public class PackageInstaller { */ public static final int STATUS_FAILURE_INCOMPATIBLE = 7; + /** + * Default value, non-streaming installation session. + * + * @see #EXTRA_DATA_LOADER_TYPE + * {@hide} + */ + @SystemApi + public static final int DATA_LOADER_TYPE_NONE = DataLoaderType.NONE; + + /** + * Streaming installation using data loader. + * + * @see #EXTRA_DATA_LOADER_TYPE + * {@hide} + */ + @SystemApi + public static final int DATA_LOADER_TYPE_STREAMING = DataLoaderType.STREAMING; + + /** + * Streaming installation using Incremental FileSystem. + * + * @see #EXTRA_DATA_LOADER_TYPE + * {@hide} + */ + @SystemApi + public static final int DATA_LOADER_TYPE_INCREMENTAL = DataLoaderType.INCREMENTAL; + private final IPackageInstaller mInstaller; private final int mUserId; private final String mInstallerPackageName; diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index a695ce8e511f..c686624fb33b 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -36,6 +36,7 @@ import android.os.CancellationSignal; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.security.identity.IdentityCredential; import android.text.TextUtils; import android.util.Log; @@ -555,6 +556,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan super(mac); } + public CryptoObject(@NonNull IdentityCredential credential) { + super(credential); + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -578,6 +583,14 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public Mac getMac() { return super.getMac(); } + + /** + * Get {@link IdentityCredential} object. + * @return {@link IdentityCredential} object or null if this doesn't contain one. + */ + public @Nullable IdentityCredential getIdentityCredential() { + return super.getIdentityCredential(); + } } /** diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index 787dc6696cd3..0af18dfb0e3a 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.annotation.NonNull; +import android.security.identity.IdentityCredential; import android.security.keystore.AndroidKeyStoreProvider; import java.security.Signature; @@ -26,7 +27,8 @@ import javax.crypto.Mac; /** * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager. - * Currently the framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and + * {@link IdentityCredential} objects. * @hide */ public class CryptoObject { @@ -44,6 +46,10 @@ public class CryptoObject { mCrypto = mac; } + public CryptoObject(@NonNull IdentityCredential credential) { + mCrypto = credential; + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -69,11 +75,23 @@ public class CryptoObject { } /** + * Get {@link IdentityCredential} object. + * @return {@link IdentityCredential} object or null if this doesn't contain one. + */ + public IdentityCredential getIdentityCredential() { + return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null; + } + + /** * @hide * @return the opId associated with this object or 0 if none */ public final long getOpId() { - return mCrypto != null - ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0; + if (mCrypto == null) { + return 0; + } else if (mCrypto instanceof IdentityCredential) { + return ((IdentityCredential) mCrypto).getCredstoreOperationHandle(); + } + return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto); } }; diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 7bc45292c350..ff9d14510d4b 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -44,6 +44,7 @@ import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; +import android.security.identity.IdentityCredential; import android.util.Slog; import java.security.Signature; @@ -125,6 +126,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing super(mac); } + public CryptoObject(@NonNull IdentityCredential credential) { + super(credential); + } + /** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. @@ -148,6 +153,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public Mac getMac() { return super.getMac(); } + + /** + * Get {@link IdentityCredential} object. + * @return {@link IdentityCredential} object or null if this doesn't contain one. + */ + public @Nullable IdentityCredential getIdentityCredential() { + return super.getIdentityCredential(); + } } /** diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java index 2c5a113a93da..7e2db4a4fa95 100644 --- a/core/java/android/net/NetworkAgentConfig.java +++ b/core/java/android/net/NetworkAgentConfig.java @@ -22,6 +22,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Allows a network transport to provide the system with policy and configuration information about * a particular network when registering a {@link NetworkAgent}. This information cannot change once the agent is registered. @@ -52,23 +54,47 @@ public final class NetworkAgentConfig implements Parcelable { public boolean explicitlySelected; /** + * @return whether this network was explicitly selected by the user. + */ + public boolean isExplicitlySelected() { + return explicitlySelected; + } + + /** * Set if the user desires to use this network even if it is unvalidated. This field has meaning * only if {@link explicitlySelected} is true. If it is, this field must also be set to the * appropriate value based on previous user choice. * + * TODO : rename this field to match its accessor * @hide */ public boolean acceptUnvalidated; /** + * @return whether the system should accept this network even if it doesn't validate. + */ + public boolean isUnvalidatedConnectivityAcceptable() { + return acceptUnvalidated; + } + + /** * Whether the user explicitly set that this network should be validated even if presence of * only partial internet connectivity. * + * TODO : rename this field to match its accessor * @hide */ public boolean acceptPartialConnectivity; /** + * @return whether the system should validate this network even if it only offers partial + * Internet connectivity. + */ + public boolean isPartialConnectivityAcceptable() { + return acceptPartialConnectivity; + } + + /** * Set to avoid surfacing the "Sign in to network" notification. * if carrier receivers/apps are registered to handle the carrier-specific provisioning * procedure, a carrier specific provisioning notification will be placed. @@ -134,9 +160,11 @@ public final class NetworkAgentConfig implements Parcelable { * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. * + * This is not parceled, because it would not make sense. + * * @hide */ - public boolean hasShownBroken; + public transient boolean hasShownBroken; /** * The name of the legacy network type. It's a free-form string used in logging. @@ -163,6 +191,7 @@ public final class NetworkAgentConfig implements Parcelable { allowBypass = nac.allowBypass; explicitlySelected = nac.explicitlySelected; acceptUnvalidated = nac.acceptUnvalidated; + acceptPartialConnectivity = nac.acceptPartialConnectivity; subscriberId = nac.subscriberId; provisioningNotificationDisabled = nac.provisioningNotificationDisabled; skip464xlat = nac.skip464xlat; @@ -178,6 +207,43 @@ public final class NetworkAgentConfig implements Parcelable { private final NetworkAgentConfig mConfig = new NetworkAgentConfig(); /** + * Sets whether the network was explicitly selected by the user. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setExplicitlySelected(final boolean explicitlySelected) { + mConfig.explicitlySelected = explicitlySelected; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found not to offer + * Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setUnvalidatedConnectivityAcceptable( + final boolean unvalidatedConnectivityAcceptable) { + mConfig.acceptUnvalidated = unvalidatedConnectivityAcceptable; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found to only offer + * partial Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setPartialConnectivityAcceptable( + final boolean partialConnectivityAcceptable) { + mConfig.acceptPartialConnectivity = partialConnectivityAcceptable; + return this; + } + + /** * Sets the subscriber ID for this network. * * @return this builder, to facilitate chaining. @@ -245,6 +311,45 @@ public final class NetworkAgentConfig implements Parcelable { } @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final NetworkAgentConfig that = (NetworkAgentConfig) o; + return allowBypass == that.allowBypass + && explicitlySelected == that.explicitlySelected + && acceptUnvalidated == that.acceptUnvalidated + && acceptPartialConnectivity == that.acceptPartialConnectivity + && provisioningNotificationDisabled == that.provisioningNotificationDisabled + && skip464xlat == that.skip464xlat + && legacyType == that.legacyType + && Objects.equals(subscriberId, that.subscriberId) + && Objects.equals(legacyTypeName, that.legacyTypeName); + } + + @Override + public int hashCode() { + return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated, + acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId, + skip464xlat, legacyType, legacyTypeName); + } + + @Override + public String toString() { + return "NetworkAgentConfig {" + + " allowBypass = " + allowBypass + + ", explicitlySelected = " + explicitlySelected + + ", acceptUnvalidated = " + acceptUnvalidated + + ", acceptPartialConnectivity = " + acceptPartialConnectivity + + ", provisioningNotificationDisabled = " + provisioningNotificationDisabled + + ", subscriberId = '" + subscriberId + '\'' + + ", skip464xlat = " + skip464xlat + + ", legacyType = " + legacyType + + ", hasShownBroken = " + hasShownBroken + + ", legacyTypeName = '" + legacyTypeName + '\'' + + "}"; + } + + @Override public int describeContents() { return 0; } @@ -254,9 +359,12 @@ public final class NetworkAgentConfig implements Parcelable { out.writeInt(allowBypass ? 1 : 0); out.writeInt(explicitlySelected ? 1 : 0); out.writeInt(acceptUnvalidated ? 1 : 0); + out.writeInt(acceptPartialConnectivity ? 1 : 0); out.writeString(subscriberId); out.writeInt(provisioningNotificationDisabled ? 1 : 0); out.writeInt(skip464xlat ? 1 : 0); + out.writeInt(legacyType); + out.writeString(legacyTypeName); } public static final @NonNull Creator<NetworkAgentConfig> CREATOR = @@ -267,9 +375,12 @@ public final class NetworkAgentConfig implements Parcelable { networkAgentConfig.allowBypass = in.readInt() != 0; networkAgentConfig.explicitlySelected = in.readInt() != 0; networkAgentConfig.acceptUnvalidated = in.readInt() != 0; + networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0; networkAgentConfig.subscriberId = in.readString(); networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0; networkAgentConfig.skip464xlat = in.readInt() != 0; + networkAgentConfig.legacyType = in.readInt(); + networkAgentConfig.legacyTypeName = in.readString(); return networkAgentConfig; } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 9a76a1b4eb79..e3fd8d297316 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -31,6 +31,7 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; @@ -188,6 +189,7 @@ public abstract class WallpaperService extends Service { DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT; final InsetsState mInsetsState = new InsetsState(); final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); + private final Point mSurfaceSize = new Point(); final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); @@ -838,12 +840,13 @@ public abstract class WallpaperService extends Service { } else { mLayout.surfaceInsets.set(0, 0, 0, 0); } + final int relayoutResult = mSession.relayout( mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, View.VISIBLE, 0, -1, mWinFrame, mContentInsets, mVisibleInsets, mStableInsets, mBackdropFrame, mDisplayCutout, mMergedConfiguration, mSurfaceControl, - mInsetsState); + mInsetsState, mSurfaceSize); if (mSurfaceControl.isValid()) { mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); mSurfaceControl.release(); diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 8f25d89fe08c..e3446e1f7b57 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -18,6 +18,7 @@ package android.view; import android.content.ClipData; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; @@ -90,6 +91,7 @@ interface IWindowSession { * since it was last displayed. * @param outSurface Object in which is placed the new display surface. * @param insetsState The current insets state in the system. + * @param outSurfaceSize The width and height of the surface control * * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS}, * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}. @@ -101,7 +103,7 @@ interface IWindowSession { out Rect outBackdropFrame, out DisplayCutout.ParcelableWrapper displayCutout, out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, - out InsetsState insetsState); + out InsetsState insetsState, out Point outSurfaceSize); /* * Notify the window manager that an application is relaunching and diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index db89d9588507..411e910e1af1 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -19,6 +19,8 @@ package android.view; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -807,20 +809,41 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @Override - public void setSystemBarsAppearance(@Appearance int appearance) { - if (mViewRoot.mWindowAttributes.insetsFlags.appearance != appearance) { - mViewRoot.mWindowAttributes.insetsFlags.appearance = appearance; + public void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask) { + mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED; + final InsetsFlags insetsFlags = mViewRoot.mWindowAttributes.insetsFlags; + if (insetsFlags.appearance != appearance) { + insetsFlags.appearance = (insetsFlags.appearance & ~mask) | (appearance & mask); mViewRoot.mWindowAttributesChanged = true; mViewRoot.scheduleTraversals(); } } @Override + public @Appearance int getSystemBarsAppearance() { + if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) { + // We only return the requested appearance, not the implied one. + return 0; + } + return mViewRoot.mWindowAttributes.insetsFlags.appearance; + } + + @Override public void setSystemBarsBehavior(@Behavior int behavior) { + mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) { mViewRoot.mWindowAttributes.insetsFlags.behavior = behavior; mViewRoot.mWindowAttributesChanged = true; mViewRoot.scheduleTraversals(); } } + + @Override + public @Appearance int getSystemBarsBehavior() { + if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) { + // We only return the requested behavior, not the implied one. + return 0; + } + return mViewRoot.mWindowAttributes.insetsFlags.behavior; + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 32622233d81e..09ebd005e7ed 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -27,15 +27,24 @@ import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE; import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; +import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; +import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; +import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE; +import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -427,6 +436,10 @@ public final class ViewRootImpl implements ViewParent, FallbackEventHandler mFallbackEventHandler; Choreographer mChoreographer; + // used in relayout to get SurfaceControl size + // for BLAST adapter surface setup + private final Point mSurfaceSize = new Point(); + final Rect mTempRect; // used in the transaction to not thrash the heap. final Rect mVisRect; // used to retrieve visible rect of focused view. private final Rect mTempBoundsRect = new Rect(); // used to set the size of the bounds surface. @@ -1937,10 +1950,28 @@ public final class ViewRootImpl implements ViewParent, final int type = inOutParams.type; final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST; - if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0) { - inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; - } else if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE) != 0) { - inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_SWIPE; + if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) { + inOutParams.insetsFlags.appearance = 0; + if ((sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { + inOutParams.insetsFlags.appearance |= APPEARANCE_LOW_PROFILE_BARS; + } + if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) { + inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_STATUS_BARS; + } + if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0) { + inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS; + } + } + + if ((inOutParams.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) { + if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0 + || (flags & FLAG_FULLSCREEN) != 0) { + inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + } else if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE) != 0) { + inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_SWIPE; + } else { + inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_TOUCH; + } } if ((inOutParams.privateFlags & PRIVATE_FLAG_FIT_INSETS_CONTROLLED) != 0) { @@ -7306,14 +7337,13 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout, - mPendingMergedConfiguration, mSurfaceControl, mTempInsets); + mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize); if (mSurfaceControl.isValid()) { if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { mSurface.copyFrom(mSurfaceControl); } else { - mSurface.transferFrom(getOrCreateBLASTSurface( - (int) (mView.getMeasuredWidth() * appScale + 0.5f), - (int) (mView.getMeasuredHeight() * appScale + 0.5f))); + mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x, + mSurfaceSize.y)); } } else { destroySurface(); diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 9d7f292dbdf5..f292ca4facbf 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -29,7 +29,7 @@ import java.lang.annotation.RetentionPolicy; /** * Interface to control windows that generate insets. * - * TODO Needs more information and examples once the API is more baked. + * TODO(118118435): Needs more information and examples once the API is more baked. */ public interface WindowInsetsController { @@ -205,21 +205,47 @@ public interface WindowInsetsController { /** * Controls the appearance of system bars. + * <p> + * For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}: + * <pre> + * setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS) + * </pre> + * And the following statement clears it: + * <pre> + * setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS) + * </pre> * * @param appearance Bitmask of {@link Appearance} flags. - * @see Appearance + * @param mask Specifies which flags of appearance should be changed. + * @see #getSystemBarsAppearance + */ + void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask); + + /** + * Retrieves the requested appearance of system bars. + * + * @return The requested bitmask of system bar appearance controlled by this window. + * @see #setSystemBarsAppearance(int, int) */ - void setSystemBarsAppearance(@Appearance int appearance); + @Appearance int getSystemBarsAppearance(); /** * Controls the behavior of system bars. * * @param behavior Determines how the bars behave when being hidden by the application. - * @see Behavior + * @see #getSystemBarsBehavior */ void setSystemBarsBehavior(@Behavior int behavior); /** + * Retrieves the requested behavior of system bars. + * + * @return the system bar behavior controlled by this window. + * @see #setSystemBarsBehavior(int) + */ + @Behavior int getSystemBarsBehavior(); + + /** * @hide */ InsetsState getState(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 290a13d3feb1..cd9dee4f7329 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1857,18 +1857,32 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000; /** + * Flag to indicate that the window is controlling the appearance of system bars. So we + * don't need to adjust it by reading its system UI flags for compatibility. + * @hide + */ + public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000; + + /** + * Flag to indicate that the window is controlling the behavior of system bars. So we don't + * need to adjust it by reading its window flags or system UI flags for compatibility. + * @hide + */ + public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000; + + /** * Flag to indicate that the window is controlling how it fits window insets on its own. * So we don't need to adjust its attributes for fitting window insets. * @hide */ - public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x04000000; + public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000; /** * Flag to indicate that the window only draws the bottom bar background so that we don't * extend it to system bar areas at other sides. * @hide */ - public static final int PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND = 0x08000000; + public static final int PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND = 0x20000000; /** * An internal annotation for flags that can be specified to {@link #softInputMode}. @@ -1970,6 +1984,14 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, name = "COLOR_SPACE_AGNOSTIC"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED, + equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED, + name = "APPEARANCE_CONTROLLED"), + @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_BEHAVIOR_CONTROLLED, + equals = PRIVATE_FLAG_BEHAVIOR_CONTROLLED, + name = "BEHAVIOR_CONTROLLED"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED, equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED, name = "FIT_INSETS_CONTROLLED"), diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 1312a9b1c158..9f2784889cab 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -18,6 +18,7 @@ package android.view; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; @@ -159,7 +160,8 @@ public class WindowlessWindowManager implements IWindowSession { Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { State state = null; synchronized (this) { state = mStateForWindow.get(window.asBinder()); diff --git a/core/tests/coretests/src/android/content/PermissionCheckerTest.java b/core/tests/coretests/src/android/content/PermissionCheckerTest.java new file mode 100644 index 000000000000..cb04a7497d3b --- /dev/null +++ b/core/tests/coretests/src/android/content/PermissionCheckerTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AppOpsManager; +import android.app.UiAutomation; +import android.os.Binder; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +// TODO(b/147877945): Add missing tests. +@RunWith(AndroidJUnit4.class) +public class PermissionCheckerTest { + private static final String INTERACT_ACROSS_PROFILES_PERMISSION = + "android.permission.INTERACT_ACROSS_PROFILES"; + private static final String MANAGE_APP_OPS_MODE = "android.permission.MANAGE_APP_OPS_MODES"; + + private final Context mContext = InstrumentationRegistry.getContext();; + private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + private UiAutomation mUiAutomation = + InstrumentationRegistry.getInstrumentation().getUiAutomation(); + + @After + public void tearDown() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeDefaultAndPermissionGranted_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity( + INTERACT_ACROSS_PROFILES_PERMISSION, MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeDefaultAndPermissionNotGranted_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeAllowed_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_packageNameIsNull_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), /* packageName= */ null)) + .isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeIgnored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_IGNORED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeErrored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ERRORED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeDefaultAndPermissionGranted_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity( + INTERACT_ACROSS_PROFILES_PERMISSION, MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeDefaultAndPermissionNotGranted_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeAllowed_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_packageNameIsNull_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), /* packageName= */ null, /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeIgnored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_IGNORED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeErrored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ERRORED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 46a7d830ac3d..18b5f5d6ac38 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -68,7 +68,6 @@ applications that come with the platform <privapp-permissions package="com.android.managedprovisioning"> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> <permission name="android.permission.CHANGE_CONFIGURATION"/> - <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.CRYPT_KEEPER"/> <permission name="android.permission.DELETE_PACKAGES"/> <permission name="android.permission.INSTALL_PACKAGES"/> @@ -375,7 +374,6 @@ applications that come with the platform </privapp-permissions> <privapp-permissions package="com.android.vpndialogs"> - <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.CONTROL_VPN"/> </privapp-permissions> diff --git a/identity/MODULE_LICENSE_APACHE2 b/identity/MODULE_LICENSE_APACHE2 new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/identity/MODULE_LICENSE_APACHE2 diff --git a/identity/NOTICE b/identity/NOTICE new file mode 100644 index 000000000000..64aaa8dbd68e --- /dev/null +++ b/identity/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2009, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/identity/OWNERS b/identity/OWNERS new file mode 100644 index 000000000000..533d90b2cd13 --- /dev/null +++ b/identity/OWNERS @@ -0,0 +1,3 @@ +swillden@google.com +zeuthen@google.com + diff --git a/identity/java/android/security/identity/AccessControlProfile.java b/identity/java/android/security/identity/AccessControlProfile.java new file mode 100644 index 000000000000..10e451c97707 --- /dev/null +++ b/identity/java/android/security/identity/AccessControlProfile.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +import java.security.cert.X509Certificate; + +/** + * A class used to specify access controls. + */ +public class AccessControlProfile { + private AccessControlProfileId mAccessControlProfileId = new AccessControlProfileId(0); + private X509Certificate mReaderCertificate = null; + private boolean mUserAuthenticationRequired = true; + private long mUserAuthenticationTimeout = 0; + + private AccessControlProfile() { + } + + AccessControlProfileId getAccessControlProfileId() { + return mAccessControlProfileId; + } + + long getUserAuthenticationTimeout() { + return mUserAuthenticationTimeout; + } + + boolean isUserAuthenticationRequired() { + return mUserAuthenticationRequired; + } + + X509Certificate getReaderCertificate() { + return mReaderCertificate; + } + + /** + * A builder for {@link AccessControlProfile}. + */ + public static final class Builder { + private AccessControlProfile mProfile; + + /** + * Each access control profile has numeric identifier that must be unique within the + * context of a Credential and may be used to reference the profile. + * + * <p>By default, the resulting {@link AccessControlProfile} will require user + * authentication with a timeout of zero, thus requiring the holder to authenticate for + * every presentation where data elements using this access control profile is used.</p> + * + * @param accessControlProfileId the access control profile identifier. + */ + public Builder(@NonNull AccessControlProfileId accessControlProfileId) { + mProfile = new AccessControlProfile(); + mProfile.mAccessControlProfileId = accessControlProfileId; + } + + /** + * Set whether user authentication is required. + * + * <p>This should be used sparingly since disabling user authentication on just a single + * data element can easily create a + * <a href="https://en.wikipedia.org/wiki/Relay_attack">Relay Attack</a> if the device + * on which the credential is stored is compromised.</p> + * + * @param userAuthenticationRequired Set to true if user authentication is required, + * false otherwise. + * @return The builder. + */ + public @NonNull Builder setUserAuthenticationRequired(boolean userAuthenticationRequired) { + mProfile.mUserAuthenticationRequired = userAuthenticationRequired; + return this; + } + + /** + * Sets the authentication timeout to use. + * + * <p>The authentication timeout specifies the amount of time, in milliseconds, for which a + * user authentication is valid, if user authentication is required (see + * {@link #setUserAuthenticationRequired(boolean)}).</p> + * + * <p>If the timeout is zero, then authentication is always required for each reader + * session.</p> + * + * @param userAuthenticationTimeoutMillis the authentication timeout, in milliseconds. + * @return The builder. + */ + public @NonNull Builder setUserAuthenticationTimeout(long userAuthenticationTimeoutMillis) { + mProfile.mUserAuthenticationTimeout = userAuthenticationTimeoutMillis; + return this; + } + + /** + * Sets the reader certificate to use when checking access control. + * + * <p>If set, this is checked against the certificate chain presented by + * reader. The access check is fulfilled only if one of the certificates + * in the chain, matches the certificate set by this method.</p> + * + * @param readerCertificate the certificate to use for the access control check. + * @return The builder. + */ + public @NonNull Builder setReaderCertificate(@NonNull X509Certificate readerCertificate) { + mProfile.mReaderCertificate = readerCertificate; + return this; + } + + /** + * Creates a new {@link AccessControlProfile} from the data supplied to the builder. + * + * @return The created {@link AccessControlProfile} object. + */ + public @NonNull AccessControlProfile build() { + return mProfile; + } + } +} diff --git a/identity/java/android/security/identity/AccessControlProfileId.java b/identity/java/android/security/identity/AccessControlProfileId.java new file mode 100644 index 000000000000..3d5945065ad7 --- /dev/null +++ b/identity/java/android/security/identity/AccessControlProfileId.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +/** + * A class used to wrap an access control profile identifiers. + */ +public class AccessControlProfileId { + private int mId = 0; + + /** + * Constructs a new object holding a numerical identifier. + * + * @param id the identifier. + */ + public AccessControlProfileId(int id) { + this.mId = id; + } + + /** + * Gets the numerical identifier wrapped by this object. + * + * @return the identifier. + */ + public int getId() { + return this.mId; + } +} diff --git a/identity/java/android/security/identity/AlreadyPersonalizedException.java b/identity/java/android/security/identity/AlreadyPersonalizedException.java new file mode 100644 index 000000000000..1933882aa4bf --- /dev/null +++ b/identity/java/android/security/identity/AlreadyPersonalizedException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to create a credential which already exists. + */ +public class AlreadyPersonalizedException extends IdentityCredentialException { + /** + * Constructs a new {@link AlreadyPersonalizedException} exception. + * + * @param message the detail message. + */ + public AlreadyPersonalizedException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link AlreadyPersonalizedException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public AlreadyPersonalizedException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/CipherSuiteNotSupportedException.java b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java new file mode 100644 index 000000000000..e7a6c8969482 --- /dev/null +++ b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to use a cipher suite which isn't supported. + */ +public class CipherSuiteNotSupportedException extends IdentityCredentialException { + /** + * Constructs a new {@link CipherSuiteNotSupportedException} exception. + * + * @param message the detail message. + */ + public CipherSuiteNotSupportedException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link CipherSuiteNotSupportedException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public CipherSuiteNotSupportedException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java new file mode 100644 index 000000000000..c520331ab72d --- /dev/null +++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java @@ -0,0 +1,424 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyAgreement; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +class CredstoreIdentityCredential extends IdentityCredential { + + private static final String TAG = "CredstoreIdentityCredential"; + private String mCredentialName; + private @IdentityCredentialStore.Ciphersuite int mCipherSuite; + private Context mContext; + private ICredential mBinder; + + CredstoreIdentityCredential(Context context, String credentialName, + @IdentityCredentialStore.Ciphersuite int cipherSuite, + ICredential binder) { + mContext = context; + mCredentialName = credentialName; + mCipherSuite = cipherSuite; + mBinder = binder; + } + + private KeyPair mEphemeralKeyPair = null; + private SecretKey mSecretKey = null; + private SecretKey mReaderSecretKey = null; + private int mEphemeralCounter; + private int mReadersExpectedEphemeralCounter; + + private void ensureEphemeralKeyPair() { + if (mEphemeralKeyPair != null) { + return; + } + try { + // This PKCS#12 blob is generated in credstore, using BoringSSL. + // + // The main reason for this convoluted approach and not just sending the decomposed + // key-pair is that this would require directly using (device-side) BouncyCastle which + // is tricky due to various API hiding efforts. So instead we have credstore generate + // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL + // doesn't support not using encryption when building a PKCS#12 blob). + // + byte[] pkcs12 = mBinder.createEphemeralKeyPair(); + String alias = "ephemeralKey"; + char[] password = {}; + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12); + ks.load(bais, password); + PrivateKey privKey = (PrivateKey) ks.getKey(alias, password); + + Certificate cert = ks.getCertificate(alias); + PublicKey pubKey = cert.getPublicKey(); + + mEphemeralKeyPair = new KeyPair(pubKey, privKey); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } catch (KeyStoreException + | CertificateException + | UnrecoverableKeyException + | NoSuchAlgorithmException + | IOException e) { + throw new RuntimeException("Unexpected exception ", e); + } + } + + @Override + public @NonNull KeyPair createEphemeralKeyPair() { + ensureEphemeralKeyPair(); + return mEphemeralKeyPair; + } + + @Override + public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) + throws InvalidKeyException { + try { + byte[] uncompressedForm = + Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey); + mBinder.setReaderEphemeralPublicKey(uncompressedForm); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + + ensureEphemeralKeyPair(); + + try { + KeyAgreement ka = KeyAgreement.getInstance("ECDH"); + ka.init(mEphemeralKeyPair.getPrivate()); + ka.doPhase(readerEphemeralPublicKey, true); + byte[] sharedSecret = ka.generateSecret(); + + byte[] salt = new byte[1]; + byte[] info = new byte[0]; + + salt[0] = 0x01; + byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); + mSecretKey = new SecretKeySpec(derivedKey, "AES"); + + salt[0] = 0x00; + derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); + mReaderSecretKey = new SecretKeySpec(derivedKey, "AES"); + + mEphemeralCounter = 0; + mReadersExpectedEphemeralCounter = 0; + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Error performing key agreement", e); + } + } + + @Override + public @NonNull byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext) { + byte[] messageCiphertextAndAuthTag = null; + try { + ByteBuffer iv = ByteBuffer.allocate(12); + iv.putInt(0, 0x00000000); + iv.putInt(4, 0x00000001); + iv.putInt(8, mEphemeralCounter); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array()); + cipher.init(Cipher.ENCRYPT_MODE, mSecretKey, encryptionParameterSpec); + messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext); + } catch (BadPaddingException + | IllegalBlockSizeException + | NoSuchPaddingException + | InvalidKeyException + | NoSuchAlgorithmException + | InvalidAlgorithmParameterException e) { + throw new RuntimeException("Error encrypting message", e); + } + mEphemeralCounter += 1; + return messageCiphertextAndAuthTag; + } + + @Override + public @NonNull byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext) + throws MessageDecryptionException { + ByteBuffer iv = ByteBuffer.allocate(12); + iv.putInt(0, 0x00000000); + iv.putInt(4, 0x00000000); + iv.putInt(8, mReadersExpectedEphemeralCounter); + byte[] plainText = null; + try { + final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, mReaderSecretKey, + new GCMParameterSpec(128, iv.array())); + plainText = cipher.doFinal(messageCiphertext); + } catch (BadPaddingException + | IllegalBlockSizeException + | InvalidAlgorithmParameterException + | InvalidKeyException + | NoSuchAlgorithmException + | NoSuchPaddingException e) { + throw new MessageDecryptionException("Error decrypting message", e); + } + mReadersExpectedEphemeralCounter += 1; + return plainText; + } + + @Override + public @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain() { + try { + byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(); + ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob); + + Collection<? extends Certificate> certs = null; + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + certs = factory.generateCertificates(bais); + } catch (CertificateException e) { + throw new RuntimeException("Error decoding certificates", e); + } + + LinkedList<X509Certificate> x509Certs = new LinkedList<>(); + for (Certificate cert : certs) { + x509Certs.add((X509Certificate) cert); + } + return x509Certs; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + private boolean mAllowUsingExhaustedKeys = true; + + @Override + public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) { + mAllowUsingExhaustedKeys = allowUsingExhaustedKeys; + } + + private boolean mOperationHandleSet = false; + private long mOperationHandle = 0; + + /** + * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an + * operation handle. + * + * @hide + */ + @Override + public long getCredstoreOperationHandle() { + if (!mOperationHandleSet) { + try { + mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys); + mOperationHandleSet = true; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) { + // The NoAuthenticationKeyAvailableException will be thrown when + // the caller proceeds to call getEntries(). + } + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + return mOperationHandle; + } + + @NonNull + @Override + public ResultData getEntries( + @Nullable byte[] requestMessage, + @NonNull Map<String, Collection<String>> entriesToRequest, + @Nullable byte[] sessionTranscript, + @Nullable byte[] readerSignature) + throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException, + InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException, + InvalidRequestMessageException { + + RequestNamespaceParcel[] rnsParcels = new RequestNamespaceParcel[entriesToRequest.size()]; + int n = 0; + for (String namespaceName : entriesToRequest.keySet()) { + Collection<String> entryNames = entriesToRequest.get(namespaceName); + rnsParcels[n] = new RequestNamespaceParcel(); + rnsParcels[n].namespaceName = namespaceName; + rnsParcels[n].entries = new RequestEntryParcel[entryNames.size()]; + int m = 0; + for (String entryName : entryNames) { + rnsParcels[n].entries[m] = new RequestEntryParcel(); + rnsParcels[n].entries[m].name = entryName; + m++; + } + n++; + } + + GetEntriesResultParcel resultParcel = null; + try { + resultParcel = mBinder.getEntries( + requestMessage != null ? requestMessage : new byte[0], + rnsParcels, + sessionTranscript != null ? sessionTranscript : new byte[0], + readerSignature != null ? readerSignature : new byte[0], + mAllowUsingExhaustedKeys); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND) { + throw new EphemeralPublicKeyNotFoundException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_INVALID_READER_SIGNATURE) { + throw new InvalidReaderSignatureException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) { + throw new NoAuthenticationKeyAvailableException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_INVALID_ITEMS_REQUEST_MESSAGE) { + throw new InvalidRequestMessageException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_SESSION_TRANSCRIPT_MISMATCH) { + throw new SessionTranscriptMismatchException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + byte[] mac = resultParcel.mac; + if (mac != null && mac.length == 0) { + mac = null; + } + CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder( + resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac); + + for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) { + for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) { + if (resultEntryParcel.status == ICredential.STATUS_OK) { + resultDataBuilder.addEntry(resultNamespaceParcel.namespaceName, + resultEntryParcel.name, resultEntryParcel.value); + } else { + resultDataBuilder.addErrorStatus(resultNamespaceParcel.namespaceName, + resultEntryParcel.name, + resultEntryParcel.status); + } + } + } + return resultDataBuilder.build(); + } + + @Override + public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) { + try { + mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override + public @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() { + try { + AuthKeyParcel[] authKeyParcels = mBinder.getAuthKeysNeedingCertification(); + LinkedList<X509Certificate> x509Certs = new LinkedList<>(); + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + for (AuthKeyParcel authKeyParcel : authKeyParcels) { + Collection<? extends Certificate> certs = null; + ByteArrayInputStream bais = new ByteArrayInputStream(authKeyParcel.x509cert); + certs = factory.generateCertificates(bais); + if (certs.size() != 1) { + throw new RuntimeException("Returned blob yields more than one X509 cert"); + } + X509Certificate authKeyCert = (X509Certificate) certs.iterator().next(); + x509Certs.add(authKeyCert); + } + return x509Certs; + } catch (CertificateException e) { + throw new RuntimeException("Error decoding authenticationKey", e); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override + public void storeStaticAuthenticationData(X509Certificate authenticationKey, + byte[] staticAuthData) + throws UnknownAuthenticationKeyException { + try { + AuthKeyParcel authKeyParcel = new AuthKeyParcel(); + authKeyParcel.x509cert = authenticationKey.getEncoded(); + mBinder.storeStaticAuthenticationData(authKeyParcel, staticAuthData); + } catch (CertificateEncodingException e) { + throw new RuntimeException("Error encoding authenticationKey", e); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_AUTHENTICATION_KEY_NOT_FOUND) { + throw new UnknownAuthenticationKeyException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override + public @NonNull int[] getAuthenticationDataUsageCount() { + try { + int[] usageCount = mBinder.getAuthenticationDataUsageCount(); + return usageCount; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } +} diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java new file mode 100644 index 000000000000..dcc6b95aec02 --- /dev/null +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -0,0 +1,162 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.ServiceManager; + +class CredstoreIdentityCredentialStore extends IdentityCredentialStore { + + private static final String TAG = "CredstoreIdentityCredentialStore"; + + private Context mContext = null; + private ICredentialStore mStore = null; + + private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) { + mContext = context; + mStore = store; + } + + static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context, + int credentialStoreType) { + ICredentialStoreFactory storeFactory = + ICredentialStoreFactory.Stub.asInterface( + ServiceManager.getService("android.security.identity")); + + ICredentialStore credStore = null; + try { + credStore = storeFactory.getCredentialStore(credentialStoreType); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_GENERIC) { + return null; + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + if (credStore == null) { + return null; + } + + return new CredstoreIdentityCredentialStore(context, credStore); + } + + private static CredstoreIdentityCredentialStore sInstanceDefault = null; + private static CredstoreIdentityCredentialStore sInstanceDirectAccess = null; + + public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) { + if (sInstanceDefault == null) { + sInstanceDefault = getInstanceForType(context, + ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DEFAULT); + } + return sInstanceDefault; + } + + public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull + Context context) { + if (sInstanceDirectAccess == null) { + sInstanceDirectAccess = getInstanceForType(context, + ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DIRECT_ACCESS); + } + return sInstanceDirectAccess; + } + + @Override + public @NonNull String[] getSupportedDocTypes() { + try { + SecurityHardwareInfoParcel info; + info = mStore.getSecurityHardwareInfo(); + return info.supportedDocTypes; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override public @NonNull WritableIdentityCredential createCredential( + @NonNull String credentialName, + @NonNull String docType) throws AlreadyPersonalizedException, + DocTypeNotSupportedException { + try { + IWritableCredential wc; + wc = mStore.createCredential(credentialName, docType); + return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_ALREADY_PERSONALIZED) { + throw new AlreadyPersonalizedException(e.getMessage(), e); + } else if (e.errorCode == ICredentialStore.ERROR_DOCUMENT_TYPE_NOT_SUPPORTED) { + throw new DocTypeNotSupportedException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override public @Nullable IdentityCredential getCredentialByName( + @NonNull String credentialName, + @Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException { + try { + ICredential credstoreCredential; + credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite); + return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite, + credstoreCredential); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) { + return null; + } else if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) { + throw new CipherSuiteNotSupportedException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override + public @Nullable byte[] deleteCredentialByName(@NonNull String credentialName) { + ICredential credstoreCredential = null; + try { + try { + credstoreCredential = mStore.getCredentialByName(credentialName, + CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) { + return null; + } + } + byte[] proofOfDeletion = credstoreCredential.deleteCredential(); + return proofOfDeletion; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + +} diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java new file mode 100644 index 000000000000..ef7afca6b888 --- /dev/null +++ b/identity/java/android/security/identity/CredstoreResultData.java @@ -0,0 +1,162 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; + +/** + * An object that contains the result of retrieving data from a credential. This is used to return + * data requested from a {@link IdentityCredential}. + */ +class CredstoreResultData extends ResultData { + + byte[] mStaticAuthenticationData = null; + byte[] mAuthenticatedData = null; + byte[] mMessageAuthenticationCode = null; + + private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>(); + + private static class EntryData { + @Status + int mStatus; + byte[] mValue; + + EntryData(byte[] value, @Status int status) { + this.mValue = value; + this.mStatus = status; + } + } + + CredstoreResultData() {} + + @Override + public @NonNull byte[] getAuthenticatedData() { + return mAuthenticatedData; + } + + @Override + public @Nullable byte[] getMessageAuthenticationCode() { + return mMessageAuthenticationCode; + } + + @Override + public @NonNull byte[] getStaticAuthenticationData() { + return mStaticAuthenticationData; + } + + @Override + public @NonNull Collection<String> getNamespaceNames() { + return Collections.unmodifiableCollection(mData.keySet()); + } + + @Override + public @Nullable Collection<String> getEntryNames(@NonNull String namespaceName) { + Map<String, EntryData> innerMap = mData.get(namespaceName); + if (innerMap == null) { + return null; + } + return Collections.unmodifiableCollection(innerMap.keySet()); + } + + @Override + public @Nullable Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) { + Map<String, EntryData> innerMap = mData.get(namespaceName); + if (innerMap == null) { + return null; + } + LinkedList<String> result = new LinkedList<String>(); + for (Map.Entry<String, EntryData> entry : innerMap.entrySet()) { + if (entry.getValue().mStatus == STATUS_OK) { + result.add(entry.getKey()); + } + } + return result; + } + + private EntryData getEntryData(@NonNull String namespaceName, @NonNull String name) { + Map<String, EntryData> innerMap = mData.get(namespaceName); + if (innerMap == null) { + return null; + } + return innerMap.get(name); + } + + @Override + @Status + public int getStatus(@NonNull String namespaceName, @NonNull String name) { + EntryData value = getEntryData(namespaceName, name); + if (value == null) { + return STATUS_NOT_REQUESTED; + } + return value.mStatus; + } + + @Override + public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) { + EntryData value = getEntryData(namespaceName, name); + if (value == null) { + return null; + } + return value.mValue; + } + + static class Builder { + private CredstoreResultData mResultData; + + Builder(byte[] staticAuthenticationData, + byte[] authenticatedData, + byte[] messageAuthenticationCode) { + this.mResultData = new CredstoreResultData(); + this.mResultData.mStaticAuthenticationData = staticAuthenticationData; + this.mResultData.mAuthenticatedData = authenticatedData; + this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode; + } + + private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) { + Map<String, EntryData> innerMap = mResultData.mData.get(namespaceName); + if (innerMap == null) { + innerMap = new LinkedHashMap<>(); + mResultData.mData.put(namespaceName, innerMap); + } + return innerMap; + } + + Builder addEntry(String namespaceName, String name, byte[] value) { + Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName); + innerMap.put(name, new EntryData(value, STATUS_OK)); + return this; + } + + Builder addErrorStatus(String namespaceName, String name, @Status int status) { + Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName); + innerMap.put(name, new EntryData(null, status)); + return this; + } + + CredstoreResultData build() { + return mResultData; + } + } + +} diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java new file mode 100644 index 000000000000..335636cb07ae --- /dev/null +++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java @@ -0,0 +1,168 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.content.Context; +import android.security.GateKeeper; + +import java.io.ByteArrayInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.LinkedList; + +class CredstoreWritableIdentityCredential extends WritableIdentityCredential { + + private static final String TAG = "CredstoreWritableIdentityCredential"; + + private String mDocType; + private String mCredentialName; + private Context mContext; + private IWritableCredential mBinder; + + CredstoreWritableIdentityCredential(Context context, + @NonNull String credentialName, + @NonNull String docType, + IWritableCredential binder) { + mContext = context; + mDocType = docType; + mCredentialName = credentialName; + mBinder = binder; + } + + @NonNull @Override + public Collection<X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[] challenge) { + try { + byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge); + ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob); + + Collection<? extends Certificate> certs = null; + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + certs = factory.generateCertificates(bais); + } catch (CertificateException e) { + throw new RuntimeException("Error decoding certificates", e); + } + + LinkedList<X509Certificate> x509Certs = new LinkedList<>(); + for (Certificate cert : certs) { + x509Certs.add((X509Certificate) cert); + } + return x509Certs; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @NonNull @Override + public byte[] personalize(@NonNull PersonalizationData personalizationData) { + + Collection<AccessControlProfile> accessControlProfiles = + personalizationData.getAccessControlProfiles(); + + AccessControlProfileParcel[] acpParcels = + new AccessControlProfileParcel[accessControlProfiles.size()]; + boolean usingUserAuthentication = false; + int n = 0; + for (AccessControlProfile profile : accessControlProfiles) { + acpParcels[n] = new AccessControlProfileParcel(); + acpParcels[n].id = profile.getAccessControlProfileId().getId(); + X509Certificate cert = profile.getReaderCertificate(); + if (cert != null) { + try { + acpParcels[n].readerCertificate = cert.getEncoded(); + } catch (CertificateException e) { + throw new RuntimeException("Error encoding reader certificate", e); + } + } else { + acpParcels[n].readerCertificate = new byte[0]; + } + acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired(); + acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout(); + if (profile.isUserAuthenticationRequired()) { + usingUserAuthentication = true; + } + n++; + } + + Collection<String> namespaceNames = personalizationData.getNamespaceNames(); + + EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaceNames.size()]; + n = 0; + for (String namespaceName : namespaceNames) { + PersonalizationData.NamespaceData nsd = + personalizationData.getNamespaceData(namespaceName); + + ensParcels[n] = new EntryNamespaceParcel(); + ensParcels[n].namespaceName = namespaceName; + + Collection<String> entryNames = nsd.getEntryNames(); + EntryParcel[] eParcels = new EntryParcel[entryNames.size()]; + int m = 0; + for (String entryName : entryNames) { + eParcels[m] = new EntryParcel(); + eParcels[m].name = entryName; + eParcels[m].value = nsd.getEntryValue(entryName); + Collection<AccessControlProfileId> acpIds = + nsd.getAccessControlProfileIds(entryName); + eParcels[m].accessControlProfileIds = new int[acpIds.size()]; + int o = 0; + for (AccessControlProfileId acpId : acpIds) { + eParcels[m].accessControlProfileIds[o++] = acpId.getId(); + } + m++; + } + ensParcels[n].entries = eParcels; + n++; + } + + // Note: The value 0 is used to convey that no user-authentication is needed for this + // credential. This is to allow creating credentials w/o user authentication on devices + // where Secure lock screen is not enabled. + long secureUserId = 0; + if (usingUserAuthentication) { + secureUserId = getRootSid(); + } + try { + byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels, + secureUserId); + return personalizationReceipt; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + private static long getRootSid() { + long rootSid = GateKeeper.getSecureUserId(); + if (rootSid == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to create credentials requiring user authentication"); + } + return rootSid; + } + + +} diff --git a/identity/java/android/security/identity/DocTypeNotSupportedException.java b/identity/java/android/security/identity/DocTypeNotSupportedException.java new file mode 100644 index 000000000000..754e44af5411 --- /dev/null +++ b/identity/java/android/security/identity/DocTypeNotSupportedException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to create a credential with an unsupported document type. + */ +public class DocTypeNotSupportedException extends IdentityCredentialException { + /** + * Constructs a new {@link DocTypeNotSupportedException} exception. + * + * @param message the detail message. + */ + public DocTypeNotSupportedException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link DocTypeNotSupportedException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public DocTypeNotSupportedException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java new file mode 100644 index 000000000000..265f271b54f7 --- /dev/null +++ b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if the ephemeral public key was not found in the session transcript + * passed to {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}. + */ +public class EphemeralPublicKeyNotFoundException extends IdentityCredentialException { + /** + * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception. + * + * @param message the detail message. + */ + public EphemeralPublicKeyNotFoundException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public EphemeralPublicKeyNotFoundException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java new file mode 100644 index 000000000000..bd439199f914 --- /dev/null +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -0,0 +1,309 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Map; + +/** + * Class used to read data from a previously provisioned credential. + * + * Use {@link IdentityCredentialStore#getCredentialByName(String, int)} to get a + * {@link IdentityCredential} instance. + */ +public abstract class IdentityCredential { + /** + * @hide + */ + protected IdentityCredential() {} + + /** + * Create an ephemeral key pair to use to establish a secure channel with a reader. + * + * <p>Most applications will use only the public key, and only to send it to the reader, + * allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])} + * and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for + * applications that wish to use a cipher suite that is not supported by + * {@link IdentityCredentialStore}. + * + * @return ephemeral key pair to use to establish a secure channel with a reader. + */ + public @NonNull abstract KeyPair createEphemeralKeyPair(); + + /** + * Set the ephemeral public key provided by the reader. This must be called before + * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called. + * + * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to + * establish a secure session. + * @throws InvalidKeyException if the given key is invalid. + */ + public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey) + throws InvalidKeyException; + + /** + * Encrypt a message for transmission to the reader. + * + * @param messagePlaintext unencrypted message to encrypt. + * @return encrypted message. + */ + public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext); + + /** + * Decrypt a message received from the reader. + * + * @param messageCiphertext encrypted message to decrypt. + * @return decrypted message. + * @throws MessageDecryptionException if the ciphertext couldn't be decrypted. + */ + public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext) + throws MessageDecryptionException; + + /** + * Gets the X.509 certificate chain for the CredentialKey which identifies this + * credential to the issuing authority. This is the same certificate chain that + * was returned by {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])} + * when the credential was first created and its Android Keystore extension will + * contain the <code>challenge</code> data set at that time. See the documentation + * for that method for important information about this certificate chain. + * + * @return the certificate chain for this credential's CredentialKey. + */ + public @NonNull abstract Collection<X509Certificate> getCredentialKeyCertificateChain(); + + /** + * Sets whether to allow using an authentication key which use count has been exceeded if no + * other key is available. This must be called prior to calling + * {@link #getEntries(byte[], Map, byte[], byte[])} or using a + * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this + * object. + * + * By default this is set to true. + * + * @param allowUsingExhaustedKeys whether to allow using an authentication key which use count + * has been exceeded if no other key is available. + */ + public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys); + + /** + * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an + * operation handle. + * + * @hide + */ + public abstract long getCredstoreOperationHandle(); + + /** + * Retrieve data entries and associated data from this {@code IdentityCredential}. + * + * <p>If an access control check fails for one of the requested entries or if the entry + * doesn't exist, the entry is simply not returned. The application can detect this + * by using the {@link ResultData#getStatus(String, String)} method on each of the requested + * entries. + * + * <p>It is the responsibility of the calling application to know if authentication is needed + * and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user + * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which + * references this object. If needed, this must be done before calling + * {@link #getEntries(byte[], Map, byte[], byte[])}. + * + * <p>If this method returns successfully (i.e. without throwing an exception), it must not be + * called again on this instance. + * + * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request + * from the verifier. The content can be defined in the way appropriate for the credential, byt + * there are three requirements that must be met to work with this API: + * <ul> + * <li>The content must be a CBOR-encoded structure.</li> + * <li>The CBOR structure must be a map.</li> + * <li>The map must contain a tstr key "nameSpaces" whose value contains a map, as described in + * the example below.</li> + * </ul> + * + * <p>Here's an example of CBOR which conforms to this requirement: + * <pre> + * ItemsRequest = { + * ? "docType" : DocType, + * "nameSpaces" : NameSpaces, + * ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide + * } + * + * NameSpaces = { + * + NameSpace => DataElements ; Requested data elements for each NameSpace + * } + * + * NameSpace = tstr + * + * DataElements = { + * + DataElement => IntentToRetain + * } + * + * DataElement = tstr + * IntentToRetain = bool + * </pre> + * + * <p>If the {@code sessionTranscript} parameter is not {@code null}, it must contain CBOR + * data conforming to the following CDDL schema: + * + * <pre> + * SessionTranscript = [ + * DeviceEngagementBytes, + * EReaderKeyBytes + * ] + * + * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) + * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) + * </pre> + * + * <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part + * of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear + * somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present + * in uncompressed form. + * + * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1 + * structure as defined in RFC 8152. For the payload nil shall be used and the + * detached payload is the ReaderAuthentication CBOR described below. + * <pre> + * ReaderAuthentication = [ + * "ReaderAuthentication", + * SessionTranscript, + * ItemsRequestBytes + * ] + * + * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) ; Bytes of ItemsRequest + * </pre> + * + * <p>The public key corresponding to the key used to made signature, can be + * found in the {@code x5chain} unprotected header element of the COSE_Sign1 + * structure (as as described in 'draft-ietf-cose-x509-04'). There will be at + * least one certificate in said element and there may be more (and if so, + * each certificate must be signed by its successor). + * + * <p>Data elements protected by reader authentication is returned if, and only if, they are + * mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most + * certificate in {@code readerCertificateChain}, and the data element is configured + * with an {@link AccessControlProfile} with a {@link X509Certificate} in + * {@code readerCertificateChain}. + * + * <p>Note that only items referenced in {@code entriesToRequest} are returned - the + * {@code requestMessage} parameter is only used to for enforcing reader authentication. + * + * @param requestMessage If not {@code null}, must contain CBOR data conforming to + * the schema mentioned above. + * @param entriesToRequest The entries to request, organized as a map of namespace + * names with each value being a collection of data elements + * in the given namespace. + * @param readerSignature COSE_Sign1 structure as described above or {@code null} + * if reader authentication is not being used. + * @return A {@link ResultData} object containing entry data organized by namespace and a + * cryptographically authenticated representation of the same data. + * @throws SessionTranscriptMismatchException Thrown when trying use multiple different + * session transcripts in the same presentation + * session. + * @throws NoAuthenticationKeyAvailableException if authentication keys were never + * provisioned, the method + * {@link #setAvailableAuthenticationKeys(int, int)} + * was called with {@code keyCount} set to 0, + * the method + * {@link #setAllowUsingExhaustedKeys(boolean)} + * was called with {@code false} and all + * available authentication keys have been + * exhausted. + * @throws InvalidReaderSignatureException if the reader signature is invalid, or it + * doesn't contain a certificate chain, or if + * the signature failed to validate. + * @throws InvalidRequestMessageException if the requestMessage is malformed. + * @throws EphemeralPublicKeyNotFoundException if the ephemeral public key was not found in + * the session transcript. + */ + public abstract @NonNull ResultData getEntries( + @Nullable byte[] requestMessage, + @NonNull Map<String, Collection<String>> entriesToRequest, + @Nullable byte[] sessionTranscript, + @Nullable byte[] readerSignature) + throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException, + InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException, + InvalidRequestMessageException; + + /** + * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain, + * and the number of times each should be used. + * + * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each + * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s + * for which this method has not been called behave as though it had been called wit + * {@code keyCount} 0 and {@code maxUsesPerKey} 1. + * + * @param keyCount The number of active, certified dynamic authentication keys the + * {@code IdentityCredential} will try to keep available. This value + * must be non-negative. + * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's + * eligible for replacement. This value must be greater than zero. + */ + public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey); + + /** + * Gets a collection of dynamic authentication keys that need certification. + * + * <p>When there aren't enough certified dynamic authentication keys, either because the key + * count has been increased or because one or more keys have reached their usage count, this + * method will generate replacement keys and certificates and return them for issuer + * certification. The issuer certificates and associated static authentication data must then + * be provided back to the {@code IdentityCredential} using + * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}. + * + * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey + * can be obtained using the {@link #getCredentialKeyCertificateChain()} method. + * + * @return A collection of X.509 certificates for dynamic authentication keys that need issuer + * certification. + */ + public @NonNull abstract Collection<X509Certificate> getAuthKeysNeedingCertification(); + + /** + * Store authentication data associated with a dynamic authentication key. + * + * This should only be called for an authenticated key returned by + * {@link #getAuthKeysNeedingCertification()}. + * + * @param authenticationKey The dynamic authentication key for which certification and + * associated static + * authentication data is being provided. + * @param staticAuthData Static authentication data provided by the issuer that validates + * the authenticity + * and integrity of the credential data fields. + * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized. + */ + public abstract void storeStaticAuthenticationData( + @NonNull X509Certificate authenticationKey, + @NonNull byte[] staticAuthData) + throws UnknownAuthenticationKeyException; + + /** + * Get the number of times the dynamic authentication keys have been used. + * + * @return int array of dynamic authentication key usage counts. + */ + public @NonNull abstract int[] getAuthenticationDataUsageCount(); +} diff --git a/identity/java/android/security/identity/IdentityCredentialException.java b/identity/java/android/security/identity/IdentityCredentialException.java new file mode 100644 index 000000000000..c8113803d5d3 --- /dev/null +++ b/identity/java/android/security/identity/IdentityCredentialException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Base class for all Identity Credential exceptions. + */ +public class IdentityCredentialException extends Exception { + /** + * Constructs a new {@link IdentityCredentialException} exception. + * + * @param message the detail message. + */ + public IdentityCredentialException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link IdentityCredentialException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public IdentityCredentialException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } + +} diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java new file mode 100644 index 000000000000..a1dfc77adb29 --- /dev/null +++ b/identity/java/android/security/identity/IdentityCredentialStore.java @@ -0,0 +1,186 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An interface to a secure store for user identity documents. + * + * <p>This interface is deliberately fairly general and abstract. To the extent possible, + * specification of the message formats and semantics of communication with credential + * verification devices and issuing authorities (IAs) is out of scope. It provides the + * interface with secure storage but a credential-specific Android application will be + * required to implement the presentation and verification protocols and processes + * appropriate for the specific credential type. + * + * <p>Multiple credentials can be created. Each credential comprises:</p> + * <ul> + * <li>A document type, which is a string.</li> + * + * <li>A set of namespaces, which serve to disambiguate value names. It is recommended + * that namespaces be structured as reverse domain names so that IANA effectively serves + * as the namespace registrar.</li> + * + * <li>For each namespace, a set of name/value pairs, each with an associated set of + * access control profile IDs. Names are strings and values are typed and can be any + * value supported by <a href="http://cbor.io/">CBOR</a>.</li> + * + * <li>A set of access control profiles, each with a profile ID and a specification + * of the conditions which satisfy the profile's requirements.</li> + * + * <li>An asymmetric key pair which is used to authenticate the credential to the Issuing + * Authority, called the <em>CredentialKey</em>.</li> + * + * <li>A set of zero or more named reader authentication public keys, which are used to + * authenticate an authorized reader to the credential.</li> + * + * <li>A set of named signing keys, which are used to sign collections of values and session + * transcripts.</li> + * </ul> + * + * <p>Implementing support for user identity documents in secure storage requires dedicated + * hardware-backed support and may not always be available. + * + * <p>Two different credential stores exist - the <em>default</em> store and the + * <em>direct access</em> store. Most often credentials will be accessed through the default + * store but that requires that the Android device be powered up and fully functional. + * It is desirable to allow identity credential usage when the Android device's battery is too + * low to boot the Android operating system, so direct access to the secure hardware via NFC + * may allow data retrieval, if the secure hardware chooses to implement it. + * + * <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader + * authentication to protect data elements. The reason for this is user authentication or user + * approval of data release is not possible when the device is off. + */ +public abstract class IdentityCredentialStore { + IdentityCredentialStore() {} + + /** + * Specifies that the cipher suite that will be used to secure communications between the reader + * is: + * + * <ul> + * <li>ECDHE with HKDF-SHA-256 for key agreement.</li> + * <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one + * for every message).</li> + * <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat + * man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential} + * for details on reader and prover signing keys.</li> + * </ul> + * + * <p> + * At present this is the only supported cipher suite. + */ + public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; + + /** + * Gets the default {@link IdentityCredentialStore}. + * + * @param context the application context. + * @return the {@link IdentityCredentialStore} or {@code null} if the device doesn't + * have hardware-backed support for secure storage of user identity documents. + */ + public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) { + return CredstoreIdentityCredentialStore.getInstance(context); + } + + /** + * Gets the {@link IdentityCredentialStore} for direct access. + * + * <p>Direct access requires specialized NFC hardware and may not be supported on all + * devices even if default store is available. Credentials provisioned to the direct + * access store should <strong>always</strong> use reader authentication to protect + * data elements. + * + * @param context the application context. + * @return the {@link IdentityCredentialStore} or {@code null} if direct access is not + * supported on this device. + */ + public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull + Context context) { + return CredstoreIdentityCredentialStore.getDirectAccessInstance(context); + } + + /** + * Gets a list of supported document types. + * + * <p>Only the direct-access store may restrict the kind of document types that can be used for + * credentials. The default store always supports any document type. + * + * @return The supported document types or the empty array if any document type is supported. + */ + public abstract @NonNull String[] getSupportedDocTypes(); + + /** + * Creates a new credential. + * + * @param credentialName The name used to identify the credential. + * @param docType The document type for the credential. + * @return A @{link WritableIdentityCredential} that can be used to create a new credential. + * @throws AlreadyPersonalizedException if a credential with the given name already exists. + * @throws DocTypeNotSupportedException if the given document type isn't supported by the store. + */ + public abstract @NonNull WritableIdentityCredential createCredential( + @NonNull String credentialName, @NonNull String docType) + throws AlreadyPersonalizedException, DocTypeNotSupportedException; + + /** + * Retrieve a named credential. + * + * @param credentialName the name of the credential to retrieve. + * @param cipherSuite the cipher suite to use for communicating with the verifier. + * @return The named credential, or null if not found. + */ + public abstract @Nullable IdentityCredential getCredentialByName(@NonNull String credentialName, + @Ciphersuite int cipherSuite) + throws CipherSuiteNotSupportedException; + + /** + * Delete a named credential. + * + * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey + * with payload set to {@code ProofOfDeletion} as defined below: + * + * <pre> + * ProofOfDeletion = [ + * "ProofOfDeletion", ; tstr + * tstr, ; DocType + * bool ; true if this is a test credential, should + * ; always be false. + * ] + * </pre> + * + * @param credentialName the name of the credential to delete. + * @return {@code null} if the credential was not found, the COSE_Sign1 data structure above + * if the credential was found and deleted. + */ + public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName); + + /** @hide */ + @IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256}) + @Retention(RetentionPolicy.SOURCE) + public @interface Ciphersuite { + } + +} diff --git a/identity/java/android/security/identity/InvalidReaderSignatureException.java b/identity/java/android/security/identity/InvalidReaderSignatureException.java new file mode 100644 index 000000000000..3f7027005830 --- /dev/null +++ b/identity/java/android/security/identity/InvalidReaderSignatureException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if the reader signature is invalid, or it doesn't contain a certificate chain, or if the + * signature failed to validate. + */ +public class InvalidReaderSignatureException extends IdentityCredentialException { + /** + * Constructs a new {@link InvalidReaderSignatureException} exception. + * + * @param message the detail message. + */ + public InvalidReaderSignatureException(@NonNull String message) { + super(message); + } + + + /** + * Constructs a new {@link InvalidReaderSignatureException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public InvalidReaderSignatureException(@NonNull String message, + @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/InvalidRequestMessageException.java b/identity/java/android/security/identity/InvalidRequestMessageException.java new file mode 100644 index 000000000000..b0c073c33a8d --- /dev/null +++ b/identity/java/android/security/identity/InvalidRequestMessageException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if message with the request doesn't satisfy the requirements documented in + * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}. + */ +public class InvalidRequestMessageException extends IdentityCredentialException { + /** + * Constructs a new {@link InvalidRequestMessageException} exception. + * + * @param message the detail message. + */ + public InvalidRequestMessageException(@NonNull String message) { + super(message); + } + + + /** + * Constructs a new {@link InvalidRequestMessageException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public InvalidRequestMessageException(@NonNull String message, + @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/MessageDecryptionException.java b/identity/java/android/security/identity/MessageDecryptionException.java new file mode 100644 index 000000000000..7a6169ea1d5b --- /dev/null +++ b/identity/java/android/security/identity/MessageDecryptionException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown when failing to decrypt a message from the reader device. + */ +public class MessageDecryptionException extends IdentityCredentialException { + + /** + * Constructs a new {@link MessageDecryptionException} exception. + * + * @param message the detail message. + */ + public MessageDecryptionException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link MessageDecryptionException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public MessageDecryptionException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java new file mode 100644 index 000000000000..7f404037fbb1 --- /dev/null +++ b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if no dynamic authentication keys are available. + */ +public class NoAuthenticationKeyAvailableException extends IdentityCredentialException { + + /** + * Constructs a new {@link NoAuthenticationKeyAvailableException} exception. + * + * @param message the detail message. + */ + public NoAuthenticationKeyAvailableException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link NoAuthenticationKeyAvailableException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public NoAuthenticationKeyAvailableException(@NonNull String message, + @NonNull Throwable cause) { + super(message, cause); + } + +} diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java new file mode 100644 index 000000000000..44370a1780f8 --- /dev/null +++ b/identity/java/android/security/identity/PersonalizationData.java @@ -0,0 +1,157 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; + +/** + * An object that holds personalization data. + * + * This data includes access control profiles and a set of data entries and values, grouped by + * namespace. + * + * This is used to provision data into a {@link WritableIdentityCredential}. + * + * @see WritableIdentityCredential#personalize + */ +public class PersonalizationData { + + private PersonalizationData() { + } + + private LinkedList<AccessControlProfile> mProfiles = new LinkedList<>(); + + private LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>(); + + Collection<AccessControlProfile> getAccessControlProfiles() { + return Collections.unmodifiableCollection(mProfiles); + } + + Collection<String> getNamespaceNames() { + return Collections.unmodifiableCollection(mNamespaces.keySet()); + } + + NamespaceData getNamespaceData(String namespace) { + return mNamespaces.get(namespace); + } + + static class NamespaceData { + + private String mNamespace; + private LinkedHashMap<String, EntryData> mEntries = new LinkedHashMap<>(); + + private NamespaceData(String namespace) { + this.mNamespace = namespace; + } + + String getNamespaceName() { + return mNamespace; + } + + Collection<String> getEntryNames() { + return Collections.unmodifiableCollection(mEntries.keySet()); + } + + Collection<AccessControlProfileId> getAccessControlProfileIds(String name) { + EntryData value = mEntries.get(name); + if (value != null) { + return value.mAccessControlProfileIds; + } + return null; + } + + byte[] getEntryValue(String name) { + EntryData value = mEntries.get(name); + if (value != null) { + return value.mValue; + } + return null; + } + } + + private static class EntryData { + byte[] mValue; + Collection<AccessControlProfileId> mAccessControlProfileIds; + + EntryData(byte[] value, Collection<AccessControlProfileId> accessControlProfileIds) { + this.mValue = value; + this.mAccessControlProfileIds = accessControlProfileIds; + } + } + + /** + * A builder for {@link PersonalizationData}. + */ + public static final class Builder { + private PersonalizationData mData; + + /** + * Creates a new builder for a given namespace. + */ + public Builder() { + this.mData = new PersonalizationData(); + } + + /** + * Adds a new entry to the builder. + * + * @param namespace The namespace to use, e.g. {@code org.iso.18013-5.2019}. + * @param name The name of the entry, e.g. {@code height}. + * @param accessControlProfileIds A set of access control profiles to use. + * @param value The value to add, in CBOR encoding. + * @return The builder. + */ + public @NonNull Builder setEntry(@NonNull String namespace, @NonNull String name, + @NonNull Collection<AccessControlProfileId> accessControlProfileIds, + @NonNull byte[] value) { + NamespaceData namespaceData = mData.mNamespaces.get(namespace); + if (namespaceData == null) { + namespaceData = new NamespaceData(namespace); + mData.mNamespaces.put(namespace, namespaceData); + } + // TODO: validate/verify that value is proper CBOR. + namespaceData.mEntries.put(name, new EntryData(value, accessControlProfileIds)); + return this; + } + + /** + * Adds a new access control profile to the builder. + * + * @param profile The access control profile. + * @return The builder. + */ + public @NonNull Builder addAccessControlProfile(@NonNull AccessControlProfile profile) { + mData.mProfiles.add(profile); + return this; + } + + /** + * Creates a new {@link PersonalizationData} with all the entries added to the builder. + * + * @return A new {@link PersonalizationData} instance. + */ + public @NonNull PersonalizationData build() { + return mData; + } + } + +} diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java new file mode 100644 index 000000000000..0982c8a4ab31 --- /dev/null +++ b/identity/java/android/security/identity/ResultData.java @@ -0,0 +1,224 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.util.Collection; + +/** + * An object that contains the result of retrieving data from a credential. This is used to return + * data requested from a {@link IdentityCredential}. + */ +public abstract class ResultData { + + /** Value was successfully retrieved. */ + public static final int STATUS_OK = 0; + + /** Requested entry does not exist. */ + public static final int STATUS_NO_SUCH_ENTRY = 1; + + /** Requested entry was not requested. */ + public static final int STATUS_NOT_REQUESTED = 2; + + /** Requested entry wasn't in the request message. */ + public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; + + /** The requested entry was not retrieved because user authentication wasn't performed. */ + public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; + + /** The requested entry was not retrieved because reader authentication wasn't performed. */ + public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; + + /** + * The requested entry was not retrieved because it was configured without any access + * control profile. + */ + public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; + + /** + * @hide + */ + protected ResultData() {} + + /** + * Returns a CBOR structure containing the retrieved data. + * + * <p>This structure - along with the session transcript - may be cryptographically + * authenticated to prove to the reader that the data is from a trusted credential and + * {@link #getMessageAuthenticationCode()} can be used to get a MAC. + * + * <p>The CBOR structure which is cryptographically authenticated is the + * {@code DeviceAuthentication} structure according to the following + * <a href="https://tools.ietf.org/html/draft-ietf-cbor-cddl-06">CDDL</a> schema: + * + * <pre> + * DeviceAuthentication = [ + * "DeviceAuthentication", + * SessionTranscript, + * DocType, + * DeviceNameSpacesBytes + * ] + * + * DocType = tstr + * + * SessionTranscript = [ + * DeviceEngagementBytes, + * EReaderKeyBytes + * ] + * + * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) + * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) + * + * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) + * </pre> + * + * where + * + * <pre> + * DeviceNameSpaces = { + * * NameSpace => DeviceSignedItems + * } + * + * DeviceSignedItems = { + * + DataItemName => DataItemValue + * } + * + * NameSpace = tstr + * DataItemName = tstr + * DataItemValue = any + * </pre> + * + * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure + * as defined above. + * + * @return The bytes of the {@code DeviceNameSpaces} CBOR structure. + */ + public abstract @NonNull byte[] getAuthenticatedData(); + + /** + * Returns a message authentication code over the data returned by + * {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted + * credential. + * + * <p>The MAC proves to the reader that the data is from a trusted credential. This code is + * produced by using the key agreement and key derivation function from the ciphersuite + * with the authentication private key and the reader ephemeral public key to compute a + * shared message authentication code (MAC) key, then using the MAC function from the + * ciphersuite to compute a MAC of the authenticated data. + * + * <p>If the {@code sessionTranscript} parameter passed to + * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null} + * or the reader ephmeral public key was never set using + * {@link IdentityCredential#setReaderEphemeralPublicKey(PublicKey)}, no message + * authencation code will be produced and this method will return {@code null}. + * + * @return A COSE_Mac0 structure with the message authentication code as described above + * or {@code null} if the conditions specified above are not met. + */ + public abstract @Nullable byte[] getMessageAuthenticationCode(); + + /** + * Returns the static authentication data associated with the dynamic authentication + * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}. + * + * @return The static authentication data associated with dynamic authentication key used to + * MAC the data. + */ + public abstract @NonNull byte[] getStaticAuthenticationData(); + + /** + * Gets the names of namespaces with retrieved entries. + * + * @return collection of name of namespaces containing retrieved entries. May be empty if no + * data was retrieved. + */ + public abstract @NonNull Collection<String> getNamespaceNames(); + + /** + * Get the names of all entries. + * + * This includes the name of entries that wasn't successfully retrieved. + * + * @param namespaceName the namespace name to get entries for. + * @return A collection of names or {@code null} if there are no entries for the given + * namespace. + */ + public abstract @Nullable Collection<String> getEntryNames(@NonNull String namespaceName); + + /** + * Get the names of all entries that was successfully retrieved. + * + * This only return entries for which {@link #getStatus(String, String)} will return + * {@link #STATUS_OK}. + * + * @param namespaceName the namespace name to get entries for. + * @return A collection of names or {@code null} if there are no entries for the given + * namespace. + */ + public abstract @Nullable Collection<String> getRetrievedEntryNames( + @NonNull String namespaceName); + + /** + * Gets the status of an entry. + * + * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY} + * if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested, + * {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't + * present in the request message, + * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value + * wasn't retrieved because the necessary user authentication wasn't performed, + * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain + * didn't match the set of certificates the entry was provisioned with, or + * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any + * access control profiles. + * + * @param namespaceName the namespace name of the entry. + * @param name the name of the entry to get the value for. + * @return the status indicating whether the value was retrieved and if not, why. + */ + @Status + public abstract int getStatus(@NonNull String namespaceName, @NonNull String name); + + /** + * Gets the raw CBOR data for the value of an entry. + * + * This should only be called on an entry for which the {@link #getStatus(String, String)} + * method returns {@link #STATUS_OK}. + * + * @param namespaceName the namespace name of the entry. + * @param name the name of the entry to get the value for. + * @return the raw CBOR data or {@code null} if no entry with the given name exists. + */ + public abstract @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name); + + /** + * The type of the entry status. + * @hide + */ + @Retention(SOURCE) + @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED, STATUS_NOT_IN_REQUEST_MESSAGE, + STATUS_USER_AUTHENTICATION_FAILED, STATUS_READER_AUTHENTICATION_FAILED, + STATUS_NO_ACCESS_CONTROL_PROFILES}) + public @interface Status { + } +} diff --git a/identity/java/android/security/identity/SessionTranscriptMismatchException.java b/identity/java/android/security/identity/SessionTranscriptMismatchException.java new file mode 100644 index 000000000000..8c2406043f67 --- /dev/null +++ b/identity/java/android/security/identity/SessionTranscriptMismatchException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown when trying use multiple different session transcripts in the same presentation session. + */ +public class SessionTranscriptMismatchException extends IdentityCredentialException { + + /** + * Constructs a new {@link SessionTranscriptMismatchException} exception. + * + * @param message the detail message. + */ + public SessionTranscriptMismatchException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link SessionTranscriptMismatchException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public SessionTranscriptMismatchException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/UnknownAuthenticationKeyException.java b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java new file mode 100644 index 000000000000..f454b2ce2af9 --- /dev/null +++ b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +/** + * Thrown if trying to certify an unknown dynamic authentication key. + */ +public class UnknownAuthenticationKeyException extends IdentityCredentialException { + /** + * Constructs a new {@link UnknownAuthenticationKeyException} exception. + * + * @param message the detail message. + */ + public UnknownAuthenticationKeyException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@link UnknownAuthenticationKeyException} exception. + * + * @param message the detail message. + * @param cause the cause. + */ + public UnknownAuthenticationKeyException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } +} diff --git a/identity/java/android/security/identity/Util.java b/identity/java/android/security/identity/Util.java new file mode 100644 index 000000000000..6eefeb8f3f2a --- /dev/null +++ b/identity/java/android/security/identity/Util.java @@ -0,0 +1,140 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECPoint; +import java.util.Collection; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +class Util { + private static final String TAG = "Util"; + + static int[] integerCollectionToArray(Collection<Integer> collection) { + int[] result = new int[collection.size()]; + int n = 0; + for (int item : collection) { + result[n++] = item; + } + return result; + } + + static byte[] stripLeadingZeroes(byte[] value) { + int n = 0; + while (n < value.length && value[n] == 0) { + n++; + } + int newLen = value.length - n; + byte[] ret = new byte[newLen]; + int m = 0; + while (n < value.length) { + ret[m++] = value[n++]; + } + return ret; + } + + static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) { + ECPoint w = ((ECPublicKey) publicKey).getW(); + // X and Y are always positive so for interop we remove any leading zeroes + // inserted by the BigInteger encoder. + byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); + byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(0x04); + baos.write(x); + baos.write(y); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Unexpected IOException", e); + } + } + + /** + * Computes an HKDF. + * + * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google + * /crypto/tink/subtle/Hkdf.java + * which is also Copyright (c) Google and also licensed under the Apache 2 license. + * + * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or + * "HMACSHA256". + * @param ikm the input keying material. + * @param salt optional salt. A possibly non-secret random value. If no salt is + * provided (i.e. if + * salt has length 0) then an array of 0s of the same size as the hash + * digest is used as salt. + * @param info optional context and application specific information. + * @param size The length of the generated pseudorandom string in bytes. The maximal + * size is + * 255.DigestSize, where DigestSize is the size of the underlying HMAC. + * @return size pseudorandom bytes. + */ + static byte[] computeHkdf( + String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) { + Mac mac = null; + try { + mac = Mac.getInstance(macAlgorithm); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("No such algorithm: " + macAlgorithm, e); + } + if (size > 255 * mac.getMacLength()) { + throw new RuntimeException("size too large"); + } + try { + if (salt == null || salt.length == 0) { + // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided + // then HKDF uses a salt that is an array of zeros of the same length as the hash + // digest. + mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm)); + } else { + mac.init(new SecretKeySpec(salt, macAlgorithm)); + } + byte[] prk = mac.doFinal(ikm); + byte[] result = new byte[size]; + int ctr = 1; + int pos = 0; + mac.init(new SecretKeySpec(prk, macAlgorithm)); + byte[] digest = new byte[0]; + while (true) { + mac.update(digest); + mac.update(info); + mac.update((byte) ctr); + digest = mac.doFinal(); + if (pos + digest.length < size) { + System.arraycopy(digest, 0, result, pos, digest.length); + pos += digest.length; + ctr++; + } else { + System.arraycopy(digest, 0, result, pos, size - pos); + break; + } + } + return result; + } catch (InvalidKeyException e) { + throw new RuntimeException("Error MACing", e); + } + } + +} diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java new file mode 100644 index 000000000000..5f575b9d56f3 --- /dev/null +++ b/identity/java/android/security/identity/WritableIdentityCredential.java @@ -0,0 +1,111 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.identity; + +import android.annotation.NonNull; + +import java.security.cert.X509Certificate; +import java.util.Collection; + +/** + * Class used to personalize a new identity credential. + * + * <p>Credentials cannot be updated or modified after creation; any changes require deletion and + * re-creation. + * + * Use {@link IdentityCredentialStore#createCredential(String, String)} to create a new credential. + */ +public abstract class WritableIdentityCredential { + /** + * Generates and returns an X.509 certificate chain for the CredentialKey which identifies this + * credential to the issuing authority. The certificate contains an + * <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a> + * attestation extension which describes the key and the security hardware in which it lives. + * + * <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates + * it is an Identity Credential key (which can only sign/MAC very specific messages) and not + * an Android Keystore key (which can be used to sign/MAC anything). + * + * <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not + * limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is + * present, the passed in challenge is present, the device has verified boot enabled, that each + * certificate in the chain is signed by its successor, that none of the certificates have been + * revoked and so on. + * + * <p>It is not strictly necessary to use this method to provision a credential if the issuing + * authority doesn't care about the nature of the security hardware. If called, however, this + * method must be called before {@link #personalize(PersonalizationData)}. + * + * @param challenge is a byte array whose contents should be unique, fresh and provided by + * the issuing authority. The value provided is embedded in the attestation + * extension and enables the issuing authority to verify that the attestation + * certificate is fresh. + * @return the X.509 certificate for this credential's CredentialKey. + */ + public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain( + @NonNull byte[] challenge); + + /** + * Stores all of the data in the credential, with the specified access control profiles. + * + * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey with payload + * set to {@code ProofOfProvisioning} as defined below. + * + * <pre> + * ProofOfProvisioning = [ + * "ProofOfProvisioning", ; tstr + * tstr, ; DocType + * [ * AccessControlProfile ], + * ProvisionedData, + * bool ; true if this is a test credential, should + * ; always be false. + * ] + * + * AccessControlProfile = { + * "id": uint, + * ? "readerCertificate" : bstr, + * ? ( + * "userAuthenticationRequired" : bool, + * "timeoutMillis" : uint, + * ) + * } + * + * ProvisionedData = { + * * Namespace => [ + Entry ] + * }, + * + * Namespace = tstr + * + * Entry = { + * "name" : tstr, + * "value" : any, + * "accessControlProfiles" : [ * uint ], + * } + * </pre> + * + * <p>This data structure provides a guarantee to the issuer about the data which may be + * returned in the CBOR returned by + * {@link ResultData#getAuthenticatedData()} during a credential + * presentation. + * + * @param personalizationData The data to provision, including access control profiles + * and data elements and their values, grouped into namespaces. + * @return A COSE_Sign1 data structure, see above. + */ + public abstract @NonNull byte[] personalize( + @NonNull PersonalizationData personalizationData); +} diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 35a885f46919..b940cff04713 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -458,9 +458,15 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, const SkMatrix& preTransform) { SkAutoCanvasRestore saver(canvas, true); auto clipRestriction = preTransform.mapRect(clip).roundOut(); - canvas->androidFramework_setDeviceClipRestriction(clipRestriction); - canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction", - nullptr); + if (CC_UNLIKELY(mCaptureMode == CaptureMode::SingleFrameSKP + || mCaptureMode == CaptureMode::MultiFrameSKP)) { + canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction", + nullptr); + } else { + // clip drawing to dirty region only when not recording SKP files (which should contain all + // draw ops on every frame) + canvas->androidFramework_setDeviceClipRestriction(clipRestriction); + } canvas->concat(preTransform); // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293 diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 79b3886338b1..1a0f13943694 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -100,6 +100,8 @@ import java.util.stream.Collectors; * <tr><td>{@link #KEY_AAC_DRC_HEAVY_COMPRESSION}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies whether to use heavy compression.</td></tr> * <tr><td>{@link #KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the maximum number of channels the decoder outputs.</td></tr> * <tr><td>{@link #KEY_AAC_DRC_EFFECT_TYPE}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the MPEG-D DRC effect type to use.</td></tr> + * <tr><td>{@link #KEY_AAC_DRC_OUTPUT_LOUDNESS}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, returns the DRC output loudness.</td></tr> + * <tr><td>{@link #KEY_AAC_DRC_ALBUM_MODE}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the whether MPEG-D DRC Album Mode is active or not.</td></tr> * <tr><td>{@link #KEY_CHANNEL_MASK}</td><td>Integer</td><td>optional, a mask of audio channel assignments</td></tr> * <tr><td>{@link #KEY_ENCODER_DELAY}</td><td>Integer</td><td>optional, the number of frames to trim from the start of the decoded audio stream.</td></tr> * <tr><td>{@link #KEY_ENCODER_PADDING}</td><td>Integer</td><td>optional, the number of frames to trim from the end of the decoded audio stream.</td></tr> @@ -736,6 +738,37 @@ public final class MediaFormat { public static final String KEY_AAC_DRC_HEAVY_COMPRESSION = "aac-drc-heavy-compression"; /** + * A key to retrieve the output loudness of a decoded bitstream. + * <p>If loudness normalization is active, the value corresponds to the Target Reference Level + * (see {@link #KEY_AAC_DRC_TARGET_REFERENCE_LEVEL}).<br> + * If loudness normalization is not active, the value corresponds to the loudness metadata + * given in the bitstream. + * <p>The value is retrieved with getInteger() and is given as an integer value between 0 and + * 231. It is calculated as -4 * Output Loudness in LKFS. Therefore, it represents the range of + * 0 to -57.75 LKFS. + * <p>A value of -1 indicates that no loudness metadata is present in the bitstream. + * <p>Loudness metadata can originate from MPEG-4 DRC or MPEG-D DRC. + * <p>This key is only used during decoding. + */ + public static final String KEY_AAC_DRC_OUTPUT_LOUDNESS = "aac-drc-output-loudness"; + + /** + * A key describing the album mode for MPEG-D DRC as defined in ISO/IEC 23003-4. + * <p>The associated value is an integer and can be set to following values: + * <table> + * <tr><th>Value</th><th>Album Mode</th></tr> + * <tr><th>0</th><th>disabled</th></tr> + * <tr><th>1</th><th>enabled</th></tr> + * </table> + * <p>Disabled album mode leads to application of gain sequences for fading in and out, if + * provided in the bitstream. Enabled album mode makes use of dedicated album loudness + * information, if provided in the bitstream. + * <p>The default value is 0 (album mode disabled). + * <p>This key is only used during decoding. + */ + public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode"; + + /** * A key describing the FLAC compression level to be used (FLAC audio format only). * The associated value is an integer ranging from 0 (fastest, least compression) * to 8 (slowest, most compression). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index 7cf5147a9f26..fb30bdec68b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -121,8 +121,13 @@ public class StatusBarWindowController { apply(mCurrentState); } + private void applyHeight() { + mLpChanged.height = mBarHeight; + } + private void apply(State state) { applyForceStatusBarVisibleFlag(state); + applyHeight(); if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { mWindowManager.updateViewLayout(mStatusBarView, mLp); } diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java index 02c7857f0cfd..5aba013a7fb8 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java @@ -39,10 +39,10 @@ import android.view.IWindowSessionCallback; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.view.SurfaceControlViewHost; import android.view.WindowlessWindowManager; import com.android.internal.os.IResultReceiver; @@ -238,11 +238,13 @@ public class SystemWindows { long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, viewVisibility, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, - cutout, mergedConfiguration, outSurfaceControl, outInsetsState); + cutout, mergedConfiguration, outSurfaceControl, outInsetsState, + outSurfaceSize); if (res != 0) { return res; } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 98b572801716..7ab345387fab 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5801,6 +5801,19 @@ public class ConnectivityService extends IConnectivityManager.Stub return INetd.PERMISSION_NONE; } + private void updateNetworkPermissions(@NonNull final NetworkAgentInfo nai, + @NonNull final NetworkCapabilities newNc) { + final int oldPermission = getNetworkPermission(nai.networkCapabilities); + final int newPermission = getNetworkPermission(newNc); + if (oldPermission != newPermission && nai.created && !nai.isVPN()) { + try { + mNMS.setNetworkPermission(nai.network.netId, newPermission); + } catch (RemoteException e) { + loge("Exception in setNetworkPermission: " + e); + } + } + } + /** * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal, @@ -5873,21 +5886,11 @@ public class ConnectivityService extends IConnectivityManager.Stub * @param nai the network having its capabilities updated. * @param nc the new network capabilities. */ - private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) { + private void updateCapabilities(final int oldScore, @NonNull final NetworkAgentInfo nai, + @NonNull final NetworkCapabilities nc) { NetworkCapabilities newNc = mixInCapabilities(nai, nc); - if (Objects.equals(nai.networkCapabilities, newNc)) return; - - final int oldPermission = getNetworkPermission(nai.networkCapabilities); - final int newPermission = getNetworkPermission(newNc); - if (oldPermission != newPermission && nai.created && !nai.isVPN()) { - try { - mNMS.setNetworkPermission(nai.network.netId, newPermission); - } catch (RemoteException e) { - loge("Exception in setNetworkPermission: " + e); - } - } - + updateNetworkPermissions(nai, newNc); final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc); updateUids(nai, prevNc, newNc); @@ -6268,6 +6271,30 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // An accumulator class to gather the list of changes that result from a rematch. + // TODO : enrich to represent an entire set of changes to apply. + private static class NetworkReassignment { + static class NetworkBgStatePair { + @NonNull final NetworkAgentInfo mNetwork; + final boolean mOldBackground; + NetworkBgStatePair(@NonNull final NetworkAgentInfo network, + final boolean oldBackground) { + mNetwork = network; + mOldBackground = oldBackground; + } + } + + @NonNull private final Set<NetworkBgStatePair> mRematchedNetworks = new ArraySet<>(); + + @NonNull Iterable<NetworkBgStatePair> getRematchedNetworks() { + return mRematchedNetworks; + } + + void addRematchedNetwork(@NonNull final NetworkBgStatePair network) { + mRematchedNetworks.add(network); + } + } + private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork( @NonNull final NetworkAgentInfo newNetwork) { final int score = newNetwork.getCurrentScore(); @@ -6313,8 +6340,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // needed. A network is needed if it is the best network for // one or more NetworkRequests, or if it is a VPN. // - // - Tears down newNetwork if it just became validated - // but turns out to be unneeded. + // - Writes into the passed reassignment object all changes that should be done for + // rematching this network with all requests, to be applied later. // // NOTE: This function only adds NetworkRequests that "newNetwork" could satisfy, // it does not remove NetworkRequests that other Networks could better satisfy. @@ -6322,15 +6349,22 @@ public class ConnectivityService extends IConnectivityManager.Stub // This function should be used when possible instead of {@code rematchAllNetworksAndRequests} // as it performs better by a factor of the number of Networks. // + // TODO : stop writing to the passed reassignment. This is temporarily more useful, but + // it's unidiomatic Java and it's hard to read. + // + // @param changes a currently-building list of changes to write to // @param newNetwork is the network to be matched against NetworkRequests. // @param now the time the rematch starts, as returned by SystemClock.elapsedRealtime(); - private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, long now) { + private void rematchNetworkAndRequests(@NonNull final NetworkReassignment changes, + @NonNull final NetworkAgentInfo newNetwork, final long now) { ensureRunningOnConnectivityServiceThread(); if (!newNetwork.everConnected) return; boolean isNewDefault = false; NetworkAgentInfo oldDefaultNetwork = null; - final boolean wasBackgroundNetwork = newNetwork.isBackgroundNetwork(); + changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(newNetwork, + newNetwork.isBackgroundNetwork())); + final int score = newNetwork.getCurrentScore(); if (VDBG || DDBG) log("rematching " + newNetwork.name()); @@ -6433,39 +6467,12 @@ public class ConnectivityService extends IConnectivityManager.Stub if (newNetwork.getCurrentScore() != score) { Slog.wtf(TAG, String.format( "BUG: %s changed score during rematch: %d -> %d", - newNetwork.name(), score, newNetwork.getCurrentScore())); + newNetwork.name(), score, newNetwork.getCurrentScore())); } // Notify requested networks are available after the default net is switched, but // before LegacyTypeTracker sends legacy broadcasts for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); - - // Finally, process listen requests and update capabilities if the background state has - // changed for this network. For consistency with previous behavior, send onLost callbacks - // before onAvailable. - processNewlyLostListenRequests(newNetwork); - - // Maybe the network changed background states. Update its capabilities. - final boolean backgroundChanged = wasBackgroundNetwork != newNetwork.isBackgroundNetwork(); - if (backgroundChanged) { - final NetworkCapabilities newNc = mixInCapabilities(newNetwork, - newNetwork.networkCapabilities); - - final int oldPermission = getNetworkPermission(newNetwork.networkCapabilities); - final int newPermission = getNetworkPermission(newNc); - if (oldPermission != newPermission) { - try { - mNMS.setNetworkPermission(newNetwork.network.netId, newPermission); - } catch (RemoteException e) { - loge("Exception in setNetworkPermission: " + e); - } - } - - newNetwork.getAndSetNetworkCapabilities(newNc); - notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED); - } - - processNewlySatisfiedListenRequests(newNetwork); } /** @@ -6487,12 +6494,24 @@ public class ConnectivityService extends IConnectivityManager.Stub // scoring network and then a higher scoring network, which could produce multiple // callbacks. Arrays.sort(nais); + final NetworkReassignment changes = new NetworkReassignment(); for (final NetworkAgentInfo nai : nais) { - rematchNetworkAndRequests(nai, now); + rematchNetworkAndRequests(changes, nai, now); } final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork(); + for (final NetworkReassignment.NetworkBgStatePair event : changes.getRematchedNetworks()) { + // Process listen requests and update capabilities if the background state has + // changed for this network. For consistency with previous behavior, send onLost + // callbacks before onAvailable. + processNewlyLostListenRequests(event.mNetwork); + if (event.mOldBackground != event.mNetwork.isBackgroundNetwork()) { + applyBackgroundChangeForRematch(event.mNetwork); + } + processNewlySatisfiedListenRequests(event.mNetwork); + } + for (final NetworkAgentInfo nai : nais) { // Rematching may have altered the linger state of some networks, so update all linger // timers. updateLingerState reads the state from the network agent and does nothing @@ -6524,6 +6543,24 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Apply a change in background state resulting from rematching networks with requests. + * + * During rematch, a network may change background states by starting to satisfy or stopping + * to satisfy a foreground request. Listens don't count for this. When a network changes + * background states, its capabilities need to be updated and callbacks fired for the + * capability change. + * + * @param nai The network that changed background states + */ + private void applyBackgroundChangeForRematch(@NonNull final NetworkAgentInfo nai) { + final NetworkCapabilities newNc = mixInCapabilities(nai, nai.networkCapabilities); + if (Objects.equals(nai.networkCapabilities, newNc)) return; + updateNetworkPermissions(nai, newNc); + nai.getAndSetNetworkCapabilities(newNc); + notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); + } + private void updateLegacyTypeTrackerAndVpnLockdownForRematch( @Nullable final NetworkAgentInfo oldDefaultNetwork, @Nullable final NetworkAgentInfo newDefaultNetwork, diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index c95d3e87f2b0..f5e342323831 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1997,10 +1997,11 @@ class StorageManagerService extends IStorageManager.Stub } catch (ExternalStorageServiceException e) { Slog.e(TAG, "Failed to mount volume " + vol, e); - Slog.i(TAG, "Scheduling reset in one minute"); + int nextResetSeconds = REMOTE_TIMEOUT_SECONDS * 2; + Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s"); mHandler.removeMessages(H_RESET); mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET), - TimeUnit.SECONDS.toMillis(REMOTE_TIMEOUT_SECONDS * 2)); + TimeUnit.SECONDS.toMillis(nextResetSeconds)); return false; } finally { try { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b0369752bdaf..d9fbc85fb6e1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -363,6 +363,7 @@ import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerService; +import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerService; import com.android.server.wm.WindowProcessController; @@ -1505,6 +1506,7 @@ public class ActivityManagerService extends IActivityManager.Stub @VisibleForTesting public WindowManagerService mWindowManager; + WindowManagerInternal mWmInternal; @VisibleForTesting public ActivityTaskManagerService mActivityTaskManager; @VisibleForTesting @@ -2085,6 +2087,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void setWindowManager(WindowManagerService wm) { synchronized (this) { mWindowManager = wm; + mWmInternal = LocalServices.getService(WindowManagerInternal.class); mActivityTaskManager.setWindowManager(wm); } } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index cbf058700909..0639db02469d 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -65,8 +65,6 @@ import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.Zygote; -import com.android.server.LocalServices; -import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; @@ -1804,9 +1802,6 @@ class ProcessRecord implements WindowProcessListener { /** current wait for debugger dialog */ private AppWaitingForDebuggerDialog mWaitDialog; - private final WindowManagerInternal mWmInternal = - LocalServices.getService(WindowManagerInternal.class); - boolean hasCrashDialogs() { return mCrashDialogs != null; } @@ -1932,7 +1927,9 @@ class ProcessRecord implements WindowProcessListener { } // If there is no foreground window display, fallback to last used display. if (displayContexts.isEmpty() || lastUsedOnly) { - displayContexts.add(mWmInternal.getTopFocusedDisplayUiContext()); + displayContexts.add(mService.mWmInternal != null + ? mService.mWmInternal.getTopFocusedDisplayUiContext() + : mService.mUiContext); } return displayContexts; } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 12f9fd936fba..1ed97becb776 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -257,10 +257,10 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic switch (status) { case PermissionChecker.PERMISSION_GRANTED: return; - case PermissionChecker.PERMISSION_DENIED: + case PermissionChecker.PERMISSION_HARD_DENIED: throw new SecurityException( String.format("Caller must have the %s permission.", permission)); - case PermissionChecker.PERMISSION_DENIED_APP_OP: + case PermissionChecker.PERMISSION_SOFT_DENIED: throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED, String.format("Caller must have the %s permission.", permission)); default: diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 8bc8ff4fb492..0e13e6c8772a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -745,7 +745,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A synchronized (mAtmService.mGlobalLock) { Slog.w(TAG, "Activity stop timeout for " + ActivityRecord.this); if (isInHistory()) { - activityStopped(null /*icicle*/, null /*persistentState*/, null /*description*/); + activityStopped( + null /*icicle*/, null /*persistentState*/, null /*description*/); } } } @@ -1286,17 +1287,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateColorTransform(); - final ActivityStack oldStack = (oldTask != null) ? oldTask.getStack() : null; - final ActivityStack newStack = (newTask != null) ? newTask.getStack() : null; - // Inform old stack (if present) of activity removal and new stack (if set) of activity - // addition. - if (oldStack != newStack) { - if (oldStack != null) { - oldStack.onActivityRemovedFromStack(this); - } - if (newStack != null) { - newStack.onActivityAddedToStack(this); - } + if (oldTask != null) { + oldTask.cleanUpActivityReferences(this); + } + if (newTask != null && isState(RESUMED)) { + newTask.setResumedActivity(this, "onParentChanged"); } } @@ -2904,8 +2899,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Note: Call before {@link #removeFromHistory(String)}. */ void cleanUp(boolean cleanServices, boolean setState) { - final ActivityStack stack = getActivityStack(); - stack.onActivityRemovedFromStack(this); + task.cleanUpActivityReferences(this); deferRelaunchUntilPaused = false; frozenBeforeDestroy = false; @@ -5833,7 +5827,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean isWaitingForTransitionStart() { final DisplayContent dc = getDisplayContent(); - return dc.mAppTransition.isTransitionSet() + return dc != null && dc.mAppTransition.isTransitionSet() && (dc.mOpeningApps.contains(this) || dc.mClosingApps.contains(this) || dc.mChangingApps.contains(this)); diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 9e32ea085135..60e0f51ef0a0 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -80,7 +80,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; @@ -119,7 +118,6 @@ import static com.android.server.wm.StackProto.MINIMIZE_AMOUNT; import static com.android.server.wm.StackProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static java.lang.Integer.MAX_VALUE; @@ -153,6 +151,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.util.DisplayMetrics; import android.util.Log; @@ -162,7 +161,6 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.ITaskOrganizer; -import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import com.android.internal.annotations.GuardedBy; @@ -190,7 +188,7 @@ import java.util.function.Consumer; /** * State and management of a single stack of activities. */ -class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAnimationTarget { +class ActivityStack extends Task implements BoundsAnimationTarget { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_ATM; static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_APP = TAG + POSTFIX_APP; @@ -251,33 +249,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn RESTARTING_PROCESS } - final ActivityTaskManagerService mAtmService; - - /** - * When we are in the process of pausing an activity, before starting the - * next one, this variable holds the activity that is currently being paused. - */ - ActivityRecord mPausingActivity = null; - - /** - * This is the last activity that we put into the paused state. This is - * used to determine if we need to do an activity transition while sleeping, - * when we normally hold the top activity paused. - */ - ActivityRecord mLastPausedActivity = null; - - /** - * Activities that specify No History must be removed once the user navigates away from them. - * If the device goes to sleep with such an activity in the paused state then we save it here - * and finish it later if another activity replaces it on wakeup. - */ - ActivityRecord mLastNoHistoryActivity = null; - - /** - * Current activity that is resumed, or null if there is none. - */ - ActivityRecord mResumedActivity = null; - // The topmost Activity passed to convertToTranslucent(). When non-null it means we are // waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they // are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the @@ -294,11 +265,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn boolean mConfigWillChange; /** - * When set, will force the stack to report as invisible. - */ - boolean mForceHidden = false; - - /** * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */ boolean mInResumeTopActivity = false; @@ -311,18 +277,12 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn int mCurrentUser; - /** The attached Display's unique identifier, or -1 if detached */ - private int mDisplayId; - // Id of the previous display the stack was on. - int mPrevDisplayId = INVALID_DISPLAY; - /** Unique identifier */ final int mStackId; /** For comparison with DisplayContent bounds. */ private Rect mTmpRect = new Rect(); private Rect mTmpRect2 = new Rect(); - private Rect mTmpRect3 = new Rect(); /** For Pinned stack controlling. */ private Rect mTmpToBounds = new Rect(); @@ -336,9 +296,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn */ private final Rect mFullyAdjustedImeBounds = new Rect(); - /** ActivityRecords that are exiting, but still on screen for animations. */ - final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>(); - /** Detach this stack from its display when animation completes. */ // TODO: maybe tie this to WindowContainer#removeChild some how... private boolean mDeferRemoval; @@ -367,8 +324,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn Rect mPreAnimationBounds = new Rect(); - private Dimmer mDimmer = new Dimmer(this); - /** * For {@link #prepareSurfaces}. */ @@ -384,10 +339,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn /** List for processing through a set of activities */ private final ArrayList<ActivityRecord> mTmpActivities = new ArrayList<>(); - /** Run all ActivityStacks through this */ - protected final ActivityStackSupervisor mStackSupervisor; - protected final RootWindowContainer mRootWindowContainer; - private boolean mTopActivityOccludesKeyguard; private ActivityRecord mTopDismissingKeyguardActivity; @@ -638,52 +589,68 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } } - ActivityStack(DisplayContent display, int stackId, ActivityStackSupervisor supervisor, - int activityType) { - super(supervisor.mService.mWindowManager); - mStackId = stackId; - mDockedStackMinimizeThickness = - supervisor.mService.mWindowManager.mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_minimize_thickness); - EventLogTags.writeWmStackCreated(stackId); - mStackSupervisor = supervisor; - mAtmService = supervisor.mService; - mRootWindowContainer = mAtmService.mRootWindowContainer; - mHandler = new ActivityStackHandler(supervisor.mLooper); - mRemoteToken = new RemoteToken(this); - mCurrentUser = mAtmService.mAmInternal.getCurrentUserId(); - // Set display id before setting activity and window type to make sure it won't affect - // stacks on a wrong display. - mDisplayId = display.mDisplayId; + ActivityStack(DisplayContent display, int id, ActivityStackSupervisor supervisor, + int activityType, ActivityInfo info, Intent intent) { + this(supervisor.mService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, + null /*taskDescription*/, null /*stack*/); + setActivityType(activityType); } - /** - * This should be called when an activity in a child task changes state. This should only - * be called from - * {@link Task#onActivityStateChanged(ActivityRecord, ActivityState, String)}. - * @param record The {@link ActivityRecord} whose state has changed. - * @param state The new state. - * @param reason The reason for the change. - */ - void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) { - if (record == mResumedActivity && state != RESUMED) { - setResumedActivity(null, reason + " - onActivityStateChanged"); - } - - if (state == RESUMED) { - if (DEBUG_STACK) Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" - + reason); - setResumedActivity(record, reason + " - onActivityStateChanged"); - if (record == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(record, reason); - } - mStackSupervisor.mRecentTasks.add(record.getTask()); - } + ActivityStack(ActivityTaskManagerService atmService, int id, ActivityInfo info, Intent _intent, + IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, + ActivityManager.TaskDescription _taskDescription, ActivityStack stack) { + this(atmService, id, _intent, null /*_affinityIntent*/, null /*_affinity*/, + null /*_rootAffinity*/, null /*_realActivity*/, null /*_origActivity*/, + false /*_rootWasReset*/, false /*_autoRemoveRecents*/, false /*_askedCompatMode*/, + UserHandle.getUserId(info.applicationInfo.uid), 0 /*_effectiveUid*/, + null /*_lastDescription*/, System.currentTimeMillis(), + true /*neverRelinquishIdentity*/, + _taskDescription != null ? _taskDescription : new ActivityManager.TaskDescription(), + id, INVALID_TASK_ID, INVALID_TASK_ID, 0 /*taskAffiliationColor*/, + info.applicationInfo.uid, info.packageName, info.resizeMode, + info.supportsPictureInPicture(), false /*_realActivitySuspended*/, + false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, info, + _voiceSession, _voiceInteractor, stack); + } + + ActivityStack(ActivityTaskManagerService atmService, int id, Intent _intent, + Intent _affinityIntent, String _affinity, String _rootAffinity, + ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, + boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid, + String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity, + ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation, + int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, + String callingPackage, int resizeMode, boolean supportsPictureInPicture, + boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, + ActivityInfo info, IVoiceInteractionSession _voiceSession, + IVoiceInteractor _voiceInteractor, ActivityStack stack) { + super(atmService, id, _intent, _affinityIntent, _affinity, _rootAffinity, + _realActivity, _origActivity, _rootWasReset, _autoRemoveRecents, _askedCompatMode, + _userId, _effectiveUid, _lastDescription, lastTimeMoved, neverRelinquishIdentity, + _lastTaskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, + callingUid, callingPackage, resizeMode, supportsPictureInPicture, + _realActivitySuspended, userSetupComplete, minWidth, minHeight, info, _voiceSession, + _voiceInteractor, stack); + + mStackId = mTaskId; + mDockedStackMinimizeThickness = mWmService.mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_minimize_thickness); + EventLogTags.writeWmStackCreated(id); + mHandler = new ActivityStackHandler(mStackSupervisor.mLooper); + mCurrentUser = mAtmService.mAmInternal.getCurrentUserId(); } @Override public void onConfigurationChanged(Configuration newParentConfig) { + // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are + // particularly for ActivityStack, like preventing bounds changes when inheriting certain + // windowing mode. + if (!isRootTask()) { + super.onConfigurationChanged(newParentConfig); + return; + } + final int prevWindowingMode = getWindowingMode(); final boolean prevIsAlwaysOnTop = isAlwaysOnTop(); final int prevRotation = getWindowConfiguration().getRotation(); @@ -801,10 +768,16 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn @Override public void setWindowingMode(int windowingMode) { + // Calling Task#setWindowingMode() for leaf task since this is the a specialization of + // {@link #setWindowingMode(int)} for ActivityStack. + if (!isRootTask()) { + super.setWindowingMode(windowingMode); + return; + } + setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, false /* creating */); - windowingMode = getWindowingMode(); /* * Different windowing modes may be managed by different task organizers. If @@ -893,7 +866,8 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn // Looks like we can't launch in split screen mode or the stack we are launching // doesn't support split-screen mode, go ahead an dismiss split-screen and display a // warning toast about it. - mAtmService.getTaskChangeNotificationController().notifyActivityDismissingDockedStack(); + mAtmService.getTaskChangeNotificationController() + .notifyActivityDismissingDockedStack(); final ActivityStack primarySplitStack = display.getSplitScreenPrimaryStack(); primarySplitStack.setWindowingModeInSurfaceTransaction(WINDOWING_MODE_UNDEFINED, false /* animate */, false /* showRecents */, @@ -972,7 +946,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn false /* preserveWindows */, true /* deferResume */); } } finally { - if (showRecents && !alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY + if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { // Make sure recents stack exist when creating a dock stack as it normally needs to // be on the other side of the docked stack and we make visibility decisions based @@ -1029,10 +1003,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return getDisplayContent(); } - int getDisplayId() { - return mDisplayId; - } - /** * Defers updating the bounds of the stack. If the stack was resized/repositioned while * deferring, the bounds will update in {@link #continueUpdateBounds()}. @@ -1138,10 +1108,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return r.getTask().mTaskId != taskId && r.appToken != notTop && r.canBeTopRunning(); } - ActivityRecord getTopNonFinishingActivity() { - return getTopActivity(false /*includeFinishing*/, true /*includeOverlays*/); - } - ActivityRecord isInStackLocked(IBinder token) { final ActivityRecord r = ActivityRecord.forTokenLocked(token); return isInStackLocked(r); @@ -1172,11 +1138,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } final boolean isOnHomeDisplay() { - return mDisplayId == DEFAULT_DISPLAY; - } - - private boolean returnsToHomeStack() { - return !inMultiWindowMode() && hasChild() && getBottomMostTask().returnsToHomeStack(); + return getDisplayId() == DEFAULT_DISPLAY; } void moveToFront(String reason) { @@ -1280,11 +1242,11 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn super.switchUser(userId); forAllTasks((t) -> { - if (t.mWmService.isCurrentProfile(t.mUserId) || t.showForAllUsers()) { + if (t.showToCurrentUser()) { mChildren.remove(t); mChildren.add(t); } - }); + }, true /* traverseTopToBottom */, this); } void minimalResumeActivityLocked(ActivityRecord r) { @@ -1609,45 +1571,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS); } - /** - * Returns true if the stack is translucent and can have other contents visible behind it if - * needed. A stack is considered translucent if it don't contain a visible or - * starting (about to be visible) activity that is fullscreen (opaque). - * @param starting The currently starting activity or null if there is none. - */ - @VisibleForTesting - boolean isStackTranslucent(ActivityRecord starting) { - if (!isAttached() || mForceHidden) { - return true; - } - final PooledPredicate p = PooledLambda.obtainPredicate(ActivityStack::isOpaqueActivity, - PooledLambda.__(ActivityRecord.class), starting); - final ActivityRecord opaque = getActivity(p); - p.recycle(); - return opaque == null; - } - - private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) { - if (r.finishing) { - // We don't factor in finishing activities when determining translucency since - // they will be gone soon. - return false; - } - - if (!r.visibleIgnoringKeyguard && r != starting) { - // Also ignore invisible activities that are not the currently starting - // activity (about to be visible). - return false; - } - - if (r.occludesParent() || r.hasWallpaper) { - // Stack isn't translucent if it has at least one fullscreen activity - // that is visible. - return true; - } - return false; - } - boolean isTopStackOnDisplay() { final DisplayContent display = getDisplay(); return display != null && display.isTopStack(this); @@ -1667,15 +1590,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return topActivity != null && topActivity.mVisibleRequested; } - /** - * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't - * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking - * the first one is ok. - */ - boolean isControlledByTaskOrganizer() { - return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null; - } - private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) { tr.setTaskOrganizer(organizer); } @@ -1690,7 +1604,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStack::transferSingleTaskToOrganizer, PooledLambda.__(Task.class), organizer); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -1699,6 +1613,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn * * @param starting The currently starting activity or null if there is none. */ + @Override boolean shouldBeVisible(ActivityRecord starting) { return getVisibility(starting) != STACK_VISIBILITY_INVISIBLE; } @@ -1756,7 +1671,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn break; } } - if (other.isStackTranslucent(starting)) { + if (other.isTranslucent(starting)) { // Can be visible behind a translucent fullscreen stack. gotTranslucentFullscreen = true; continue; @@ -1765,7 +1680,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !gotOpaqueSplitScreenPrimary) { gotSplitScreenStack = true; - gotTranslucentSplitScreenPrimary = other.isStackTranslucent(starting); + gotTranslucentSplitScreenPrimary = other.isTranslucent(starting); gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary; if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && gotOpaqueSplitScreenPrimary) { @@ -1775,7 +1690,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY && !gotOpaqueSplitScreenSecondary) { gotSplitScreenStack = true; - gotTranslucentSplitScreenSecondary = other.isStackTranslucent(starting); + gotTranslucentSplitScreenSecondary = other.isTranslucent(starting); gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary; if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY && gotOpaqueSplitScreenSecondary) { @@ -1876,13 +1791,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return inPinnedWindowingMode(); } - @Override - public boolean supportsSplitScreenWindowingMode() { - final Task topTask = getTopMostTask(); - return super.supportsSplitScreenWindowingMode() - && (topTask == null || topTask.supportsSplitScreenWindowingMode()); - } - + // TODO(NOW!) /** * Returns {@code true} if this is the top-most split-screen-primary or * split-screen-secondary stack, {@code false} otherwise. @@ -1918,7 +1827,9 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn * @return true if {@param r} is visible taken Keyguard state into account, false otherwise */ boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible, boolean isTop) { - final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY; + int displayId = getDisplayId(); + if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY; + final boolean keyguardOrAodShowing = mStackSupervisor.getKeyguardController() .isKeyguardOrAodShowing(displayId); final boolean keyguardLocked = mStackSupervisor.getKeyguardController().isKeyguardLocked(); @@ -2084,24 +1995,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return result; } - /** - * Returns the currently resumed activity. - */ - protected ActivityRecord getResumedActivity() { - return mResumedActivity; - } - - private void setResumedActivity(ActivityRecord r, String reason) { - if (mResumedActivity == r) { - return; - } - - if (DEBUG_STACK) Slog.d(TAG_STACK, "setResumedActivity stack:" + this + " + from: " - + mResumedActivity + " to:" + r + " reason:" + reason); - mResumedActivity = r; - mStackSupervisor.updateTopResumedActivityIfNeeded(); - } - @GuardedBy("mService") private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { if (!mAtmService.isBooting() && !mAtmService.isBooted()) { @@ -2412,7 +2305,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn // result of invisible window resize. // TODO: Remove this once visibilities are set correctly immediately when // starting an activity. - notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayId, + notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(), true /* markFrozenIfConfigChanged */, false /* deferResume */); } @@ -2553,7 +2446,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home"); - return mRootWindowContainer.resumeHomeActivity(prev, reason, mDisplayId); + return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayId()); } void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, @@ -2827,7 +2720,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn void finishVoiceTask(IVoiceInteractionSession session) { final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::finishIfVoiceTask, PooledLambda.__(Task.class), session.asBinder()); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3020,29 +2913,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return foundParentInTask; } - /** - * Remove any state associated with the {@link ActivityRecord}. This should be called whenever - * an activity moves away from the stack. - */ - void onActivityRemovedFromStack(ActivityRecord r) { - r.removeTimeouts(); - - mExitingActivities.remove(r); - - if (mResumedActivity != null && mResumedActivity == r) { - setResumedActivity(null, "onActivityRemovedFromStack"); - } - if (mPausingActivity != null && mPausingActivity == r) { - mPausingActivity = null; - } - } - - void onActivityAddedToStack(ActivityRecord r) { - if (r.isState(RESUMED)) { - setResumedActivity(r, "onActivityAddedToStack"); - } - } - void removeLaunchTickMessages() { forAllActivities(ActivityRecord::removeLaunchTickRunnable); } @@ -3141,7 +3011,8 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mRootWindowContainer.resumeFocusedStacksTopActivities(); } EventLogTags.writeWmTaskToFront(tr.mUserId, tr.mTaskId); - mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(tr.getTaskInfo()); + mAtmService.getTaskChangeNotificationController() + .notifyTaskMovedToFront(tr.getTaskInfo()); } finally { getDisplay().continueUpdateImeTarget(); } @@ -3239,7 +3110,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStack::processTaskResizeBounds, PooledLambda.__(Task.class), taskBounds, tempTaskInsetBounds); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); setBounds(bounds); @@ -3276,7 +3147,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds, PooledLambda.__(Task.class), bounds); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3292,7 +3163,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskDisplayedBounds, PooledLambda.__(Task.class), bounds); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3384,7 +3255,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn private boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient, String dumpPackage, boolean needSep) { - if (!hasChild()) { return false; } @@ -3403,11 +3273,11 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final ArrayList<ActivityRecord> activities = new ArrayList<>(); // Add activities by traversing the hierarchy from bottom to top, since activities // are dumped in reverse order in {@link ActivityStackSupervisor#dumpHistoryList()}. - forAllActivities((Consumer<ActivityRecord>) activities::add, + task.forAllActivities((Consumer<ActivityRecord>) activities::add, false /* traverseTopToBottom */); dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient, dumpPackage, false, null, task); - }); + }, true /* traverseTopToBottom */, this); return true; } @@ -3458,49 +3328,9 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } } - /** - * Removes the input task from this stack. - * - * @param child to remove. - * @param reason for removal. - */ - void removeChild(WindowContainer child, String reason) { - if (!mChildren.contains(child)) { - // Not really in this stack anymore... - return; - } - - final DisplayContent display = getDisplay(); - if (DEBUG_TASK_MOVEMENT) { - Slog.d(TAG_WM, "removeChild: task=" + child + " reason=" + reason); - } - - super.removeChild(child); - - EventLogTags.writeWmRemoveTask(((Task) child).mTaskId, mStackId); - - if (display.isSingleTaskInstance()) { - mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId); - } - - display.mDisplayContent.setLayoutNeeded(); - - if (!hasChild()) { - // Stack is now empty... - removeIfPossible(); - } - } - - @Override - void removeChild(WindowContainer child) { - removeChild(child, "removeChild"); - } - - Task createTask(int taskId, ActivityInfo info, Intent intent, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - boolean toTop) { - return createTask(taskId, info, intent, voiceSession, voiceInteractor, toTop, - null /*activity*/, null /*source*/, null /*options*/); + Task createTask(int taskId, ActivityInfo info, Intent intent, boolean toTop) { + return createTask(taskId, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, + toTop, null /*activity*/, null /*source*/, null /*options*/); } Task createTask(int taskId, ActivityInfo info, Intent intent, @@ -3511,7 +3341,8 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mAtmService, taskId, info, intent, voiceSession, voiceInteractor, this); // add the task to stack first, mTaskPositioner might need the stack association addChild(task, toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); - final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY; + int displayId = getDisplayId(); + if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY; final boolean isLockscreenShown = mAtmService.mStackSupervisor.getKeyguardController() .isKeyguardOrAodShowing(displayId); if (!mStackSupervisor.getLaunchParamsController() @@ -3522,14 +3353,25 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return task; } - void addChild(final Task task, final boolean toTop, boolean showForAllUsers) { + void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { if (isSingleTaskInstance() && hasChild()) { throw new IllegalStateException("Can only have one child on stack=" + this); } - // We only want to move the parents to the parents if we are creating this task at the - // top of its stack. - addChild(task, toTop ? MAX_VALUE : 0, showForAllUsers, toTop /*moveParents*/); + Task task = child.asTask(); + try { + + if (task != null) { + task.setForceShowForAllUsers(showForAllUsers); + } + // We only want to move the parents to the parents if we are creating this task at the + // top of its stack. + addChild(child, toTop ? MAX_VALUE : 0, toTop /*moveParents*/); + } finally { + if (task != null) { + task.setForceShowForAllUsers(false); + } + } } void positionChildAt(Task task, int position) { @@ -3747,16 +3589,12 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStackSupervisor::updatePictureInPictureMode, mStackSupervisor, PooledLambda.__(Task.class), targetStackBounds, forceUpdate); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } - public int getStackId() { - return mStackId; - } - void prepareFreezingTaskBounds() { - forAllTasks(Task::prepareFreezingBounds); + forAllTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */, this); } /** @@ -3788,7 +3626,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final PooledConsumer c = PooledLambda.obtainConsumer(Task::alignToAdjustedBounds, PooledLambda.__(Task.class), adjusted ? mAdjustedBounds : getRawBounds(), insetBounds, alignBottom); - forAllTasks(c); + forAllTasks(c, true /* traverseTopToBottom */, this); c.recycle(); } @@ -3798,7 +3636,13 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn @Override public int setBounds(Rect bounds) { - return setBounds(getRequestedOverrideBounds(), bounds); + // Calling Task#setBounds() for leaf task since this is the a specialization of + // {@link #setBounds(int)} for ActivityStack. + if (!isRootTask()) { + return super.setBounds(bounds); + } else { + return setBounds(getRequestedOverrideBounds(), bounds); + } } private int setBounds(Rect existing, Rect bounds) { @@ -4038,30 +3882,15 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn * Put a Task in this stack. Used for adding only. * When task is added to top of the stack, the entire branch of the hierarchy (including stack * and display) will be brought to top. - * @param task The task to add. + * @param child The child to add. * @param position Target position to add the task to. - * @param showForAllUsers Whether to show the task regardless of the current user. */ - private void addChild(Task task, int position, boolean showForAllUsers, boolean moveParents) { - try { - // Force show for all user so task can be position correctly based on which user is - // active. We clear the force show below. - task.setForceShowForAllUsers(showForAllUsers); - // Add child task. - addChild(task, null); - - // Move child to a proper position, as some restriction for position might apply. - positionChildAt(position, task, moveParents /* includingParents */); - - } finally { - task.setForceShowForAllUsers(false); - } - } + private void addChild(WindowContainer child, int position, boolean moveParents) { + // Add child task. + addChild(child, null); - @Override - void addChild(WindowContainer child, int position) { - final Task task = (Task) child; - addChild(task, position, task.showForAllUsers(), false /* includingParents */); + // Move child to a proper position, as some restriction for position might apply. + positionChildAt(position, child, moveParents /* includingParents */); } void positionChildAtTop(Task child) { @@ -4099,29 +3928,18 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } @Override - void positionChildAt(int position, WindowContainer child, boolean includingParents) { - final Task task = (Task) child; - final int targetPosition = findPositionForTask(task, position); - super.positionChildAt(targetPosition, child, includingParents); - - // Log positioning. - if (DEBUG_TASK_MOVEMENT) { - Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position); - } - - final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0; - EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, targetPosition); - } - - @Override void onChildPositionChanged(WindowContainer child) { if (!mChildren.contains(child)) { return; } - final Task task = (Task) child; - final boolean isTop = getTopChild() == task; - task.updateTaskMovement(isTop); + final boolean isTop = getTopChild() == child; + + final Task task = child.asTask(); + if (task != null) { + task.updateTaskMovement(isTop); + } + if (isTop) { final DisplayContent displayContent = getDisplayContent(); displayContent.layoutAndAssignWindowLayersIfNeeded(); @@ -4129,34 +3947,17 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } @Override - protected void onParentChanged( - ConfigurationContainer newParent, ConfigurationContainer oldParent) { + void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { final DisplayContent display = newParent != null ? ((WindowContainer) newParent).getDisplayContent() : null; final DisplayContent oldDisplay = oldParent != null ? ((WindowContainer) oldParent).getDisplayContent() : null; - mDisplayId = (display != null) ? display.mDisplayId : INVALID_DISPLAY; - mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY; - - if (display != null) { - // Rotations are relative to the display. This means if there are 2 displays rotated - // differently (eg. 2 monitors with one landscape and one portrait), moving a stack - // from one to the other could look like a rotation change. To prevent this - // apparent rotation change (and corresponding bounds rotation), pretend like our - // current rotation is already the same as the new display. - // Note, if ActivityStack or related logic ever gets nested, this logic will need - // to move to onConfigurationChanged. - getConfiguration().windowConfiguration.setRotation( - display.getWindowConfiguration().getRotation()); - } super.onParentChanged(newParent, oldParent); - if (getParent() == null && mDisplayContent != null) { - EventLogTags.writeWmStackRemoved(mStackId); - mDisplayContent = null; - mWmService.mWindowPlacerLocked.requestTraversal(); - } - if (display != null && inSplitScreenPrimaryWindowingMode()) { + + if (display != null && inSplitScreenPrimaryWindowingMode() + // only do this for the base stack + && !newParent.inSplitScreenPrimaryWindowingMode()) { // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */, @@ -4164,7 +3965,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect, mTmpRect2, null, null, PRESERVE_WINDOWS); } - mRootWindowContainer.updateUIDsPresentOnDisplay(); // Resume next focusable stack after reparenting to another display if we aren't removing // the prevous display. @@ -4178,68 +3978,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn newParent.moveStackToDisplay(this, onTop); } - // TODO: We should really have users as a window container in the hierarchy so that we don't - // have to do complicated things like we are doing in this method. - int findPositionForTask(Task task, int targetPosition) { - final boolean canShowTask = task.showToCurrentUser(); - - final int stackSize = mChildren.size(); - int minPosition = 0; - int maxPosition = stackSize - 1; - - if (canShowTask) { - minPosition = computeMinPosition(minPosition, stackSize); - } else { - maxPosition = computeMaxPosition(maxPosition); - } - - // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid. - if (targetPosition == POSITION_BOTTOM && minPosition == 0) { - return POSITION_BOTTOM; - } else if (targetPosition == POSITION_TOP && maxPosition == (stackSize - 1)) { - return POSITION_TOP; - } - // Reset position based on minimum/maximum possible positions. - return Math.min(Math.max(targetPosition, minPosition), maxPosition); - } - - /** Calculate the minimum possible position for a task that can be shown to the user. - * The minimum position will be above all other tasks that can't be shown. - * @param minPosition The minimum position the caller is suggesting. - * We will start adjusting up from here. - * @param size The size of the current task list. - */ - // TODO(task-hierarchy): Move user to their own window container. - private int computeMinPosition(int minPosition, int size) { - while (minPosition < size) { - final Task tmpTask = (Task) mChildren.get(minPosition); - final boolean canShowTmpTask = tmpTask.showToCurrentUser(); - if (canShowTmpTask) { - break; - } - minPosition++; - } - return minPosition; - } - - /** Calculate the maximum possible position for a task that can't be shown to the user. - * The maximum position will be below all other tasks that can be shown. - * @param maxPosition The maximum position the caller is suggesting. - * We will start adjusting down from here. - */ - // TODO(task-hierarchy): Move user to their own window container. - private int computeMaxPosition(int maxPosition) { - while (maxPosition > 0) { - final Task tmpTask = (Task) mChildren.get(maxPosition); - final boolean canShowTmpTask = tmpTask.showToCurrentUser(); - if (!canShowTmpTask) { - break; - } - maxPosition--; - } - return maxPosition; - } - private void updateSurfaceBounds() { updateSurfaceSize(getPendingTransaction()); updateSurfacePosition(); @@ -4456,15 +4194,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn false /* deferResume */); } - @Override - void removeIfPossible() { - if (isAnimating(TRANSITION | CHILDREN)) { - mDeferRemoval = true; - return; - } - removeImmediately(); - } - /** * Adjusts the stack bounds if the IME is visible. * @@ -4568,12 +4297,14 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn t.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER); t.setWaitingForDrawnIfResizingChanged(); } - }); + }, true /* traverseTopToBottom */, this); } /** Resets the resizing state of all windows. */ void endImeAdjustAnimation() { - forAllTasks((t) -> { t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); }); + forAllTasks((t) -> { + t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); + }, true /* traverseTopToBottom */, this); } private int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) { @@ -4751,14 +4482,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return mMinimizeAmount != 0f; } - /** - * @return {@code true} if we have a {@link Task} that is animating (currently only used for the - * recents animation); {@code false} otherwise. - */ - boolean isTaskAnimating() { - return getTask(Task::isTaskAnimating) != null; - } - @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { pw.println(prefix + "mStackId=" + mStackId); @@ -4792,11 +4515,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix); } - @Override - boolean fillsParent() { - return matchParentBounds(); - } - String getName() { return toShortString(); } @@ -4996,7 +4714,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn /** Called immediately prior to resizing the tasks at the end of the pinned stack animation. */ void onPipAnimationEndResize() { mBoundsAnimating = false; - forAllTasks(Task::clearPreserveNonFloatingState, false); + forAllTasks(Task::clearPreserveNonFloatingState, false /* traverseTopToBottom */, this); mWmService.requestTraversal(); } @@ -5096,24 +4814,6 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } @Override - Dimmer getDimmer() { - return mDimmer; - } - - @Override - void prepareSurfaces() { - mDimmer.resetDimStates(); - super.prepareSurfaces(); - getDimBounds(mTmpDimBoundsRect); - - // Bounds need to be relative, as the dim layer is a child. - mTmpDimBoundsRect.offsetTo(0, 0); - if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { - scheduleAnimation(); - } - } - - @Override public boolean setPinnedStackAlpha(float alpha) { // Hold the lock since this is called from the BoundsAnimator running on the UiThread synchronized (mWmService.mGlobalLock) { @@ -5132,49 +4832,10 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return mDisplayContent.getDisplayInfo(); } - void dim(float alpha) { - mDimmer.dimAbove(getPendingTransaction(), alpha); - scheduleAnimation(); - } - - void stopDimming() { - mDimmer.stopDim(getPendingTransaction()); - scheduleAnimation(); - } - AnimatingActivityRegistry getAnimatingActivityRegistry() { return mAnimatingActivityRegistry; } - @Override - void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets, - Rect outSurfaceInsets) { - final Task task = getTopMostTask(); - if (task != null) { - task.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); - } else { - super.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); - } - } - - @Override - RemoteAnimationTarget createRemoteAnimationTarget( - RemoteAnimationController.RemoteAnimationRecord record) { - final Task task = getTopMostTask(); - return task != null ? task.createRemoteAnimationTarget(record) : null; - } - - @Override - public String toString() { - return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this)) - + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode()) - + " visible=" + shouldBeVisible(null /* starting */) - + " translucent=" + isStackTranslucent(null /* starting */) - + ", " - + getChildCount() + " tasks}"; - } - void executeAppTransition(ActivityOptions options) { getDisplay().mDisplayContent.executeAppTransition(); ActivityOptions.abort(options); @@ -5197,6 +4858,7 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn return shouldSleepActivities() || mAtmService.mShuttingDown; } + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { final long token = proto.start(fieldId); @@ -5204,12 +4866,12 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn proto.write(com.android.server.am.ActivityStackProto.ID, mStackId); forAllTasks((t) -> { - t.dumpDebug(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel); - }); + t.dumpDebugInner(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel); + }, true /* traverseTopToBottom */, this); if (mResumedActivity != null) { mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY); } - proto.write(DISPLAY_ID, mDisplayId); + proto.write(DISPLAY_ID, getDisplayId()); if (!matchParentBounds()) { final Rect bounds = getRequestedOverrideBounds(); bounds.dumpDebug(proto, com.android.server.am.ActivityStackProto.BOUNDS); @@ -5230,7 +4892,9 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn final long token = proto.start(fieldId); super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); proto.write(StackProto.ID, mStackId); - forAllTasks((t) -> { t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel); }); + forAllTasks((t) -> { + t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel); + }, true /* traverseTopToBottom */, this); proto.write(FILLS_PARENT, matchParentBounds()); getRawBounds().dumpDebug(proto, StackProto.BOUNDS); proto.write(DEFER_REMOVAL, mDeferRemoval); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 8ef01e3f4776..f2ce7e86bc3b 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -419,7 +419,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final PooledConsumer c = PooledLambda.obtainConsumer( MoveTaskToFullscreenHelper::processTask, this, PooledLambda.__(Task.class)); - fromStack.forAllTasks(c, false); + fromStack.forAllTasks(c, false /* traverseTopToBottom */, fromStack); c.recycle(); mToDisplay = null; mTopTask = null; @@ -1724,7 +1724,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } else { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class)); - stack.forAllTasks(c); + stack.forAllTasks(c, true /* traverseTopToBottom */, stack); c.recycle(); } } @@ -1849,14 +1849,14 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { boolean restoreRecentTaskLocked(Task task, ActivityOptions aOptions, boolean onTop) { final ActivityStack stack = mRootWindowContainer.getLaunchStack(null, aOptions, task, onTop); - final ActivityStack currentStack = task.getStack(); + final WindowContainer parent = task.getParent(); - if (currentStack == stack) { + if (parent == stack) { // Nothing else to do since it is already restored in the right stack. return true; } - if (currentStack != null) { + if (parent != null) { // Task has already been restored once. Just re-parent it to the new stack. task.reparent(stack, POSITION_TOP, true /*moveParents*/, "restoreRecentTaskLocked"); return true; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 47e8b87dcf08..8491bc2c4756 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -306,7 +306,7 @@ import java.util.Set; */ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM; - private static final String TAG_STACK = TAG + POSTFIX_STACK; + static final String TAG_STACK = TAG + POSTFIX_STACK; static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; @@ -2057,8 +2057,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public int getDisplayId(IBinder activityToken) throws RemoteException { synchronized (mGlobalLock) { final ActivityStack stack = ActivityRecord.getStackLocked(activityToken); - if (stack != null && stack.getDisplayId() != INVALID_DISPLAY) { - return stack.getDisplayId(); + if (stack != null) { + final int displayId = stack.getDisplayId(); + return displayId != INVALID_DISPLAY ? displayId : DEFAULT_DISPLAY; } return DEFAULT_DISPLAY; } @@ -3216,8 +3217,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityStack stack = r.getActivityStack(); final Task task = stack.createTask( - mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent, - null /* voiceSession */, null /* voiceInteractor */, !ON_TOP); + mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent, !ON_TOP); if (!mRecentTasks.addToBottom(task)) { // The app has too many tasks already and we can't add any more stack.removeChild(task, "addAppTask"); @@ -4385,7 +4385,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (params.hasSetAspectRatio() && !mWindowManager.isValidPictureInPictureAspectRatio( - r.getDisplayId(), params.getAspectRatio())) { + r.getDisplay(), params.getAspectRatio())) { final float minAspectRatio = mContext.getResources().getFloat( com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio); final float maxAspectRatio = mContext.getResources().getFloat( diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 32802eed94f4..0d19ef723236 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -69,7 +69,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFI import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; @@ -167,8 +166,10 @@ import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; +import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -306,8 +307,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // on the IME target. We mainly have this container grouping so we can keep track of all the IME // window containers together and move them in-sync if/when needed. We use a subclass of // WindowContainer which is omitted from screen magnification, as the IME is never magnified. - private final NonAppWindowContainers mImeWindowsContainers = - new NonAppWindowContainers("mImeWindowsContainers", mWmService); + // TODO(display-area): is "no magnification" in the comment still true? + private final ImeContainer mImeWindowsContainers = new ImeContainer(mWmService); private WindowState mTmpWindow; private WindowState mTmpWindow2; @@ -2026,6 +2027,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTaskStackContainers.getVisibleTasks(); } + SurfaceControl getSplitScreenDividerAnchor() { + return mTaskStackContainers.getSplitScreenDividerAnchor(); + } + void onStackWindowingModeChanged(ActivityStack stack) { mTaskStackContainers.onStackWindowingModeChanged(stack); } @@ -2112,7 +2117,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } boolean forAllImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { - return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom); + return mImeWindowsContainers.forAllWindowForce(callback, traverseTopToBottom); } /** @@ -2370,11 +2375,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo throw new UnsupportedOperationException("See DisplayChildWindowContainer"); } + void positionDisplayAt(int position, boolean includingParents) { + getParent().positionChildAt(position, this, includingParents); + } + @Override void positionChildAt(int position, DisplayChildWindowContainer child, boolean includingParents) { // Children of the display are statically ordered, so the real intention here is to perform // the operation on the display and not the static direct children. - getParent().positionChildAt(position, this, includingParents); + positionDisplayAt(position, includingParents); } void positionStackAt(int position, ActivityStack child, boolean includingParents) { @@ -4243,7 +4252,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo ArrayList<Task> getVisibleTasks() { final ArrayList<Task> visibleTasks = new ArrayList<>(); forAllTasks(task -> { - if (task.isVisible()) { + if (!task.isRootTask() && task.isVisible()) { visibleTasks.add(task); } }); @@ -4262,36 +4271,48 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private void addStackReferenceIfNeeded(ActivityStack stack) { if (stack.isActivityTypeHome()) { if (mHomeStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" - + mHomeStack + " already exist on display=" + this + " stack=" + stack); - + if (!stack.isDescendantOf(mHomeStack)) { + throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" + + mHomeStack + " already exist on display=" + this + + " stack=" + stack); + } + } else { + mHomeStack = stack; } - mHomeStack = stack; } else if (stack.isActivityTypeRecents()) { if (mRecentsStack != null && mRecentsStack != stack) { - throw new IllegalArgumentException( - "addStackReferenceIfNeeded: recents stack=" + mRecentsStack - + " already exist on display=" + this + " stack=" + stack); + if (!stack.isDescendantOf(mRecentsStack)) { + throw new IllegalArgumentException( + "addStackReferenceIfNeeded: recents stack=" + mRecentsStack + + " already exist on display=" + this + " stack=" + stack); + } + } else { + mRecentsStack = stack; } - mRecentsStack = stack; } final int windowingMode = stack.getWindowingMode(); if (windowingMode == WINDOWING_MODE_PINNED) { if (mPinnedStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack=" - + mPinnedStack + " already exist on display=" + this - + " stack=" + stack); + if (!stack.isDescendantOf(mPinnedStack)) { + throw new IllegalArgumentException( + "addStackReferenceIfNeeded: pinned stack=" + mPinnedStack + + " already exist on display=" + this + " stack=" + stack); + } + } else { + mPinnedStack = stack; } - mPinnedStack = stack; } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { if (mSplitScreenPrimaryStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded:" - + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack - + " already exist on display=" + this + " stack=" + stack); + if (!stack.isDescendantOf(mSplitScreenPrimaryStack)) { + throw new IllegalArgumentException("addStackReferenceIfNeeded:" + + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack + + " already exist on display=" + this + " stack=" + stack); + } + } else { + mSplitScreenPrimaryStack = stack; + mDisplayContent.onSplitScreenModeActivated(); + mDividerControllerLocked.notifyDockedStackExistsChanged(true); } - mSplitScreenPrimaryStack = stack; - mDisplayContent.onSplitScreenModeActivated(); - mDividerControllerLocked.notifyDockedStackExistsChanged(true); } } @@ -4340,8 +4361,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void positionChildAt(int position, ActivityStack child, boolean includingParents) { - if (child.getWindowConfiguration().isAlwaysOnTop() - && position != POSITION_TOP && position != mChildren.size()) { + final boolean moveToTop = (position == POSITION_TOP || position == getChildCount()); + final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0); + if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) { // This stack is always-on-top, override the default behavior. Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom"); @@ -4357,18 +4379,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo includingParents = false; } final int targetPosition = findPositionForStack(position, child, false /* adding */); - super.positionChildAt(targetPosition, child, includingParents); - - if (includingParents) { - // We still want to move the display of this stack container to top because even the - // target position is adjusted to non-top, the intention of the condition is to have - // higher z-order to gain focus (e.g. moving a task of a fullscreen stack to front - // in a non-top display which is using picture-in-picture mode). - final int topChildPosition = getChildCount() - 1; - if (targetPosition < topChildPosition && position >= topChildPosition) { - getParent().positionChildAt(POSITION_TOP, this /* child */, - true /* includingParents */); - } + super.positionChildAt(targetPosition, child, false /* includingParents */); + + if (includingParents && (moveToTop || moveToBottom)) { + // The DisplayContent children do not re-order, but we still want to move the + // display of this stack container because the intention of positioning is to have + // higher z-order to gain focus. + positionDisplayAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM, + true /* includingParents */); } setLayoutNeeded(); @@ -4688,38 +4706,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } @Override - SurfaceControl.Builder makeChildSurface(WindowContainer child) { - final SurfaceControl.Builder builder = super.makeChildSurface(child); - if (child instanceof WindowToken && ((WindowToken) child).mRoundedCornerOverlay) { - // To draw above the ColorFade layer during the screen off transition, the - // rounded corner overlays need to be at the root of the surface hierarchy. - // TODO: move the ColorLayer into the display overlay layer such that this is not - // necessary anymore. - builder.setParent(null); - } - return builder; - } - - @Override void assignChildLayers(SurfaceControl.Transaction t) { - assignChildLayers(t, null /* imeContainer */); - } - - void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) { - boolean needAssignIme = imeContainer != null - && imeContainer.getSurfaceControl() != null; + boolean needAssignIme = mImeWindowsContainers.getSurfaceControl() != null; for (int j = 0; j < mChildren.size(); ++j) { final WindowToken wt = mChildren.get(j); - // See {@link mSplitScreenDividerAnchor} - if (wt.windowType == TYPE_DOCK_DIVIDER) { - wt.assignRelativeLayer(t, mTaskStackContainers.getSplitScreenDividerAnchor(), 1); - continue; - } - if (wt.mRoundedCornerOverlay) { - wt.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); - continue; - } wt.assignLayer(t, j); wt.assignChildLayers(t); @@ -4728,13 +4719,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (needAssignIme && layer >= mWmService.mPolicy.getWindowLayerFromTypeLw( TYPE_INPUT_METHOD_DIALOG, true)) { - imeContainer.assignRelativeLayer(t, wt.getSurfaceControl(), -1); + mImeWindowsContainers.assignRelativeLayer(t, wt.getSurfaceControl(), -1); needAssignIme = false; } } - if (needAssignIme) { - imeContainer.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE); - } } } @@ -4748,6 +4736,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void assignChildLayers(SurfaceControl.Transaction t) { + mImeWindowsContainers.setNeedsLayer(); mBelowAppWindowsContainers.assignLayer(t, 0); mTaskStackContainers.assignLayer(t, 1); mAboveAppWindowsContainers.assignLayer(t, 2); @@ -4783,15 +4772,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // TODO: We need to use an extra level on the app surface to ensure // this is always above SurfaceView but always below attached window. 1); - needAssignIme = false; } // Above we have assigned layers to our children, now we ask them to assign // layers to their children. mBelowAppWindowsContainers.assignChildLayers(t); mTaskStackContainers.assignChildLayers(t); - mAboveAppWindowsContainers.assignChildLayers(t, - needAssignIme ? mImeWindowsContainers : null); + mAboveAppWindowsContainers.assignChildLayers(t); + mImeWindowsContainers.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE); mImeWindowsContainers.assignChildLayers(t); } @@ -4807,47 +4795,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo addChild(mImeWindowsContainers, null); } - /** - * In split-screen mode we process the IME containers above the docked divider - * rather than directly above their target. - */ - private boolean skipTraverseChild(WindowContainer child) { - return child == mImeWindowsContainers && mInputMethodTarget != null - && !hasSplitScreenPrimaryStack(); - } - - @Override - boolean forAllWindows(ToBooleanFunction<WindowState> callback, - boolean traverseTopToBottom) { - // Special handling so we can process IME windows with #forAllImeWindows above their IME - // target, or here in order if there isn't an IME target. - if (traverseTopToBottom) { - for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer child = mChildren.get(i); - if (skipTraverseChild(child)) { - continue; - } - - if (child.forAllWindows(callback, traverseTopToBottom)) { - return true; - } - } - } else { - final int count = mChildren.size(); - for (int i = 0; i < count; i++) { - final WindowContainer child = mChildren.get(i); - if (skipTraverseChild(child)) { - continue; - } - - if (child.forAllWindows(callback, traverseTopToBottom)) { - return true; - } - } - } - return false; - } - @Override void positionChildAt(int position, WindowContainer child, boolean includingParents) { // Children of the WindowContainers are statically ordered, so the real intention here @@ -4961,6 +4908,68 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + /** + * Container for IME windows. + * + * This has some special behaviors: + * - layers assignment is ignored except if setNeedsLayer() has been called before (and no + * layer has been assigned since), to facilitate assigning the layer from the IME target, or + * fall back if there is no target. + * - the container doesn't always participate in window traversal, according to + * {@link #skipImeWindowsDuringTraversal()} + */ + private class ImeContainer extends NonAppWindowContainers { + boolean mNeedsLayer = false; + + ImeContainer(WindowManagerService wms) { + super("ImeContainer", wms); + } + + public void setNeedsLayer() { + mNeedsLayer = true; + } + + @Override + boolean forAllWindows(ToBooleanFunction<WindowState> callback, + boolean traverseTopToBottom) { + final DisplayContent dc = mDisplayContent; + if (skipImeWindowsDuringTraversal(dc)) { + return false; + } + return super.forAllWindows(callback, traverseTopToBottom); + } + + private boolean skipImeWindowsDuringTraversal(DisplayContent dc) { + // We skip IME windows so they're processed just above their target, except + // in split-screen mode where we process the IME containers above the docked divider. + return dc.mInputMethodTarget != null && !dc.hasSplitScreenPrimaryStack(); + } + + /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ + boolean forAllWindowForce(ToBooleanFunction<WindowState> callback, + boolean traverseTopToBottom) { + return super.forAllWindows(callback, traverseTopToBottom); + } + + @Override + void assignLayer(Transaction t, int layer) { + if (!mNeedsLayer) { + return; + } + super.assignLayer(t, layer); + mNeedsLayer = false; + } + + @Override + void assignRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { + if (!mNeedsLayer) { + return; + } + super.assignRelativeLayer(t, relativeTo, layer); + mNeedsLayer = false; + } + } + @Override SurfaceSession getSession() { return mSession; @@ -5064,6 +5073,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * with {@link WindowState#assignLayer} */ void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) { + mImeWindowsContainers.setNeedsLayer(); child.assignRelativeLayer(t, mImeWindowsContainers.getSurfaceControl(), 1); } @@ -5740,6 +5750,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mAtmService.mStackSupervisor.getNextTaskIdForUser(); } + ActivityStack createStack(int windowingMode, int activityType, boolean onTop) { + return createStack(windowingMode, activityType, onTop, null /*info*/, null /*intent*/); + } + /** * Creates a stack matching the input windowing mode and activity type on this display. * @param windowingMode The windowing mode the stack should be created in. If @@ -5751,13 +5765,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @param onTop If true the stack will be created at the top of the display, else at the bottom. * @return The newly created stack. */ - ActivityStack createStack(int windowingMode, int activityType, boolean onTop) { + ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info, + Intent intent) { if (mSingleTaskInstance && getStackCount() > 0) { // Create stack on default display instead since this display can only contain 1 stack. // TODO: Kinda a hack, but better that having the decision at each call point. Hoping // this goes away once ActivityView is no longer using virtual displays. return mRootWindowContainer.getDefaultDisplay().createStack( - windowingMode, activityType, onTop); + windowingMode, activityType, onTop, info, intent); } if (activityType == ACTIVITY_TYPE_UNDEFINED) { @@ -5785,18 +5800,23 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } final int stackId = getNextStackId(); - return createStackUnchecked(windowingMode, activityType, stackId, onTop); + return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent); } @VisibleForTesting ActivityStack createStackUnchecked(int windowingMode, int activityType, - int stackId, boolean onTop) { + int stackId, boolean onTop, ActivityInfo info, Intent intent) { if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) { throw new IllegalArgumentException("Stack with windowing mode cannot with non standard " + "activity type."); } + if (info == null) { + info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + } + final ActivityStack stack = new ActivityStack(this, stackId, - mRootWindowContainer.mStackSupervisor, activityType); + mRootWindowContainer.mStackSupervisor, activityType, info, intent); addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 5df80fcc7c06..e1dfc177a881 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -32,7 +32,6 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.Process.SYSTEM_UID; -import static android.view.Display.DEFAULT_DISPLAY; import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; @@ -1382,7 +1381,7 @@ class RecentTasks { // Ignore tasks from different displays // TODO (b/115289124): No Recents on non-default displays. - if (stack.getDisplayId() != DEFAULT_DISPLAY) { + if (!stack.isOnHomeDisplay()) { return false; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index b255b5eb7c0e..614809505dee 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -374,7 +374,7 @@ public class RecentsAnimationController implements DeathRecipient { final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) -> { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class), visibleTasks); - targetStack.forAllTasks(c); + targetStack.forAllTasks(c, true /* traverseTopToBottom */, targetStack); c.recycle(); } diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index e310fc18b6a7..2f61ca05ada5 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -247,8 +247,7 @@ class ResetTargetTaskHelper { } else { targetTask = mTargetStack.createTask( atmService.mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info, - null /* intent */, null /* voiceSession */, null /* voiceInteractor */, - false /* toTop */); + null /* intent */, false /* toTop */); targetTask.affinityIntent = r.intent; createdTasks.add(targetTask); if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 8b1005e2e847..8202833783a9 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2131,23 +2131,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // We will then perform a windowing mode change for both scenarios. stack = display.createStack( r.getActivityStack().getRequestedOverrideWindowingMode(), - r.getActivityType(), ON_TOP); + r.getActivityType(), ON_TOP, r.info, r.intent); // There are multiple activities in the task and moving the top activity should // reveal/leave the other activities in their original task. - // Currently, we don't support reparenting activities across tasks in two different - // stacks, so instead, just create a new task in the same stack, reparent the - // activity into that task, and then reparent the whole task to the new stack. This - // ensures that all the necessary work to migrate states in the old and new stacks - // is also done. - final Task newTask = task.getStack().createTask( - mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info, - r.intent, null, null, true); + Task newTask = stack.createTask(mStackSupervisor.getNextTaskIdForUser(r.mUserId), + r.info, r.intent, true); r.reparent(newTask, MAX_VALUE, "moveActivityToStack"); - - // Defer resume until below, and do not schedule PiP changes until we animate below - newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, - DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason); } stack.setWindowingMode(WINDOWING_MODE_PINNED); @@ -2416,17 +2406,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> info.position = display != null ? display.getIndexOf(stack) : 0; info.configuration.setTo(stack.getConfiguration()); - final int numTasks = stack.getChildCount(); + final int numTasks = stack.getDescendantTaskCount(); info.taskIds = new int[numTasks]; info.taskNames = new String[numTasks]; info.taskBounds = new Rect[numTasks]; info.taskUserIds = new int[numTasks]; - final int[] currenIndex = {0}; + final int[] currentIndex = {0}; final PooledConsumer c = PooledLambda.obtainConsumer( RootWindowContainer::processTaskForStackInfo, PooledLambda.__(Task.class), info, - currenIndex); - stack.forAllTasks(c, false); + currentIndex); + stack.forAllTasks(c, false /* traverseTopToBottom */, stack); c.recycle(); final ActivityRecord top = stack.topRunningActivity(); @@ -2569,7 +2559,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } ActivityStack findStackBehind(ActivityStack stack) { - final DisplayContent display = getDisplayContent(stack.getDisplayId()); + final DisplayContent display = stack.getDisplayContent(); if (display != null) { for (int i = display.getStackCount() - 1; i >= 0; i--) { if (display.getStackAt(i) == stack && i > 0) { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 98127ab2eec8..6ebbf776feba 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -93,6 +93,9 @@ class RunningTasks { } private void processTask(Task task) { + if (task.isRootTask()) { + return; + } if (task.getTopNonFinishingActivity() == null) { // Skip if there are no activities in the task return; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 3b349b817108..5babdafc856d 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -31,6 +31,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.Nullable; import android.content.ClipData; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; @@ -189,7 +190,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); @@ -197,7 +199,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrame, outContentInsets, outVisibleInsets, outStableInsets, outBackdropFrame, cutout, - mergedConfiguration, outSurfaceControl, outInsetsState); + mergedConfiguration, outSurfaceControl, outInsetsState, outSurfaceSize); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index b7201b4dbde5..9e6cb6884820 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -30,6 +30,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; @@ -69,6 +71,7 @@ import static com.android.server.am.TaskRecordProto.RESIZE_MODE; import static com.android.server.am.TaskRecordProto.STACK_ID; import static com.android.server.am.TaskRecordProto.TASK; import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN; +import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; @@ -82,6 +85,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECEN import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS; @@ -93,6 +97,7 @@ import static com.android.server.wm.TaskProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; @@ -202,9 +207,9 @@ class Task extends WindowContainer<WindowContainer> { // Current version of the task record we persist. Used to check if we need to run any upgrade // code. - private static final int PERSIST_TASK_VERSION = 1; + static final int PERSIST_TASK_VERSION = 1; - private static final int INVALID_MIN_SIZE = -1; + static final int INVALID_MIN_SIZE = -1; /** * The modes to control how the stack is moved to the front when calling {@link Task#reparent}. @@ -331,6 +336,8 @@ class Task extends WindowContainer<WindowContainer> { final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport(); final ActivityTaskManagerService mAtmService; + final ActivityStackSupervisor mStackSupervisor; + final RootWindowContainer mRootWindowContainer; /* Unique identifier for this task. */ final int mTaskId; @@ -345,8 +352,12 @@ class Task extends WindowContainer<WindowContainer> { // TODO(b/119687367): This member is temporary. private final Rect mOverrideDisplayedBounds = new Rect(); + // Id of the previous display the stack was on. + int mPrevDisplayId = INVALID_DISPLAY; + /** ID of the display which rotation {@link #mRotation} has. */ private int mLastRotationDisplayId = INVALID_DISPLAY; + /** * Display rotation as of the last time {@link #setBounds(Rect)} was called or this task was * moved to a new display. @@ -388,8 +399,37 @@ class Task extends WindowContainer<WindowContainer> { private static Exception sTmpException; + /** ActivityRecords that are exiting, but still on screen for animations. */ + final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>(); + + /** + * When we are in the process of pausing an activity, before starting the + * next one, this variable holds the activity that is currently being paused. + */ + ActivityRecord mPausingActivity = null; + + /** + * This is the last activity that we put into the paused state. This is + * used to determine if we need to do an activity transition while sleeping, + * when we normally hold the top activity paused. + */ + ActivityRecord mLastPausedActivity = null; + + /** + * Activities that specify No History must be removed once the user navigates away from them. + * If the device goes to sleep with such an activity in the paused state then we save it here + * and finish it later if another activity replaces it on wakeup. + */ + ActivityRecord mLastNoHistoryActivity = null; + + /** Current activity that is resumed, or null if there is none. */ + ActivityRecord mResumedActivity = null; + private boolean mForceShowForAllUsers; + /** When set, will force the task to report as invisible. */ + boolean mForceHidden = false; + private final FindRootHelper mFindRootHelper = new FindRootHelper(); private class FindRootHelper { private ActivityRecord mRoot; @@ -488,6 +528,8 @@ class Task extends WindowContainer<WindowContainer> { EventLogTags.writeWmTaskCreated(_taskId, stack != null ? stack.mStackId : INVALID_STACK_ID); mAtmService = atmService; + mStackSupervisor = atmService.mStackSupervisor; + mRootWindowContainer = mAtmService.mRootWindowContainer; mTaskId = _taskId; mUserId = _userId; mResizeMode = resizeMode; @@ -551,7 +593,7 @@ class Task extends WindowContainer<WindowContainer> { if (autoRemoveFromRecents() || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. - mAtmService.mStackSupervisor.mRecentTasks.remove(this); + mStackSupervisor.mRecentTasks.remove(this); } removeIfPossible(); @@ -560,13 +602,18 @@ class Task extends WindowContainer<WindowContainer> { @VisibleForTesting @Override void removeIfPossible() { - mAtmService.getLockTaskController().clearLockedTask(this); + final boolean isRootTask = isRootTask(); + if (!isRootTask) { + mAtmService.getLockTaskController().clearLockedTask(this); + } if (shouldDeferRemoval()) { if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId); return; } removeImmediately(); - mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId); + if (!isRootTask) { + mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId); + } } void setResizeMode(int resizeMode) { @@ -574,8 +621,8 @@ class Task extends WindowContainer<WindowContainer> { return; } mResizeMode = resizeMode; - mAtmService.mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities(); + mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); + mRootWindowContainer.resumeFocusedStacksTopActivities(); updateTaskDescription(); } @@ -592,7 +639,7 @@ class Task extends WindowContainer<WindowContainer> { setBounds(bounds); if (!inFreeformWindowingMode()) { // re-restore the task so it can have the proper stack association. - mAtmService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP); + mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP); } return true; } @@ -628,10 +675,9 @@ class Task extends WindowContainer<WindowContainer> { // this won't cause tons of irrelevant windows being preserved because only // activities in this task may experience a bounds change. Configs for other // activities stay the same. - mAtmService.mRootWindowContainer.ensureActivitiesVisible(r, 0, - preserveWindow); + mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow); if (!kept) { - mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities(); + mRootWindowContainer.resumeFocusedStacksTopActivities(); } } } @@ -695,8 +741,8 @@ class Task extends WindowContainer<WindowContainer> { boolean reparent(ActivityStack preferredStack, int position, @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange, String reason) { - final ActivityStackSupervisor supervisor = mAtmService.mStackSupervisor; - final RootWindowContainer root = mAtmService.mRootWindowContainer; + final ActivityStackSupervisor supervisor = mStackSupervisor; + final RootWindowContainer root = mRootWindowContainer; final WindowManagerService windowManager = mAtmService.mWindowManager; final ActivityStack sourceStack = getStack(); final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack, @@ -765,7 +811,7 @@ class Task extends WindowContainer<WindowContainer> { wasPaused, reason); } if (!animate) { - mAtmService.mStackSupervisor.mNoAnimActivities.add(topActivity); + mStackSupervisor.mNoAnimActivities.add(topActivity); } // We might trigger a configuration change. Save the current task bounds for freezing. @@ -784,7 +830,7 @@ class Task extends WindowContainer<WindowContainer> { } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) { Rect bounds = getLaunchBounds(); if (bounds == null) { - mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null); + mStackSupervisor.getLaunchParamsController().layoutTask(this, null); bounds = configBounds; } kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume); @@ -792,7 +838,7 @@ class Task extends WindowContainer<WindowContainer> { if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) { // Move recents to front so it is not behind home stack when going into docked // mode - mAtmService.mStackSupervisor.moveRecentsStackToFront(reason); + mStackSupervisor.moveRecentsStackToFront(reason); } kept = resize(toStack.getRequestedOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow, deferResume); @@ -858,6 +904,14 @@ class Task extends WindowContainer<WindowContainer> { mCallingPackage = r.launchedFromPackage; setIntent(r.intent, r.info); setLockTaskAuth(r); + + final WindowContainer parent = getParent(); + if (parent != null) { + final Task t = parent.asTask(); + if (t != null) { + t.setIntent(r); + } + } } /** Sets the original intent, _without_ updating the calling uid or package. */ @@ -971,8 +1025,13 @@ class Task extends WindowContainer<WindowContainer> { } boolean returnsToHomeStack() { - final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; - return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + if (inMultiWindowMode() || !hasChild()) return false; + if (intent != null) { + final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; + return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + } + final Task bottomTask = getBottomMostTask(); + return bottomTask != this && bottomTask.returnsToHomeStack(); } void setPrevAffiliate(Task prevAffiliate) { @@ -987,37 +1046,72 @@ class Task extends WindowContainer<WindowContainer> { @Override void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { - final ActivityStack oldStack = ((ActivityStack) oldParent); - final ActivityStack newStack = ((ActivityStack) newParent); + final DisplayContent display = newParent != null + ? ((WindowContainer) newParent).getDisplayContent() : null; + final DisplayContent oldDisplay = oldParent != null + ? ((WindowContainer) oldParent).getDisplayContent() : null; + + mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY; // Task is going to be removed, clean it up before detaching from hierarchy. if (oldParent != null && newParent == null) { cleanUpResourcesForDestroy(); } + if (display != null) { + // TODO(NOW!): Chat with the erosky@ of this code to see if this really makes sense here... + // Rotations are relative to the display. This means if there are 2 displays rotated + // differently (eg. 2 monitors with one landscape and one portrait), moving a stack + // from one to the other could look like a rotation change. To prevent this + // apparent rotation change (and corresponding bounds rotation), pretend like our + // current rotation is already the same as the new display. + // Note, if ActivityStack or related logic ever gets nested, this logic will need + // to move to onConfigurationChanged. + getConfiguration().windowConfiguration.setRotation( + display.getWindowConfiguration().getRotation()); + } + super.onParentChanged(newParent, oldParent); - if (oldStack != null) { - final PooledConsumer c = PooledLambda.obtainConsumer( - ActivityStack::onActivityRemovedFromStack, oldStack, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(c); - c.recycle(); + // TODO(NOW): The check for null display content and setting it to null doesn't really + // make sense here... + + // TODO(stack-merge): This is mostly taking care of the case where the stask is removing from + // the display, so we should probably consolidate it there instead. + + if (getParent() == null && mDisplayContent != null) { + EventLogTags.writeWmStackRemoved(getStackId()); + mDisplayContent = null; + mWmService.mWindowPlacerLocked.requestTraversal(); + } - if (oldStack.inPinnedWindowingMode() - && (newStack == null || !newStack.inPinnedWindowingMode())) { + if (oldParent != null) { + final Task oldParentTask = ((WindowContainer) oldParent).asTask(); + if (oldParentTask != null) { + final PooledConsumer c = PooledLambda.obtainConsumer( + Task::cleanUpActivityReferences, oldParentTask, + PooledLambda.__(ActivityRecord.class)); + forAllActivities(c); + c.recycle(); + } + + if (oldParent.inPinnedWindowingMode() + && (newParent == null || !newParent.inPinnedWindowingMode())) { // Notify if a task from the pinned stack is being removed // (or moved depending on the mode). mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned(); } } - if (newStack != null) { - final PooledConsumer c = PooledLambda.obtainConsumer( - ActivityStack::onActivityAddedToStack, newStack, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(c); - c.recycle(); + if (newParent != null) { + final Task newParentTask = ((WindowContainer) newParent).asTask(); + if (newParentTask != null) { + final ActivityRecord top = newParentTask.getTopNonFinishingActivity( + false /* includeOverlays */); + if (top != null && top.isState(RESUMED)) { + newParentTask.setResumedActivity(top, "addedToTask"); + } + } // TODO: Ensure that this is actually necessary here // Notify the voice session if required @@ -1048,7 +1142,41 @@ class Task extends WindowContainer<WindowContainer> { forceWindowsScaleable(false /* force */); } - mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay(); + mRootWindowContainer.updateUIDsPresentOnDisplay(); + } + + void cleanUpActivityReferences(ActivityRecord r) { + final WindowContainer parent = getParent(); + if (parent != null && parent.asTask() != null) { + parent.asTask().cleanUpActivityReferences(r); + return; + } + r.removeTimeouts(); + mExitingActivities.remove(r); + + if (mResumedActivity != null && mResumedActivity == r) { + setResumedActivity(null, "cleanUpActivityReferences"); + } + if (mPausingActivity != null && mPausingActivity == r) { + mPausingActivity = null; + } + } + + /** @return the currently resumed activity. */ + ActivityRecord getResumedActivity() { + return mResumedActivity; + } + + void setResumedActivity(ActivityRecord r, String reason) { + if (mResumedActivity == r) { + return; + } + + if (ActivityTaskManagerDebugConfig.DEBUG_STACK) Slog.d(TAG_STACK, + "setResumedActivity stack:" + this + " + from: " + + mResumedActivity + " to:" + r + " reason:" + reason); + mResumedActivity = r; + mStackSupervisor.updateTopResumedActivityIfNeeded(); } void updateTaskMovement(boolean toFront) { @@ -1061,7 +1189,7 @@ class Task extends WindowContainer<WindowContainer> { mLastTimeMoved *= -1; } } - mAtmService.mRootWindowContainer.invalidateTaskLayers(); + mRootWindowContainer.invalidateTaskLayers(); } // Close up recents linked list. @@ -1114,7 +1242,11 @@ class Task extends WindowContainer<WindowContainer> { /** Returns the intent for the root activity for this task */ Intent getBaseIntent() { - return intent != null ? intent : affinityIntent; + if (intent != null) return intent; + if (affinityIntent != null) return affinityIntent; + // Probably a task that contains other tasks, so return the intent for the top task? + final Task topTask = getTopMostTask(); + return topTask != null ? topTask.getBaseIntent() : null; } /** Returns the first non-finishing activity from the bottom. */ @@ -1199,11 +1331,18 @@ class Task extends WindowContainer<WindowContainer> { // If this task had any child before we added this one. boolean hadChild = hasChild(); - final ActivityRecord r = (ActivityRecord) child; - index = getAdjustedAddPosition(r, index); - super.addChild(r, index); + index = getAdjustedChildPosition(child, index); + super.addChild(child, index); ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addChild: %s at top.", this); + + // Make sure the list of display UID whitelists is updated + // now that this record is in a new task. + mRootWindowContainer.updateUIDsPresentOnDisplay(); + + final ActivityRecord r = child.asActivityRecord(); + if (r == null) return; + r.inHistory = true; // Only set this based on the first activity @@ -1228,10 +1367,6 @@ class Task extends WindowContainer<WindowContainer> { } updateEffectiveIntent(); - - // Make sure the list of display UID whitelists is updated - // now that this record is in a new task. - mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay(); } void addChild(ActivityRecord r) { @@ -1239,12 +1374,19 @@ class Task extends WindowContainer<WindowContainer> { } @Override - void removeChild(WindowContainer r) { + void removeChild(WindowContainer child) { + removeChild(child, "removeChild"); + } + + void removeChild(WindowContainer r, String reason) { if (!mChildren.contains(r)) { Slog.e(TAG, "removeChild: r=" + r + " not found in t=" + this); return; } + if (DEBUG_TASK_MOVEMENT) { + Slog.d(TAG_WM, "removeChild: child=" + r + " reason=" + reason); + } super.removeChild(r); if (inPinnedWindowingMode()) { @@ -1254,7 +1396,15 @@ class Task extends WindowContainer<WindowContainer> { mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); } - final String reason = "removeChild"; + final boolean isRootTask = isRootTask(); + if (isRootTask) { + final DisplayContent display = getDisplayContent(); + if (display.isSingleTaskInstance()) { + mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId); + } + display.mDisplayContent.setLayoutNeeded(); + } + if (hasChild()) { updateEffectiveIntent(); @@ -1269,12 +1419,14 @@ class Task extends WindowContainer<WindowContainer> { // work. // TODO: If the callers to removeChild() changes such that we have multiple places // where we are destroying the task, move this back into removeChild() - mAtmService.mStackSupervisor.removeTask(this, false /* killProcess */, + mStackSupervisor.removeTask(this, false /* killProcess */, !REMOVE_FROM_RECENTS, reason); } } else if (!mReuseTask) { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse - getStack().removeChild(this, reason); + if (!isRootTask) { + getStack().removeChild(this, reason); + } EventLogTags.writeWmTaskRemoved(mTaskId, "removeChild: last r=" + r + " in t=" + this); removeIfPossible(); @@ -1445,11 +1597,15 @@ class Task extends WindowContainer<WindowContainer> { @Override public boolean supportsSplitScreenWindowingMode() { + final Task topTask = getTopMostTask(); + return super.supportsSplitScreenWindowingMode() + && (topTask == null || topTask.supportsSplitScreenWindowingModeInner()); + } + + private boolean supportsSplitScreenWindowingModeInner() { // A task can not be docked even if it is considered resizeable because it only supports // picture-in-picture mode but has a non-resizeable resizeMode return super.supportsSplitScreenWindowingMode() - // TODO(task-group): Probably makes sense to move this and associated code into - // WindowContainer so it affects every node. && mAtmService.mSupportsSplitScreenMultiWindow && (mAtmService.mForceResizableActivities || (isResizeable(false /* checkSupportsPip */) @@ -1464,7 +1620,7 @@ class Task extends WindowContainer<WindowContainer> { * secondary display. */ boolean canBeLaunchedOnDisplay(int displayId) { - return mAtmService.mStackSupervisor.canPlaceEntityOnDisplay(displayId, + return mStackSupervisor.canPlaceEntityOnDisplay(displayId, -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */); } @@ -1534,6 +1690,14 @@ class Task extends WindowContainer<WindowContainer> { } mAtmService.getTaskChangeNotificationController().notifyTaskDescriptionChanged( getTaskInfo()); + + final WindowContainer parent = getParent(); + if (parent != null) { + final Task t = parent.asTask(); + if (t != null) { + t.updateTaskDescription(); + } + } } private static boolean setTaskDescriptionFromActivityAboveRoot( @@ -1576,9 +1740,11 @@ class Task extends WindowContainer<WindowContainer> { @VisibleForTesting void updateEffectiveIntent() { final ActivityRecord root = getRootActivity(true /*setToBottomIfNone*/); - setIntent(root); - // Update the task description when the activities change - updateTaskDescription(); + if (root != null) { + setIntent(root); + // Update the task description when the activities change + updateTaskDescription(); + } } void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { @@ -1592,9 +1758,8 @@ class Task extends WindowContainer<WindowContainer> { // If the task has no requested minimal size, we'd like to enforce a minimal size // so that the user can not render the task too small to manipulate. We don't need // to do this for the pinned stack as the bounds are controlled by the system. - if (!inPinnedWindowingMode() && getDisplayContent() != null) { - final int defaultMinSizeDp = - mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; + if (!inPinnedWindowingMode() && getStack() != null) { + final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; final DisplayContent display = getDisplayContent(); final float density = (float) display.getConfiguration().densityDpi / DisplayMetrics.DENSITY_DEFAULT; @@ -1658,10 +1823,25 @@ class Task extends WindowContainer<WindowContainer> { * @param reason The reason for the change. */ void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) { - final ActivityStack parent = getStack(); + final Task parentTask = getParent().asTask(); + if (parentTask != null) { + parentTask.onActivityStateChanged(record, state, reason); + return; + } - if (parent != null) { - parent.onActivityStateChanged(record, state, reason); + if (record == mResumedActivity && state != RESUMED) { + setResumedActivity(null, reason + " - onActivityStateChanged"); + } + + if (state == RESUMED) { + if (ActivityTaskManagerDebugConfig.DEBUG_STACK) { + Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" + reason); + } + setResumedActivity(record, reason + " - onActivityStateChanged"); + if (record == mRootWindowContainer.getTopResumedActivity()) { + mAtmService.setResumedActivityUncheckLocked(record, reason); + } + mStackSupervisor.mRecentTasks.add(record.getTask()); } } @@ -1683,7 +1863,7 @@ class Task extends WindowContainer<WindowContainer> { final boolean wasInMultiWindowMode = inMultiWindowMode(); super.onConfigurationChanged(newParentConfig); if (wasInMultiWindowMode != inMultiWindowMode()) { - mAtmService.mStackSupervisor.scheduleUpdateMultiWindowMode(this); + mStackSupervisor.scheduleUpdateMultiWindowMode(this); } // If the configuration supports persistent bounds (eg. Freeform), keep track of the @@ -1724,7 +1904,7 @@ class Task extends WindowContainer<WindowContainer> { } // Saves the new state so that we can launch the activity at the same location. - mAtmService.mStackSupervisor.mLaunchParamsPersister.saveTask(this); + mStackSupervisor.mLaunchParamsPersister.saveTask(this); } /** @@ -1986,6 +2166,10 @@ class Task extends WindowContainer<WindowContainer> { @Override void resolveOverrideConfiguration(Configuration newParentConfig) { + if (isRootTask()) { + super.resolveOverrideConfiguration(newParentConfig); + return; + } mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); super.resolveOverrideConfiguration(newParentConfig); int windowingMode = @@ -2010,12 +2194,12 @@ class Task extends WindowContainer<WindowContainer> { final Rect parentBounds = new Rect(newParentConfig.windowConfiguration.getBounds()); final DisplayContent display = getDisplayContent(); - if (display != null && display.mDisplayContent != null) { + if (display != null) { // If a freeform window moves below system bar, there is no way to move it again // by touch. Because its caption is covered by system bar. So we exclude them // from stack bounds. and then caption will be shown inside stable area. final Rect stableBounds = new Rect(); - display.mDisplayContent.getStableRect(stableBounds); + display.getStableRect(stableBounds); parentBounds.intersect(stableBounds); } @@ -2090,6 +2274,7 @@ class Task extends WindowContainer<WindowContainer> { * input stack. */ void updateOverrideConfigurationForStack(ActivityStack inStack) { final ActivityStack stack = getStack(); + if (stack != null && stack == inStack) { return; } @@ -2105,7 +2290,7 @@ class Task extends WindowContainer<WindowContainer> { if (mLastNonFullscreenBounds != null) { setBounds(mLastNonFullscreenBounds); } else { - mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null); + mStackSupervisor.getLaunchParamsController().layoutTask(this, null); } } else { setBounds(inStack.getRequestedOverrideBounds()); @@ -2148,7 +2333,10 @@ class Task extends WindowContainer<WindowContainer> { @Override DisplayContent getDisplayContent() { - return getStack() != null ? getStack().getDisplayContent() : null; + // TODO: Why aren't we just using our own display content vs. parent's??? + final ActivityStack stack = getStack(); + return stack != null && stack != this + ? stack.getDisplayContent() : super.getDisplayContent(); } int getDisplayId() { @@ -2157,7 +2345,8 @@ class Task extends WindowContainer<WindowContainer> { } ActivityStack getStack() { - return (ActivityStack) getParent(); + final WindowContainer parent = getParent(); + return (ActivityStack) (parent instanceof ActivityStack ? parent : this); } /** @@ -2168,27 +2357,99 @@ class Task extends WindowContainer<WindowContainer> { return stack != null ? stack.mStackId : INVALID_STACK_ID; } - // TODO(task-hierarchy): Needs to take a generic WindowManager when task contains other tasks. - int getAdjustedAddPosition(ActivityRecord r, int suggestedPosition) { - int maxPosition = mChildren.size(); - if (!r.isTaskOverlay()) { - // We want to place all non-overlay activities below overlays. - final ActivityRecord bottomMostOverlay = getActivity((ar) -> ar.isTaskOverlay(), false); - if (bottomMostOverlay != null) { - maxPosition = Math.max(mChildren.indexOf(bottomMostOverlay) - 1, 0); + int getDescendantTaskCount() { + final int[] currentCount = {0}; + final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, + PooledLambda.__(Task.class), currentCount); + forAllTasks(c, false /* traverseTopToBottom */, this); + c.recycle(); + return currentCount[0]; + } + + /** Calculate the minimum possible position for a task that can be shown to the user. + * The minimum position will be above all other tasks that can't be shown. + * @param minPosition The minimum position the caller is suggesting. + * We will start adjusting up from here. + * @param size The size of the current task list. + */ + // TODO: Move user to their own window container. + private int computeMinUserPosition(int minPosition, int size) { + while (minPosition < size) { + final WindowContainer child = mChildren.get(minPosition); + final boolean canShow = child.showToCurrentUser(); + if (canShow) { + break; + } + minPosition++; + } + return minPosition; + } + + /** Calculate the maximum possible position for a task that can't be shown to the user. + * The maximum position will be below all other tasks that can be shown. + * @param maxPosition The maximum position the caller is suggesting. + * We will start adjusting down from here. + */ + // TODO: Move user to their own window container. + private int computeMaxUserPosition(int maxPosition) { + while (maxPosition > 0) { + final WindowContainer child = mChildren.get(maxPosition); + final boolean canShow = child.showToCurrentUser(); + if (!canShow) { + break; + } + maxPosition--; + } + return maxPosition; + } + + private int getAdjustedChildPosition(WindowContainer wc, int suggestedPosition) { + final boolean canShowChild = wc.showToCurrentUser(); + + final int size = mChildren.size(); + + // Figure-out min/max possible position depending on if child can show for current user. + int minPosition = (canShowChild) ? computeMinUserPosition(0, size) : 0; + int maxPosition = (canShowChild) ? size : computeMaxUserPosition(size - 1); + + // Factor in always-on-top children in max possible position. + if (!wc.isAlwaysOnTop()) { + + // We want to place all non-always-on-top containers below always-on-top ones. + while (maxPosition > minPosition) { + if (!mChildren.get(maxPosition - 1).isAlwaysOnTop()) break; + --maxPosition; } } - return Math.min(maxPosition, suggestedPosition); + // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid. + if (suggestedPosition == POSITION_BOTTOM && minPosition == 0) { + return POSITION_BOTTOM; + } else if (suggestedPosition == POSITION_TOP && maxPosition == (size - 1)) { + return POSITION_TOP; + } + // Reset position based on minimum/maximum possible positions. + return Math.min(Math.max(suggestedPosition, minPosition), maxPosition); } @Override void positionChildAt(int position, WindowContainer child, boolean includingParents) { - position = getAdjustedAddPosition((ActivityRecord) child, position); + position = getAdjustedChildPosition(child, position); super.positionChildAt(position, child, includingParents); + + // Log positioning. + if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "positionChildAt: child=" + child + + " position=" + position + " parent=" + this); + + final int toTop = position >= (mChildren.size() - 1) ? 1 : 0; + final Task task = child.asTask(); + if (task != null) { + EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, position); + } } - private boolean hasWindowsAlive() { + @VisibleForTesting + boolean hasWindowsAlive() { return getActivity(ActivityRecord::hasWindowsAlive) != null; } @@ -2218,8 +2479,6 @@ class Task extends WindowContainer<WindowContainer> { + " from stack=" + getStack()); EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason); - position = stack.findPositionForTask(this, position); - reparent(stack, position); stack.positionChildAt(position, this, moveParents); @@ -2285,11 +2544,16 @@ class Task extends WindowContainer<WindowContainer> { @Override void onDisplayChanged(DisplayContent dc) { - adjustBoundsForDisplayChangeIfNeeded(dc); + final boolean isRootTask = isRootTask(); + if (!isRootTask) { + adjustBoundsForDisplayChangeIfNeeded(dc); + } super.onDisplayChanged(dc); - final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; - mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged( - mTaskId, displayId); + if (!isRootTask) { + final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; + mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged( + mTaskId, displayId); + } } /** @@ -2431,7 +2695,7 @@ class Task extends WindowContainer<WindowContainer> { } /** Bounds of the task to be used for dimming, as well as touch related tests. */ - public void getDimBounds(Rect out) { + void getDimBounds(Rect out) { final DisplayContent displayContent = getStack().getDisplayContent(); // It doesn't matter if we in particular are part of the resize, since we couldn't have // a DimLayer anyway if we weren't visible. @@ -2564,6 +2828,11 @@ class Task extends WindowContainer<WindowContainer> { mForceShowForAllUsers = forceShowForAllUsers; } + public boolean isAttached() { + final DisplayContent display = getDisplayContent(); + return display != null && !display.isRemoved(); + } + /** * When we are in a floating stack (Freeform, Pinned, ...) we calculate * insets differently. However if we are animating to the fullscreen stack @@ -2575,6 +2844,45 @@ class Task extends WindowContainer<WindowContainer> { && !getStack().isAnimatingBoundsToFullscreen() && !mPreserveNonFloatingState; } + /** + * Returns true if the stack is translucent and can have other contents visible behind it if + * needed. A stack is considered translucent if it don't contain a visible or + * starting (about to be visible) activity that is fullscreen (opaque). + * @param starting The currently starting activity or null if there is none. + */ + @VisibleForTesting + boolean isTranslucent(ActivityRecord starting) { + if (!isAttached() || mForceHidden) { + return true; + } + final PooledPredicate p = PooledLambda.obtainPredicate(Task::isOpaqueActivity, + PooledLambda.__(ActivityRecord.class), starting); + final ActivityRecord opaque = getActivity(p); + p.recycle(); + return opaque == null; + } + + private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) { + if (r.finishing) { + // We don't factor in finishing activities when determining translucency since + // they will be gone soon. + return false; + } + + if (!r.visibleIgnoringKeyguard && r != starting) { + // Also ignore invisible activities that are not the currently starting + // activity (about to be visible). + return false; + } + + if (r.occludesParent() || r.hasWallpaper) { + // Stack isn't translucent if it has at least one fullscreen activity + // that is visible. + return true; + } + return false; + } + @Override public SurfaceControl getAnimationLeashParent() { if (WindowManagerService.sHierarchicalAnimations) { @@ -2626,7 +2934,7 @@ class Task extends WindowContainer<WindowContainer> { return true; } } - return false; + return forAllTasks((t) -> { return t != this && t.isTaskAnimating(); }); } /** @@ -2702,26 +3010,45 @@ class Task extends WindowContainer<WindowContainer> { return mTaskDescription; } + // TODO(task-merge): Figure out what's the right thing to do for places that used it. + boolean isRootTask() { + return getParent() == null || getParent().asTask() == null; + } + @Override boolean fillsParent() { - return matchParentBounds() || !getWindowConfiguration().canResizeTask(); + return matchParentBounds(); + } + + void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) { + if (traverseTopToBottom) { + super.forAllTasks(callback, traverseTopToBottom); + if (excludedTask != this) { + callback.accept(this); + } + } else { + super.forAllTasks(callback, traverseTopToBottom); + if (excludedTask != this) { + callback.accept(this); + } + } } @Override void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) { - // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks. - callback.accept(this); + forAllTasks(callback, traverseTopToBottom, null /* excludedTask */); } @Override boolean forAllTasks(Function<Task, Boolean> callback) { + if (super.forAllTasks(callback)) return true; return callback.apply(this); } @Override Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) { - // I'm a task! - // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks. + final Task t = super.getTask(callback, traverseTopToBottom); + if (t != null) return t; return callback.test(this) ? this : null; } @@ -2757,6 +3084,16 @@ class Task extends WindowContainer<WindowContainer> { return mDimmer; } + void dim(float alpha) { + mDimmer.dimAbove(getPendingTransaction(), alpha); + scheduleAnimation(); + } + + void stopDimming() { + mDimmer.stopDim(getPendingTransaction()); + scheduleAnimation(); + } + boolean isTaskForUser(int userId) { return mUserId == userId; } @@ -2804,7 +3141,7 @@ class Task extends WindowContainer<WindowContainer> { } @Override - public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); final String doublePrefix = prefix + " "; @@ -2864,6 +3201,17 @@ class Task extends WindowContainer<WindowContainer> { return mTaskId == taskId; } + @Override + Task asTask() { + // I'm a task! + return this; + } + + // TODO(task-merge): Figure-out how this should work with hierarchy tasks. + boolean shouldBeVisible(ActivityRecord starting) { + return true; + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(mUserId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); @@ -2950,7 +3298,7 @@ class Task extends WindowContainer<WindowContainer> { if (mRootProcess != null) { pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess); } - pw.print(prefix); pw.print("stackId="); pw.println(getStackId()); + pw.print(prefix); pw.print("taskId=" + mTaskId); pw.println(" stackId=" + getStackId()); pw.print(prefix + "hasBeenVisible=" + hasBeenVisible); pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode)); pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture); @@ -2977,6 +3325,10 @@ class Task extends WindowContainer<WindowContainer> { sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" #"); sb.append(mTaskId); + sb.append(" visible=" + shouldBeVisible(null /* starting */)); + sb.append(" type=" + activityTypeToString(getActivityType())); + sb.append(" mode=" + windowingModeToString(getWindowingMode())); + sb.append(" translucent=" + isTranslucent(null /* starting */)); if (affinity != null) { sb.append(" A="); sb.append(affinity); @@ -2993,8 +3345,7 @@ class Task extends WindowContainer<WindowContainer> { return toString(); } - @Override - public void dumpDebug(ProtoOutputStream proto, long fieldId, + void dumpDebugInner(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { return; @@ -3205,13 +3556,13 @@ class Task extends WindowContainer<WindowContainer> { Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, ActivityStack stack) { - return new Task(service, taskId, info, intent, voiceSession, voiceInteractor, + return new ActivityStack(service, taskId, info, intent, voiceSession, voiceInteractor, null /*taskDescription*/, stack); } Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info, Intent intent, TaskDescription taskDescription, ActivityStack stack) { - return new Task(service, taskId, info, intent, null /*voiceSession*/, + return new ActivityStack(service, taskId, info, intent, null /*voiceSession*/, null /*voiceInteractor*/, taskDescription, stack); } @@ -3228,7 +3579,7 @@ class Task extends WindowContainer<WindowContainer> { int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage, int resizeMode, boolean supportsPictureInPicture, boolean realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight, ActivityStack stack) { - return new Task(service, taskId, intent, affinityIntent, affinity, + return new ActivityStack(service, taskId, intent, affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootWasReset, autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription, lastTimeMoved, neverRelinquishIdentity, lastTaskDescription, taskAffiliation, @@ -3470,7 +3821,9 @@ class Task extends WindowContainer<WindowContainer> { } boolean isControlledByTaskOrganizer() { - return mTaskOrganizer != null; + // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally + // we only give control of the root task. + return getTopMostTask().mTaskOrganizer != null; } @Override @@ -3546,13 +3899,16 @@ class Task extends WindowContainer<WindowContainer> { public void setWindowingMode(int windowingMode) { super.setWindowingMode(windowingMode); windowingMode = getWindowingMode(); - /* - * Different windowing modes may be managed by different task organizers. If - * getTaskOrganizer returns null, we still call transferToTaskOrganizer to - * make sure we clear it. - */ - final ITaskOrganizer org = - mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); - setTaskOrganizer(org); + + // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally + // we only give control of the root task. + // Different windowing modes may be managed by different task organizers. If + // getTaskOrganizer returns null, we still call transferToTaskOrganizer to make sure we + // clear it. + if (!isRootTask()) { + final ITaskOrganizer org = + mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + setTaskOrganizer(org); + } } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index e2a21a9e9db4..57de7536409f 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -55,6 +55,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.GraphicBuffer; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; @@ -115,6 +116,7 @@ class TaskSnapshotSurface implements StartingSurface { private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM; private static final int MSG_REPORT_DRAW = 0; private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; + private static final Point sSurfaceSize = new Point(); //tmp var for unused relayout param private final Window mWindow; private final Surface mSurface; private SurfaceControl mSurfaceControl; @@ -227,7 +229,8 @@ class TaskSnapshotSurface implements StartingSurface { try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, - tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState); + tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState, + sSurfaceSize); } catch (RemoteException e) { // Local call. } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index a43f595e4a9f..1c876d9cc02f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2249,4 +2249,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void setSurfaceControl(SurfaceControl sc) { mSurfaceControl = sc; } + + /** Cheap way of doing cast and instanceof. */ + Task asTask() { + return null; + } + + /** Cheap way of doing cast and instanceof. */ + ActivityRecord asActivityRecord() { + return null; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 77d755d6f36d..92cb948875ab 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2039,7 +2039,8 @@ public class WindowManagerService extends IWindowManager.Stub long frameNumber, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState) { + SurfaceControl outSurfaceControl, InsetsState outInsetsState, + Point outSurfaceSize) { int result = 0; boolean configChanged; final int pid = Binder.getCallingPid(); @@ -2361,6 +2362,10 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.sendNewConfiguration(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + if (winAnimator.mSurfaceController != null) { + outSurfaceSize.set(winAnimator.mSurfaceController.getWidth(), + winAnimator.mSurfaceController.getHeight()); + } } Binder.restoreCallingIdentity(origId); @@ -2414,8 +2419,8 @@ public class WindowManagerService extends IWindowManager.Stub return focusMayChange; } - private int createSurfaceControl(SurfaceControl outSurfaceControl, int result, WindowState win, - WindowStateAnimator winAnimator) { + private int createSurfaceControl(SurfaceControl outSurfaceControl, + int result, WindowState win, WindowStateAnimator winAnimator) { if (!win.mHasSurface) { result |= RELAYOUT_RES_SURFACE_CHANGED; } @@ -2694,8 +2699,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.getDockedDividerController().checkMinimizeChanged(animate); } - boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) { return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio( aspectRatio); } @@ -2767,7 +2771,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null && mRoot.getTopChild() != displayContent) { - mRoot.positionChildAt(WindowContainer.POSITION_TOP, displayContent, + displayContent.positionDisplayAt(WindowContainer.POSITION_TOP, true /* includingParents */); } } @@ -7694,7 +7698,7 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent displayContent = touchedWindow.getDisplayContent(); if (!displayContent.isOnTop()) { - displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent, + displayContent.positionDisplayAt(WindowContainer.POSITION_TOP, true /* includingParents */); } handleTaskFocusChange(touchedWindow.getTask()); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 2a1e980dfb3d..43cd66dc0616 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; @@ -38,7 +39,9 @@ import android.annotation.CallSuper; import android.os.Debug; import android.os.IBinder; import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; +import com.android.server.policy.WindowManagerPolicy; import com.android.server.protolog.common.ProtoLog; import java.io.PrintWriter; @@ -228,12 +231,6 @@ class WindowToken extends WindowContainer<WindowState> { return false; } - ActivityRecord asActivityRecord() { - // TODO: Not sure if this is the best way to handle this vs. using instanceof and casting. - // I am not an app window token! - return null; - } - @Override void removeImmediately() { if (mDisplayContent != null) { @@ -256,6 +253,27 @@ class WindowToken extends WindowContainer<WindowState> { super.onDisplayChanged(dc); } + @Override + void assignLayer(SurfaceControl.Transaction t, int layer) { + if (windowType == TYPE_DOCK_DIVIDER) { + // See {@link DisplayContent#mSplitScreenDividerAnchor} + super.assignRelativeLayer(t, mDisplayContent.getSplitScreenDividerAnchor(), 1); + } else if (mRoundedCornerOverlay) { + super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); + } else { + super.assignLayer(t, layer); + } + } + + @Override + SurfaceControl.Builder makeSurface() { + final SurfaceControl.Builder builder = super.makeSurface(); + if (mRoundedCornerOverlay) { + builder.setParent(null); + } + return builder; + } + @CallSuper @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @@ -315,4 +333,8 @@ class WindowToken extends WindowContainer<WindowState> { mOwnerCanManageAppTokens); return mOwnerCanManageAppTokens && (layer > navLayer); } + + int getWindowLayerFromType() { + return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index ad63d078fa67..c60ca48f0b3d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -123,13 +123,13 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testStackCleanupOnClearingTask() { mActivity.onParentChanged(null /*newParent*/, mActivity.getTask()); - verify(mStack, times(1)).onActivityRemovedFromStack(any()); + verify(mStack, times(1)).cleanUpActivityReferences(any()); } @Test public void testStackCleanupOnActivityRemoval() { mTask.removeChild(mActivity); - verify(mStack, times(1)).onActivityRemovedFromStack(any()); + verify(mStack, times(1)).cleanUpActivityReferences(any()); } @Test @@ -141,10 +141,9 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testNoCleanupMovingActivityInSameStack() { - final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack) - .build(); + final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); mActivity.reparent(newTask, 0, null /*reason*/); - verify(mStack, times(0)).onActivityRemovedFromStack(any()); + verify(mStack, times(0)).cleanUpActivityReferences(any()); } @Test @@ -490,7 +489,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); try { - doReturn(false).when(stack).isStackTranslucent(any()); + doReturn(false).when(stack).isTranslucent(any()); assertFalse(mStack.shouldBeVisible(null /* starting */)); mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), @@ -613,8 +612,7 @@ public class ActivityRecordTests extends ActivityTestsBase { // Sending 'null' for saved state can only happen due to timeout, so previously stored saved // states should not be overridden. mActivity.setState(STOPPING, "test"); - mActivity.activityStopped(null /* savedState */, null /* persistentSavedState */, - "desc"); + mActivity.activityStopped(null /* savedState */, null /* persistentSavedState */, "desc"); assertTrue(mActivity.hasSavedState()); assertEquals(savedState, mActivity.getSavedState()); assertEquals(persistentSavedState, mActivity.getPersistentSavedState()); @@ -1013,7 +1011,9 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() { // Empty the home stack. final ActivityStack homeStack = mActivity.getDisplay().getHomeStack(); - homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); }); + homeStack.forAllTasks((t) -> { + homeStack.removeChild(t, "test"); + }, true /* traverseTopToBottom */, homeStack); mActivity.finishing = true; doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities(); spyOn(mStack); @@ -1037,7 +1037,9 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testCompleteFinishing_lastActivityAboveEmptyHomeStack() { // Empty the home stack. final ActivityStack homeStack = mActivity.getDisplay().getHomeStack(); - homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); }); + homeStack.forAllTasks((t) -> { + homeStack.removeChild(t, "test"); + }, true /* traverseTopToBottom */, homeStack); mActivity.finishing = true; spyOn(mStack); @@ -1143,7 +1145,7 @@ public class ActivityRecordTests extends ActivityTestsBase { assertNull(mActivity.app); assertNull(mActivity.getTask()); assertEquals(0, task.getChildCount()); - assertNull(task.getStack()); + assertEquals(task.getStack(), task); assertEquals(0, stack.getChildCount()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index a5157fe98daf..393d8b83cad2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -313,13 +313,13 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack // should be visible since it is always on-top. - doReturn(false).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack).isTranslucent(any()); assertFalse(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); assertTrue(fullscreenStack.shouldBeVisible(null /* starting */)); // Home stack should be visible behind a translucent fullscreen stack. - doReturn(true).when(fullscreenStack).isStackTranslucent(any()); + doReturn(true).when(fullscreenStack).isTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); } @@ -338,8 +338,8 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible if both halves of split-screen are opaque. - doReturn(false).when(splitScreenPrimary).isStackTranslucent(any()); - doReturn(false).when(splitScreenSecondary).isStackTranslucent(any()); + doReturn(false).when(splitScreenPrimary).isTranslucent(any()); + doReturn(false).when(splitScreenSecondary).isTranslucent(any()); assertFalse(homeStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -350,7 +350,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary.getVisibility(null /* starting */)); // Home stack should be visible if one of the halves of split-screen is translucent. - doReturn(true).when(splitScreenPrimary).isStackTranslucent(any()); + doReturn(true).when(splitScreenPrimary).isTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -366,7 +366,7 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // First split-screen secondary shouldn't be visible behind another opaque split-split // secondary. - doReturn(false).when(splitScreenSecondary2).isStackTranslucent(any()); + doReturn(false).when(splitScreenSecondary2).isTranslucent(any()); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(STACK_VISIBILITY_INVISIBLE, @@ -376,7 +376,7 @@ public class ActivityStackTests extends ActivityTestsBase { // First split-screen secondary should be visible behind another translucent split-screen // secondary. - doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any()); + doReturn(true).when(splitScreenSecondary2).isTranslucent(any()); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -388,7 +388,7 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. - doReturn(false).when(assistantStack).isStackTranslucent(any()); + doReturn(false).when(assistantStack).isTranslucent(any()); assertTrue(assistantStack.shouldBeVisible(null /* starting */)); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -403,7 +403,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary2.getVisibility(null /* starting */)); // Split-screen stacks should be visible behind a translucent fullscreen stack. - doReturn(true).when(assistantStack).isStackTranslucent(any()); + doReturn(true).when(assistantStack).isTranslucent(any()); assertTrue(assistantStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -418,9 +418,9 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary2.getVisibility(null /* starting */)); // Assistant stack shouldn't be visible behind translucent split-screen stack - doReturn(false).when(assistantStack).isStackTranslucent(any()); - doReturn(true).when(splitScreenPrimary).isStackTranslucent(any()); - doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any()); + doReturn(false).when(assistantStack).isTranslucent(any()); + doReturn(true).when(splitScreenPrimary).isTranslucent(any()); + doReturn(true).when(splitScreenSecondary2).isTranslucent(any()); splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen"); splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen"); assertFalse(assistantStack.shouldBeVisible(null /* starting */)); @@ -555,7 +555,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack translucentStack = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(true).when(translucentStack).isStackTranslucent(any()); + doReturn(true).when(translucentStack).isTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(translucentStack.shouldBeVisible(null /* starting */)); @@ -603,8 +603,8 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -622,8 +622,8 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(true).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(true).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -641,8 +641,8 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure we don't move the home stack if it is already on top int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -666,9 +666,9 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack2).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack1).isTranslucent(any()); + doReturn(false).when(fullscreenStack2).isTranslucent(any()); // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the // pinned stack @@ -691,9 +691,9 @@ public class ActivityStackTests extends ActivityTestsBase { mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); - doReturn(true).when(fullscreenStack2).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack1).isTranslucent(any()); + doReturn(true).when(fullscreenStack2).isTranslucent(any()); // Ensure that we move the home stack behind the bottom most non-translucent fullscreen // stack @@ -715,9 +715,9 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - doReturn(false).when(homeStack).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); - doReturn(false).when(fullscreenStack2).isStackTranslucent(any()); + doReturn(false).when(homeStack).isTranslucent(any()); + doReturn(false).when(fullscreenStack1).isTranslucent(any()); + doReturn(false).when(fullscreenStack2).isTranslucent(any()); // Ensure we don't move the home stack behind itself int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -810,9 +810,9 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - doReturn(false).when(splitScreenPrimary).isStackTranslucent(any()); - doReturn(false).when(splitScreenSecondary).isStackTranslucent(any()); - doReturn(false).when(assistantStack).isStackTranslucent(any()); + doReturn(false).when(splitScreenPrimary).isTranslucent(any()); + doReturn(false).when(splitScreenSecondary).isTranslucent(any()); + doReturn(false).when(assistantStack).isTranslucent(any()); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -829,7 +829,7 @@ public class ActivityStackTests extends ActivityTestsBase { boolean translucent) { final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay, windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(translucent).when(stack).isStackTranslucent(any()); + doReturn(translucent).when(stack).isTranslucent(any()); return stack; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 4beede93aea2..eb84d0af35c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -375,7 +375,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { intent.setComponent(mComponent); intent.setFlags(mFlags); - final Task task = new Task(mSupervisor.mService, mTaskId, aInfo, + final Task task = new ActivityStack(mSupervisor.mService, mTaskId, aInfo, intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/, null /*taskDescription*/, mStack); spyOn(task); @@ -398,6 +398,8 @@ class ActivityTestsBase extends SystemServiceTestsBase { private int mActivityType = ACTIVITY_TYPE_STANDARD; private boolean mOnTop = true; private boolean mCreateActivity = true; + private ActivityInfo mInfo; + private Intent mIntent; StackBuilder(RootWindowContainer root) { mRootWindowContainer = root; @@ -434,13 +436,22 @@ class ActivityTestsBase extends SystemServiceTestsBase { return this; } + StackBuilder setActivityInfo(ActivityInfo info) { + mInfo = info; + return this; + } + + StackBuilder setIntent(Intent intent) { + mIntent = intent; + return this; + } + ActivityStack build() { final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId(); - final ActivityStack stack; + final ActivityStack stack = mDisplay.createStackUnchecked(mWindowingMode, + mActivityType, stackId, mOnTop, mInfo, mIntent); final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor; - stack = mDisplay.createStackUnchecked(mWindowingMode, mActivityType, stackId, mOnTop); - if (mCreateActivity) { new ActivityBuilder(supervisor.mService) .setCreateTask(true) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index 9562fa41a4b7..fa0485c8678b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -152,6 +152,8 @@ public class TaskRecordTests extends ActivityTestsBase { @Test public void testReturnsToHomeStack() throws Exception { final Task task = createTask(1); + spyOn(task); + doReturn(true).when(task).hasChild(); assertFalse(task.returnsToHomeStack()); task.intent = null; assertFalse(task.returnsToHomeStack()); @@ -906,7 +908,7 @@ public class TaskRecordTests extends ActivityTestsBase { } private Task createTask(int taskId) { - return new Task(mService, taskId, new Intent(), null, null, null, + return new ActivityStack(mService, taskId, new Intent(), null, null, null, ActivityBuilder.getDefaultComponent(), null, false, false, false, 0, 10050, null, 0, false, null, 0, 0, 0, 0, 0, null, 0, false, false, false, 0, 0, null /*ActivityInfo*/, null /*_voiceSession*/, null /*_voiceInteractor*/, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java index b4f5751a6ca2..6e4be88a31fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -115,7 +115,7 @@ public class TaskStackTests extends WindowTestsBase { // Remove stack and check if its child is also removed. stack.removeImmediately(); assertNull(stack.getDisplayContent()); - assertNull(task.getStack()); + assertNull(task.getParent()); } @Test @@ -131,7 +131,7 @@ public class TaskStackTests extends WindowTestsBase { assertEquals(0, stack.getChildCount()); assertNull(stack.getDisplayContent()); assertNull(task.getDisplayContent()); - assertNull(task.getStack()); + assertNull(task.getParent()); } @Test @@ -140,6 +140,7 @@ public class TaskStackTests extends WindowTestsBase { final Task task = createTaskInStack(stack, 0 /* userId */); // Stack removal is deferred if one of its child is animating. + doReturn(true).when(stack).hasWindowsAlive(); doReturn(true).when(task).isAnimating(TRANSITION | CHILDREN); stack.removeIfPossible(); diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt new file mode 100644 index 000000000000..d250ad3a2b12 --- /dev/null +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkAgentConfigTest { + @Test + fun testParcelNetworkAgentConfig() { + val config = NetworkAgentConfig.Builder().apply { + setExplicitlySelected(true) + setLegacyType(ConnectivityManager.TYPE_ETHERNET) + setSubscriberId("MySubId") + setPartialConnectivityAcceptable(false) + setUnvalidatedConnectivityAcceptable(true) + }.build() + assertParcelSane(config, 9) + } +} |