Merge "Log setting of password complexity."
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 531436e..fd4b106 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -307,6 +307,41 @@
}
}
+ /**
+ * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
+ * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
+ * {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchema}.
+ *
+ * <p> An empty {@code queryExpression} matches all documents.
+ *
+ * <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
+ * the current database.
+ *
+ * @param queryExpression Query String to search.
+ * @param searchSpec Defines what and how to remove
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive errors resulting from removing the documents. If the
+ * operation succeeds, the callback will be invoked with {@code null}.
+ */
+ public void removeByQuery(@NonNull String queryExpression,
+ @NonNull SearchSpec searchSpec,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<AppSearchResult<Void>> callback) {
+ Objects.requireNonNull(queryExpression);
+ Objects.requireNonNull(searchSpec);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mService.removeByQuery(mDatabaseName, queryExpression, searchSpec.getBundle(),
+ new IAppSearchResultCallback.Stub() {
+ public void onResult(AppSearchResult result) {
+ executor.execute(() -> callback.accept(result));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// TODO(b/162450968) port query() and SearchResults.java to platform.
- // TODO(b/162450968) port removeByQuery() to platform.
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index 4a98102..62e60d7 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -119,13 +119,14 @@
* @param databaseName The databaseName this query for.
* @param queryExpression String to search for
* @param searchSpecBundle SearchSpec bundle
- * @param callback {@link AndroidFuture}<{@link AppSearchResult}<{@link SearchResults}>>
+ * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
+ * {@link AppSearchResult}<{@link Void}>.
*/
void removeByQuery(
in String databaseName,
in String queryExpression,
in Bundle searchSpecBundle,
- in AndroidFuture<AppSearchResult> callback);
+ in IAppSearchResultCallback callback);
/**
* Creates and initializes AppSearchImpl for the calling app.
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 8269799..c9dd89c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -225,21 +225,21 @@
@NonNull String databaseName,
@NonNull String queryExpression,
@NonNull Bundle searchSpecBundle,
- @NonNull AndroidFuture<AppSearchResult> callback) {
+ @NonNull IAppSearchResultCallback callback) {
Preconditions.checkNotNull(databaseName);
Preconditions.checkNotNull(queryExpression);
Preconditions.checkNotNull(searchSpecBundle);
- Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
- impl.removeByQuery(databaseName, queryExpression, new SearchSpec(searchSpecBundle));
- callback.complete(AppSearchResult.newSuccessfulResult(/*result= */null));
+ impl.removeByQuery(databaseName, queryExpression,
+ new SearchSpec(searchSpecBundle));
+ invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
} catch (Throwable t) {
- callback.complete(throwableToFailedResult(t));
+ invokeCallbackOnError(callback, t);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index d8cccbf..07f8395 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6968,6 +6968,7 @@
method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
method public boolean hasGrantedPolicy(@NonNull android.content.ComponentName, int);
+ method public boolean hasKeyPair(@NonNull String);
method public boolean hasLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
method public boolean installCaCert(@Nullable android.content.ComponentName, byte[]);
method public boolean installExistingPackage(@NonNull android.content.ComponentName, String);
diff --git a/core/java/android/annotation/RequiresFeature.java b/core/java/android/annotation/RequiresFeature.java
index fc93f03..08861d4 100644
--- a/core/java/android/annotation/RequiresFeature.java
+++ b/core/java/android/annotation/RequiresFeature.java
@@ -30,7 +30,6 @@
* Denotes that the annotated element requires one or more device features. This
* is used to auto-generate documentation.
*
- * @see PackageManager#hasSystemFeature(String)
* @hide
*/
@Retention(SOURCE)
@@ -38,8 +37,16 @@
public @interface RequiresFeature {
/**
* The name of the device feature that is required.
- *
- * @see PackageManager#hasSystemFeature(String)
*/
String value();
+
+ /**
+ * Defines the name of the method that should be called to check whether the feature is
+ * available, using the same signature format as javadoc. The feature checking method can have
+ * multiple parameters, but the feature name parameter must be of type String and must also be
+ * the first String-type parameter.
+ * <p>
+ * By default, the enforcement is {@link PackageManager#hasSystemFeature(String)}.
+ */
+ String enforcement() default("android.content.pm.PackageManager#hasSystemFeature");
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 247d6f0..26784f2c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5370,6 +5370,27 @@
}
}
+ // STOPSHIP(b/174298501): clarify the expected return value following generateKeyPair call.
+ /**
+ * Called by a device or profile owner, or delegated certificate installer, to query whether a
+ * certificate and private key are installed under a given alias.
+ *
+ * @param alias The alias under which the key pair is installed.
+ * @return {@code true} if a key pair with this alias exists, {@code false} otherwise.
+ * @throws SecurityException if the caller is not a device or profile owner or a delegated
+ * certificate installer.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
+ */
+ public boolean hasKeyPair(@NonNull String alias) {
+ throwIfParentInstance("hasKeyPair");
+ try {
+ return mService.hasKeyPair(mContext.getPackageName(), alias);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Called by a device or profile owner, or delegated certificate installer, to generate a
* new private/public key pair. If the device supports key generation via secure hardware,
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 37d3451..8be3cdc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -185,6 +185,7 @@
in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess,
boolean isUserSelectable);
boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
+ boolean hasKeyPair(in String callerPackage, in String alias);
boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
in ParcelableKeyGenParameterSpec keySpec,
in int idAttestationFlags, out KeymasterCertificateChain attestationChain);
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 901494b..237a9f2 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -200,6 +200,21 @@
internalPath = parcel.readString8();
}
+ public VolumeInfo(VolumeInfo volumeInfo) {
+ this.id = volumeInfo.id;
+ this.type = volumeInfo.type;
+ this.disk = volumeInfo.disk;
+ this.partGuid = volumeInfo.partGuid;
+ this.mountFlags = volumeInfo.mountFlags;
+ this.mountUserId = volumeInfo.mountUserId;
+ this.state = volumeInfo.state;
+ this.fsType = volumeInfo.fsType;
+ this.fsUuid = volumeInfo.fsUuid;
+ this.fsLabel = volumeInfo.fsLabel;
+ this.path = volumeInfo.path;
+ this.internalPath = volumeInfo.internalPath;
+ }
+
@UnsupportedAppUsage
public static @NonNull String getEnvironmentForState(int state) {
final String envState = sStateToEnvironment.get(state);
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index defa58e..c8bfd36 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -15,8 +15,13 @@
*/
package android.view;
+
+import android.annotation.IntDef;
import android.graphics.Rect;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Standard constants and tools for placing an object within a potentially
* larger container.
@@ -122,6 +127,32 @@
*/
public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = START | END;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ Gravity.FILL,
+ Gravity.FILL_HORIZONTAL,
+ Gravity.FILL_VERTICAL,
+ Gravity.START,
+ Gravity.END,
+ Gravity.LEFT,
+ Gravity.RIGHT,
+ Gravity.TOP,
+ Gravity.BOTTOM,
+ Gravity.CENTER,
+ Gravity.CENTER_HORIZONTAL,
+ Gravity.CENTER_VERTICAL,
+ Gravity.DISPLAY_CLIP_HORIZONTAL,
+ Gravity.DISPLAY_CLIP_VERTICAL,
+ Gravity.CLIP_HORIZONTAL,
+ Gravity.CLIP_VERTICAL,
+ Gravity.NO_GRAVITY
+ })
+ public @interface GravityFlags {}
+
/**
* Apply a gravity constant to an object. This supposes that the layout direction is LTR.
*
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 1a6eea5..a23b7e1 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -32,6 +32,7 @@
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
+import android.service.attestation.ImpressionToken;
import android.view.DisplayCutout;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
@@ -760,4 +761,23 @@
* {@link android.content.pm.PackageManager#getHoldLockToken()}.
*/
void holdLock(in IBinder token, in int durationMs);
+
+ /**
+ * Gets an array of support hashing algorithms that can be used to generate the hash of the
+ * screenshot. The String value of one algorithm should be used when requesting to generate
+ * the impression attestation token.
+ *
+ * @return a String array of supported hashing algorithms.
+ */
+ String[] getSupportedImpressionAlgorithms();
+
+ /**
+ * Validate the impression token was generated by the system. The impression token passed in
+ * should be the token generated when calling {@link IWindowSession#generateImpressionToken}
+ *
+ * @param impressionToken The token to verify that it was generated by the system.
+ * @return true if the token was generated by the system or false if the token cannot be
+ * verified.
+ */
+ boolean verifyImpressionToken(in ImpressionToken impressionToken);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 0089a85..cfdaf8c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -22,6 +22,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
+import android.service.attestation.ImpressionToken;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.InputChannel;
@@ -344,4 +345,16 @@
* window, the system will try to find a new focus target.
*/
void grantEmbeddedWindowFocus(IWindow window, in IBinder inputToken, boolean grantFocus);
+
+ /**
+ * Generates an impression token that can be used to validate whether specific content was on
+ * screen.
+ *
+ * @param window The token for the window where the view to attest is shown.
+ * @param boundsInWindow The size and position of the ads view in the window
+ * @param hashAlgorithm The String for the hashing algorithm to use based on values returned
+ * from {@link IWindowManager#getSupportedImpressionAlgorithms()}
+ */
+ ImpressionToken generateImpressionToken(IWindow window, in Rect boundsInWindow,
+ in String hashAlgorithm);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index ef2153e..b9afbc9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -104,6 +104,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import android.view.Gravity.GravityFlags;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Side.InsetsSide;
@@ -2572,6 +2573,7 @@
*
* @see Gravity
*/
+ @GravityFlags
public int gravity;
/**
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 0ed7ca7..673073e 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -24,6 +24,7 @@
import android.graphics.Region;
import android.os.IBinder;
import android.os.RemoteException;
+import android.service.attestation.ImpressionToken;
import android.util.Log;
import android.util.MergedConfiguration;
import android.window.ClientWindowFrames;
@@ -460,4 +461,10 @@
public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
boolean grantFocus) {
}
+
+ @Override
+ public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+ String hashAlgorithm) {
+ return null;
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c3d3985..8d2c2d9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -88,8 +88,10 @@
import android.view.autofill.AutofillManager;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.Completable;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.inputmethod.ResultCallbacks;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
import com.android.internal.inputmethod.UnbindReason;
@@ -666,6 +668,7 @@
final int startInputReason =
nextFocusHasConnection ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
: WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+ final Completable.InputBindResult value = Completable.createInputBindResult();
mService.startInputOrWindowGainedFocus(
startInputReason, mClient,
focusedView.getWindowToken(), startInputFlags, softInputMode,
@@ -673,7 +676,9 @@
null,
null,
0 /* missingMethodFlags */,
- mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+ ResultCallbacks.of(value));
+ Completable.getResult(value); // ignore the result
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2039,10 +2044,13 @@
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags));
- res = mService.startInputOrWindowGainedFocus(
+ final Completable.InputBindResult value = Completable.createInputBindResult();
+ mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
- view.getContext().getApplicationInfo().targetSdkVersion);
+ view.getContext().getApplicationInfo().targetSdkVersion,
+ ResultCallbacks.of(value));
+ res = Completable.getResult(value);
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res == null) {
Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
diff --git a/core/java/com/android/internal/inputmethod/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java
new file mode 100644
index 0000000..ec67792
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.os.RemoteException;
+
+import com.android.internal.view.InputBindResult;
+
+import java.util.function.Supplier;
+
+/**
+ * Defines a set of helper methods to callback corresponding results in {@link ResultCallbacks}.
+ */
+public final class CallbackUtils {
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private CallbackUtils() {
+ }
+
+ /**
+ * A utility method using given {@link IInputBindResultResultCallback} to callback the
+ * {@link InputBindResult}.
+ *
+ * @param callback {@link IInputBindResultResultCallback} to be called back.
+ * @param resultSupplier the supplier from which {@link InputBindResult} is provided.
+ */
+ @AnyThread
+ public static void onResult(@NonNull IInputBindResultResultCallback callback,
+ @NonNull Supplier<InputBindResult> resultSupplier) {
+ try {
+ callback.onResult(resultSupplier.get());
+ } catch (RemoteException ignored) { }
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java
index d8d1a7d..b9e1cf0 100644
--- a/core/java/com/android/internal/inputmethod/Completable.java
+++ b/core/java/com/android/internal/inputmethod/Completable.java
@@ -124,6 +124,16 @@
return true;
}
}
+
+ /**
+ * Blocks the calling thread until this object becomes ready to return the value.
+ */
+ @AnyThread
+ public void await() {
+ try {
+ mLatch.await();
+ } catch (InterruptedException ignored) { }
+ }
}
/**
@@ -250,6 +260,13 @@
}
/**
+ * @return an instance of {@link Completable.InputBindResult}.
+ */
+ public static Completable.InputBindResult createInputBindResult() {
+ return new Completable.InputBindResult();
+ }
+
+ /**
* Completable object of {@link java.lang.Boolean}.
*/
public static final class Boolean extends Values<java.lang.Boolean> { }
@@ -278,6 +295,18 @@
extends Values<com.android.internal.view.InputBindResult> { }
/**
+ * Await the result by the {@link Completable.Values}.
+ *
+ * @return the result once {@link ValueBase#onComplete()}
+ */
+ @AnyThread
+ @Nullable
+ public static <T> T getResult(@NonNull Completable.Values<T> value) {
+ value.await();
+ return value.getValue();
+ }
+
+ /**
* Await the result by the {@link Completable.Int}, and log it if there is no result after
* given timeout.
*
diff --git a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl b/core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl
similarity index 69%
copy from services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
copy to core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl
index 45e4c69..b52b3b1 100644
--- a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.net.wifi;
+package com.android.internal.inputmethod;
-/** @hide */
-parcelable WifiApiServiceInfo {
- String name;
- IBinder binder;
-}
+import com.android.internal.view.InputBindResult;
+
+oneway interface IInputBindResultResultCallback {
+ void onResult(in InputBindResult result);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
index 7131284..c59dcf4 100644
--- a/core/java/com/android/internal/inputmethod/ResultCallbacks.java
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -21,6 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.internal.view.InputBindResult;
+
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicReference;
@@ -154,4 +156,31 @@
}
};
}
+
+ /**
+ * Creates {@link IInputBindResultResultCallback.Stub} that is to set
+ * {@link Completable.InputBindResult} when receiving the result.
+ *
+ * @param value {@link Completable.InputBindResult} to be set when receiving the result.
+ * @return {@link IInputBindResultResultCallback.Stub} that can be passed as a binder IPC
+ * parameter.
+ */
+ @AnyThread
+ public static IInputBindResultResultCallback.Stub of(
+ @NonNull Completable.InputBindResult value) {
+ final AtomicReference<WeakReference<Completable.InputBindResult>>
+ atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+ return new IInputBindResultResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(InputBindResult result) {
+ final Completable.InputBindResult value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+ };
+ }
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 33abbe8..e78ed4e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -24,6 +24,7 @@
import com.android.internal.view.InputBindResult;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
+import com.android.internal.inputmethod.IInputBindResultResultCallback;
/**
* Public interface to the global input method manager, used by all client
@@ -48,14 +49,15 @@
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'attribute' is non-null then also does startInput.
// @NonNull
- InputBindResult startInputOrWindowGainedFocus(
+ void startInputOrWindowGainedFocus(
/* @StartInputReason */ int startInputReason,
in IInputMethodClient client, in IBinder windowToken,
/* @StartInputFlags */ int startInputFlags,
/* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
int windowFlags, in EditorInfo attribute, IInputContext inputContext,
/* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags,
- int unverifiedTargetSdkVersion);
+ int unverifiedTargetSdkVersion,
+ in IInputBindResultResultCallback inputBindResult);
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index 4bb56f8..062485d 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -35,7 +35,7 @@
optional int32 height = 5;
optional float horizontal_margin = 6;
optional float vertical_margin = 7;
- optional int32 gravity = 8;
+ optional int32 gravity = 8 [(.android.typedef) = "android.view.Gravity.GravityFlags"];
optional int32 soft_input_mode = 9 [(.android.typedef) = "android.view.WindowManager.LayoutParams.SoftInputModeFlags"];
optional .android.graphics.PixelFormatProto.Format format = 10;
optional int32 window_animations = 11;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index db80287..5a69056 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -120,6 +120,12 @@
<protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
<protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
+ <protected-broadcast android:name="android.app.action.USER_ADDED" />
+ <protected-broadcast android:name="android.app.action.USER_REMOVED" />
+ <protected-broadcast android:name="android.app.action.USER_STARTED" />
+ <protected-broadcast android:name="android.app.action.USER_STOPPED" />
+ <protected-broadcast android:name="android.app.action.USER_SWITCHED" />
+
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
@@ -3104,6 +3110,12 @@
<permission android:name="android.permission.DUMP"
android:protectionLevel="signature|privileged|development" />
+ <!-- Allows an application to start tracing for InputMethod and WindowManager.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_UI_TRACING"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- Allows an application to read the low-level system log files.
<p>Not for use by third-party applications, because
Log entries can contain the user's private information. -->
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8406fdf..1fb63f0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -322,6 +322,7 @@
<permission name="android.permission.DELETE_CACHE_FILES"/>
<permission name="android.permission.DELETE_PACKAGES"/>
<permission name="android.permission.DUMP"/>
+ <permission name="android.permission.CONTROL_UI_TRACING"/>
<permission name="android.permission.ACTIVITY_EMBEDDING"/>
<permission name="android.permission.FORCE_STOP_PACKAGES"/>
<permission name="android.permission.GET_APP_OPS_STATS"/>
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index 97da3cc..1ae6a63 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -46,6 +46,7 @@
boolean installKeyPair(
in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid);
boolean removeKeyPair(String alias);
+ boolean containsKeyPair(String alias);
// APIs used by Settings
boolean deleteCaCertificate(String alias);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
index 061d3f8..6e87f13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
@@ -107,6 +107,7 @@
transaction.setWindowCrop(leash, crop);
}
+ // TODO(b/173440321): Correct presentation of letterboxed activities in One-handed mode.
private void resolveTaskPositionAndCrop(
ActivityManager.RunningTaskInfo taskInfo,
Point positionInParent,
@@ -125,15 +126,18 @@
final Rect activityBounds = taskInfo.letterboxActivityBounds;
Insets insets = getInsets();
+ Rect displayBoundsWithInsets =
+ new Rect(mWindowManager.getMaximumWindowMetrics().getBounds());
+ displayBoundsWithInsets.inset(insets);
Rect taskBoundsWithInsets = new Rect(taskBounds);
- applyInsets(taskBoundsWithInsets, insets, taskInfo.parentBounds);
+ taskBoundsWithInsets.intersect(displayBoundsWithInsets);
Rect activityBoundsWithInsets = new Rect(activityBounds);
- applyInsets(activityBoundsWithInsets, insets, taskInfo.parentBounds);
+ activityBoundsWithInsets.intersect(displayBoundsWithInsets);
Rect parentBoundsWithInsets = new Rect(parentBounds);
- applyInsets(parentBoundsWithInsets, insets, parentBounds);
+ parentBoundsWithInsets.intersect(displayBoundsWithInsets);
// Crop need to be in the task coordinates.
crop.set(activityBoundsWithInsets);
@@ -217,10 +221,4 @@
| WindowInsets.Type.displayCutout());
}
- private void applyInsets(Rect innerBounds, Insets insets, Rect outerBounds) {
- Rect outerBoundsWithInsets = new Rect(outerBounds);
- outerBoundsWithInsets.inset(insets);
- innerBounds.intersect(outerBoundsWithInsets);
- }
-
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
index 0f719af..fc0e20b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
@@ -96,6 +96,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(75, 0, 125, 75),
/* taskBounds */ new Rect(50, 0, 125, 100)),
@@ -109,6 +110,7 @@
mLetterboxTaskListener.onTaskInfoChanged(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
// Activity is offset by 25 to the left
/* activityBounds */ new Rect(50, 0, 100, 75),
@@ -130,6 +132,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(150, 0, 200, 75),
/* taskBounds */ new Rect(125, 0, 200, 100)),
@@ -150,6 +153,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(150, 0, 200, 75),
/* taskBounds */ new Rect(125, 0, 200, 100)),
@@ -170,6 +174,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(50, 0, 100, 75),
/* taskBounds */ new Rect(25, 0, 100, 100)),
@@ -190,6 +195,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 100, 150),
/* activityBounds */ new Rect(0, 75, 50, 125),
/* taskBounds */ new Rect(0, 50, 100, 125)),
@@ -210,6 +216,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 100, 150),
/* activityBounds */ new Rect(0, 75, 50, 125),
/* taskBounds */ new Rect(0, 50, 100, 125)),
@@ -230,6 +237,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 100, 150),
/* activityBounds */ new Rect(0, 75, 50, 125),
/* taskBounds */ new Rect(0, 50, 100, 125)),
@@ -250,6 +258,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 125), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 125),
/* activityBounds */ new Rect(15, 0, 175, 120),
/* taskBounds */ new Rect(0, 0, 100, 125)), // equal to parent bounds
@@ -272,6 +281,7 @@
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150),
/* parentBounds */ new Rect(0, 75, 100, 225),
/* activityBounds */ new Rect(25, 75, 75, 125),
/* taskBounds */ new Rect(0, 75, 100, 125)),
@@ -285,7 +295,7 @@
public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() {
setWindowBoundsAndInsets(new Rect(), Insets.NONE);
RunningTaskInfo taskInfo =
- createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect());
+ createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect(), new Rect());
mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
}
@@ -306,11 +316,13 @@
private static RunningTaskInfo createTaskInfo(
int taskId,
+ final Rect maxBounds,
final Rect parentBounds,
final Rect activityBounds,
final Rect taskBounds) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setMaxBounds(maxBounds);
taskInfo.parentBounds = parentBounds;
taskInfo.configuration.windowConfiguration.setBounds(taskBounds);
taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4ed5457..cd53217 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -429,6 +429,7 @@
whole_static_libs: ["libskia"],
srcs: [
+ "canvas/CanvasFrontend.cpp",
"canvas/CanvasOpBuffer.cpp",
"canvas/CanvasOpRasterizer.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
@@ -607,6 +608,7 @@
"tests/unit/CacheManagerTests.cpp",
"tests/unit/CanvasContextTests.cpp",
"tests/unit/CanvasOpTests.cpp",
+ "tests/unit/CanvasFrontendTests.cpp",
"tests/unit/CommonPoolTests.cpp",
"tests/unit/DamageAccumulatorTests.cpp",
"tests/unit/DeferredLayerUpdaterTests.cpp",
diff --git a/libs/hwui/SaveFlags.h b/libs/hwui/SaveFlags.h
new file mode 100644
index 0000000..f3579a8
--- /dev/null
+++ b/libs/hwui/SaveFlags.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+// TODO: Move this to an enum class
+namespace android::SaveFlags {
+
+// These must match the corresponding Canvas API constants.
+enum {
+ Matrix = 0x01,
+ Clip = 0x02,
+ HasAlphaLayer = 0x04,
+ ClipToLayer = 0x10,
+
+ // Helper constant
+ MatrixClip = Matrix | Clip,
+};
+typedef uint32_t Flags;
+
+} // namespace android::SaveFlags
diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp
new file mode 100644
index 0000000..2c839b0
--- /dev/null
+++ b/libs/hwui/canvas/CanvasFrontend.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#include "CanvasFrontend.h"
+#include "CanvasOps.h"
+#include "CanvasOpBuffer.h"
+
+namespace android::uirenderer {
+
+CanvasStateHelper::CanvasStateHelper(int width, int height) {
+ mInitialBounds = SkIRect::MakeWH(width, height);
+ mSaveStack.emplace_back();
+ mClipStack.emplace_back().setRect(mInitialBounds);
+ mTransformStack.emplace_back();
+ mCurrentClipIndex = 0;
+ mCurrentTransformIndex = 0;
+}
+
+bool CanvasStateHelper::internalSave(SaveEntry saveEntry) {
+ mSaveStack.push_back(saveEntry);
+ if (saveEntry.matrix) {
+ // We need to push before accessing transform() to ensure the reference doesn't move
+ // across vector resizes
+ mTransformStack.emplace_back() = transform();
+ mCurrentTransformIndex += 1;
+ }
+ if (saveEntry.clip) {
+ // We need to push before accessing clip() to ensure the reference doesn't move
+ // across vector resizes
+ mClipStack.emplace_back() = clip();
+ mCurrentClipIndex += 1;
+ return true;
+ }
+ return false;
+}
+
+// Assert that the cast from SkClipOp to SkRegion::Op is valid
+static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op);
+static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op);
+static_assert(static_cast<int>(SkClipOp::kUnion_deprecated) == SkRegion::Op::kUnion_Op);
+static_assert(static_cast<int>(SkClipOp::kXOR_deprecated) == SkRegion::Op::kXOR_Op);
+static_assert(static_cast<int>(SkClipOp::kReverseDifference_deprecated) == SkRegion::Op::kReverseDifference_Op);
+static_assert(static_cast<int>(SkClipOp::kReplace_deprecated) == SkRegion::Op::kReplace_Op);
+
+void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
+ clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false);
+}
+
+void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
+ clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true);
+}
+
+bool CanvasStateHelper::internalRestore() {
+ // Prevent underflows
+ if (saveCount() <= 1) {
+ return false;
+ }
+
+ SaveEntry entry = mSaveStack[mSaveStack.size() - 1];
+ mSaveStack.pop_back();
+ bool needsRestorePropagation = entry.layer;
+ if (entry.matrix) {
+ mTransformStack.pop_back();
+ mCurrentTransformIndex -= 1;
+ }
+ if (entry.clip) {
+ // We need to push before accessing clip() to ensure the reference doesn't move
+ // across vector resizes
+ mClipStack.pop_back();
+ mCurrentClipIndex -= 1;
+ needsRestorePropagation = true;
+ }
+ return needsRestorePropagation;
+}
+
+SkRect CanvasStateHelper::getClipBounds() const {
+ SkIRect ibounds = clip().getBounds();
+
+ if (ibounds.isEmpty()) {
+ return SkRect::MakeEmpty();
+ }
+
+ SkMatrix inverse;
+ // if we can't invert the CTM, we can't return local clip bounds
+ if (!transform().invert(&inverse)) {
+ return SkRect::MakeEmpty();
+ }
+
+ SkRect ret = SkRect::MakeEmpty();
+ inverse.mapRect(&ret, SkRect::Make(ibounds));
+ return ret;
+}
+
+bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
+ // TODO: Implement
+ return false;
+}
+
+bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
+ // TODO: Implement
+ return false;
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h
new file mode 100644
index 0000000..5fccccb
--- /dev/null
+++ b/libs/hwui/canvas/CanvasFrontend.h
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+// TODO: Can we get the dependencies scoped down more?
+#include "CanvasOps.h"
+#include "CanvasOpBuffer.h"
+#include <SaveFlags.h>
+
+#include <SkRasterClip.h>
+#include <ui/FatVector.h>
+
+#include <optional>
+
+namespace android::uirenderer {
+
+// Exists to avoid forcing all this common logic into the templated class
+class CanvasStateHelper {
+protected:
+ CanvasStateHelper(int width, int height);
+ ~CanvasStateHelper() = default;
+
+ struct SaveEntry {
+ bool clip : 1 = false;
+ bool matrix : 1 = false;
+ bool layer : 1 = false;
+ };
+
+ constexpr SaveEntry saveEntryForLayer() {
+ return {
+ .clip = true,
+ .matrix = true,
+ .layer = true,
+ };
+ }
+
+ constexpr SaveEntry flagsToSaveEntry(SaveFlags::Flags flags) {
+ return SaveEntry {
+ .clip = static_cast<bool>(flags & SaveFlags::Clip),
+ .matrix = static_cast<bool>(flags & SaveFlags::Matrix),
+ .layer = false
+ };
+ }
+
+ bool internalSave(SaveEntry saveEntry);
+ bool internalSave(SaveFlags::Flags flags) {
+ return internalSave(flagsToSaveEntry(flags));
+ }
+ void internalSaveLayer(const SkCanvas::SaveLayerRec& layerRec) {
+ internalSave({
+ .clip = true,
+ .matrix = true,
+ .layer = true
+ });
+ internalClipRect(*layerRec.fBounds, SkClipOp::kIntersect);
+ }
+
+ bool internalRestore();
+
+ void internalClipRect(const SkRect& rect, SkClipOp op);
+ void internalClipPath(const SkPath& path, SkClipOp op);
+
+ SkIRect mInitialBounds;
+ FatVector<SaveEntry, 6> mSaveStack;
+ FatVector<SkMatrix, 6> mTransformStack;
+ FatVector<SkConservativeClip, 6> mClipStack;
+
+ size_t mCurrentTransformIndex;
+ size_t mCurrentClipIndex;
+
+ const SkConservativeClip& clip() const {
+ return mClipStack[mCurrentClipIndex];
+ }
+
+ SkConservativeClip& clip() {
+ return mClipStack[mCurrentClipIndex];
+ }
+
+public:
+ int saveCount() const { return mSaveStack.size(); }
+
+ SkRect getClipBounds() const;
+ bool quickRejectRect(float left, float top, float right, float bottom) const;
+ bool quickRejectPath(const SkPath& path) const;
+
+ const SkMatrix& transform() const {
+ return mTransformStack[mCurrentTransformIndex];
+ }
+
+ SkMatrix& transform() {
+ return mTransformStack[mCurrentTransformIndex];
+ }
+
+ // For compat with existing HWUI Canvas interface
+ void getMatrix(SkMatrix* outMatrix) const {
+ *outMatrix = transform();
+ }
+
+ void setMatrix(const SkMatrix& matrix) {
+ transform() = matrix;
+ }
+
+ void concat(const SkMatrix& matrix) {
+ transform().preConcat(matrix);
+ }
+
+ void rotate(float degrees) {
+ SkMatrix m;
+ m.setRotate(degrees);
+ concat(m);
+ }
+
+ void scale(float sx, float sy) {
+ SkMatrix m;
+ m.setScale(sx, sy);
+ concat(m);
+ }
+
+ void skew(float sx, float sy) {
+ SkMatrix m;
+ m.setSkew(sx, sy);
+ concat(m);
+ }
+
+ void translate(float dx, float dy) {
+ transform().preTranslate(dx, dy);
+ }
+};
+
+// Front-end canvas that handles queries, up-front state, and produces CanvasOp<> output downstream
+template <typename CanvasOpReceiver>
+class CanvasFrontend final : public CanvasStateHelper {
+public:
+ template<class... Args>
+ CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height),
+ mReceiver(std::forward<Args>(args)...) { }
+ ~CanvasFrontend() = default;
+
+ void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) {
+ if (internalSave(flagsToSaveEntry(flags))) {
+ submit<CanvasOpType::Save>({});
+ }
+ }
+
+ void restore() {
+ if (internalRestore()) {
+ submit<CanvasOpType::Restore>({});
+ }
+ }
+
+ template <CanvasOpType T>
+ void draw(CanvasOp<T>&& op) {
+ // The front-end requires going through certain front-doors, which these aren't.
+ static_assert(T != CanvasOpType::Save, "Must use CanvasFrontend::save() call instead");
+ static_assert(T != CanvasOpType::Restore, "Must use CanvasFrontend::restore() call instead");
+
+ if constexpr (T == CanvasOpType::SaveLayer) {
+ internalSaveLayer(op.saveLayerRec);
+ }
+ if constexpr (T == CanvasOpType::SaveBehind) {
+ // Don't use internalSaveLayer as this doesn't apply clipping, it's a "regular" save
+ // But we do want to flag it as a layer, such that restore is Definitely Required
+ internalSave(saveEntryForLayer());
+ }
+ if constexpr (T == CanvasOpType::ClipRect) {
+ internalClipRect(op.rect, op.op);
+ }
+ if constexpr (T == CanvasOpType::ClipPath) {
+ internalClipPath(op.path, op.op);
+ }
+
+ submit(std::move(op));
+ }
+
+ const CanvasOpReceiver& receiver() const { return mReceiver; }
+
+private:
+ CanvasOpReceiver mReceiver;
+
+ template <CanvasOpType T>
+ void submit(CanvasOp<T>&& op) {
+ mReceiver.push_container(CanvasOpContainer(std::move(op), transform()));
+ }
+};
+
+} // namespace android::uirenderer
diff --git a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl b/libs/hwui/canvas/CanvasOpRecorder.cpp
similarity index 72%
rename from services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
rename to libs/hwui/canvas/CanvasOpRecorder.cpp
index 45e4c69..bb968ee8 100644
--- a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
+++ b/libs/hwui/canvas/CanvasOpRecorder.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package android.net.wifi;
+#include "CanvasOpRecorder.h"
-/** @hide */
-parcelable WifiApiServiceInfo {
- String name;
- IBinder binder;
-}
+#include "CanvasOpBuffer.h"
+#include "CanvasOps.h"
+
+namespace android::uirenderer {} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasOpRecorder.h b/libs/hwui/canvas/CanvasOpRecorder.h
new file mode 100644
index 0000000..7d95bc4
--- /dev/null
+++ b/libs/hwui/canvas/CanvasOpRecorder.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "hwui/Canvas.h"
+#include "CanvasOpBuffer.h"
+
+#include <vector>
+
+namespace android::uirenderer {
+
+// Interop with existing HWUI Canvas
+class CanvasOpRecorder final : /* todo: public Canvas */ {
+public:
+ // Transform ops
+private:
+ struct SaveEntry {
+
+ };
+
+ std::vector<SaveEntry> mSaveStack;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index f94bae274..4d67166 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -18,6 +18,7 @@
#include <cutils/compiler.h>
#include <utils/Functor.h>
+#include <SaveFlags.h>
#include <androidfw/ResourceTypes.h>
#include "Properties.h"
@@ -57,22 +58,6 @@
using DisplayList = skiapipeline::SkiaDisplayList;
}
-namespace SaveFlags {
-
-// These must match the corresponding Canvas API constants.
-enum {
- Matrix = 0x01,
- Clip = 0x02,
- HasAlphaLayer = 0x04,
- ClipToLayer = 0x10,
-
- // Helper constant
- MatrixClip = Matrix | Clip,
-};
-typedef uint32_t Flags;
-
-} // namespace SaveFlags
-
namespace uirenderer {
namespace VectorDrawable {
class Tree;
diff --git a/libs/hwui/tests/unit/CanvasFrontendTests.cpp b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
new file mode 100644
index 0000000..05b1179
--- /dev/null
+++ b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <canvas/CanvasFrontend.h>
+#include <canvas/CanvasOpBuffer.h>
+#include <canvas/CanvasOps.h>
+#include <canvas/CanvasOpRasterizer.h>
+
+#include <tests/common/CallCountingCanvas.h>
+
+#include "SkPictureRecorder.h"
+#include "SkColor.h"
+#include "SkLatticeIter.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include <SkNoDrawCanvas.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::test;
+
+class CanvasOpCountingReceiver {
+public:
+ template <CanvasOpType T>
+ void push_container(CanvasOpContainer<T>&& op) {
+ mOpCounts[static_cast<size_t>(T)] += 1;
+ }
+
+ int operator[](CanvasOpType op) const {
+ return mOpCounts[static_cast<size_t>(op)];
+ }
+
+private:
+ std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
+};
+
+TEST(CanvasFrontend, saveCount) {
+ SkNoDrawCanvas skiaCanvas(100, 100);
+ CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+ const auto& receiver = opCanvas.receiver();
+
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::MatrixClip);
+ EXPECT_EQ(2, skiaCanvas.getSaveCount());
+ EXPECT_EQ(2, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ EXPECT_EQ(1, receiver[CanvasOpType::Save]);
+ EXPECT_EQ(1, receiver[CanvasOpType::Restore]);
+}
+
+TEST(CanvasFrontend, transform) {
+ SkNoDrawCanvas skiaCanvas(100, 100);
+ CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+
+ skiaCanvas.translate(10, 10);
+ opCanvas.translate(10, 10);
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+ {
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::Matrix);
+ skiaCanvas.scale(2.0f, 1.125f);
+ opCanvas.scale(2.0f, 1.125f);
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+ skiaCanvas.restore();
+ opCanvas.restore();
+ }
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+ {
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::Matrix);
+ skiaCanvas.rotate(90.f);
+ opCanvas.rotate(90.f);
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+ {
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::Matrix);
+ skiaCanvas.skew(5.0f, 2.25f);
+ opCanvas.skew(5.0f, 2.25f);
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+ skiaCanvas.restore();
+ opCanvas.restore();
+ }
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ }
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+}
+
+TEST(CanvasFrontend, drawOpTransform) {
+ CanvasFrontend<CanvasOpBuffer> opCanvas(100, 100);
+ const auto& receiver = opCanvas.receiver();
+
+ auto makeDrawRect = [] {
+ return CanvasOp<CanvasOpType::DrawRect>{
+ .rect = SkRect::MakeWH(50, 50),
+ .paint = SkPaint(SkColors::kBlack),
+ };
+ };
+
+ opCanvas.draw(makeDrawRect());
+
+ opCanvas.translate(10, 10);
+ opCanvas.draw(makeDrawRect());
+
+ opCanvas.save();
+ opCanvas.scale(2.0f, 4.0f);
+ opCanvas.draw(makeDrawRect());
+ opCanvas.restore();
+
+ opCanvas.save();
+ opCanvas.translate(20, 15);
+ opCanvas.draw(makeDrawRect());
+ opCanvas.save();
+ opCanvas.rotate(90.f);
+ opCanvas.draw(makeDrawRect());
+ opCanvas.restore();
+ opCanvas.restore();
+
+ // Validate the results
+ std::vector<SkMatrix> transforms;
+ transforms.reserve(5);
+ receiver.for_each([&](auto op) {
+ // Filter for the DrawRect calls; ignore the save & restores
+ // (TODO: Add a filtered for_each variant to OpBuffer?)
+ if (op->type() == CanvasOpType::DrawRect) {
+ transforms.push_back(op->transform());
+ }
+ });
+
+ EXPECT_EQ(transforms.size(), 5);
+
+ {
+ // First result should be identity
+ const auto& result = transforms[0];
+ EXPECT_EQ(SkMatrix::kIdentity_Mask, result.getType());
+ EXPECT_EQ(SkMatrix::I(), result);
+ }
+
+ {
+ // Should be translate 10, 10
+ const auto& result = transforms[1];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
+ SkMatrix m;
+ m.setTranslate(10, 10);
+ EXPECT_EQ(m, result);
+ }
+
+ {
+ // Should be translate 10, 10 + scale 2, 4
+ const auto& result = transforms[2];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask, result.getType());
+ SkMatrix m;
+ m.setTranslate(10, 10);
+ m.preScale(2.0f, 4.0f);
+ EXPECT_EQ(m, result);
+ }
+
+ {
+ // Should be translate 10, 10 + translate 20, 15
+ const auto& result = transforms[3];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
+ SkMatrix m;
+ m.setTranslate(30, 25);
+ EXPECT_EQ(m, result);
+ }
+
+ {
+ // Should be translate 10, 10 + translate 20, 15 + rotate 90
+ const auto& result = transforms[4];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask,
+ result.getType());
+ SkMatrix m;
+ m.setTranslate(30, 25);
+ m.preRotate(90.f);
+ EXPECT_EQ(m, result);
+ }
+}
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index b15c322..f186e55 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -16,6 +16,7 @@
#include <gtest/gtest.h>
+#include <canvas/CanvasFrontend.h>
#include <canvas/CanvasOpBuffer.h>
#include <canvas/CanvasOps.h>
#include <canvas/CanvasOpRasterizer.h>
@@ -26,6 +27,7 @@
#include "SkColor.h"
#include "SkLatticeIter.h"
#include "pipeline/skia/AnimatedDrawables.h"
+#include <SkNoDrawCanvas.h>
using namespace android;
using namespace android::uirenderer;
@@ -78,6 +80,21 @@
using MockBuffer = OpBuffer<MockTypes, MockOpContainer>;
+class CanvasOpCountingReceiver {
+public:
+ template <CanvasOpType T>
+ void push_container(CanvasOpContainer<T>&& op) {
+ mOpCounts[static_cast<size_t>(T)] += 1;
+ }
+
+ int operator[](CanvasOpType op) const {
+ return mOpCounts[static_cast<size_t>(op)];
+ }
+
+private:
+ std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
+};
+
template<typename T>
static int countItems(const T& t) {
int count = 0;
@@ -614,4 +631,35 @@
rasterizer.draw(op);
EXPECT_EQ(1, canvas->drawRectCount);
EXPECT_EQ(1, canvas->sumTotalDrawCalls());
+}
+
+TEST(CanvasOp, frontendSaveCount) {
+ SkNoDrawCanvas skiaCanvas(100, 100);
+ CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+ const auto& receiver = opCanvas.receiver();
+
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::MatrixClip);
+ EXPECT_EQ(2, skiaCanvas.getSaveCount());
+ EXPECT_EQ(2, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ EXPECT_EQ(1, receiver[Op::Save]);
+ EXPECT_EQ(1, receiver[Op::Restore]);
+}
+
+TEST(CanvasOp, frontendTransform) {
+
}
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
index 5e5a9d9..c33f02df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
@@ -82,6 +82,7 @@
private static PendingIntent getPendingActivity(Context context) {
Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2a699ea..2e3ea24 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -70,6 +70,7 @@
<uses-permission android:name="android.permission.SET_PROCESS_LIMIT" />
<uses-permission android:name="android.permission.SET_ALWAYS_FINISH" />
<uses-permission android:name="android.permission.DUMP" />
+ <uses-permission android:name="android.permission.CONTROL_UI_TRACING" />
<uses-permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<!-- Internal permissions granted to the shell. -->
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7120cc2..52b41a4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -530,6 +530,14 @@
androidprv:alwaysFocusable="true"
android:excludeFromRecents="true" />
+ <!-- started from TvNotificationPanel -->
+ <activity
+ android:name=".statusbar.tv.notifications.TvNotificationPanelActivity"
+ android:excludeFromRecents="true"
+ android:launchMode="singleTask"
+ android:noHistory="true"
+ android:theme="@style/TvSidePanelTheme" />
+
<!-- started from SliceProvider -->
<activity android:name=".SlicePermissionActivity"
android:theme="@style/Theme.SystemUI.Dialog.Alert"
diff --git a/packages/SystemUI/res-keyguard/drawable/circle_white.xml b/packages/SystemUI/res-keyguard/drawable/circle_white.xml
new file mode 100644
index 0000000..d1b2097
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/circle_white.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="#33FFFFFF" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/disabled_udfps_view.xml b/packages/SystemUI/res/layout/disabled_udfps_view.xml
new file mode 100644
index 0000000..aab8661
--- /dev/null
+++ b/packages/SystemUI/res/layout/disabled_udfps_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.keyguard.DisabledUdfpsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/disabled_udfps_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/circle_white"
+/>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
index e7c7b5f..13572fa 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -26,11 +26,19 @@
android:layout_gravity="center_vertical|center_horizontal"
android:background="@android:color/transparent">
+ <ImageView
+ android:id="@+id/primary_footer_icon"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:gravity="start"
+ android:layout_marginEnd="8dp"
+ android:contentDescription="@null"
+ android:tint="?android:attr/textColorPrimary" />
+
<com.android.systemui.util.AutoMarqueeTextView
android:id="@+id/footer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="start"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml
index 1a35676..3e40321 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml
@@ -29,8 +29,8 @@
android:orientation="vertical">
<ImageView
android:id="@+id/parental_controls_icon"
- android:layout_width="36dip"
- android:layout_height="36dip"
+ android:layout_width="24dip"
+ android:layout_height="24dip"
android:layout_gravity="center_horizontal"
/>
diff --git a/packages/SystemUI/res/layout/tv_notification_item.xml b/packages/SystemUI/res/layout/tv_notification_item.xml
new file mode 100644
index 0000000..711cd4e
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_notification_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/tv_notification_panel_width"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:padding="12dp">
+
+ <TextView
+ android:id="@+id/tv_notification_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="12dp"
+ android:textColor="@color/tv_notification_text_color"
+ android:textSize="18sp" />
+
+ <TextView
+ android:id="@+id/tv_notification_details"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@color/tv_notification_text_color" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_notification_panel.xml b/packages/SystemUI/res/layout/tv_notification_panel.xml
new file mode 100644
index 0000000..8f00a72
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_notification_panel.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tv_notification_panel"
+ android:layout_width="@dimen/tv_notification_panel_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="@color/tv_notification_background_color"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="12dp"
+ android:paddingTop="24dp"
+ android:text="@string/tv_notification_panel_title"
+ android:textColor="@color/tv_notification_text_color"
+ android:textSize="24sp"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/no_tv_notifications"
+ style="?android:attr/titleTextStyle"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:gravity="top|center"
+ android:paddingTop="24dp"
+ android:text="@string/tv_notification_panel_no_notifications"
+ android:textColor="@color/tv_notification_text_color"
+ android:visibility="gone" />
+
+ <androidx.leanback.widget.VerticalGridView
+ android:id="@+id/notifications_list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index ccd235d..c078805 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -1,4 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
<com.android.systemui.biometrics.UdfpsView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 09ec439..0c8c5c4 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -29,7 +29,8 @@
<item>com.android.systemui.util.NotificationChannels</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.statusbar.tv.TvStatusBar</item>
- <item>com.android.systemui.statusbar.tv.TvNotificationPanel</item>
+ <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item>
+ <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item>
<item>com.android.systemui.statusbar.tv.VpnStatusObserver</item>
<item>com.android.systemui.usb.StorageNotification</item>
<item>com.android.systemui.power.PowerUI</item>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 9b0ae1d..0961f50 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -33,4 +33,7 @@
<color name="tv_volume_dialog_seek_bar_background">#A03C4043</color>
<color name="tv_volume_dialog_seek_bar_fill">#FFF8F9FA</color>
<color name="tv_volume_dialog_accent">#FFDADCE0</color>
+
+ <color name="tv_notification_background_color">#383838</color>
+ <color name="tv_notification_text_color">#FFFFFF</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values/dimens_tv.xml
new file mode 100644
index 0000000..9545bfd
--- /dev/null
+++ b/packages/SystemUI/res/values/dimens_tv.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<resources>
+ <dimen name="tv_notification_panel_width">360dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index 13271d6..b51cb56 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -26,4 +26,6 @@
<!-- Disclosure text in the connected notification that indicates that the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=40] -->
<string name="notification_disclosure_vpn_text">Via <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+ <string name="tv_notification_panel_title">Notifications</string>
+ <string name="tv_notification_panel_no_notifications">No Notifications</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles_tv.xml b/packages/SystemUI/res/values/styles_tv.xml
index 0c4fd23..cb433f3 100644
--- a/packages/SystemUI/res/values/styles_tv.xml
+++ b/packages/SystemUI/res/values/styles_tv.xml
@@ -23,4 +23,12 @@
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowDisablePreview">true</item>
</style>
+
+ <style name="TvSidePanelTheme">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
new file mode 100644
index 0000000..f01b67b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
+
+import android.hardware.biometrics.BiometricSourceType;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.util.ViewController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Controls when to show the DisabledUdfpsView to unlock the device on the lockscreen.
+ * If the device is not authenticated, the bouncer will show.
+ *
+ * This tap target will only show when:
+ * - User has UDFPS enrolled
+ * - UDFPS is currently unavailable see {@link KeyguardUpdateMonitor#shouldListenForUdfps}
+ */
+@SysUISingleton
+public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> implements Dumpable {
+ @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @NonNull private final KeyguardViewController mKeyguardViewController;
+ @NonNull private final StatusBarStateController mStatusBarStateController;
+
+ private boolean mIsDozing;
+ private boolean mIsBouncerShowing;
+ private boolean mIsKeyguardShowing;
+ private boolean mRunningFPS;
+ private boolean mAuthenticated;
+
+ private boolean mShowButton;
+
+ public DisabledUdfpsController(
+ @NonNull DisabledUdfpsView view,
+ @NonNull StatusBarStateController statusBarStateController,
+ @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull AuthController authController,
+ @NonNull KeyguardViewController keyguardViewController
+ ) {
+ super(view);
+ mView.setOnClickListener(mOnClickListener);
+ mView.setSensorProperties(authController.getUdfpsProps().get(0));
+
+ mStatusBarStateController = statusBarStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardViewController = keyguardViewController;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+ mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
+
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mIsKeyguardShowing = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ mIsDozing = mStatusBarStateController.isDozing();
+ mAuthenticated = false;
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ }
+
+ private void updateButtonVisibility() {
+ mShowButton = !mAuthenticated && !mIsDozing && mIsKeyguardShowing
+ && !mIsBouncerShowing && !mRunningFPS;
+ if (mShowButton) {
+ mView.setVisibility(View.VISIBLE);
+ } else {
+ mView.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("DisabledUdfpsController state:");
+ pw.println(" mShowBouncerButton: " + mShowButton);
+ pw.println(" mIsDozing: " + mIsDozing);
+ pw.println(" mIsKeyguardShowing: " + mIsKeyguardShowing);
+ pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
+ pw.println(" mRunningFPS: " + mRunningFPS);
+ pw.println(" mAuthenticated: " + mAuthenticated);
+ }
+
+ private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mKeyguardViewController.showBouncer(/* scrim */ true);
+ }
+ };
+
+ private StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ mIsKeyguardShowing = newState == StatusBarState.KEYGUARD;
+ updateButtonVisibility();
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ mIsDozing = isDozing;
+ updateButtonVisibility();
+ }
+ };
+
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardBouncerChanged(boolean bouncer) {
+ mIsBouncerShowing = bouncer;
+ updateButtonVisibility();
+ }
+
+ @Override
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) {
+ mRunningFPS = running && biometricSourceType == FINGERPRINT;
+ updateButtonVisibility();
+ }
+
+ @Override
+ public void onUserUnlocked() {
+ mAuthenticated = true;
+ updateButtonVisibility();
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java
new file mode 100644
index 0000000..d8ab780
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.RectF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+/**
+ * A full screen view with an oval target where the UDFPS sensor is.
+ * Controlled by {@link DisabledUdfpsController}.
+ */
+public class DisabledUdfpsView extends Button {
+ @NonNull private final RectF mSensorRect;
+ @NonNull private final Context mContext;
+
+ // Used to obtain the sensor location.
+ @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
+
+ public DisabledUdfpsView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mSensorRect = new RectF();
+ }
+
+ public void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
+ mSensorProps = properties;
+ }
+
+ // The "h" and "w" are the display's height and width relative to its current rotation.
+ private void updateSensorRect(int h, int w) {
+ // mSensorProps coordinates assume portrait mode.
+ mSensorRect.set(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
+ mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
+ mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
+ mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
+
+ // Transform mSensorRect if the device is in landscape mode.
+ switch (mContext.getDisplay().getRotation()) {
+ case Surface.ROTATION_90:
+ mSensorRect.set(mSensorRect.top, h - mSensorRect.right, mSensorRect.bottom,
+ h - mSensorRect.left);
+ break;
+ case Surface.ROTATION_270:
+ mSensorRect.set(w - mSensorRect.bottom, mSensorRect.left, w - mSensorRect.top,
+ mSensorRect.right);
+ break;
+ default:
+ // Do nothing to stay in portrait mode.
+ }
+
+ setX(mSensorRect.left);
+ setY(mSensorRect.top);
+ setLayoutParams(new FrameLayout.LayoutParams(
+ (int) (mSensorRect.right - mSensorRect.left),
+ (int) (mSensorRect.bottom - mSensorRect.top)));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ // Always re-compute the layout regardless of whether "changed" is true. It is usually false
+ // when the device goes from landscape to seascape and vice versa, but mSensorRect and
+ // its dependencies need to be recalculated to stay at the same physical location on the
+ // screen.
+ final int w = getLayoutParams().width;
+ final int h = getLayoutParams().height;
+ updateSensorRect(h, w);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 611131f..a7e5195 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1925,6 +1925,7 @@
}
// TODO: Add support for multiple fingerprint sensors, b/173730729
+ updateUdfpsEnrolled(getCurrentUser());
boolean shouldListenForFingerprint =
isUdfpsEnrolled() ? shouldListenForUdfps() : shouldListenForFingerprint();
boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
@@ -2137,7 +2138,6 @@
}
if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
int userId = getCurrentUser();
- updateUdfpsEnrolled(userId);
if (isUnlockWithFingerprintPossible(userId)) {
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
@@ -2627,7 +2627,6 @@
Assert.isMainThread();
if (DEBUG) Log.d(TAG, "handleKeyguardBouncerChanged(" + bouncerVisible + ")");
mBouncer = bouncerVisible == 1;
-
if (mBouncer) {
// If the bouncer is shown, always clear this flag. This can happen in the following
// situations: 1) Default camera with SHOW_WHEN_LOCKED is not chosen yet. 2) Secure
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 061d8cf..9edfee7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -410,6 +410,11 @@
mCurrentDialog.onHelp(message);
}
+ @Nullable
+ public List<FingerprintSensorPropertiesInternal> getUdfpsProps() {
+ return mUdfpsProps;
+ }
+
private String getErrorString(int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 13ff3f5..ec4a91c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.brightness.BrightnessDialog;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
import com.android.systemui.tuner.TunerActivity;
import com.android.systemui.usb.UsbDebuggingActivity;
import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
@@ -85,4 +86,10 @@
@IntoMap
@ClassKey(CreateUserActivity.class)
public abstract Activity bindCreateUserActivity(CreateUserActivity activity);
+
+ /** Inject into TvNotificationPanelActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvNotificationPanelActivity.class)
+ public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index c0013d8..9f6c19b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -34,8 +34,8 @@
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.InstantAppNotifier;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.tv.TvNotificationPanel;
import com.android.systemui.statusbar.tv.TvStatusBar;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel;
import com.android.systemui.theme.ThemeOverlayController;
import com.android.systemui.toast.ToastUI;
import com.android.systemui.util.leak.GarbageMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 4789239..5afe526 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -68,6 +68,7 @@
private final View mRootView;
private final TextView mFooterText;
private final ImageView mFooterIcon;
+ private final ImageView mPrimaryFooterIcon;
private final Context mContext;
private final Callback mCallback = new Callback();
private final SecurityController mSecurityController;
@@ -83,6 +84,7 @@
private CharSequence mFooterTextContent = null;
private int mFooterTextId;
private int mFooterIconId;
+ private Drawable mPrimaryFooterIconDrawable;
@Inject
QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView, Context context,
@@ -92,6 +94,7 @@
mRootView.setOnClickListener(this);
mFooterText = mRootView.findViewById(R.id.footer_text);
mFooterIcon = mRootView.findViewById(R.id.footer_icon);
+ mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
mFooterIconId = R.drawable.ic_info_outline;
mContext = context;
mMainHandler = mainHandler;
@@ -188,6 +191,18 @@
mFooterIconId = footerIconId;
mMainHandler.post(mUpdateIcon);
}
+
+ // Update the primary icon
+ if (isParentalControlsEnabled) {
+ if (mPrimaryFooterIconDrawable == null) {
+ DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
+ mPrimaryFooterIconDrawable = mSecurityController.getIcon(info);
+ }
+ } else {
+ mPrimaryFooterIconDrawable = null;
+ }
+ mMainHandler.post(mUpdatePrimaryIcon);
+
mMainHandler.post(mUpdateDisplayState);
}
@@ -532,6 +547,15 @@
}
};
+ private final Runnable mUpdatePrimaryIcon = new Runnable() {
+ @Override
+ public void run() {
+ mPrimaryFooterIcon.setVisibility(mPrimaryFooterIconDrawable != null
+ ? View.VISIBLE : View.GONE);
+ mPrimaryFooterIcon.setImageDrawable(mPrimaryFooterIconDrawable);
+ }
+ };
+
private final Runnable mUpdateDisplayState = new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index f7139aa..bb46172 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -68,6 +68,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.DisabledUdfpsController;
import com.android.keyguard.KeyguardClockSwitchController;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
@@ -256,7 +257,25 @@
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
mDelayShowingKeyguardStatusBar = false;
}
- };
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (mDisabledUdfpsController == null
+ && mAuthController.getUdfpsRegion() != null
+ && mAuthController.isUdfpsEnrolled(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ LayoutInflater.from(mView.getContext())
+ .inflate(R.layout.disabled_udfps_view, mView);
+ mDisabledUdfpsController = new DisabledUdfpsController(
+ mView.findViewById(R.id.disabled_udfps_view),
+ mStatusBarStateController,
+ mUpdateMonitor,
+ mAuthController,
+ mStatusBarKeyguardViewManager);
+ mDisabledUdfpsController.init();
+ }
+ }
+ };
private final InjectionInflationController mInjectionInflationController;
private final PowerManager mPowerManager;
@@ -284,6 +303,7 @@
private QS mQs;
private FrameLayout mQsFrame;
private KeyguardStatusViewController mKeyguardStatusViewController;
+ private DisabledUdfpsController mDisabledUdfpsController;
private View mQsNavbarScrim;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
private boolean mAnimateNextPositionUpdate;
@@ -3044,6 +3064,9 @@
if (mKeyguardStatusBar != null) {
mKeyguardStatusBar.dump(fd, pw, args);
}
+ if (mDisabledUdfpsController != null) {
+ mDisabledUdfpsController.dump(fd, pw, args);
+ }
}
public boolean hasActiveClearableNotifications() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 2795857..bdf2b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -71,7 +71,6 @@
// Creating AudioRecordingDisclosureBar and just letting it run
new AudioRecordingDisclosureBar(mContext);
}
-
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
new file mode 100644
index 0000000..3b1a4db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.tv.notifications;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.R;
+
+/**
+ * Adapter for the VerticalGridView of the TvNotificationsPanelView.
+ */
+public class TvNotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private static final String TAG = "TvNotificationAdapter";
+ private SparseArray<StatusBarNotification> mNotifications;
+
+ public TvNotificationAdapter() {
+ setHasStableIds(true);
+ }
+
+ @NonNull
+ @Override
+ public TvNotificationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.tv_notification_item,
+ parent, false);
+ return new TvNotificationViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
+ if (mNotifications == null) {
+ Log.e(TAG, "Could not bind view holder because the notification is missing");
+ return;
+ }
+
+ TvNotificationViewHolder holder = (TvNotificationViewHolder) viewHolder;
+ Notification notification = mNotifications.valueAt(position).getNotification();
+ holder.mTitle.setText(notification.extras.getString(Notification.EXTRA_TITLE));
+ holder.mDetails.setText(notification.extras.getString(Notification.EXTRA_TEXT));
+ holder.mPendingIntent = notification.contentIntent;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mNotifications == null ? 0 : mNotifications.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ // the item id is the notification id
+ return mNotifications.keyAt(position);
+ }
+
+ /**
+ * Updates the notifications and calls notifyDataSetChanged().
+ */
+ public void setNotifications(SparseArray<StatusBarNotification> notifications) {
+ this.mNotifications = notifications;
+ notifyDataSetChanged();
+ }
+
+ private static class TvNotificationViewHolder extends RecyclerView.ViewHolder implements
+ View.OnClickListener {
+ final TextView mTitle;
+ final TextView mDetails;
+ PendingIntent mPendingIntent;
+
+ protected TvNotificationViewHolder(View itemView) {
+ super(itemView);
+ mTitle = itemView.findViewById(R.id.tv_notification_title);
+ mDetails = itemView.findViewById(R.id.tv_notification_details);
+ itemView.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ try {
+ if (mPendingIntent != null) {
+ mPendingIntent.send();
+ }
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, "Pending intent canceled for : " + mPendingIntent);
+ }
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
new file mode 100644
index 0000000..d985803
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.tv.notifications;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.statusbar.NotificationListener;
+
+import javax.inject.Inject;
+
+/**
+ * Keeps track of the notifications on TV.
+ */
+public class TvNotificationHandler extends SystemUI implements
+ NotificationListener.NotificationHandler {
+ private static final String TAG = "TvNotificationHandler";
+ private final NotificationListener mNotificationListener;
+ private final SparseArray<StatusBarNotification> mNotifications = new SparseArray<>();
+ @Nullable
+ private Listener mUpdateListener;
+
+ @Inject
+ public TvNotificationHandler(Context context, NotificationListener notificationListener) {
+ super(context);
+ mNotificationListener = notificationListener;
+ }
+
+ public SparseArray<StatusBarNotification> getCurrentNotifications() {
+ return mNotifications;
+ }
+
+ public void setTvNotificationListener(Listener listener) {
+ mUpdateListener = listener;
+ }
+
+ @Override
+ public void start() {
+ mNotificationListener.addNotificationHandler(this);
+ mNotificationListener.registerAsSystemService();
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn,
+ NotificationListenerService.RankingMap rankingMap) {
+ if (!new Notification.TvExtender(sbn.getNotification()).isAvailableOnTv()) {
+ Log.v(TAG, "Notification not added because it isn't relevant for tv");
+ return;
+ }
+
+ mNotifications.put(sbn.getId(), sbn);
+ if (mUpdateListener != null) {
+ mUpdateListener.notificationsUpdated(mNotifications);
+ }
+ Log.d(TAG, "Notification added");
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ NotificationListenerService.RankingMap rankingMap) {
+
+ if (mNotifications.contains(sbn.getId())) {
+ mNotifications.remove(sbn.getId());
+ Log.d(TAG, "Notification removed");
+
+ if (mUpdateListener != null) {
+ mUpdateListener.notificationsUpdated(mNotifications);
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ NotificationListenerService.RankingMap rankingMap, int reason) {
+ onNotificationRemoved(sbn, rankingMap);
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
+ // noop
+ }
+
+ @Override
+ public void onNotificationsInitialized() {
+ // noop
+ }
+
+ /**
+ * Get notified when the notifications are updated.
+ */
+ interface Listener {
+ void notificationsUpdated(SparseArray<StatusBarNotification> sbns);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
index 0bd3624..477424c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.tv;
+package com.android.systemui.statusbar.tv.notifications;
import android.Manifest;
import android.app.NotificationManager;
@@ -59,9 +59,8 @@
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL));
} else {
- Log.w(TAG,
- "Not toggling notification panel: config_notificationHandlerPackage is "
- + "empty");
+ openInternalNotificationPanel(
+ NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL);
}
}
@@ -71,9 +70,8 @@
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL));
} else {
- Log.w(TAG,
- "Not expanding notification panel: config_notificationHandlerPackage is "
- + "empty");
+ openInternalNotificationPanel(
+ NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL);
}
}
@@ -86,11 +84,17 @@
closeNotificationIntent.setPackage(mNotificationHandlerPackage);
mContext.sendBroadcastAsUser(closeNotificationIntent, UserHandle.CURRENT);
} else {
- Log.w(TAG,
- "Not closing notification panel: config_notificationHandlerPackage is empty");
+ openInternalNotificationPanel(
+ NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL);
}
}
+ private void openInternalNotificationPanel(String action) {
+ Intent intent = new Intent(mContext, TvNotificationPanelActivity.class);
+ intent.setAction(action);
+ mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
+ }
+
/**
* Starts the activity intent if all of the following are true
* <ul>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java
new file mode 100644
index 0000000..30f401b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.tv.notifications;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.util.SparseArray;
+import android.view.View;
+
+import androidx.leanback.widget.VerticalGridView;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * This Activity shows a notification panel for tv. It is used if no other app (e.g. a launcher) can
+ * be found to show the notifications.
+ */
+public class TvNotificationPanelActivity extends Activity implements
+ TvNotificationHandler.Listener {
+ private final TvNotificationHandler mTvNotificationHandler;
+ private TvNotificationAdapter mTvNotificationAdapter;
+ private VerticalGridView mNotificationListView;
+ private View mNotificationPlaceholder;
+ private boolean mPanelAlreadyOpen = false;
+
+ @Inject
+ public TvNotificationPanelActivity(TvNotificationHandler tvNotificationHandler) {
+ super();
+ mTvNotificationHandler = tvNotificationHandler;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (maybeClosePanel(getIntent())) {
+ return;
+ }
+ mPanelAlreadyOpen = true;
+
+ setContentView(R.layout.tv_notification_panel);
+
+ mNotificationPlaceholder = findViewById(R.id.no_tv_notifications);
+ mTvNotificationAdapter = new TvNotificationAdapter();
+
+ mNotificationListView = findViewById(R.id.notifications_list);
+ mNotificationListView.setAdapter(mTvNotificationAdapter);
+ mNotificationListView.setColumnWidth(R.dimen.tv_notification_panel_width);
+
+ mTvNotificationHandler.setTvNotificationListener(this);
+ notificationsUpdated(mTvNotificationHandler.getCurrentNotifications());
+ }
+
+ @Override
+ public void notificationsUpdated(@NonNull SparseArray<StatusBarNotification> notificationList) {
+ mTvNotificationAdapter.setNotifications(notificationList);
+
+ boolean noNotifications = notificationList.size() == 0;
+ mNotificationListView.setVisibility(noNotifications ? View.GONE : View.VISIBLE);
+ mNotificationPlaceholder.setVisibility(noNotifications ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ maybeClosePanel(intent);
+ }
+
+ /**
+ * Handles intents from onCreate and onNewIntent.
+ *
+ * @return true if the panel is being closed, false if it is being opened
+ */
+ private boolean maybeClosePanel(Intent intent) {
+ if (NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL.equals(intent.getAction())
+ || (mPanelAlreadyOpen
+ && NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL.equals(
+ intent.getAction()))) {
+ finish();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mTvNotificationHandler.setTvNotificationListener(null);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index 2c3ea4f..353333f 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -16,14 +16,22 @@
package com.android.systemui.tv;
+import com.android.systemui.SystemUI;
import com.android.systemui.dagger.GlobalRootComponent;
-import com.android.systemui.wmshell.TvPipModule;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
@Module
interface TvSystemUIBinder {
@Binds
GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
+
+ @Binds
+ @IntoMap
+ @ClassKey(TvNotificationHandler.class)
+ SystemUI bindTvNotificationHandler(TvNotificationHandler systemui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 8ffc7cf..56a4c203 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -43,6 +43,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -62,6 +63,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
import javax.inject.Named;
@@ -164,4 +166,11 @@
@Binds
abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
+
+ @Provides
+ @SysUISingleton
+ static TvNotificationHandler provideTvNotificationHandler(Context context,
+ NotificationListener notificationListener) {
+ return new TvNotificationHandler(context, notificationListener);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 6fa6f31..fd0715b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -15,6 +15,7 @@
package com.android.systemui.qs;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.any;
@@ -24,6 +25,8 @@
import static org.mockito.Mockito.when;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
@@ -75,6 +78,7 @@
private ViewGroup mRootView;
private TextView mFooterText;
private TestableImageView mFooterIcon;
+ private TestableImageView mPrimaryFooterIcon;
private QSSecurityFooter mFooter;
@Mock
private SecurityController mSecurityController;
@@ -95,6 +99,7 @@
mActivityStarter, mSecurityController, looper);
mFooterText = mRootView.findViewById(R.id.footer_text);
mFooterIcon = mRootView.findViewById(R.id.footer_icon);
+ mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
mFooter.setHostEnvironment(null);
}
@@ -119,6 +124,7 @@
mFooterText.getText());
assertEquals(View.VISIBLE, mRootView.getVisibility());
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
// -1 == never set.
assertEquals(-1, mFooterIcon.getLastImageResource());
}
@@ -136,6 +142,7 @@
mFooterText.getText());
assertEquals(View.VISIBLE, mRootView.getVisibility());
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
// -1 == never set.
assertEquals(-1, mFooterIcon.getLastImageResource());
}
@@ -165,6 +172,7 @@
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
mFooterText.getText());
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
// -1 == never set.
assertEquals(-1, mFooterIcon.getLastImageResource());
@@ -203,6 +211,7 @@
VPN_PACKAGE),
mFooterText.getText());
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource());
// Same situation, but with organization name set
@@ -229,6 +238,7 @@
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns),
mFooterText.getText());
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource());
// Same situation, but with organization name set
@@ -252,6 +262,7 @@
TestableLooper.get(this).processAllMessages();
assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource());
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
mFooterText.getText());
@@ -534,12 +545,27 @@
@Test
public void testParentalControls() {
when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
+
+ Drawable testDrawable = new VectorDrawable();
+ when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
+ assertNotNull(mSecurityController.getIcon(null));
+
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
mFooterText.getText());
+ assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
+
+ assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
+
+ // Ensure the primary icon is set to gone when parental controls is disabled.
+ when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
+ mFooter.refreshState();
+ TestableLooper.get(this).processAllMessages();
+
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
}
@Test
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 8c2d309..dbd27af4 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1312,12 +1312,13 @@
final int oldState = vol.state;
final int newState = state;
vol.state = newState;
+ final VolumeInfo vInfo = new VolumeInfo(vol);
final SomeArgs args = SomeArgs.obtain();
- args.arg1 = vol;
+ args.arg1 = vInfo;
args.arg2 = oldState;
args.arg3 = newState;
mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
- onVolumeStateChangedLocked(vol, oldState, newState);
+ onVolumeStateChangedLocked(vInfo, oldState, newState);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5189945..a257cde 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -158,6 +158,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.inputmethod.CallbackUtils;
+import com.android.internal.inputmethod.IInputBindResultResultCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodDebug;
@@ -208,6 +210,7 @@
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
/**
* This class provides a system service that manages input methods.
@@ -3347,63 +3350,68 @@
@NonNull
@Override
- public InputBindResult startInputOrWindowGainedFocus(
+ public void startInputOrWindowGainedFocus(
@StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
- @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) {
- if (windowToken == null) {
- Slog.e(TAG, "windowToken cannot be null.");
- return InputBindResult.NULL;
- }
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "IMMS.startInputOrWindowGainedFocus");
- ImeTracing.getInstance().triggerManagerServiceDump(
- "InputMethodManagerService#startInputOrWindowGainedFocus");
- final int callingUserId = UserHandle.getCallingUserId();
- final int userId;
- if (attribute != null && attribute.targetInputMethodUser != null
- && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
- mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "Using EditorInfo.targetInputMethodUser requires"
- + " INTERACT_ACROSS_USERS_FULL.");
- userId = attribute.targetInputMethodUser.getIdentifier();
- if (!mUserManagerInternal.isUserRunning(userId)) {
- // There is a chance that we hit here because of race condition. Let's just
- // return an error code instead of crashing the caller process, which at least
- // has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important
- // process.
- Slog.e(TAG, "User #" + userId + " is not running.");
- return InputBindResult.INVALID_USER;
- }
- } else {
- userId = callingUserId;
- }
- final InputBindResult result;
- synchronized (mMethodMap) {
- final long ident = Binder.clearCallingIdentity();
- try {
- result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
- windowToken, startInputFlags, softInputMode, windowFlags, attribute,
- inputContext, missingMethods, unverifiedTargetSdkVersion, userId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- if (result == null) {
- // This must never happen, but just in case.
- Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
- + InputMethodDebug.startInputReasonToString(startInputReason)
- + " windowFlags=#" + Integer.toHexString(windowFlags)
- + " editorInfo=" + attribute);
+ @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion,
+ IInputBindResultResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () -> {
+ if (windowToken == null) {
+ Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
}
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+ "IMMS.startInputOrWindowGainedFocus");
+ ImeTracing.getInstance().triggerManagerServiceDump(
+ "InputMethodManagerService#startInputOrWindowGainedFocus");
+ final int callingUserId = UserHandle.getCallingUserId();
+ final int userId;
+ if (attribute != null && attribute.targetInputMethodUser != null
+ && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "Using EditorInfo.targetInputMethodUser requires"
+ + " INTERACT_ACROSS_USERS_FULL.");
+ userId = attribute.targetInputMethodUser.getIdentifier();
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ // There is a chance that we hit here because of race condition. Let's just
+ // return an error code instead of crashing the caller process, which at
+ // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+ // important process.
+ Slog.e(TAG, "User #" + userId + " is not running.");
+ return InputBindResult.INVALID_USER;
+ }
+ } else {
+ userId = callingUserId;
+ }
+ final InputBindResult result;
+ synchronized (mMethodMap) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
+ client, windowToken, startInputFlags, softInputMode, windowFlags,
+ attribute, inputContext, missingMethods, unverifiedTargetSdkVersion,
+ userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ if (result == null) {
+ // This must never happen, but just in case.
+ Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
+ + InputMethodDebug.startInputReasonToString(startInputReason)
+ + " windowFlags=#" + Integer.toHexString(windowFlags)
+ + " editorInfo=" + attribute);
+ return InputBindResult.NULL;
+ }
- return result;
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
+ return result;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ });
}
@NonNull
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 62d817c..6bdae63 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -72,6 +72,8 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.CallbackUtils;
+import com.android.internal.inputmethod.IInputBindResultResultCallback;
import com.android.internal.inputmethod.IMultiClientInputMethod;
import com.android.internal.inputmethod.IMultiClientInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.IMultiClientInputMethodSession;
@@ -104,6 +106,7 @@
import java.util.Collections;
import java.util.List;
import java.util.WeakHashMap;
+import java.util.function.Supplier;
/**
* Actual implementation of multi-client InputMethodManagerService.
@@ -1588,7 +1591,26 @@
@BinderThread
@Override
- public InputBindResult startInputOrWindowGainedFocus(
+ public void startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason,
+ @Nullable IInputMethodClient client,
+ @Nullable IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @SoftInputModeFlags int softInputMode,
+ int windowFlags,
+ @Nullable EditorInfo editorInfo,
+ @Nullable IInputContext inputContext,
+ @MissingMethodFlags int missingMethods,
+ int unverifiedTargetSdkVersion,
+ IInputBindResultResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () ->
+ startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, editorInfo, inputContext,
+ missingMethods, unverifiedTargetSdkVersion));
+ }
+
+ @BinderThread
+ private InputBindResult startInputOrWindowGainedFocusInternal(
@StartInputReason int startInputReason,
@Nullable IInputMethodClient client,
@Nullable IBinder windowToken,
@@ -1676,8 +1698,7 @@
clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus(
inputContext, missingMethods, editorInfo, startInputFlags,
softInputMode, windowHandle);
- } catch (RemoteException e) {
- }
+ } catch (RemoteException ignored) { }
break;
}
return InputBindResult.NULL_EDITOR_INFO;
@@ -1708,8 +1729,7 @@
clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus(
inputContext, missingMethods, editorInfo, startInputFlags,
softInputMode, windowHandle);
- } catch (RemoteException e) {
- }
+ } catch (RemoteException ignored) { }
clientInfo.mState = InputMethodClientState.ALREADY_SENT_BIND_RESULT;
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6379080..5df5050 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1679,6 +1679,28 @@
}
}
+ void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
+ if (mFixedRotationLaunchingApp != null) {
+ // The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
+ // of insets sources are consistent with the latest state.
+ final InsetsState rotatedState =
+ mFixedRotationLaunchingApp.getFixedRotationTransformInsetsState();
+ if (rotatedState != null) {
+ final InsetsState state = mInsetsStateController.getRawInsetsState();
+ for (int i = 0; i < InsetsState.SIZE; i++) {
+ final InsetsSource source = state.peekSource(i);
+ if (source != null) {
+ rotatedState.setSourceVisible(i, source.isVisible());
+ }
+ }
+ }
+ }
+ forAllWindows(dispatchInsetsChanged, true /* traverseTopToBottom */);
+ if (mRemoteInsetsControlTarget != null) {
+ mRemoteInsetsControlTarget.notifyInsetsChanged();
+ }
+ }
+
/**
* Update rotation of the display.
*
@@ -5702,4 +5724,8 @@
}
return count;
}
+
+ MagnificationSpec getMagnificationSpec() {
+ return mMagnificationSpec;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ImpressionAttestationController.java b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
index 4793e1b..b0afc57 100644
--- a/services/core/java/com/android/server/wm/ImpressionAttestationController.java
+++ b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
@@ -18,6 +18,9 @@
import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,7 +32,9 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.Bundle;
@@ -43,6 +48,7 @@
import android.service.attestation.ImpressionAttestationService;
import android.service.attestation.ImpressionToken;
import android.util.Slog;
+import android.view.MagnificationSpec;
import com.android.internal.annotations.GuardedBy;
@@ -59,7 +65,8 @@
* blocking calls into another service.
*/
public class ImpressionAttestationController {
- private static final String TAG = "ImpressionAttestationController";
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "ImpressionAttestationController" : TAG_WM;
private static final boolean DEBUG = false;
private final Object mServiceConnectionLock = new Object();
@@ -81,6 +88,10 @@
private final String mSalt;
+ private final float[] mTmpFloat9 = new float[9];
+ private final Matrix mTmpMatrix = new Matrix();
+ private final RectF mTmpRectF = new RectF();
+
private interface Command {
void run(IImpressionAttestationService service) throws RemoteException;
}
@@ -151,6 +162,79 @@
}
/**
+ * Calculate the bounds to take the screenshot when generating the impression token. This takes
+ * into account window transform, magnification, and display bounds.
+ *
+ * Call while holding {@link WindowManagerService#mGlobalLock}
+ *
+ * @param win Window that the impression token is generated for.
+ * @param boundsInWindow The bounds passed in about where in the window to take the screenshot.
+ * @param outBounds The result of the calculated bounds
+ */
+ void calculateImpressionTokenBoundsLocked(WindowState win, Rect boundsInWindow,
+ Rect outBounds) {
+ if (DEBUG) {
+ Slog.d(TAG, "calculateImpressionTokenBounds: boundsInWindow=" + boundsInWindow);
+ }
+ outBounds.set(boundsInWindow);
+
+ DisplayContent displayContent = win.getDisplayContent();
+ if (displayContent == null) {
+ return;
+ }
+
+ // Intersect boundsInWindow with the window to make sure it's not outside the window
+ // requesting the token. Offset the window bounds to 0,0 since the boundsInWindow are
+ // offset from the window location, not display.
+ final Rect windowBounds = new Rect();
+ win.getBounds(windowBounds);
+ windowBounds.offsetTo(0, 0);
+ outBounds.intersectUnchecked(windowBounds);
+
+ if (DEBUG) {
+ Slog.d(TAG, "calculateImpressionTokenBounds: boundsIntersectWindow=" + outBounds);
+ }
+
+ if (outBounds.isEmpty()) {
+ return;
+ }
+
+ // Transform the bounds using the window transform in case there's a scale or offset.
+ // This allows the bounds to be in display space.
+ win.getTransformationMatrix(mTmpFloat9, mTmpMatrix);
+ mTmpRectF.set(outBounds);
+ mTmpMatrix.mapRect(mTmpRectF, mTmpRectF);
+ outBounds.set((int) mTmpRectF.left, (int) mTmpRectF.top, (int) mTmpRectF.right,
+ (int) mTmpRectF.bottom);
+ if (DEBUG) {
+ Slog.d(TAG, "calculateImpressionTokenBounds: boundsInDisplay=" + outBounds);
+ }
+
+ // Apply the magnification spec values to the bounds since the content could be magnified
+ final MagnificationSpec magSpec = displayContent.getMagnificationSpec();
+ if (magSpec != null) {
+ outBounds.scale(magSpec.scale);
+ outBounds.offset((int) magSpec.offsetX, (int) magSpec.offsetY);
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "calculateImpressionTokenBounds: boundsWithMagnification=" + outBounds);
+ }
+
+ if (outBounds.isEmpty()) {
+ return;
+ }
+
+ // Intersect with the display bounds since it shouldn't take a screenshot of content
+ // outside the display since it's not visible to the user.
+ final Rect displayBounds = displayContent.getBounds();
+ outBounds.intersectUnchecked(displayBounds);
+ if (DEBUG) {
+ Slog.d(TAG, "calculateImpressionTokenBounds: finalBounds=" + outBounds);
+ }
+ }
+
+ /**
* Run a command, starting the service connection if necessary.
*/
private void connectAndRun(@NonNull Command command) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b49d83d..25d779f 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -40,6 +40,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
@@ -655,6 +656,7 @@
|| type == TYPE_SECURE_SYSTEM_OVERLAY
|| type == TYPE_DOCK_DIVIDER
|| type == TYPE_ACCESSIBILITY_OVERLAY
- || type == TYPE_INPUT_CONSUMER;
+ || type == TYPE_INPUT_CONSUMER
+ || type == TYPE_VOICE_INTERACTION;
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 9d70fd7..752d6b4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -496,10 +496,7 @@
}
void notifyInsetsChanged() {
- mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
- if (mDisplayContent.mRemoteInsetsControlTarget != null) {
- mDisplayContent.mRemoteInsetsControlTarget.notifyInsetsChanged();
- }
+ mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged);
}
void dump(String prefix, PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b46e796..c414c64 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -38,7 +38,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
-import android.app.ActivityManagerInternal;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -56,6 +55,7 @@
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
+import android.service.attestation.ImpressionToken;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.MergedConfiguration;
@@ -848,4 +848,15 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ @Override
+ public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+ String hashAlgorithm) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return mService.generateImpressionToken(this, window, boundsInWindow, hashAlgorithm);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c585f22..25049ae 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -199,6 +199,7 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
+import android.service.attestation.ImpressionToken;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.sysprop.SurfaceFlingerProperties;
@@ -767,6 +768,8 @@
final EmbeddedWindowController mEmbeddedWindowController;
final AnrController mAnrController;
+ private final ImpressionAttestationController mImpressionAttestationController;
+
@VisibleForTesting
final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
@@ -1367,6 +1370,7 @@
mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources(
mContext.getResources());
+ mImpressionAttestationController = new ImpressionAttestationController(mContext);
setGlobalShadowSettings();
mAnrController = new AnrController(this);
mStartingSurfaceController = new StartingSurfaceController(this);
@@ -8428,4 +8432,64 @@
SystemClock.sleep(durationMs);
}
}
+
+ @Override
+ public String[] getSupportedImpressionAlgorithms() {
+ return mImpressionAttestationController.getSupportedImpressionAlgorithms();
+ }
+
+ @Override
+ public boolean verifyImpressionToken(ImpressionToken impressionToken) {
+ return mImpressionAttestationController.verifyImpressionToken(impressionToken);
+ }
+
+ ImpressionToken generateImpressionToken(Session session, IWindow window,
+ Rect boundsInWindow, String hashAlgorithm) {
+ final SurfaceControl displaySurfaceControl;
+ final Rect boundsInDisplay = new Rect(boundsInWindow);
+ synchronized (mGlobalLock) {
+ final WindowState win = windowForClientLocked(session, window, false);
+ if (win == null) {
+ Slog.w(TAG, "Failed to generate impression token. Invalid window");
+ return null;
+ }
+
+ DisplayContent displayContent = win.getDisplayContent();
+ if (displayContent == null) {
+ Slog.w(TAG, "Failed to generate impression token. Window is not on a display");
+ return null;
+ }
+
+ displaySurfaceControl = displayContent.getSurfaceControl();
+ mImpressionAttestationController.calculateImpressionTokenBoundsLocked(win,
+ boundsInWindow, boundsInDisplay);
+
+ if (boundsInDisplay.isEmpty()) {
+ Slog.w(TAG, "Failed to generate impression token. Bounds are not on screen");
+ return null;
+ }
+ }
+
+ // A screenshot of the entire display is taken rather than just the window. This is
+ // because if we take a screenshot of the window, it will not include content that might
+ // be covering it with the same uid. We want to make sure we include content that's
+ // covering to ensure we get as close as possible to what the user sees
+ final int uid = session.mUid;
+ SurfaceControl.LayerCaptureArgs args =
+ new SurfaceControl.LayerCaptureArgs.Builder(displaySurfaceControl)
+ .setUid(uid)
+ .setSourceCrop(boundsInDisplay)
+ .build();
+
+ SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
+ SurfaceControl.captureLayers(args);
+ if (screenshotHardwareBuffer == null
+ || screenshotHardwareBuffer.getHardwareBuffer() == null) {
+ Slog.w(TAG, "Failed to generate impression token. Failed to take screenshot");
+ return null;
+ }
+
+ return mImpressionAttestationController.generateImpressionToken(
+ screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow, hashAlgorithm);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index ce61d50..05b1e425 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -101,4 +101,9 @@
public boolean canProfileOwnerResetPasswordWhenLocked(int userId) {
return false;
}
+
+ public boolean hasKeyPair(String callerPackage, String alias) {
+ // STOPSHIP: implement delegation code in ArcDevicePolicyManagerWrapperService & nuke this.
+ return false;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f4bd0112..83b4c82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5111,6 +5111,30 @@
}
@Override
+ public boolean hasKeyPair(String callerPackage, String alias) {
+ final CallerIdentity caller = getCallerIdentity(callerPackage);
+ Preconditions.checkCallAuthorization(canManageCertificates(caller));
+
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ try (KeyChainConnection keyChainConnection =
+ KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
+ return keyChainConnection.getService().containsKeyPair(alias);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Querying keypair", e);
+ } catch (InterruptedException e) {
+ Log.w(LOG_TAG, "Interrupted while querying keypair", e);
+ Thread.currentThread().interrupt();
+ }
+ return false;
+ });
+ }
+
+ private boolean canManageCertificates(CallerIdentity caller) {
+ return isProfileOwner(caller) || isDeviceOwner(caller)
+ || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
+ }
+
+ @Override
public boolean setKeyGrantForApp(ComponentName who, String callerPackage, String alias,
String packageName, boolean hasGrant) {
Preconditions.checkStringNotEmpty(alias, "Alias to grant cannot be empty");
@@ -5188,9 +5212,7 @@
*/
if (hasProfileOwner(caller.getUserId())) {
// Make sure that the caller is the profile owner or delegate.
- Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller) || isCallerDelegate(
- caller, DELEGATION_CERT_INSTALL));
+ Preconditions.checkCallAuthorization(canManageCertificates(caller));
// Verify that the managed profile is on an organization-owned device and as such
// the profile owner can access Device IDs.
if (isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d921718..64065e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1202,6 +1202,17 @@
assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
ANIMATION_TYPE_FIXED_TRANSFORM));
+ // If the visibility of insets state is changed, the rotated state should be updated too.
+ final InsetsState rotatedState = app.getFixedRotationTransformInsetsState();
+ final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+ assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
+ rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+ state.getSource(ITYPE_STATUS_BAR).setVisible(
+ !rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+ mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+ assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
+ rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
final Rect outStableInsets = new Rect();
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index d12a6ae..5848be8 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -105,10 +105,17 @@
public @interface RequestResult {}
/**
+ * The base class of {@link OptionsBuilder} and {@link PresenceBuilder}
+ */
+ public static abstract class RcsUcsCapabilityBuilder {
+ public abstract @NonNull RcsContactUceCapability build();
+ }
+
+ /**
* Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
* queried through SIP OPTIONS.
*/
- public static class OptionsBuilder {
+ public static class OptionsBuilder extends RcsUcsCapabilityBuilder {
private final RcsContactUceCapability mCapabilities;
@@ -155,6 +162,7 @@
/**
* @return the constructed instance.
*/
+ @Override
public @NonNull RcsContactUceCapability build() {
return mCapabilities;
}
@@ -164,7 +172,7 @@
* Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
* queried through a presence server.
*/
- public static class PresenceBuilder {
+ public static class PresenceBuilder extends RcsUcsCapabilityBuilder {
private final RcsContactUceCapability mCapabilities;
@@ -205,6 +213,7 @@
/**
* @return the RcsContactUceCapability instance.
*/
+ @Override
public @NonNull RcsContactUceCapability build() {
return mCapabilities;
}
diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/TEST_MAPPING
new file mode 100644
index 0000000..1b569f9
--- /dev/null
+++ b/tests/BootImageProfileTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "BootImageProfileTest"
+ }
+ ]
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 1f03c4d..686ddcb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -105,8 +105,7 @@
configuration.endRotation)
navBarLayerIsAlwaysVisible(enabled = false)
statusBarLayerIsAlwaysVisible(enabled = false)
- visibleLayersShownMoreThanOneConsecutiveEntry(
- enabled = Surface.ROTATION_0 == configuration.endRotation)
+ visibleLayersShownMoreThanOneConsecutiveEntry(bugId = 174541970)
appLayerReplacesWallpaperLayer(testApp)
}
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
index b90e1bb..8cae14a 100644
--- a/tools/codegen/src/com/android/codegen/ClassPrinter.kt
+++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
@@ -145,7 +145,6 @@
}
return when {
cliArgs.contains("--hidden-$kebabCase") -> true
- this == FeatureFlag.BUILD_UPON -> FeatureFlag.BUILDER.hidden
else -> false
}
}
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 6a635d0..d9ad649 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.21"
+const val CODEGEN_VERSION = "1.0.22"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"