Merge "Add tests for AtomMetricStats."
diff --git a/api/current.txt b/api/current.txt
index c0d10a6..b793470b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -48997,6 +48997,7 @@
method public boolean[] hasKeys(int...);
method public boolean hasMicrophone();
method public boolean isEnabled();
+ method public boolean isExternal();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
@@ -52612,7 +52613,6 @@
public abstract class Animation implements java.lang.Cloneable {
ctor public Animation();
ctor public Animation(android.content.Context, android.util.AttributeSet);
- method public void addAnimationListener(android.view.animation.Animation.AnimationListener);
method protected void applyTransformation(float, android.view.animation.Transformation);
method public void cancel();
method protected android.view.animation.Animation clone() throws java.lang.CloneNotSupportedException;
@@ -52637,7 +52637,6 @@
method public void initialize(int, int, int, int);
method public boolean isFillEnabled();
method public boolean isInitialized();
- method public void removeAnimationListener(android.view.animation.Animation.AnimationListener);
method public void reset();
method protected float resolveSize(int, float, int, int);
method public void restrictDuration(long);
@@ -56375,6 +56374,7 @@
ctor public ProgressBar(android.content.Context, android.util.AttributeSet);
ctor public ProgressBar(android.content.Context, android.util.AttributeSet, int);
ctor public ProgressBar(android.content.Context, android.util.AttributeSet, int, int);
+ method @Nullable public android.graphics.drawable.Drawable getCurrentDrawable();
method public android.graphics.drawable.Drawable getIndeterminateDrawable();
method @Nullable public android.content.res.ColorStateList getIndeterminateTintList();
method @Nullable public android.graphics.PorterDuff.Mode getIndeterminateTintMode();
@@ -61199,9 +61199,9 @@
method public String getName();
method public int getReferenceKind();
method public default boolean isVarArgs();
- method public static boolean refKindIsField(int);
- method public static boolean refKindIsValid(int);
- method public static String refKindName(int);
+ method @Deprecated public static boolean refKindIsField(int);
+ method @Deprecated public static boolean refKindIsValid(int);
+ method @Deprecated public static String refKindName(int);
method public static String referenceKindToString(int);
method public <T extends java.lang.reflect.Member> T reflectAs(Class<T>, java.lang.invoke.MethodHandles.Lookup);
method public static String toString(int, Class<?>, String, java.lang.invoke.MethodType);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 7295259..868a9de 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
+import android.os.Build;
import android.os.NullVibrator;
import android.os.Parcel;
import android.os.Parcelable;
@@ -54,7 +55,7 @@
private final int mProductId;
private final String mDescriptor;
private final InputDeviceIdentifier mIdentifier;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private final boolean mIsExternal;
private final int mSources;
private final int mKeyboardType;
@@ -608,10 +609,7 @@
* peripheral bus), otherwise it is built-in.
*
* @return True if the device is external.
- *
- * @hide
*/
- @UnsupportedAppUsage
public boolean isExternal() {
return mIsExternal;
}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index dfd9a2e..e095094 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.RectF;
+import android.os.Build;
import android.os.Handler;
import android.os.SystemProperties;
import android.util.AttributeSet;
@@ -30,9 +31,6 @@
import dalvik.system.CloseGuard;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Abstraction for an Animation that can be applied to Views, Surfaces, or
* other objects. See the {@link android.view.animation animation package
@@ -187,15 +185,12 @@
/**
* An animation listener to be notified when the animation starts, ends or repeats.
*/
- @UnsupportedAppUsage
+ // If you need to chain the AnimationListener, wrap the existing Animation into an AnimationSet
+ // and add your new listener to that set
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117519981)
private AnimationListener mListener;
/**
- * A list of animation listeners to be notified when the animation starts, ends or repeats.
- */
- private List<AnimationListener> mListeners;
-
- /**
* Desired Z order mode during animation.
*/
private int mZAdjustment;
@@ -833,7 +828,7 @@
}
private boolean hasAnimationListener() {
- return mListener != null || (mListeners != null && !mListeners.isEmpty());
+ return mListener != null;
}
/**
@@ -848,32 +843,6 @@
}
/**
- * <p>Adds an animation listener to this animation. The animation listener
- * is notified of animation events such as the end of the animation or the
- * repetition of the animation.</p>
- *
- * @param listener the animation listener to be notified
- */
- public void addAnimationListener(AnimationListener listener) {
- if (mListeners == null) {
- mListeners = new ArrayList<>(1);
- }
- mListeners.add(listener);
- }
-
- /**
- * <p>Removes an animation listener that has been added with
- * {@link #addAnimationListener(AnimationListener)}.</p>
- *
- * @param listener the animation listener to be removed
- */
- public void removeAnimationListener(AnimationListener listener) {
- if (mListeners != null) {
- mListeners.remove(listener);
- }
- }
-
- /**
* Gurantees that this animation has an interpolator. Will use
* a AccelerateDecelerateInterpolator is nothing else was specified.
*/
@@ -1003,33 +972,18 @@
if (mListener != null) {
mListener.onAnimationStart(this);
}
- if (mListeners != null && !mListeners.isEmpty()) {
- for (AnimationListener listener : mListeners) {
- listener.onAnimationStart(this);
- }
- }
}
void dispatchAnimationRepeat() {
if (mListener != null) {
mListener.onAnimationRepeat(this);
}
- if (mListeners != null && !mListeners.isEmpty()) {
- for (AnimationListener listener : mListeners) {
- listener.onAnimationRepeat(this);
- }
- }
}
void dispatchAnimationEnd() {
if (mListener != null) {
mListener.onAnimationEnd(this);
}
- if (mListeners != null && !mListeners.isEmpty()) {
- for (AnimationListener listener : mListeners) {
- listener.onAnimationEnd(this);
- }
- }
}
/**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 6b48c65..30df5b5 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -211,7 +211,12 @@
private Drawable mIndeterminateDrawable;
private Drawable mProgressDrawable;
- @UnsupportedAppUsage
+ /**
+ * Outside the framework, instead of accessing this directly, please use
+ * {@link #getCurrentDrawable()}, {@link #setProgressDrawable(Drawable)},
+ * {@link #setIndeterminateDrawable(Drawable)} and their tiled versions.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private Drawable mCurrentDrawable;
private ProgressTintInfo mProgressTintInfo;
@@ -1298,9 +1303,14 @@
}
/**
- * @return The drawable currently used to draw the progress bar
+ * Returns the drawable currently used to draw the progress bar. This will be
+ * either {@link #getProgressDrawable()} or {@link #getIndeterminateDrawable()}
+ * depending on whether the progress bar is in determinate or indeterminate mode.
+ *
+ * @return the drawable currently used to draw the progress bar
*/
- Drawable getCurrentDrawable() {
+ @Nullable
+ public Drawable getCurrentDrawable() {
return mCurrentDrawable;
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 7f462cb..106b909 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -610,8 +610,16 @@
resolver.registerContentObserver(uri, true,
mFormatChangeObserver, UserHandle.USER_ALL);
} else {
+ // UserHandle.myUserId() is needed. This class is supported by the
+ // remote views mechanism and as a part of that the remote views
+ // can be inflated by a context for another user without the app
+ // having interact users permission - just for loading resources.
+ // For example, when adding widgets from a managed profile to the
+ // home screen. Therefore, we register the ContentObserver with the user
+ // the app is running (e.g. the launcher) and not the user of the
+ // context (e.g. the widget's profile).
resolver.registerContentObserver(uri, true,
- mFormatChangeObserver);
+ mFormatChangeObserver, UserHandle.myUserId());
}
}
}
diff --git a/core/jni/runtime_native_boot-flags-test.sh b/core/jni/runtime_native_boot-flags-test.sh
new file mode 100755
index 0000000..66e18bb
--- /dev/null
+++ b/core/jni/runtime_native_boot-flags-test.sh
@@ -0,0 +1,244 @@
+#!/bin/bash
+
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Test Android Runtime (Boot) device configuration flags (living in namespace
+# `runtime_native_boot`).
+
+me=$(basename $0)
+
+# Namespace containing the tested flag.
+namespace=runtime_native_boot
+# Default set of checked zygote processes.
+zygotes=
+
+# Status of whole test script.
+exit_status=0
+
+function say {
+ echo "$me: $*"
+}
+
+function banner {
+ local separator=$(echo "$@" | sed s/./=/g )
+ say "$separator"
+ say "$@"
+ say "$separator"
+}
+
+function fail {
+ say "FAILED: $@"
+ exit_status=1
+}
+
+function reboot_and_wait_for_device {
+ say "Rebooting device..."
+ adb reboot
+ adb wait-for-device >/dev/null
+ # Wait until the device has finished booting. Give the device 60 iterations
+ # (~60 seconds) to try and finish booting before declaring defeat.
+ local niters=60
+ for i in $(seq $niters); do
+ [[ $(adb shell getprop sys.boot_completed) -eq 1 ]] && return 0
+ sleep 1
+ done
+ fail "Device did not finish booting before timeout (~$niters seconds)"
+}
+
+# check_device_config_flag CONTEXT FLAG VALUE
+# -------------------------------------------
+# Check that the device configuration flag FLAG is set to VALUE. Use CONTEXT in
+# logging.
+function check_device_config_flag {
+ local context=$1
+ local flag=$2
+ local value=$3
+
+ say "[$context] Check that the device configuration flag is set..."
+ local flag_value=$(adb shell device_config get "$namespace" "$flag")
+ [[ "$flag_value" = "$value" ]] \
+ || fail "Device configuration flag \`$flag\` set to \`$flag_value\` (expected \`$value\`)"
+}
+
+# check_no_device_config_flag CONTEXT FLAG
+# ----------------------------------------
+# Check that the device configuration flag FLAG is not set. Use CONTEXT in
+# logging.
+function check_no_device_config_flag {
+ local context=$1
+ local flag=$2
+
+ say "[$context] Check that the device configuration flag is not set..."
+ local flag_value=$(adb shell device_config get "$namespace" "$flag")
+ [[ "$flag_value" = null ]] \
+ || fail "Device configuration flag \`$flag\` set to \`$flag_value\` (expected `null`)"
+}
+
+# get_system_property PROP
+# ------------------------
+# Get system property PROP associated with a device configuration flag.
+function get_system_property {
+ local prop=$1
+
+ # Note that we need to be root to read that system property.
+ adb root >/dev/null
+ local prop_value=$(adb shell getprop "$prop")
+ adb unroot >/dev/null
+ echo "$prop_value"
+}
+
+# check_system_property CONTEXT PROP VALUE
+# ----------------------------------------
+# Check that the system property PROP associated with a device configuration
+# flag is set to VALUE. Use CONTEXT in logging.
+function check_system_property {
+ local context=$1
+ local prop=$2
+ local value=$3
+
+ say "[$context] Check that the persistent system property is set..."
+ local prop_value=$(get_system_property "$prop")
+ [[ "$prop_value" = "$value" ]] \
+ || fail "System property \`$prop\` set to \`$prop_value\` (expected \`$value\`)"
+}
+
+# check_no_system_property CONTEXT PROP
+# -------------------------------------
+# Check that the system property PROP associated with a device configuration
+# flag is not set. Use CONTEXT in logging.
+function check_no_system_property {
+ local context=$1
+ local prop=$2
+
+ say "[$context] Check that the persistent system property is not set..."
+ local prop_value=$(get_system_property "$prop")
+ [[ -z "$prop_value" ]] \
+ || fail "System property \`$prop\` set to \`$prop_value\` (expected unset property)"
+}
+
+# find_zygote_runtime_option ZYGOTE RUNTIME_OPTION
+# ------------------------------------------------
+# Return whether ZYGOTE is passed RUNTIME_OPTION.
+function find_zygote_runtime_option {
+ local zygote=$1
+ local runtime_option=$2
+
+ adb logcat -d -s "$zygote" | grep -q -e "option\[[0-9]\+\]=$runtime_option"
+}
+
+# check_zygote_gc_runtime_option CONTEXT VALUE
+# --------------------------------------------
+# Check that all zygote processes are passed device configuration flag VALUE as
+# GC runtime option. Use CONTEXT in logging.
+function check_zygote_gc_runtime_option {
+ local context=$1
+ local value=$2
+
+ say \
+ "[$context] Check that all zygote processes are passed the flag value as a GC runtime option..."
+ local runtime_option="-Xgc:$value"
+ for zygote in $zygotes; do
+ find_zygote_runtime_option "$zygote" "$runtime_option"\
+ || fail "Found no \`$runtime_option\` among runtime options passed to \`$zygote\`"
+ done
+}
+
+# check_no_zygote_gc_runtime_option CONTEXT VALUE
+# -----------------------------------------------
+# Check that no zygote process is passed device configuration flag VALUE as GC
+# runtime option. Use CONTEXT in logging.
+function check_no_zygote_gc_runtime_option {
+ local context=$1
+ local value=$2
+
+ say "[$context] Check no zygote process is passed the flag value as a GC runtime option..."
+ local runtime_option="-Xgc:$value"
+ for zygote in $zygotes; do
+ find_zygote_runtime_option "$zygote" "$runtime_option"\
+ && fail "Found \`$runtime_option\` among runtime options passed to \`$zygote\`"
+ done
+}
+
+# test_android_runtime_flag FLAG VALUE
+# ------------------------------------
+# Test device configuration FLAG with VALUE.
+function test_android_runtime_flag {
+ local flag=$1
+ local value=$2
+
+ # Persistent system property (set after a reboot) associated with the device
+ # configuration flag.
+ local prop="persist.device_config.$namespace.$flag"
+
+ banner "Testing \`$flag\` value \`$value\`."
+
+ say "Setting device configuration flag..."
+ adb shell device_config put "$namespace" "$flag" "$value"
+ # Give some time to the device to digest this change before rebooting.
+ sleep 3
+
+ # Check that both the device configuration flag and the associated system
+ # property are set, but that the zygote hasn't had the flag passed to it as a
+ # GC runtime option (as we haven't rebooted yet).
+ local context="Flag set, before reboot"
+ check_device_config_flag "$context" "$flag" "$value"
+ check_system_property "$context" "$prop" "$value"
+ check_no_zygote_gc_runtime_option "$context" "$value"
+
+ # Reboot device for the flag value to take effect.
+ reboot_and_wait_for_device
+ context="Flag set, after 1st reboot"
+ check_device_config_flag "$context" "$flag" "$value"
+ check_system_property "$context" "$prop" "$value"
+ check_zygote_gc_runtime_option "$context" "$value"
+
+ # Reboot device a second time and check that the state has persisted.
+ reboot_and_wait_for_device
+ context="Flag set, after 2nd reboot"
+ check_device_config_flag "$context" "$flag" "$value"
+ check_system_property "$context" "$prop" "$value"
+ check_zygote_gc_runtime_option "$context" "$value"
+
+ say "Unsetting device configuration flag..."
+ adb shell device_config delete "$namespace" "$flag" >/dev/null
+ # Give some time to the device to digest this change before rebooting.
+ sleep 3
+
+ # Reboot and check that the device is back to its default state.
+ reboot_and_wait_for_device
+ context="Flag unset, after 3rd reboot"
+ check_no_device_config_flag "$context" "$flag"
+ check_no_system_property "$context" "$prop"
+ check_no_zygote_gc_runtime_option "$context" "$value"
+}
+
+# Enumerate Zygote processes.
+case $(adb shell getprop ro.zygote) in
+ (zygote32) zygotes="zygote";;
+ (zygote64) zygotes="zygote64";;
+ (zygote32_64|zygote64_32) zygotes="zygote zygote64";;
+esac
+
+# Test "gctype" flag values.
+test_android_runtime_flag gctype nogenerational_cc
+test_android_runtime_flag gctype generational_cc
+
+if [[ "$exit_status" -eq 0 ]]; then
+ banner "All tests passed."
+else
+ banner "Test(s) failed."
+fi
+exit $exit_status
diff --git a/core/res/res/drawable/ic_action_open.xml b/core/res/res/drawable/ic_action_open.xml
new file mode 100644
index 0000000..3d3d36e
--- /dev/null
+++ b/core/res/res/drawable/ic_action_open.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF737373"
+ android:pathData="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 410a8c16..8e251fd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3643,4 +3643,6 @@
<java-symbol type="array" name="config_displayWhiteBalanceDecreaseThresholds" />
<java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientBrightnessThreshold" />
<java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientColorTemperature" />
+
+ <java-symbol type="drawable" name="ic_action_open" />
</resources>
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index b6b229c..c5e598d 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -230,6 +230,10 @@
NotificationEntry entry =
new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper);
SmartActionsHelper.SmartSuggestions suggestions = mSmartActionsHelper.suggest(entry);
+ if (DEBUG) {
+ Log.d(TAG, String.format("Creating Adjustment for %s, with %d actions, and %d replies.",
+ sbn.getKey(), suggestions.actions.size(), suggestions.replies.size()));
+ }
return createEnqueuedNotificationAdjustment(
entry, suggestions.actions, suggestions.replies);
}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 08cc39f..f372fe5 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -21,6 +21,7 @@
import android.app.Person;
import android.app.RemoteAction;
import android.content.Context;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.Process;
@@ -42,6 +43,7 @@
import java.util.Collections;
import java.util.Deque;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
public class SmartActionsHelper {
@@ -120,6 +122,12 @@
if (messages.isEmpty()) {
return Collections.emptyList();
}
+ // Do not generate smart actions if the last message is from the local user.
+ ConversationActions.Message lastMessage = messages.get(messages.size() - 1);
+ if (arePersonsEqual(
+ ConversationActions.Message.PERSON_USER_SELF, lastMessage.getAuthor())) {
+ return Collections.emptyList();
+ }
TextClassifier.EntityConfig.Builder typeConfigBuilder =
new TextClassifier.EntityConfig.Builder();
@@ -230,8 +238,11 @@
private Notification.Action createNotificationAction(
RemoteAction remoteAction, String actionType) {
+ Icon icon = remoteAction.shouldShowIcon()
+ ? remoteAction.getIcon()
+ : Icon.createWithResource(mContext, com.android.internal.R.drawable.ic_action_open);
return new Notification.Action.Builder(
- remoteAction.getIcon(),
+ icon,
remoteAction.getTitle(),
remoteAction.getActionIntent())
.setContextual(true)
@@ -312,13 +323,12 @@
if (message == null) {
continue;
}
+ // As per the javadoc of Notification.addMessage, null means local user.
Person senderPerson = message.getSenderPerson();
- // Skip encoding once the sender is missing as it is important to distinguish
- // local user and remote user when generating replies.
if (senderPerson == null) {
- break;
+ senderPerson = localUser;
}
- Person author = localUser != null && localUser.equals(senderPerson)
+ Person author = localUser != null && arePersonsEqual(localUser, senderPerson)
? ConversationActions.Message.PERSON_USER_SELF : senderPerson;
extractMessages.push(new ConversationActions.Message.Builder(author)
.setText(message.getText())
@@ -333,6 +343,12 @@
return new ArrayList<>(extractMessages);
}
+ private static boolean arePersonsEqual(@NonNull Person left, @NonNull Person right) {
+ return Objects.equals(left.getKey(), right.getKey())
+ && Objects.equals(left.getName(), right.getName())
+ && Objects.equals(left.getUri(), right.getUri());
+ }
+
static class SmartSuggestions {
public final ArrayList<CharSequence> replies;
public final ArrayList<Notification.Action> actions;
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java
index ebbd961..74c20fc 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionsHelperTest.java
@@ -222,22 +222,29 @@
List<ConversationActions.Message> messages =
runSuggestAndCaptureRequest().getConversation();
- assertThat(messages).hasSize(3);
+ assertThat(messages).hasSize(4);
- ConversationActions.Message secondMessage = messages.get(0);
+ ConversationActions.Message firstMessage = messages.get(0);
+ MessageSubject.assertThat(firstMessage).hasText("firstMessage");
+ MessageSubject.assertThat(firstMessage)
+ .hasPerson(ConversationActions.Message.PERSON_USER_SELF);
+ MessageSubject.assertThat(firstMessage)
+ .hasReferenceTime(createZonedDateTimeFromMsUtc(1000));
+
+ ConversationActions.Message secondMessage = messages.get(1);
MessageSubject.assertThat(secondMessage).hasText("secondMessage");
MessageSubject.assertThat(secondMessage)
.hasPerson(ConversationActions.Message.PERSON_USER_SELF);
MessageSubject.assertThat(secondMessage)
.hasReferenceTime(createZonedDateTimeFromMsUtc(2000));
- ConversationActions.Message thirdMessage = messages.get(1);
+ ConversationActions.Message thirdMessage = messages.get(2);
MessageSubject.assertThat(thirdMessage).hasText("thirdMessage");
MessageSubject.assertThat(thirdMessage).hasPerson(userA);
MessageSubject.assertThat(thirdMessage)
.hasReferenceTime(createZonedDateTimeFromMsUtc(3000));
- ConversationActions.Message fourthMessage = messages.get(2);
+ ConversationActions.Message fourthMessage = messages.get(3);
MessageSubject.assertThat(fourthMessage).hasText("fourthMessage");
MessageSubject.assertThat(fourthMessage).hasPerson(userB);
MessageSubject.assertThat(fourthMessage)
@@ -245,6 +252,28 @@
}
@Test
+ public void testSuggest_lastMessageLocalUser() {
+ Person me = new Person.Builder().setName("Me").build();
+ Person userA = new Person.Builder().setName("A").build();
+ Notification.MessagingStyle style =
+ new Notification.MessagingStyle(me)
+ .addMessage("firstMessage", 1000, userA)
+ .addMessage("secondMessage", 2000, me);
+ Notification notification =
+ mNotificationBuilder
+ .setContentText("You have two new messages")
+ .setStyle(style)
+ .setActions(createReplyAction())
+ .build();
+ when(mStatusBarNotification.getNotification()).thenReturn(notification);
+
+ mSmartActionsHelper.suggest(createNotificationEntry());
+
+ verify(mTextClassifier, never())
+ .suggestConversationActions(any(ConversationActions.Request.class));
+ }
+
+ @Test
public void testSuggest_messageStyle_noPerson() {
Person me = new Person.Builder().setName("Me").build();
Notification.MessagingStyle style =
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 36d0659..f1158ef 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -17,6 +17,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#4a4a4a" />
- <padding android:padding="@dimen/ongoing_appops_chip_bg_padding" />
+ <padding android:paddingTop="@dimen/ongoing_appops_chip_bg_padding"
+ android:paddingBottom="@dimen/ongoing_appops_chip_bg_padding"/>
<corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index cbdd51b..58fe811 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -29,14 +29,25 @@
android:orientation="horizontal"
android:paddingStart="@dimen/ongoing_appops_chip_side_padding"
android:paddingEnd="@dimen/ongoing_appops_chip_side_padding"
- android:background="@drawable/privacy_chip_bg"
android:focusable="true">
+ <TextView
+ android:id="@+id/in_use_text"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginEnd="@dimen/ongoing_appops_chip_icon_margin_collapsed"
+ android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:textColor="@color/status_bar_clock_color"
+ android:text="@string/ongoing_privacy_chip_in_use"
+ />
+
<LinearLayout
android:id="@+id/icons_container"
android:layout_height="match_parent"
android:layout_width="wrap_content"
- android:layout_gravity="center_vertical|start"
+ android:layout_gravity="center_vertical"
android:gravity="center_vertical"
/>
@@ -51,7 +62,7 @@
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:textColor="@color/status_bar_clock_color"
- android:layout_marginStart="@dimen/ongoing_appops_chip_icon_margin"
- android:layout_marginEnd="@dimen/ongoing_appops_chip_icon_margin"
+ android:layout_marginStart="@dimen/ongoing_appops_chip_icon_margin_collapsed"
+ android:layout_marginEnd="@dimen/ongoing_appops_chip_icon_margin_collapsed"
/>
</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0344535..1e1245f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -967,7 +967,7 @@
<!-- Height and width of App Opp icons in Ongoing App Ops dialog -->
<dimen name="ongoing_appops_dialog_icon_size">24dp</dimen>
<!-- Left margin of App Opp icons in Ongoing App Ops dialog -->
- <dimen name="ongoing_appops_dialog_icon_margin">8dp</dimen>
+ <dimen name="ongoing_appops_dialog_icon_margin">12dp</dimen>
<!-- Height and width of Application icons in Ongoing App Ops dialog -->
<dimen name="ongoing_appops_dialog_app_icon_size">32dp</dimen>
<!-- Height and width of Plus sign in Ongoing App Ops dialog -->
@@ -988,12 +988,14 @@
<dimen name="ongoing_appops_chip_side_padding">6dp</dimen>
<!-- Padding between background of Ongoing App Ops chip and content -->
<dimen name="ongoing_appops_chip_bg_padding">0dp</dimen>
- <!-- Margin between icons of Ongoing App Ops chip -->
- <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
+ <!-- Margin between icons of Ongoing App Ops chip when QQS-->
+ <dimen name="ongoing_appops_chip_icon_margin_collapsed">0dp</dimen>
+ <!-- Margin between icons of Ongoing App Ops chip when QS-->
+ <dimen name="ongoing_appops_chip_icon_margin_expanded">8dp</dimen>
<!-- Icon size of Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_icon_size">18dp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
- <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen>
+ <dimen name="ongoing_appops_chip_bg_corner_radius">4dp</dimen>
<!-- Text size for Ongoing App Ops dialog title -->
<dimen name="ongoing_appops_dialog_title_size">20sp</dimen>
<!-- Text size for Ongoing App Ops dialog items -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index db4a6cc..89c6c8a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2301,6 +2301,9 @@
<!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]-->
<string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string>
+ <!-- Ongoing Privacy "Chip" in use text [CHAR LIMIT=10]-->
+ <string name="ongoing_privacy_chip_in_use">In use:</string>
+
<!-- Content description for ongoing privacy chip. Use with multiple apps using same app op[CHAR LIMIT=NONE]-->
<plurals name="ongoing_privacy_chip_content_multiple_apps_single_op">
<item quantity="one"><xliff:g id="num_apps" example="1">%1$d</xliff:g> application is using your <xliff:g id="type" example="camera">%2$s</xliff:g>.</item>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index bf9d7ba..976a766 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -34,8 +34,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.StatsLog;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@@ -234,6 +236,8 @@
mStackView.collapseStack(() -> {
try {
n.contentIntent.send();
+ logBubbleClickEvent(mEntry.notification,
+ StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_APP);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed to send intent for bubble with key: "
+ (mEntry != null ? mEntry.key : " null entry"));
@@ -242,7 +246,11 @@
} else if (id == R.id.settings_button) {
Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
mEntry.notification.getUid());
- mStackView.collapseStack(() -> mContext.startActivity(intent));
+ mStackView.collapseStack(() -> {
+ mContext.startActivity(intent);
+ logBubbleClickEvent(mEntry.notification,
+ StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+ });
} else if (id == R.id.no_bubbles_button) {
setBubblesAllowed(false);
} else if (id == R.id.yes_bubbles_button) {
@@ -262,6 +270,9 @@
} else if (mOnBubbleBlockedListener != null) {
mOnBubbleBlockedListener.onBubbleBlocked(mEntry);
}
+ logBubbleClickEvent(mEntry.notification,
+ allowed ? StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_IN :
+ StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_OUT);
} catch (RemoteException e) {
Log.w(TAG, e);
}
@@ -318,4 +329,22 @@
*/
void onBubbleBlocked(NotificationEntry entry);
}
+
+ /**
+ * Logs bubble UI click event.
+ *
+ * @param notification the bubble notification that user is interacting with.
+ * @param action the user interaction enum.
+ */
+ private void logBubbleClickEvent(StatusBarNotification notification, int action) {
+ StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
+ notification.getPackageName(),
+ notification.getNotification().getChannelId(),
+ notification.getId(),
+ mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
+ mStackView.getBubbleCount(),
+ action,
+ mStackView.getNormalizedXPosition(),
+ mStackView.getNormalizedYPosition());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f6f3fa6..2b344f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -773,7 +773,7 @@
/**
* @return the number of bubbles in the stack view.
*/
- private int getBubbleCount() {
+ public int getBubbleCount() {
return mBubbleContainer.getChildCount();
}
@@ -784,14 +784,14 @@
* @return the index of the bubble view within the bubble stack. The range of the position
* is between 0 and the bubble count minus 1.
*/
- private int getBubbleIndex(BubbleView bubbleView) {
+ public int getBubbleIndex(BubbleView bubbleView) {
return mBubbleContainer.indexOfChild(bubbleView);
}
/**
* @return the normalized x-axis position of the bubble stack rounded to 4 decimal places.
*/
- private float getNormalizedXPosition() {
+ public float getNormalizedXPosition() {
return new BigDecimal(getPosition().x / mDisplaySize.x)
.setScale(4, RoundingMode.CEILING.HALF_UP)
.floatValue();
@@ -800,7 +800,7 @@
/**
* @return the normalized y-axis position of the bubble stack rounded to 4 decimal places.
*/
- private float getNormalizedYPosition() {
+ public float getNormalizedYPosition() {
return new BigDecimal(getPosition().y / mDisplaySize.y)
.setScale(4, RoundingMode.CEILING.HALF_UP)
.floatValue();
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 65ed889..ecbf024 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -16,6 +16,7 @@
import android.content.Context
import android.util.AttributeSet
+import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
@@ -29,14 +30,25 @@
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
- private val iconMargin =
- context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
+ private val iconMarginExpanded = context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_appops_chip_icon_margin_expanded)
+ private val iconMarginCollapsed = context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_appops_chip_icon_margin_collapsed)
private val iconSize =
context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
- val iconColor = context.resources.getColor(
+ private val iconColor = context.resources.getColor(
R.color.status_bar_clock_color, context.theme)
+ private val backgroundDrawable = context.getDrawable(R.drawable.privacy_chip_bg)
private lateinit var text: TextView
private lateinit var iconsContainer: LinearLayout
+ private lateinit var inUseText: TextView
+ var expanded = false
+ set(value) {
+ if (value != field) {
+ field = value
+ updateView()
+ }
+ }
var builder = PrivacyDialogBuilder(context, emptyList<PrivacyItem>())
var privacyList = emptyList<PrivacyItem>()
set(value) {
@@ -48,15 +60,18 @@
override fun onFinishInflate() {
super.onFinishInflate()
+ inUseText = findViewById(R.id.in_use_text)
text = findViewById(R.id.text_container)
iconsContainer = findViewById(R.id.icons_container)
}
// Should only be called if the builder icons or app changed
private fun updateView() {
+ inUseText.visibility = if (expanded) View.GONE else View.VISIBLE
+ background = if (expanded) backgroundDrawable else null
fun setIcons(dialogBuilder: PrivacyDialogBuilder, iconsContainer: ViewGroup) {
iconsContainer.removeAllViews()
- dialogBuilder.generateIcons().forEach {
+ dialogBuilder.generateIcons().forEachIndexed { i, it ->
it.mutate()
it.setTint(iconColor)
val image = ImageView(context).apply {
@@ -64,17 +79,19 @@
scaleType = ImageView.ScaleType.CENTER_INSIDE
}
iconsContainer.addView(image, iconSize, iconSize)
- val lp = image.layoutParams as MarginLayoutParams
- lp.marginStart = iconMargin
- image.layoutParams = lp
+ if (i != 0) {
+ val lp = image.layoutParams as MarginLayoutParams
+ lp.marginStart = if (expanded) iconMarginExpanded else iconMarginCollapsed
+ image.layoutParams = lp
+ }
}
}
if (!privacyList.isEmpty()) {
generateContentDescription()
setIcons(builder, iconsContainer)
- text.visibility = if (builder.types.size == 1) VISIBLE else GONE
- if (builder.types.size == 1) {
+ text.visibility = if (builder.types.size == 1 && expanded) VISIBLE else GONE
+ if (builder.types.size == 1 && expanded) {
if (builder.app != null) {
text.setText(builder.app?.applicationName)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index b865ce8..f91c9d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -189,6 +189,7 @@
addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
oldBottom) -> updateAnimator(right - left));
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ updateEverything();
}
private void updateAnimator(int width) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 74e82b2..ee9255c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -124,6 +124,7 @@
private TintedIconManager mIconManager;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
+ private TouchAnimator mPrivacyChipAlphaAnimator;
private View mSystemIconsView;
private View mQuickQsStatusIcons;
@@ -212,6 +213,8 @@
mNextAlarmTextView = findViewById(R.id.next_alarm_text);
mRingerModeIcon = findViewById(R.id.ringer_mode_icon);
mRingerModeTextView = findViewById(R.id.ringer_mode_text);
+ mPrivacyChip = findViewById(R.id.privacy_chip);
+ mPrivacyChip.setOnClickListener(this);
updateResources();
@@ -230,8 +233,6 @@
mClockView = findViewById(R.id.clock);
mClockView.setOnClickListener(this);
mDateView = findViewById(R.id.date);
- mPrivacyChip = findViewById(R.id.privacy_chip);
- mPrivacyChip.setOnClickListener(this);
mSpace = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
@@ -383,6 +384,7 @@
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
+ updatePrivacyChipAlphaAnimator();
}
private void updateStatusIconAlphaAnimator() {
@@ -398,6 +400,12 @@
.build();
}
+ private void updatePrivacyChipAlphaAnimator() {
+ mPrivacyChipAlphaAnimator = new TouchAnimator.Builder()
+ .addFloat(mPrivacyChip, "alpha", 1, 0, 1)
+ .build();
+ }
+
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
@@ -431,6 +439,10 @@
if (mHeaderTextContainerAlphaAnimator != null) {
mHeaderTextContainerAlphaAnimator.setPosition(keyguardExpansionFraction);
}
+ if (mPrivacyChipAlphaAnimator != null) {
+ mPrivacyChip.setExpanded(expansionFraction > 0.5);
+ mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
+ }
// Check the original expansion fraction - we don't want to show the tooltip until the
// panel is pulled all the way out.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 8095615..1dc48d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -68,6 +68,7 @@
public class NotificationContentView extends FrameLayout {
private static final String TAG = "NotificationContentView";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -1319,6 +1320,14 @@
SmartRepliesAndActions smartRepliesAndActions =
chooseSmartRepliesAndActions(mSmartReplyConstants, entry);
+ if (DEBUG) {
+ Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.",
+ entry.notification.getKey(),
+ smartRepliesAndActions.smartActions == null ? 0 :
+ smartRepliesAndActions.smartActions.actions.size(),
+ smartRepliesAndActions.smartReplies == null ? 0 :
+ smartRepliesAndActions.smartReplies.choices.length));
+ }
applyRemoteInput(entry, smartRepliesAndActions.hasFreeformRemoteInput);
applySmartReplyView(smartRepliesAndActions, entry);
@@ -1341,6 +1350,10 @@
notification.findRemoteInputActionPair(true /* freeform */);
if (!smartReplyConstants.isEnabled()) {
+ if (DEBUG) {
+ Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for "
+ + entry.notification.getKey());
+ }
return new SmartRepliesAndActions(null, null, freeformRemoteInputActionPair != null);
}
// Only use smart replies from the app if they target P or above. We have this check because
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 1f9b027..e7a71b9 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.os.RemoteException;
import android.security.Scrypt;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
@@ -162,7 +163,7 @@
}
}
- private void syncKeys() {
+ private void syncKeys() throws RemoteException {
if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
// Application keys for the user will not be available for sync.
Log.w(TAG, "Credentials are not set for user " + mUserId);
@@ -195,7 +196,7 @@
&& mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
}
- private void syncKeysForAgent(int recoveryAgentUid) throws IOException {
+ private void syncKeysForAgent(int recoveryAgentUid) throws IOException, RemoteException {
boolean shouldRecreateCurrentVersion = false;
if (!shouldCreateSnapshot(recoveryAgentUid)) {
shouldRecreateCurrentVersion =
@@ -412,7 +413,7 @@
private Map<String, Pair<SecretKey, byte[]>> getKeysToSync(int recoveryAgentUid)
throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
- InvalidKeyException, InvalidAlgorithmParameterException, IOException {
+ InvalidKeyException, InvalidAlgorithmParameterException, IOException, RemoteException {
PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);;
Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
mUserId, recoveryAgentUid, decryptKey.getGenerationId());
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 9ca052b..1c18771 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -18,15 +18,21 @@
import android.app.KeyguardManager;
import android.content.Context;
+import android.os.RemoteException;
+import android.security.GateKeeper;
import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
+import android.service.gatekeeper.IGateKeeperService;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -34,9 +40,11 @@
import java.security.cert.CertificateException;
import java.util.Locale;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
-import javax.security.auth.DestroyFailedException;
+import javax.crypto.spec.GCMParameterSpec;
/**
* Manages creating and checking the validity of the platform key.
@@ -67,6 +75,10 @@
private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt";
private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+ private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final int GCM_TAG_LENGTH_BITS = 128;
+ // Only used for checking if a key is usable
+ private static final byte[] GCM_INSECURE_NONCE_BYTES = new byte[12];
private final Context mContext;
private final KeyStoreProxy mKeyStore;
@@ -158,12 +170,14 @@
* @throws KeyStoreException if there is an error in AndroidKeyStore.
* @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
+ * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
* @hide
*/
@VisibleForTesting
void regenerate(int userId)
- throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException {
+ throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
+ RemoteException {
if (!isAvailable(userId)) {
throw new InsecureUserException(String.format(
Locale.US, "%d does not have a lock screen set.", userId));
@@ -190,11 +204,13 @@
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
* @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
+ * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
* @hide
*/
- public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
- UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException {
+ public PlatformEncryptionKey getEncryptKey(int userId)
+ throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
+ InsecureUserException, IOException, RemoteException {
init(userId);
try {
// Try to see if the decryption key is still accessible before using the encryption key.
@@ -243,14 +259,18 @@
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
* @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
+ * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
* @hide
*/
- public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
- UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException {
+ public PlatformDecryptionKey getDecryptKey(int userId)
+ throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
+ InsecureUserException, IOException, RemoteException {
init(userId);
try {
- return getDecryptKeyInternal(userId);
+ PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
+ ensureDecryptionKeyIsValid(userId, decryptionKey);
+ return decryptionKey;
} catch (UnrecoverableKeyException e) {
Log.i(TAG, String.format(Locale.US,
"Regenerating permanently invalid Platform key for user %d.",
@@ -284,6 +304,29 @@
}
/**
+ * Tries to use the decryption key to make sure it is not permanently invalidated. The exception
+ * {@code KeyPermanentlyInvalidatedException} is thrown only when the key is in use.
+ *
+ * <p>Note that we ignore all other InvalidKeyException exceptions, because such an exception
+ * may be thrown for auth-bound keys if there's no recent unlock event.
+ */
+ private void ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey)
+ throws UnrecoverableKeyException {
+ try {
+ Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM).init(Cipher.UNWRAP_MODE,
+ decryptionKey.getKey(),
+ new GCMParameterSpec(GCM_TAG_LENGTH_BITS, GCM_INSECURE_NONCE_BYTES));
+ } catch (KeyPermanentlyInvalidatedException e) {
+ Log.e(TAG, String.format(Locale.US, "The platform key for user %d became invalid.",
+ userId));
+ throw new UnrecoverableKeyException(e.getMessage());
+ } catch (NoSuchAlgorithmException | InvalidKeyException
+ | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
+ // Ignore all other exceptions
+ }
+ }
+
+ /**
* Initializes the class. If there is no current platform key, and the user has a lock screen
* set, will create the platform key and set the generation ID.
*
@@ -291,11 +334,13 @@
* @throws KeyStoreException if there was an error in AndroidKeyStore.
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
* @throws IOException if there was an issue with local database update.
+ * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
* @hide
*/
void init(int userId)
- throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException {
+ throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
+ RemoteException {
if (!isAvailable(userId)) {
throw new InsecureUserException(String.format(
Locale.US, "%d does not have a lock screen set.", userId));
@@ -372,6 +417,11 @@
&& mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
}
+ @VisibleForTesting
+ IGateKeeperService getGateKeeperService() {
+ return GateKeeper.getService();
+ }
+
/**
* Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given
* {@code generationId} determining its aliases.
@@ -380,15 +430,23 @@
* available since API version 1.
* @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
* @throws IOException if there was an issue with local database update.
+ * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*/
private void generateAndLoadKey(int userId, int generationId)
- throws NoSuchAlgorithmException, KeyStoreException, IOException {
+ throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException {
String encryptAlias = getEncryptAlias(userId, generationId);
String decryptAlias = getDecryptAlias(userId, generationId);
// SecretKey implementation doesn't provide reliable way to destroy the secret
// so it may live in memory for some time.
SecretKey secretKey = generateAesKey();
+ long secureUserId = getGateKeeperService().getSecureUserId(userId);
+ // TODO(b/124095438): Propagate this failure instead of silently failing.
+ if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) {
+ Log.e(TAG, "No SID available for user " + userId);
+ return;
+ }
+
// Store decryption key first since it is more likely to fail.
mKeyStore.setEntry(
decryptAlias,
@@ -399,7 +457,7 @@
USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .setBoundToSpecificSecureUserId(userId)
+ .setBoundToSpecificSecureUserId(secureUserId)
.build());
mKeyStore.setEntry(
encryptAlias,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6c3e1f4..00105be 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -188,6 +188,7 @@
import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -843,7 +844,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
synchronized (mGlobalLock) {
SurfaceControl.closeTransaction();
- traceStateLocked(where);
+ mWindowTracing.logState(where);
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -967,7 +968,8 @@
mWindowPlacerLocked = new WindowSurfacePlacer(this);
mTaskSnapshotController = new TaskSnapshotController(this);
- mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);
+ mWindowTracing = WindowTracing.createDefaultAndStartLooper(this,
+ Choreographer.getInstance());
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
@@ -5765,17 +5767,6 @@
proto.write(LAST_ORIENTATION, defaultDisplayContent.getLastOrientation());
}
- void traceStateLocked(String where) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
- try {
- mWindowTracing.traceStateLocked(where, this);
- } catch (Exception e) {
- Log.wtf(TAG, "Exception while tracing state", e);
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 8b1ffa8..abc474d 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -24,12 +24,14 @@
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
import android.annotation.Nullable;
-import android.content.Context;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
@@ -48,6 +50,10 @@
private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024;
private static final String TAG = "WindowTracing";
+ private final WindowManagerService mService;
+ private final Choreographer mChoreographer;
+ private final WindowManagerGlobalLock mGlobalLock;
+
private final Object mLock = new Object();
private final WindowTraceBuffer.Builder mBufferBuilder;
@@ -57,11 +63,24 @@
private boolean mContinuousMode;
private boolean mEnabled;
private volatile boolean mEnabledLockFree;
+ private boolean mScheduled;
+ private Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
+ log("onFrame" /* where */);
- WindowTracing(File file) {
+ private WindowTracing(File file, WindowManagerService service, Choreographer choreographer) {
+ this(file, service, choreographer, service.mGlobalLock);
+ }
+
+ @VisibleForTesting
+ WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
+ WindowManagerGlobalLock globalLock) {
mBufferBuilder = new WindowTraceBuffer.Builder()
.setTraceFile(file)
.setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE);
+
+ mChoreographer = choreographer;
+ mService = service;
+ mGlobalLock = globalLock;
}
void startTrace(@Nullable PrintWriter pw) throws IOException {
@@ -111,7 +130,8 @@
}
}
- private void setContinuousMode(boolean continuous, PrintWriter pw) {
+ @VisibleForTesting
+ void setContinuousMode(boolean continuous, PrintWriter pw) {
logAndPrintln(pw, "Setting window tracing continuous mode to " + continuous);
if (mEnabled) {
@@ -123,21 +143,14 @@
WindowTraceLogLevel.TRIM;
}
- private void appendTraceEntry(ProtoOutputStream proto) {
- if (!mEnabledLockFree) {
- return;
- }
-
- mTraceBuffer.add(proto);
- }
-
boolean isEnabled() {
return mEnabledLockFree;
}
- static WindowTracing createDefaultAndStartLooper(Context context) {
+ static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
+ Choreographer choreographer) {
File file = new File("/data/misc/wmtrace/wm_trace.pb");
- return new WindowTracing(file);
+ return new WindowTracing(file, service, choreographer);
}
int onShellCommand(ShellCommand shell) {
@@ -164,28 +177,65 @@
}
}
- void traceStateLocked(String where, WindowManagerService service) {
+ /**
+ * If tracing is enabled, log the current state or schedule the next frame to be logged,
+ * according to {@link #mContinuousMode}.
+ *
+ * @param where Logging point descriptor
+ */
+ void logState(String where) {
if (!isEnabled()) {
return;
}
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToBufferLocked");
- try {
- ProtoOutputStream os = new ProtoOutputStream();
- long tokenOuter = os.start(ENTRY);
- os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
- os.write(WHERE, where);
+ if (mContinuousMode) {
+ schedule();
+ } else {
+ log(where);
+ }
+ }
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
- try {
- long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
- service.writeToProtoLocked(os, mWindowTraceLogLevel);
- os.end(tokenInner);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ /**
+ * Schedule the log to trace the next frame
+ */
+ private void schedule() {
+ if (mScheduled) {
+ return;
+ }
+
+ mScheduled = true;
+ mChoreographer.postFrameCallback(mFrameCallback);
+ }
+
+ /**
+ * Write the current frame to the buffer
+ *
+ * @param where Logging point descriptor
+ */
+ private void log(String where) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
+ try {
+ synchronized (mGlobalLock) {
+ ProtoOutputStream os = new ProtoOutputStream();
+ long tokenOuter = os.start(ENTRY);
+ os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ os.write(WHERE, where);
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
+ try {
+ long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+ mService.writeToProtoLocked(os, mWindowTraceLogLevel);
+ os.end(tokenInner);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ os.end(tokenOuter);
+ mTraceBuffer.add(os);
+
+ mScheduled = false;
}
- os.end(tokenOuter);
- appendTraceEntry(os);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 13436e7..821d97a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -24,14 +24,21 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
import android.app.KeyguardManager;
import android.content.Context;
+import android.os.RemoteException;
+import android.security.GateKeeper;
+import android.security.keystore.AndroidKeyStoreSecretKey;
+import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
+import android.service.gatekeeper.IGateKeeperService;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -53,6 +60,8 @@
import java.security.UnrecoverableKeyException;
import java.util.List;
+import javax.crypto.KeyGenerator;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PlatformKeyManagerTest {
@@ -60,10 +69,15 @@
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
private static final int USER_ID_FIXTURE = 42;
+ private static final long USER_SID = 4200L;
+ private static final String KEY_ALGORITHM = "AES";
+ private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
+ private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias";
@Mock private Context mContext;
@Mock private KeyStoreProxy mKeyStoreProxy;
@Mock private KeyguardManager mKeyguardManager;
+ @Mock private IGateKeeperService mGateKeeperService;
@Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor;
@Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor;
@@ -74,18 +88,19 @@
private PlatformKeyManager mPlatformKeyManager;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getTargetContext();
mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
- mPlatformKeyManager = new PlatformKeyManager(
- mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
+ mPlatformKeyManager = new PlatformKeyManagerTestable(
+ mContext, mKeyStoreProxy, mRecoverableKeyStoreDb, mGateKeeperService);
when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
when(mContext.getSystemServiceName(any())).thenReturn("test");
when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(true);
+ when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenReturn(USER_SID);
}
@After
@@ -192,11 +207,36 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
assertEquals(
- USER_ID_FIXTURE,
+ USER_SID,
getDecryptKeyProtection().getBoundToSpecificSecureUserId());
}
@Test
+ public void init_doesNotCreateDecryptKeyIfNoSid() throws Exception {
+ when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE))
+ .thenReturn(GateKeeper.INVALID_SECURE_USER_ID);
+
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+ verify(mKeyStoreProxy, never()).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any(),
+ any());
+ }
+
+ @Test
+ public void init_doesNotCreateDecryptKeyOnGateKeeperException() throws Exception {
+ when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenThrow(new RemoteException());
+
+ expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE));
+
+ verify(mKeyStoreProxy, never()).setEntry(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any(),
+ any());
+ }
+
+ @Test
public void init_createsBothKeysWithSameMaterial() throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
@@ -259,6 +299,9 @@
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/1/encrypt")).thenReturn(true);
+ when(mKeyStoreProxy.getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ any())).thenReturn(generateAndroidKeyStoreKey());
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
@@ -281,6 +324,9 @@
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
+ "platform/42/2/decrypt")).thenReturn(true);
+ when(mKeyStoreProxy.getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ any())).thenReturn(generateAndroidKeyStoreKey());
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
@@ -352,6 +398,9 @@
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
any());
+ when(mKeyStoreProxy.getKey(
+ eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ any())).thenReturn(generateAndroidKeyStoreKey());
when(mKeyStoreProxy
.containsAlias("com.android.server.locksettings.recoverablekeystore/"
@@ -536,4 +585,34 @@
mProtectionParameterCaptor.capture());
return (KeyProtection) mProtectionParameterCaptor.getValue();
}
+
+ private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KEY_ALGORITHM,
+ ANDROID_KEY_STORE_PROVIDER);
+ keyGenerator.init(new KeyGenParameterSpec.Builder(TESTING_KEYSTORE_KEY_ALIAS,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+ }
+
+ class PlatformKeyManagerTestable extends PlatformKeyManager {
+ private IGateKeeperService mGateKeeperService;
+
+ PlatformKeyManagerTestable(
+ Context context,
+ KeyStoreProxy keyStoreProxy,
+ RecoverableKeyStoreDb database,
+ IGateKeeperService gateKeeperService) {
+ super(context, keyStoreProxy, database);
+ mGateKeeperService = gateKeeperService;
+ }
+
+ @Override
+ IGateKeeperService getGateKeeperService() {
+ return mGateKeeperService;
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
index b6b9a86..2970c21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
@@ -34,8 +34,8 @@
import android.platform.test.annotations.Presubmit;
import android.testing.DexmakerShareClassLoaderRule;
import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.util.Preconditions;
@@ -74,6 +74,8 @@
@Mock
private WindowManagerService mWmMock;
+ @Mock
+ private Choreographer mChoreographer;
private WindowTracing mWindowTracing;
private File mFile;
@@ -85,7 +87,9 @@
mFile = testContext.getFileStreamPath("tracing_test.dat");
mFile.delete();
- mWindowTracing = new WindowTracing(mFile);
+ mWindowTracing = new WindowTracing(mFile, mWmMock, mChoreographer,
+ new WindowManagerGlobalLock());
+ mWindowTracing.setContinuousMode(false /* continuous */, null /* pw */);
}
@After
@@ -113,15 +117,14 @@
@Test
public void trace_discared_whenNotTracing() {
- mWindowTracing.traceStateLocked("where", mWmMock);
+ mWindowTracing.logState("where");
verifyZeroInteractions(mWmMock);
}
@Test
public void trace_dumpsWindowManagerState_whenTracing() throws Exception {
mWindowTracing.startTrace(mock(PrintWriter.class));
- mWindowTracing.traceStateLocked("where", mWmMock);
-
+ mWindowTracing.logState("where");
verify(mWmMock).writeToProtoLocked(any(), eq(WindowTraceLogLevel.TRIM));
}
@@ -147,7 +150,7 @@
WindowManagerTraceProto.WHERE, "TEST_WM_PROTO");
return null;
}).when(mWmMock).writeToProtoLocked(any(), any());
- mWindowTracing.traceStateLocked("TEST_WHERE", mWmMock);
+ mWindowTracing.logState("TEST_WHERE");
mWindowTracing.stopTrace(mock(PrintWriter.class));
diff --git a/wifi/java/android/net/wifi/aware/TlvBufferUtils.java b/wifi/java/android/net/wifi/aware/TlvBufferUtils.java
index 29f10e9..b3b5b29 100644
--- a/wifi/java/android/net/wifi/aware/TlvBufferUtils.java
+++ b/wifi/java/android/net/wifi/aware/TlvBufferUtils.java
@@ -61,6 +61,7 @@
public static class TlvConstructor {
private int mTypeSize;
private int mLengthSize;
+ private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
private byte[] mArray;
private int mArrayLength;
@@ -84,6 +85,20 @@
}
mTypeSize = typeSize;
mLengthSize = lengthSize;
+ mPosition = 0;
+ }
+
+ /**
+ * Configure the TLV constructor to use a particular byte order. Should be
+ * {@link ByteOrder#BIG_ENDIAN} (the default at construction) or
+ * {@link ByteOrder#LITTLE_ENDIAN}.
+ *
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor setByteOrder(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ return this;
}
/**
@@ -96,6 +111,7 @@
public TlvConstructor wrap(@Nullable byte[] array) {
mArray = array;
mArrayLength = (array == null) ? 0 : array.length;
+ mPosition = 0;
return this;
}
@@ -109,6 +125,7 @@
public TlvConstructor allocate(int capacity) {
mArray = new byte[capacity];
mArrayLength = capacity;
+ mPosition = 0;
return this;
}
@@ -155,6 +172,18 @@
}
/**
+ * Copies a raw byte into the TLV buffer - without a type or a length.
+ *
+ * @param b The byte to be inserted into the structure.
+ * @return The constructor to facilitate chaining {@code cts.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putRawByte(byte b) {
+ checkRawLength(1);
+ mArray[mPosition++] = b;
+ return this;
+ }
+
+ /**
* Copies a byte array into the TLV with the indicated type. For an LV
* formatted structure (i.e. typeLength=0 in {@link TlvConstructor
* TlvConstructor(int, int)} ) the type field is ignored.
@@ -193,6 +222,22 @@
}
/**
+ * Copies a byte array into the TLV - without a type or a length.
+ *
+ * @param array The array to be copied (in full) into the TLV structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putRawByteArray(@Nullable byte[] array) {
+ if (array == null) return this;
+
+ checkRawLength(array.length);
+ System.arraycopy(array, 0, mArray, mPosition, array.length);
+ mPosition += array.length;
+ return this;
+ }
+
+ /**
* Places a zero length element (i.e. Length field = 0) into the TLV.
* For an LV formatted structure (i.e. typeLength=0 in
* {@link TlvConstructor TlvConstructor(int, int)} ) the type field is
@@ -221,7 +266,7 @@
public TlvConstructor putShort(int type, short data) {
checkLength(2);
addHeader(type, 2);
- Memory.pokeShort(mArray, mPosition, data, ByteOrder.BIG_ENDIAN);
+ Memory.pokeShort(mArray, mPosition, data, mByteOrder);
mPosition += 2;
return this;
}
@@ -239,7 +284,7 @@
public TlvConstructor putInt(int type, int data) {
checkLength(4);
addHeader(type, 4);
- Memory.pokeInt(mArray, mPosition, data, ByteOrder.BIG_ENDIAN);
+ Memory.pokeInt(mArray, mPosition, data, mByteOrder);
mPosition += 4;
return this;
}
@@ -294,18 +339,24 @@
}
}
+ private void checkRawLength(int dataLength) {
+ if (mPosition + dataLength > mArrayLength) {
+ throw new BufferOverflowException();
+ }
+ }
+
private void addHeader(int type, int length) {
if (mTypeSize == 1) {
mArray[mPosition] = (byte) type;
} else if (mTypeSize == 2) {
- Memory.pokeShort(mArray, mPosition, (short) type, ByteOrder.BIG_ENDIAN);
+ Memory.pokeShort(mArray, mPosition, (short) type, mByteOrder);
}
mPosition += mTypeSize;
if (mLengthSize == 1) {
mArray[mPosition] = (byte) length;
} else if (mLengthSize == 2) {
- Memory.pokeShort(mArray, mPosition, (short) length, ByteOrder.BIG_ENDIAN);
+ Memory.pokeShort(mArray, mPosition, (short) length, mByteOrder);
}
mPosition += mLengthSize;
}
@@ -330,13 +381,19 @@
public int length;
/**
+ * Control of the endianess of the TLV element - true for big-endian, false for little-
+ * endian.
+ */
+ public ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
+
+ /**
* The Value (V) field - a raw byte array representing the current TLV
* element where the entry starts at {@link TlvElement#offset}.
*/
- public byte[] refArray;
+ private byte[] mRefArray;
/**
- * The offset to be used into {@link TlvElement#refArray} to access the
+ * The offset to be used into {@link TlvElement#mRefArray} to access the
* raw data representing the current TLV element.
*/
public int offset;
@@ -344,7 +401,7 @@
private TlvElement(int type, int length, @Nullable byte[] refArray, int offset) {
this.type = type;
this.length = length;
- this.refArray = refArray;
+ mRefArray = refArray;
this.offset = offset;
if (offset + length > refArray.length) {
@@ -353,6 +410,15 @@
}
/**
+ * Return the raw byte array of the Value (V) field.
+ *
+ * @return The Value (V) field as a byte array.
+ */
+ public byte[] getRawData() {
+ return Arrays.copyOfRange(mRefArray, offset, offset + length);
+ }
+
+ /**
* Utility function to return a byte representation of a TLV element of
* length 1. Note: an attempt to call this function on a TLV item whose
* {@link TlvElement#length} is != 1 will result in an exception.
@@ -364,7 +430,7 @@
throw new IllegalArgumentException(
"Accesing a byte from a TLV element of length " + length);
}
- return refArray[offset];
+ return mRefArray[offset];
}
/**
@@ -379,7 +445,7 @@
throw new IllegalArgumentException(
"Accesing a short from a TLV element of length " + length);
}
- return Memory.peekShort(refArray, offset, ByteOrder.BIG_ENDIAN);
+ return Memory.peekShort(mRefArray, offset, byteOrder);
}
/**
@@ -394,7 +460,7 @@
throw new IllegalArgumentException(
"Accesing an int from a TLV element of length " + length);
}
- return Memory.peekInt(refArray, offset, ByteOrder.BIG_ENDIAN);
+ return Memory.peekInt(mRefArray, offset, byteOrder);
}
/**
@@ -403,7 +469,7 @@
* @return String repersentation of the current TLV element.
*/
public String getString() {
- return new String(refArray, offset, length);
+ return new String(mRefArray, offset, length);
}
}
@@ -413,6 +479,7 @@
public static class TlvIterable implements Iterable<TlvElement> {
private int mTypeSize;
private int mLengthSize;
+ private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
private byte[] mArray;
private int mArrayLength;
@@ -440,6 +507,13 @@
}
/**
+ * Configure the TLV iterator to use little-endian byte ordering.
+ */
+ public void setByteOrder(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ }
+
+ /**
* Prints out a parsed representation of the TLV-formatted byte array.
* Whenever possible bytes, shorts, and integer are printed out (for
* fields whose length is 1, 2, or 4 respectively).
@@ -486,7 +560,7 @@
public List<byte[]> toList() {
List<byte[]> list = new ArrayList<>();
for (TlvElement tlv : this) {
- list.add(Arrays.copyOfRange(tlv.refArray, tlv.offset, tlv.offset + tlv.length));
+ list.add(Arrays.copyOfRange(tlv.mRefArray, tlv.offset, tlv.offset + tlv.length));
}
return list;
@@ -516,7 +590,7 @@
if (mTypeSize == 1) {
type = mArray[mOffset];
} else if (mTypeSize == 2) {
- type = Memory.peekShort(mArray, mOffset, ByteOrder.BIG_ENDIAN);
+ type = Memory.peekShort(mArray, mOffset, mByteOrder);
}
mOffset += mTypeSize;
@@ -524,11 +598,12 @@
if (mLengthSize == 1) {
length = mArray[mOffset];
} else if (mLengthSize == 2) {
- length = Memory.peekShort(mArray, mOffset, ByteOrder.BIG_ENDIAN);
+ length = Memory.peekShort(mArray, mOffset, mByteOrder);
}
mOffset += mLengthSize;
TlvElement tlv = new TlvElement(type, length, mArray, mOffset);
+ tlv.byteOrder = mByteOrder;
mOffset += length;
return tlv;
}
@@ -543,7 +618,8 @@
/**
* Validates that a (T)LV array is constructed correctly. I.e. that its specified Length
- * fields correctly fill the specified length (and do not overshoot).
+ * fields correctly fill the specified length (and do not overshoot). Uses big-endian
+ * byte ordering.
*
* @param array The (T)LV array to verify.
* @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2.
@@ -551,6 +627,22 @@
* @return A boolean indicating whether the array is valid (true) or invalid (false).
*/
public static boolean isValid(@Nullable byte[] array, int typeSize, int lengthSize) {
+ return isValidEndian(array, typeSize, lengthSize, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Validates that a (T)LV array is constructed correctly. I.e. that its specified Length
+ * fields correctly fill the specified length (and do not overshoot).
+ *
+ * @param array The (T)LV array to verify.
+ * @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2.
+ * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2.
+ * @param byteOrder The endianness of the byte array: {@link ByteOrder#BIG_ENDIAN} or
+ * {@link ByteOrder#LITTLE_ENDIAN}.
+ * @return A boolean indicating whether the array is valid (true) or invalid (false).
+ */
+ public static boolean isValidEndian(@Nullable byte[] array, int typeSize, int lengthSize,
+ ByteOrder byteOrder) {
if (typeSize < 0 || typeSize > 2) {
throw new IllegalArgumentException(
"Invalid arguments - typeSize must be 0, 1, or 2: typeSize=" + typeSize);
@@ -569,8 +661,7 @@
if (lengthSize == 1) {
nextTlvIndex += lengthSize + array[nextTlvIndex];
} else {
- nextTlvIndex += lengthSize + Memory.peekShort(array, nextTlvIndex,
- ByteOrder.BIG_ENDIAN);
+ nextTlvIndex += lengthSize + Memory.peekShort(array, nextTlvIndex, byteOrder);
}
}
diff --git a/wifi/tests/src/android/net/wifi/aware/TlvBufferUtilsTest.java b/wifi/tests/src/android/net/wifi/aware/TlvBufferUtilsTest.java
index 83affed..971aa8e 100644
--- a/wifi/tests/src/android/net/wifi/aware/TlvBufferUtilsTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/TlvBufferUtilsTest.java
@@ -28,6 +28,7 @@
import java.util.ArrayList;
import java.util.List;
+
/**
* Unit test harness for TlvBufferUtils class.
*/
@@ -69,6 +70,24 @@
}
/**
+ * Validate that re-using a TLV by any of the reallocation method resets it completely.
+ */
+ @Test
+ public void testTlvReuse() {
+ TlvBufferUtils.TlvConstructor tlv = new TlvBufferUtils.TlvConstructor(1, 1);
+
+ tlv.allocate(10);
+ tlv.putByte(0, (byte) 2);
+ tlv.putByte(1, (byte) 104);
+
+ collector.checkThat("initial", tlv.getArray(), equalTo(new byte[]{0, 1, 2, 1, 1, 104}));
+
+ tlv.allocate(8);
+ tlv.putByte(5, (byte) 7);
+ collector.checkThat("re-alloc", tlv.getArray(), equalTo(new byte[]{5, 1, 7}));
+ }
+
+ /**
* Verify that can build a valid TLV from a List of byte[].
*/
@Test
@@ -121,6 +140,23 @@
List<byte[]> data = new TlvBufferUtils.TlvIterable(0, 1, invalidTlv01).toList();
}
+ /**
+ * Validate the API which places raw bytes into the TLV (without a TL structure).
+ */
+ @Test
+ public void testRawPuts() {
+ TlvBufferUtils.TlvConstructor tlv = new TlvBufferUtils.TlvConstructor(1, 1);
+
+ tlv.allocate(10);
+ tlv.putByte(0, (byte) 2);
+ tlv.putRawByte((byte) 55);
+ tlv.putByte(1, (byte) 104);
+ tlv.putRawByteArray(new byte[]{66, 77});
+
+ collector.checkThat("data", tlv.getArray(),
+ equalTo(new byte[]{0, 1, 2, 55, 1, 1, 104, 66, 77}));
+ }
+
@Test
public void testTlvIterate() {
final String ascii = "ABC";
@@ -163,6 +199,7 @@
tlv02.putByte(0, (byte) 2);
tlv02.putString(0, ascii);
tlv02.putString(0, nonAscii);
+ tlv02.putByteArray(0, new byte[]{5, 4, 3, 2, 1});
TlvBufferUtils.TlvIterable tlv02It = new TlvBufferUtils.TlvIterable(0, 2, tlv02.getArray());
count = 0;
@@ -181,6 +218,11 @@
equalTo(nonAscii.getBytes().length));
collector.checkThat("tlv02-correct-iteration-DATA",
tlv.getString().equals(nonAscii), equalTo(true));
+ } else if (count == 3) {
+ collector.checkThat("tlv02-correct-iteration-mLength", tlv.length,
+ equalTo(5));
+ collector.checkThat("tlv02-correct-iteration-DATA", tlv.getRawData(),
+ equalTo(new byte[]{5, 4, 3, 2, 1}));
} else {
collector.checkThat("Invalid number of iterations in loop - tlv02", true,
equalTo(false));
@@ -188,7 +230,7 @@
++count;
}
collector.checkThat("Invalid number of iterations outside loop - tlv02", count,
- equalTo(3));
+ equalTo(4));
collector.checkThat("tlv22-valid",
TlvBufferUtils.isValid(tlv22.getArray(), 2, 2),