Merge "Revert "Revert "Exposing content insets and minimized home bounds for recents transition"""
diff --git a/Android.mk b/Android.mk
index a78a01a..7e2f472 100644
--- a/Android.mk
+++ b/Android.mk
@@ -827,35 +827,35 @@
# ==== hiddenapi lists =======================================
-# Copy blacklist and light greylist over into the build folder.
+# Copy light greylist and dark greylist over into the build folder.
# This is for ART buildbots which need to mock these lists and have alternative
# rules for building them. Other rules in the build system should depend on the
# files in the build folder.
-$(eval $(call copy-one-file,frameworks/base/config/hiddenapi-blacklist.txt,\
- $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)))
$(eval $(call copy-one-file,frameworks/base/config/hiddenapi-light-greylist.txt,\
$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)))
+$(eval $(call copy-one-file,frameworks/base/config/hiddenapi-dark-greylist.txt,\
+ $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)))
-# Generate dark greylist as private API minus (blacklist plus light greylist).
+# Generate blacklist as private API minus (light greylist plus dark greylist).
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): PRIVATE_API := $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE)
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): BLACKLIST := $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): LIGHT_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)
-$(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST): $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \
- $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) \
- $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)
- if [ ! -z "`comm -12 <(sort $(BLACKLIST)) <(sort $(LIGHT_GREYLIST))`" ]; then \
- echo "There should be no overlap between $(BLACKLIST) and $(LIGHT_GREYLIST)" 1>&2; \
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): PRIVATE_API := $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE)
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): LIGHT_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): DARK_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST): $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \
+ $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \
+ $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+ if [ ! -z "`comm -12 <(sort $(LIGHT_GREYLIST)) <(sort $(DARK_GREYLIST))`" ]; then \
+ echo "There should be no overlap between $(LIGHT_GREYLIST) and $(DARK_GREYLIST)" 1>&2; \
exit 1; \
- elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST))`" ]; then \
- echo "$(BLACKLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
- exit 2; \
elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(LIGHT_GREYLIST))`" ]; then \
echo "$(LIGHT_GREYLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
+ exit 2; \
+ elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(DARK_GREYLIST))`" ]; then \
+ echo "$(DARK_GREYLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
exit 3; \
fi
- comm -23 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST) $(LIGHT_GREYLIST)) > $@
+ comm -23 <(sort $(PRIVATE_API)) <(sort $(LIGHT_GREYLIST) $(DARK_GREYLIST)) > $@
# Include subdirectory makefiles
# ============================================================
diff --git a/api/current.txt b/api/current.txt
index b485715..449c761 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -40507,6 +40507,7 @@
public final class Call {
method public void answer(int);
method public void conference(android.telecom.Call);
+ method public void deflect(android.net.Uri);
method public void disconnect();
method public java.util.List<java.lang.String> getCannedTextResponses();
method public java.util.List<android.telecom.Call> getChildren();
@@ -40617,6 +40618,7 @@
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
+ field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
@@ -40764,6 +40766,7 @@
method public void onAnswer();
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onCallEvent(java.lang.String, android.os.Bundle);
+ method public void onDeflect(android.net.Uri);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
method public void onHandoverComplete();
@@ -40832,6 +40835,7 @@
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
+ field public static final int CAPABILITY_SUPPORT_DEFLECT = 33554432; // 0x2000000
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
field public static final java.lang.String EVENT_CALL_MERGE_FAILED = "android.telecom.event.CALL_MERGE_FAILED";
diff --git a/api/system-current.txt b/api/system-current.txt
index 9922df6..7211500 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5965,6 +5965,7 @@
ctor public ImsCallSessionImplBase();
method public void accept(int, android.telephony.ims.ImsStreamMediaProfile);
method public void close();
+ method public void deflect(java.lang.String);
method public void extendToConference(java.lang.String[]);
method public java.lang.String getCallId();
method public android.telephony.ims.ImsCallProfile getCallProfile();
diff --git a/api/test-current.txt b/api/test-current.txt
index 4cfe401..b02da04 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1038,6 +1038,7 @@
public class AccessibilityNodeInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
+ method public void writeToParcelNoRecycle(android.os.Parcel, int);
}
public final class AccessibilityWindowInfo implements android.os.Parcelable {
diff --git a/config/hiddenapi-blacklist.txt b/config/hiddenapi-blacklist.txt
deleted file mode 100644
index e69de29..0000000
--- a/config/hiddenapi-blacklist.txt
+++ /dev/null
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 4626cb2..5ee7ede 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -348,9 +348,4 @@
* Returns is the caller has the same uid as the Recents component
*/
public abstract boolean isCallerRecents(int callingUid);
-
- /**
- * Whether an UID is active or idle.
- */
- public abstract boolean isUidActive(int uid);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f4836b7..c805658 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4616,10 +4616,15 @@
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
for (int i=0; i<N; i++) {
Action action = mActions.get(i);
- validRemoteInput |= hasValidRemoteInput(action);
+ boolean actionHasValidInput = hasValidRemoteInput(action);
+ validRemoteInput |= actionHasValidInput;
final RemoteViews button = generateActionButton(action, emphazisedMode,
i % 2 != 0, p.ambient);
+ if (actionHasValidInput) {
+ // Clear the drawable
+ button.setInt(R.id.action0, "setBackgroundResource", 0);
+ }
big.addView(R.id.actions, button);
}
} else {
diff --git a/core/java/android/content/pm/VerifierDeviceIdentity.java b/core/java/android/content/pm/VerifierDeviceIdentity.java
index a8cdb6a..90be6f31 100644
--- a/core/java/android/content/pm/VerifierDeviceIdentity.java
+++ b/core/java/android/content/pm/VerifierDeviceIdentity.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Random;
@@ -86,6 +88,7 @@
* @return verifier device identity based on the input from the provided
* random number generator
*/
+ @VisibleForTesting
static VerifierDeviceIdentity generate(Random rng) {
long identity = rng.nextLong();
return new VerifierDeviceIdentity(identity);
diff --git a/core/java/android/transition/ArcMotion.java b/core/java/android/transition/ArcMotion.java
index da14834..172c837 100644
--- a/core/java/android/transition/ArcMotion.java
+++ b/core/java/android/transition/ArcMotion.java
@@ -216,7 +216,13 @@
boolean isMovingUpwards = startY > endY;
- if ((Math.abs(deltaX) < Math.abs(deltaY))) {
+ if (deltaY == 0) {
+ ex = dx;
+ ey = dy + (Math.abs(deltaX) * 0.5f * mMinimumHorizontalTangent);
+ } else if (deltaX == 0) {
+ ex = dx + (Math.abs(deltaY) * 0.5f * mMinimumVerticalTangent);
+ ey = dy;
+ } else if ((Math.abs(deltaX) < Math.abs(deltaY))) {
// Similar triangles bfa and bde mean that (ab/fb = eb/bd)
// Therefore, eb = ab * bd / fb
// ab = hypotenuse
@@ -254,7 +260,7 @@
float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent;
float newArcDistance2 = 0;
- if (arcDist2 < minimumArcDist2) {
+ if (arcDist2 != 0 && arcDist2 < minimumArcDist2) {
newArcDistance2 = minimumArcDist2;
} else if (arcDist2 > maximumArcDist2) {
newArcDistance2 = maximumArcDist2;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 410cdc6..1ead0b4 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -37,13 +37,13 @@
private static final Map<String, String> DEFAULT_FLAGS;
static {
DEFAULT_FLAGS = new HashMap<>();
- DEFAULT_FLAGS.put("device_info_v2", "true");
DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
DEFAULT_FLAGS.put("settings_battery_v2", "true");
DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
DEFAULT_FLAGS.put("settings_zone_picker_v2", "true");
- DEFAULT_FLAGS.put("settings_about_phone_v2", "false");
+ DEFAULT_FLAGS.put("settings_about_phone_v2", "true");
DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false");
+ DEFAULT_FLAGS.put("settings_data_usage_v2", "false");
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c460a33..0910c11 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -907,6 +907,13 @@
*/
private static boolean sThrowOnInvalidFloatProperties;
+ /**
+ * Prior to P, {@code #startDragAndDrop} accepts a builder which produces an empty drag shadow.
+ * Currently zero size SurfaceControl cannot be created thus we create a dummy 1x1 surface
+ * instead.
+ */
+ private static boolean sAcceptZeroSizeDragShadow;
+
/** @hide */
@IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
@Retention(RetentionPolicy.SOURCE)
@@ -4841,6 +4848,8 @@
sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P;
+ sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;
+
sCompatibilityDone = true;
}
}
@@ -23628,8 +23637,7 @@
* constructor variant is only useful when the {@link #onProvideShadowMetrics(Point, Point)}
* and {@link #onDrawShadow(Canvas)} methods are also overridden in order
* to supply the drag shadow's dimensions and appearance without
- * reference to any View object. If they are not overridden, then the result is an
- * invisible drag shadow.
+ * reference to any View object.
*/
public DragShadowBuilder() {
mView = new WeakReference<View>(null);
@@ -23783,6 +23791,9 @@
// Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder
// does not accept zero size surface.
if (shadowSize.x == 0 || shadowSize.y == 0) {
+ if (!sAcceptZeroSizeDragShadow) {
+ throw new IllegalStateException("Drag shadow dimensions must be positive");
+ }
shadowSize.x = 1;
shadowSize.y = 1;
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 23e7d61..417a725 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3173,6 +3173,15 @@
*/
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ writeToParcelNoRecycle(parcel, flags);
+ // Since instances of this class are fetched via synchronous i.e. blocking
+ // calls in IPCs we always recycle as soon as the instance is marshaled.
+ recycle();
+ }
+
+ /** @hide */
+ @TestApi
+ public void writeToParcelNoRecycle(Parcel parcel, int flags) {
// Write bit set of indices of fields with values differing from default
long nonDefaultFields = 0;
int fieldIndex = 0; // index of the current field
@@ -3406,10 +3415,6 @@
+ " vs " + fieldIndex);
}
}
-
- // Since instances of this class are fetched via synchronous i.e. blocking
- // calls in IPCs we always recycle as soon as the instance is marshaled.
- recycle();
}
/**
@@ -3557,7 +3562,7 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mContentDescription = parcel.readCharSequence();
}
- if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ecab15f..0fd28e5 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -678,7 +678,8 @@
StopwatchTimer mCameraOnTimer;
int mGpsSignalQualityBin = -1;
- final StopwatchTimer[] mGpsSignalQualityTimer =
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected final StopwatchTimer[] mGpsSignalQualityTimer =
new StopwatchTimer[GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS];
int mPhoneSignalStrengthBin = -1;
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 660c744..7b239f0 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -20,6 +20,7 @@
import android.os.Looper;
import android.util.SparseIntArray;
+import com.android.internal.location.gnssmetrics.GnssMetrics;
import java.util.ArrayList;
import java.util.concurrent.Future;
@@ -40,6 +41,11 @@
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
setExternalStatsSyncLocked(new DummyExternalStatsSync());
+ for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+ mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000-i, null,
+ mOnBatteryTimeBase);
+ }
+
// A no-op handler.
mHandler = new Handler(Looper.getMainLooper()) {};
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d3c6edd..d194796 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1271,6 +1271,9 @@
final String allowedProviders = Settings.Secure.getStringForUser(
mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
userHandle.getIdentifier());
+ if (allowedProviders == null) {
+ return false;
+ }
final List<String> providerList = Arrays.asList(allowedProviders.split(","));
for(String provider : getAllProviders()) {
if (provider.equals(PASSIVE_PROVIDER)) {
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index d0963cb..3847530 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1416,6 +1416,7 @@
/*
* Call BEFORE adding a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testEnableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback();
@@ -1425,6 +1426,7 @@
/*
* Call AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testDisableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_disableDeviceCallback();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 8e822a5..2d5fad5 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2821,6 +2821,7 @@
/*
* Call BEFORE adding a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testEnableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback();
@@ -2830,6 +2831,7 @@
/*
* Call AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void testDisableNativeRoutingCallbacksLocked() {
if (mRoutingChangeListeners.size() == 0) {
native_disableDeviceCallback();
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index e9ffe60..bd6c7e6 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -30,7 +30,7 @@
import android.media.session.MediaSessionManager;
import android.media.update.ApiLoader;
import android.media.update.MediaController2Provider;
-import android.media.update.PlaybackInfoProvider;
+import android.media.update.MediaController2Provider.PlaybackInfoProvider;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1bc3dfa..fe5e822 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1484,6 +1484,7 @@
/*
* Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void enableNativeRoutingCallbacksLocked(boolean enabled) {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index e3d5ac0..d4e9aac 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -1417,6 +1417,7 @@
/*
* Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void enableNativeRoutingCallbacksLocked(boolean enabled) {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 62240ce..823410f 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -1353,6 +1353,7 @@
/*
* Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
*/
+ @GuardedBy("mRoutingChangeListeners")
private void enableNativeRoutingCallbacksLocked(boolean enabled) {
if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback(enabled);
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 71bc64a..c492d307 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -18,6 +18,7 @@
import android.annotation.SystemApi;
import android.app.PendingIntent;
+import android.media.AudioAttributes;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
import android.media.MediaSession2.Command;
@@ -65,4 +66,12 @@
PlaylistParams getPlaylistParams_impl();
void setPlaylistParams_impl(PlaylistParams params);
PlaybackState2 getPlaybackState_impl();
+
+ interface PlaybackInfoProvider {
+ int getPlaybackType_impl();
+ AudioAttributes getAudioAttributes_impl();
+ int getControlType_impl();
+ int getMaxVolume_impl();
+ int getCurrentVolume_impl();
+ }
}
diff --git a/media/java/android/media/update/PlaybackInfoProvider.java b/media/java/android/media/update/PlaybackInfoProvider.java
deleted file mode 100644
index 36eb58a..0000000
--- a/media/java/android/media/update/PlaybackInfoProvider.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.update;
-
-import android.media.AudioAttributes;
-
-/**
- * @hide
- */
-// TODO(jaewan): @SystemApi
-public interface PlaybackInfoProvider {
- int getPlaybackType_impl();
- AudioAttributes getAudioAttributes_impl();
- int getControlType_impl();
- int getMaxVolume_impl();
- int getCurrentVolume_impl();
-}
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2bcf4ef..1e48213 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -56,7 +56,6 @@
SystemUI-proto
LOCAL_JAVA_LIBRARIES := telephony-common
-LOCAL_JAVA_LIBRARIES += android.car
LOCAL_PACKAGE_NAME := SystemUI
LOCAL_CERTIFICATE := platform
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9613a6a..cbb3e8f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -176,9 +176,6 @@
<!-- It's like, reality, but, you know, virtual -->
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
- <!-- To control car audio module volume -->
- <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
-
<!-- the ability to rename notifications posted by other apps -->
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
diff --git a/packages/SystemUI/shared/tests/Android.mk b/packages/SystemUI/shared/tests/Android.mk
index 239a4e3..1715983 100644
--- a/packages/SystemUI/shared/tests/Android.mk
+++ b/packages/SystemUI/shared/tests/Android.mk
@@ -41,7 +41,7 @@
testables \
truth-prebuilt \
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
# sign this with platform cert, so this test is allowed to inject key events into
# UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
index a44fd9a..b8f6784 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -14,8 +14,11 @@
package com.android.systemui.qs;
+import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Canvas;
import android.support.v4.widget.NestedScrollView;
+import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -23,6 +26,8 @@
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.qs.touch.OverScroll;
+import com.android.systemui.qs.touch.SwipeDetector;
/**
* Quick setting scroll view containing the brightness slider and the QS tiles.
@@ -35,6 +40,9 @@
private final int mTouchSlop;
private final int mFooterHeight;
private int mLastMotionY;
+ private final SwipeDetector mSwipeDetector;
+ private final OverScrollHelper mOverScrollHelper;
+ private float mContentTranslationY;
public QSScrollLayout(Context context, View... children) {
super(context);
@@ -49,15 +57,19 @@
linearLayout.addView(view);
}
addView(linearLayout);
+ setOverScrollMode(OVER_SCROLL_NEVER);
+ mOverScrollHelper = new OverScrollHelper();
+ mSwipeDetector = new SwipeDetector(context, mOverScrollHelper, SwipeDetector.VERTICAL);
+ mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
}
-
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (canScrollVertically(1) || canScrollVertically(-1)) {
return super.onInterceptTouchEvent(ev);
}
- return false;
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onInterceptTouchEvent(ev) || mOverScrollHelper.isInOverScroll();
}
@Override
@@ -65,7 +77,15 @@
if (canScrollVertically(1) || canScrollVertically(-1)) {
return super.onTouchEvent(ev);
}
- return false;
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ canvas.translate(0, mContentTranslationY);
+ super.dispatchDraw(canvas);
+ canvas.translate(0, -mContentTranslationY);
}
public boolean shouldIntercept(MotionEvent ev) {
@@ -98,4 +118,81 @@
parent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
+
+ private void setContentTranslationY(float contentTranslationY) {
+ mContentTranslationY = contentTranslationY;
+ invalidate();
+ }
+
+ private static final Property<QSScrollLayout, Float> CONTENT_TRANS_Y =
+ new Property<QSScrollLayout, Float>(Float.class, "qsScrollLayoutContentTransY") {
+ @Override
+ public Float get(QSScrollLayout qsScrollLayout) {
+ return qsScrollLayout.mContentTranslationY;
+ }
+
+ @Override
+ public void set(QSScrollLayout qsScrollLayout, Float y) {
+ qsScrollLayout.setContentTranslationY(y);
+ }
+ };
+
+ private class OverScrollHelper implements SwipeDetector.Listener {
+ private boolean mIsInOverScroll;
+
+ // We use this value to calculate the actual amount the user has overscrolled.
+ private float mFirstDisplacement = 0;
+
+ @Override
+ public void onDragStart(boolean start) {}
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ // Only overscroll if the user is scrolling down when they're already at the bottom
+ // or scrolling up when they're already at the top.
+ boolean wasInOverScroll = mIsInOverScroll;
+ mIsInOverScroll = (!canScrollVertically(1) && displacement < 0) ||
+ (!canScrollVertically(-1) && displacement > 0);
+
+ if (wasInOverScroll && !mIsInOverScroll) {
+ // Exit overscroll. This can happen when the user is in overscroll and then
+ // scrolls the opposite way. Note that this causes the reset translation animation
+ // to run while the user is dragging, which feels a bit unnatural.
+ reset();
+ } else if (mIsInOverScroll) {
+ if (Float.compare(mFirstDisplacement, 0) == 0) {
+ // Because users can scroll before entering overscroll, we need to
+ // subtract the amount where the user was not in overscroll.
+ mFirstDisplacement = displacement;
+ }
+ float overscrollY = displacement - mFirstDisplacement;
+ setContentTranslationY(getDampedOverScroll(overscrollY));
+ }
+
+ return mIsInOverScroll;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ reset();
+ }
+
+ private void reset() {
+ if (Float.compare(mContentTranslationY, 0) != 0) {
+ ObjectAnimator.ofFloat(QSScrollLayout.this, CONTENT_TRANS_Y, 0)
+ .setDuration(100)
+ .start();
+ }
+ mIsInOverScroll = false;
+ mFirstDisplacement = 0;
+ }
+
+ public boolean isInOverScroll() {
+ return mIsInOverScroll;
+ }
+
+ private float getDampedOverScroll(float y) {
+ return OverScroll.dampedScroll(y, getHeight());
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
new file mode 100644
index 0000000..0464886
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/OverScroll.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.qs.touch;
+
+/**
+ * Utility methods for overscroll damping and related effect.
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
+ */
+public class OverScroll {
+
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
+ /**
+ * This curve determines how the effect of scrolling over the limits of the page diminishes
+ * as the user pulls further and further from the bounds
+ *
+ * @param f The percentage of how much the user has overscrolled.
+ * @return A transformed percentage based on the influence curve.
+ */
+ private static float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ /**
+ * @param amount The original amount overscrolled.
+ * @param max The maximum amount that the View can overscroll.
+ * @return The dampened overscroll amount.
+ */
+ public static int dampedScroll(float amount, int max) {
+ if (Float.compare(amount, 0) == 0) return 0;
+
+ float f = amount / max;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
new file mode 100644
index 0000000..2522052
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/touch/SwipeDetector.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2018 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.qs.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/SwipeDetector.java
+ */
+public class SwipeDetector {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SwipeDetector";
+
+ private int mScrollConditions;
+ public static final int DIRECTION_POSITIVE = 1 << 0;
+ public static final int DIRECTION_NEGATIVE = 1 << 1;
+ public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+ private static final float ANIMATION_DURATION = 1200;
+
+ protected int mActivePointerId = INVALID_POINTER_ID;
+
+ /**
+ * The minimum release velocity in pixels per millisecond that triggers fling..
+ */
+ public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+
+ /**
+ * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
+ * Cutoff frequency is set at 10 Hz.
+ */
+ public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
+
+ /* Scroll state, this is set to true during dragging and animation. */
+ private ScrollState mState = ScrollState.IDLE;
+
+ enum ScrollState {
+ IDLE,
+ DRAGGING, // onDragStart, onDrag
+ SETTLING // onDragEnd
+ }
+
+ public static abstract class Direction {
+
+ abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint);
+
+ /**
+ * Distance in pixels a touch can wander before we think the user is scrolling.
+ */
+ abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+ }
+
+ public static final Direction VERTICAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getY(pointerIndex) - refPoint.y;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getX(pointerIndex) - downPos.x);
+ }
+ };
+
+ public static final Direction HORIZONTAL = new Direction() {
+
+ @Override
+ float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint) {
+ return ev.getX(pointerIndex) - refPoint.x;
+ }
+
+ @Override
+ float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
+ return Math.abs(ev.getY(pointerIndex) - downPos.y);
+ }
+ };
+
+ //------------------- ScrollState transition diagram -----------------------------------
+ //
+ // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+ // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+ // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+ // SETTLING -> (View settled) -> IDLE
+
+ private void setState(ScrollState newState) {
+ if (DBG) {
+ Log.d(TAG, "setState:" + mState + "->" + newState);
+ }
+ // onDragStart and onDragEnd is reported ONLY on state transition
+ if (newState == ScrollState.DRAGGING) {
+ initializeDragging();
+ if (mState == ScrollState.IDLE) {
+ reportDragStart(false /* recatch */);
+ } else if (mState == ScrollState.SETTLING) {
+ reportDragStart(true /* recatch */);
+ }
+ }
+ if (newState == ScrollState.SETTLING) {
+ reportDragEnd();
+ }
+
+ mState = newState;
+ }
+
+ public boolean isDraggingOrSettling() {
+ return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+ }
+
+ /**
+ * There's no touch and there's no animation.
+ */
+ public boolean isIdleState() {
+ return mState == ScrollState.IDLE;
+ }
+
+ public boolean isSettlingState() {
+ return mState == ScrollState.SETTLING;
+ }
+
+ public boolean isDraggingState() {
+ return mState == ScrollState.DRAGGING;
+ }
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ private final Direction mDir;
+
+ private final float mTouchSlop;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private long mCurrentMillis;
+
+ private float mVelocity;
+ private float mLastDisplacement;
+ private float mDisplacement;
+
+ private float mSubtractDisplacement;
+ private boolean mIgnoreSlopWhenSettling;
+
+ public interface Listener {
+ void onDragStart(boolean start);
+
+ boolean onDrag(float displacement, float velocity);
+
+ void onDragEnd(float velocity, boolean fling);
+ }
+
+ public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
+ this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir);
+ }
+
+ @VisibleForTesting
+ protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) {
+ mTouchSlop = touchSlope;
+ mListener = l;
+ mDir = dir;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollConditions = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
+ // reject cases where the angle or slop condition is not met.
+ if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
+ > Math.abs(mDisplacement)) {
+ return false;
+ }
+
+ // Check if the client is interested in scroll in current direction.
+ if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDisplacement > 0) ||
+ ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDisplacement < 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mLastDisplacement = 0;
+ mDisplacement = 0;
+ mVelocity = 0;
+
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ setState(ScrollState.DRAGGING);
+ }
+ break;
+ //case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos);
+ computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos),
+ ev.getEventTime());
+
+ // handle state and listener calls.
+ if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
+ setState(ScrollState.DRAGGING);
+ }
+ if (mState == ScrollState.DRAGGING) {
+ reportDragging();
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mState == ScrollState.DRAGGING) {
+ setState(ScrollState.SETTLING);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public void finishedScrolling() {
+ setState(ScrollState.IDLE);
+ }
+
+ private boolean reportDragStart(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ if (DBG) {
+ Log.d(TAG, "onDragStart recatch:" + recatch);
+ }
+ return true;
+ }
+
+ private void initializeDragging() {
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ mSubtractDisplacement = 0;
+ }
+ if (mDisplacement > 0) {
+ mSubtractDisplacement = mTouchSlop;
+ } else {
+ mSubtractDisplacement = -mTouchSlop;
+ }
+ }
+
+ private boolean reportDragging() {
+ if (mDisplacement != mLastDisplacement) {
+ if (DBG) {
+ Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+
+ mLastDisplacement = mDisplacement;
+ return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity);
+ }
+ return true;
+ }
+
+ private void reportDragEnd() {
+ if (DBG) {
+ Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+ mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+
+ }
+
+ /**
+ * Computes the damped velocity.
+ */
+ public float computeVelocity(float delta, long currentMillis) {
+ long previousMillis = mCurrentMillis;
+ mCurrentMillis = currentMillis;
+
+ float deltaTimeMillis = mCurrentMillis - previousMillis;
+ float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
+ if (Math.abs(mVelocity) < 0.001f) {
+ mVelocity = velocity;
+ } else {
+ float alpha = computeDampeningFactor(deltaTimeMillis);
+ mVelocity = interpolate(mVelocity, velocity, alpha);
+ }
+ return mVelocity;
+ }
+
+ /**
+ * Returns a time-dependent dampening factor using delta time.
+ */
+ private static float computeDampeningFactor(float deltaTime) {
+ return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
+ }
+
+ /**
+ * Returns the linear interpolation between two values
+ */
+ private static float interpolate(float from, float to, float alpha) {
+ return (1.0f - alpha) * from + alpha * to;
+ }
+
+ public static long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3ebeb4d..3dfb913 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -379,7 +379,7 @@
// Because space is usually constrained in the auto use-case, there should not be a
// pinned notification when the shade has been expanded. Ensure this by removing all heads-
// up notifications.
- mHeadsUpManager.removeAllHeadsUpEntries();
+ mHeadsUpManager.releaseAllImmediately();
super.animateExpandNotificationsPanel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
new file mode 100644
index 0000000..aba5cdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2018 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.phone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.util.ArraySet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Stack;
+
+/**
+ * A implementation of HeadsUpManager for phone and car.
+ */
+public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
+ ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
+ OnHeadsUpChangedListener {
+ private static final String TAG = "HeadsUpManagerPhone";
+ private static final boolean DEBUG = false;
+
+ private final View mStatusBarWindowView;
+ private final int mStatusBarHeight;
+ private final NotificationGroupManager mGroupManager;
+ private final StatusBar mBar;
+ private final VisualStabilityManager mVisualStabilityManager;
+
+ private boolean mReleaseOnExpandFinish;
+ private boolean mTrackingHeadsUp;
+ private HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+ = new ArraySet<>();
+ private boolean mIsExpanded;
+ private int[] mTmpTwoArray = new int[2];
+ private boolean mHeadsUpGoingAway;
+ private boolean mWaitingOnCollapseWhenGoingAway;
+ private boolean mIsObserving;
+ private int mStatusBarState;
+
+ private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
+ private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntryPhone acquire() {
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntryPhone();
+ }
+
+ @Override
+ public boolean release(@NonNull HeadsUpEntryPhone instance) {
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor:
+
+ public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView,
+ @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar,
+ @NonNull VisualStabilityManager visualStabilityManager) {
+ super(context);
+
+ mStatusBarWindowView = statusBarWindowView;
+ mGroupManager = groupManager;
+ mBar = bar;
+ mVisualStabilityManager = visualStabilityManager;
+
+ Resources resources = mContext.getResources();
+ mStatusBarHeight = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+
+ addListener(new OnHeadsUpChangedListener() {
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
+ if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged");
+ updateTouchableRegionListener();
+ }
+ });
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Public methods:
+
+ /**
+ * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
+ * that a user might have consciously clicked on it.
+ *
+ * @param key the key of the touched notification
+ * @return whether the touch is invalid and should be discarded
+ */
+ public boolean shouldSwallowClick(@NonNull String key) {
+ HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+ return entry != null && mClock.currentTimeMillis() < entry.postTime;
+ }
+
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+
+ /**
+ * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+ * from the list even after a Heads Up Notification is gone.
+ */
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ /**
+ * Notify that the status bar panel gets expanded or collapsed.
+ *
+ * @param isExpanded True to notify expanded, false to notify collapsed.
+ */
+ public void setIsPanelExpanded(boolean isExpanded) {
+ if (isExpanded != mIsExpanded) {
+ mIsExpanded = isExpanded;
+ if (isExpanded) {
+ // make sure our state is sane
+ mWaitingOnCollapseWhenGoingAway = false;
+ mHeadsUpGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ }
+
+ /**
+ * Set the current state of the statusbar.
+ */
+ public void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ }
+
+ /**
+ * Set that we are exiting the headsUp pinned mode, but some notifications might still be
+ * animating out. This is used to keep the touchable regions in a sane state.
+ */
+ public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+ if (headsUpGoingAway != mHeadsUpGoingAway) {
+ mHeadsUpGoingAway = headsUpGoingAway;
+ if (!headsUpGoingAway) {
+ waitForStatusBarLayout();
+ }
+ updateTouchableRegionListener();
+ }
+ }
+
+ /**
+ * Notifies that a remote input textbox in notification gets active or inactive.
+ * @param entry The entry of the target notification.
+ * @param remoteInputActive True to notify active, False to notify inactive.
+ */
+ public void setRemoteInputActive(
+ @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
+ if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
+ headsUpEntry.remoteInputActive = remoteInputActive;
+ if (remoteInputActive) {
+ headsUpEntry.removeAutoRemovalCallbacks();
+ } else {
+ headsUpEntry.updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void removeMinimumDisplayTimeForTesting() {
+ mMinimumDisplayTime = 0;
+ mHeadsUpNotificationDecay = 0;
+ mTouchAcceptanceDelay = 0;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager public methods overrides:
+
+ @Override
+ public boolean isTrackingHeadsUp() {
+ return mTrackingHeadsUp;
+ }
+
+ @Override
+ public void snooze() {
+ super.snooze();
+ mReleaseOnExpandFinish = true;
+ }
+
+ /**
+ * React to the removal of the notification in the heads up.
+ *
+ * @return true if the notification was removed and false if it still needs to be kept around
+ * for a bit since it wasn't shown long enough
+ */
+ @Override
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
+ return super.removeNotification(key, ignoreEarliestRemovalTime);
+ } else {
+ HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key);
+ entry.removeAsSoonAsPossible();
+ return false;
+ }
+ }
+
+ public void addSwipedOutNotification(@NonNull String key) {
+ mSwipedOutKeys.add(key);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Dumpable overrides:
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("HeadsUpManagerPhone state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ViewTreeObserver.OnComputeInternalInsetsListener overrides:
+
+ /**
+ * Overridden from TreeObserver.
+ */
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (mIsExpanded || mBar.isBouncerShowing()) {
+ // The touchable region is always the full area when expanded
+ return;
+ }
+ if (hasPinnedHeadsUp()) {
+ ExpandableNotificationRow topEntry = getTopEntry().row;
+ if (topEntry.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
+ if (groupSummary != null) {
+ topEntry = groupSummary;
+ }
+ }
+ topEntry.getLocationOnScreen(mTmpTwoArray);
+ int minX = mTmpTwoArray[0];
+ int maxX = mTmpTwoArray[0] + topEntry.getWidth();
+ int maxY = topEntry.getIntrinsicHeight();
+
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(minX, 0, maxX, maxY);
+ } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VisualStabilityManager.Callback overrides:
+
+ @Override
+ public void onReorderingAllowed() {
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
+ for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
+ }
+ mEntriesToRemoveWhenReorderingAllowed.clear();
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpManager utility (protected) methods overrides:
+
+ @Override
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return mEntryPool.acquire();
+ }
+
+ @Override
+ protected void releaseHeadsUpEntry(HeadsUpEntry entry) {
+ entry.reset();
+ mEntryPool.release((HeadsUpEntryPhone) entry);
+ }
+
+ @Override
+ protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+ return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
+ || super.shouldHeadsUpBecomePinned(entry);
+ }
+
+ @Override
+ protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpInternal(fd, pw, args);
+ pw.print(" mStatusBarState="); pw.println(mStatusBarState);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Private utility methods:
+
+ @Nullable
+ private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
+ return (HeadsUpEntryPhone) getHeadsUpEntry(key);
+ }
+
+ @Nullable
+ private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
+ return (HeadsUpEntryPhone) getTopHeadsUpEntry();
+ }
+
+ private boolean wasShownLongEnough(@NonNull String key) {
+ if (mSwipedOutKeys.contains(key)) {
+ // We always instantly dismiss views being manually swiped out.
+ mSwipedOutKeys.remove(key);
+ return true;
+ }
+
+ HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
+ HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
+ return headsUpEntry != topEntry || headsUpEntry.wasShownLongEnough();
+ }
+
+ /**
+ * We need to wait on the whole panel to collapse, before we can remove the touchable region
+ * listener.
+ */
+ private void waitForStatusBarLayout() {
+ mWaitingOnCollapseWhenGoingAway = true;
+ mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
+ mStatusBarWindowView.removeOnLayoutChangeListener(this);
+ mWaitingOnCollapseWhenGoingAway = false;
+ updateTouchableRegionListener();
+ }
+ }
+ });
+ }
+
+ private void updateTouchableRegionListener() {
+ boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
+ || mWaitingOnCollapseWhenGoingAway;
+ if (shouldObserve == mIsObserving) {
+ return;
+ }
+ if (shouldObserve) {
+ mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mStatusBarWindowView.requestLayout();
+ } else {
+ mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+ mIsObserving = shouldObserve;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // HeadsUpEntryPhone:
+
+ protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry {
+ public void setEntry(@NonNull final NotificationData.Entry entry) {
+ Runnable removeHeadsUpRunnable = () -> {
+ if (!mVisualStabilityManager.isReorderingAllowed()) {
+ mEntriesToRemoveWhenReorderingAllowed.add(entry);
+ mVisualStabilityManager.addReorderingAllowedCallback(
+ HeadsUpManagerPhone.this);
+ } else if (!mTrackingHeadsUp) {
+ removeHeadsUpEntry(entry);
+ } else {
+ mEntriesToRemoveAfterExpand.add(entry);
+ }
+ };
+
+ super.setEntry(entry, removeHeadsUpRunnable);
+ }
+
+ public boolean wasShownLongEnough() {
+ return earliestRemovaltime < mClock.currentTimeMillis();
+ }
+
+ @Override
+ public void updateEntry(boolean updatePostTime) {
+ super.updateEntry(updatePostTime);
+
+ if (mEntriesToRemoveAfterExpand.contains(entry)) {
+ mEntriesToRemoveAfterExpand.remove(entry);
+ }
+ if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
+ mEntriesToRemoveWhenReorderingAllowed.remove(entry);
+ }
+ }
+
+ @Override
+ public void expanded(boolean expanded) {
+ if (this.expanded == expanded) {
+ return;
+ }
+
+ this.expanded = expanded;
+ if (expanded) {
+ removeAutoRemovalCallbacks();
+ } else {
+ updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index c85571c..2bfdefe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,7 +23,7 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
/**
@@ -31,7 +31,7 @@
*/
public class HeadsUpTouchHelper implements Gefingerpoken {
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private NotificationStackScrollLayout mStackScroller;
private int mTrackingPointer;
private float mTouchSlop;
@@ -43,7 +43,7 @@
private NotificationPanelView mPanel;
private ExpandableNotificationRow mPickedChild;
- public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
+ public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
NotificationStackScrollLayout stackScroller,
NotificationPanelView notificationPanelView) {
mHeadsUpManager = headsUpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index cd2e77a..52d005c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -68,14 +68,12 @@
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.List;
-import java.util.Collection;
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener,
@@ -1571,7 +1569,7 @@
private void updatePanelExpanded() {
boolean isExpanded = !isFullyCollapsed();
if (mPanelExpanded != isExpanded) {
- mHeadsUpManager.setIsExpanded(isExpanded);
+ mHeadsUpManager.setIsPanelExpanded(isExpanded);
mStatusBar.setPanelExpanded(isExpanded);
mPanelExpanded = isExpanded;
}
@@ -2338,7 +2336,7 @@
}
@Override
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
super.setHeadsUpManager(headsUpManager);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
this);
@@ -2630,8 +2628,8 @@
}
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
- mKeyguardStatusView.setPulsing(pulsing != null);
+ public void setPulsing(boolean pulsing) {
+ mKeyguardStatusView.setPulsing(pulsing);
positionClockAndNotifications();
mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
+ mKeyguardStatusView.getClockBottom());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 2b7e474..6daabed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -50,7 +50,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -75,7 +75,7 @@
}
protected StatusBar mStatusBar;
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private float mPeekHeight;
private float mHintDistance;
@@ -1252,7 +1252,7 @@
*/
protected abstract int getClearAllHeight();
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1bf719a..3777a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -208,6 +208,7 @@
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -219,6 +220,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -809,15 +811,14 @@
.commit();
mIconController = Dependency.get(StatusBarIconController.class);
- mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
- mHeadsUpManager.setBar(this);
+ mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this,
+ mVisualStabilityManager);
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mVisualStabilityManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
- mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
putComponent(HeadsUpManager.class, mHeadsUpManager);
mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
@@ -1348,7 +1349,8 @@
@Override
public void onPerformRemoveNotification(StatusBarNotification n) {
- if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+ if (mStackScroller.hasPulsingNotifications() &&
+ !mHeadsUpManager.hasHeadsUpNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
mDozeScrimController.pulseOutNow();
@@ -2097,9 +2099,8 @@
}
public void maybeEscalateHeadsUp() {
- Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
- for (HeadsUpManager.HeadsUpEntry entry : entries) {
- final StatusBarNotification sbn = entry.entry.notification;
+ mHeadsUpManager.getAllEntries().forEach(entry -> {
+ final StatusBarNotification sbn = entry.notification;
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
if (DEBUG) {
@@ -2109,11 +2110,11 @@
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
notification.fullScreenIntent.send();
- entry.entry.notifyFullScreenIntentLaunched();
+ entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
}
}
- }
+ });
mHeadsUpManager.releaseAllImmediately();
}
@@ -4658,24 +4659,22 @@
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries =
- mHeadsUpManager.getAllEntries();
- if (!pulsingEntries.isEmpty()) {
+ if (mHeadsUpManager.hasHeadsUpNotifications()) {
// Only pulse the stack scroller if there's actually something to show.
// Otherwise just show the always-on screen.
- setPulsing(pulsingEntries);
+ setPulsing(true);
}
}
@Override
public void onPulseFinished() {
callback.onPulseFinished();
- setPulsing(null);
+ setPulsing(false);
}
- private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
+ private void setPulsing(boolean pulsing) {
mNotificationPanel.setPulsing(pulsing);
- mVisualStabilityManager.setPulsing(pulsing != null);
+ mVisualStabilityManager.setPulsing(pulsing);
mIgnoreTouchWhilePulsing = false;
}
}, reason);
@@ -4823,7 +4822,7 @@
// for heads up notifications
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private AboveShelfObserver mAboveShelfObserver;
@@ -4926,7 +4925,7 @@
// Release the HUN notification to the shade.
if (isPresenterFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
+ HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
}
//
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 53dfb24..040d7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,119 +16,69 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.os.SystemClock;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.support.v4.util.ArraySet;
import android.util.ArrayMap;
+import android.provider.Settings;
import android.util.Log;
-import android.util.Pools;
-import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Iterator;
+import java.util.stream.Stream;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Stack;
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
*/
-public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener,
- VisualStabilityManager.Callback {
+public class HeadsUpManager {
private static final String TAG = "HeadsUpManager";
private static final boolean DEBUG = false;
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
- private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
- private final int mHeadsUpNotificationDecay;
- private final int mMinimumDisplayTime;
+ protected final Clock mClock = new Clock();
+ protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ protected final Handler mHandler = new Handler(Looper.getMainLooper());
- private final int mTouchAcceptanceDelay;
+ protected final Context mContext;
+
+ protected int mHeadsUpNotificationDecay;
+ protected int mMinimumDisplayTime;
+ protected int mTouchAcceptanceDelay;
+ protected int mSnoozeLengthMs;
+ protected boolean mHasPinnedNotification;
+ protected int mUser;
+
+ private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
private final ArrayMap<String, Long> mSnoozedPackages;
- private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
- private final int mDefaultSnoozeLengthMs;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
- private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
-
- @Override
- public HeadsUpEntry acquire() {
- if (!mPoolObjects.isEmpty()) {
- return mPoolObjects.pop();
- }
- return new HeadsUpEntry();
- }
-
- @Override
- public boolean release(HeadsUpEntry instance) {
- instance.reset();
- mPoolObjects.push(instance);
- return true;
- }
- };
-
- private final View mStatusBarWindowView;
- private final int mStatusBarHeight;
- private final Context mContext;
- private final NotificationGroupManager mGroupManager;
- private StatusBar mBar;
- private int mSnoozeLengthMs;
- private ContentObserver mSettingsObserver;
- private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
- private HashSet<String> mSwipedOutKeys = new HashSet<>();
- private int mUser;
- private Clock mClock;
- private boolean mReleaseOnExpandFinish;
- private boolean mTrackingHeadsUp;
- private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
- private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
- = new ArraySet<>();
- private boolean mIsExpanded;
- private boolean mHasPinnedNotification;
- private int[] mTmpTwoArray = new int[2];
- private boolean mHeadsUpGoingAway;
- private boolean mWaitingOnCollapseWhenGoingAway;
- private boolean mIsObserving;
- private boolean mRemoteInputActive;
- private float mExpandedHeight;
- private VisualStabilityManager mVisualStabilityManager;
- private int mStatusBarState;
-
- public HeadsUpManager(final Context context, View statusBarWindowView,
- NotificationGroupManager groupManager) {
+ public HeadsUpManager(@NonNull final Context context) {
mContext = context;
- Resources resources = mContext.getResources();
- mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
- mSnoozedPackages = new ArrayMap<>();
- mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
- mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+ Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
- mClock = new Clock();
+ mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
+ mSnoozedPackages = new ArrayMap<>();
+ int defaultSnoozeLengthMs =
+ resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
- SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
- mSettingsObserver = new ContentObserver(mHandler) {
+ SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs);
+ ContentObserver settingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
final int packageSnoozeLengthMs = Settings.Global.getInt(
@@ -141,48 +91,27 @@
};
context.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
- mSettingsObserver);
- mStatusBarWindowView = statusBarWindowView;
- mGroupManager = groupManager;
- mStatusBarHeight = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
+ settingsObserver);
}
- private void updateTouchableRegionListener() {
- boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway
- || mWaitingOnCollapseWhenGoingAway;
- if (shouldObserve == mIsObserving) {
- return;
- }
- if (shouldObserve) {
- mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mStatusBarWindowView.requestLayout();
- } else {
- mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- }
- mIsObserving = shouldObserve;
- }
-
- public void setBar(StatusBar bar) {
- mBar = bar;
- }
-
- public void addListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Adds an OnHeadUpChangedListener to observe events.
+ */
+ public void addListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.add(listener);
}
- public void removeListener(OnHeadsUpChangedListener listener) {
+ /**
+ * Removes the OnHeadUpChangedListener from the observer list.
+ */
+ public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
mListeners.remove(listener);
}
- public StatusBar getBar() {
- return mBar;
- }
-
/**
* Called when posting a new notification to the heads up.
*/
- public void showNotification(NotificationData.Entry headsUp) {
+ public void showNotification(@NonNull NotificationData.Entry headsUp) {
if (DEBUG) Log.v(TAG, "showNotification");
addHeadsUpEntry(headsUp);
updateNotification(headsUp, true);
@@ -192,7 +121,7 @@
/**
* Called when updating or posting a notification to the heads up.
*/
- public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) {
if (DEBUG) Log.v(TAG, "updateNotification");
headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -204,14 +133,13 @@
// with the groupmanager
return;
}
- headsUpEntry.updateEntry();
+ headsUpEntry.updateEntry(true /* updatePostTime */);
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
}
}
- private void addHeadsUpEntry(NotificationData.Entry entry) {
- HeadsUpEntry headsUpEntry = mEntryPool.acquire();
-
+ private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) {
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
// This will also add the entry to the sortedList
headsUpEntry.setEntry(entry);
mHeadsUpEntries.put(entry.key, headsUpEntry);
@@ -223,16 +151,17 @@
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
- private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
- return mStatusBarState != StatusBarState.KEYGUARD
- && !mIsExpanded || hasFullScreenIntent(entry);
+ protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
+ return hasFullScreenIntent(entry);
}
- private boolean hasFullScreenIntent(NotificationData.Entry entry) {
+ protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
return entry.notification.getNotification().fullScreenIntent != null;
}
- private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) {
+ protected void setEntryPinned(
+ @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
+ if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned);
ExpandableNotificationRow row = headsUpEntry.entry.row;
if (row.isPinned() != isPinned) {
row.setPinned(isPinned);
@@ -247,33 +176,35 @@
}
}
- private void removeHeadsUpEntry(NotificationData.Entry entry) {
+ protected void removeHeadsUpEntry(@NonNull NotificationData.Entry entry) {
HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+ onHeadsUpEntryRemoved(remove);
+ }
+
+ protected void onHeadsUpEntryRemoved(@NonNull HeadsUpEntry remove) {
+ NotificationData.Entry entry = remove.entry;
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
entry.row.setHeadsUp(false);
setEntryPinned(remove, false /* isPinned */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
- mEntryPool.release(remove);
+ releaseHeadsUpEntry(remove);
}
- public void removeAllHeadsUpEntries() {
- for (String key : mHeadsUpEntries.keySet()) {
- removeHeadsUpEntry(mHeadsUpEntries.get(key).entry);
- }
- }
-
- private void updatePinnedMode() {
+ protected void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
}
+ if (DEBUG) {
+ Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " +
+ hasPinnedNotification);
+ }
mHasPinnedNotification = hasPinnedNotification;
if (mHasPinnedNotification) {
MetricsLogger.count(mContext, "note_peek", 1);
}
- updateTouchableRegionListener();
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
}
@@ -285,47 +216,36 @@
* @return true if the notification was removed and false if it still needs to be kept around
* for a bit since it wasn't shown long enough
*/
- public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) {
- if (DEBUG) Log.v(TAG, "remove");
- if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) {
- releaseImmediately(key);
- return true;
- } else {
- getHeadsUpEntry(key).removeAsSoonAsPossible();
- return false;
- }
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (DEBUG) Log.v(TAG, "removeNotification");
+ releaseImmediately(key);
+ return true;
}
- private boolean wasShownLongEnough(String key) {
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- HeadsUpEntry topEntry = getTopEntry();
- if (mSwipedOutKeys.contains(key)) {
- // We always instantly dismiss views being manually swiped out.
- mSwipedOutKeys.remove(key);
- return true;
- }
- if (headsUpEntry != topEntry) {
- return true;
- }
- return headsUpEntry.wasShownLongEnough();
- }
-
- public boolean isHeadsUp(String key) {
+ /**
+ * Returns if the given notification is in the Heads Up Notification list or not.
+ */
+ public boolean isHeadsUp(@NonNull String key) {
return mHeadsUpEntries.containsKey(key);
}
/**
- * Push any current Heads Up notification down into the shade.
+ * Pushes any current Heads Up notification down into the shade.
*/
public void releaseAllImmediately() {
if (DEBUG) Log.v(TAG, "releaseAllImmediately");
- ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet());
- for (String key : keys) {
- releaseImmediately(key);
+ Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator();
+ while (iterator.hasNext()) {
+ HeadsUpEntry entry = iterator.next();
+ iterator.remove();
+ onHeadsUpEntryRemoved(entry);
}
}
- public void releaseImmediately(String key) {
+ /**
+ * Pushes the given Heads Up notification down into the shade.
+ */
+ public void releaseImmediately(@NonNull String key) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
if (headsUpEntry == null) {
return;
@@ -334,11 +254,14 @@
removeHeadsUpEntry(shadeEntry);
}
- public boolean isSnoozed(String packageName) {
+ /**
+ * Returns if the given notification is snoozed or not.
+ */
+ public boolean isSnoozed(@NonNull String packageName) {
final String key = snoozeKey(packageName, mUser);
Long snoozedUntil = mSnoozedPackages.get(key);
if (snoozedUntil != null) {
- if (snoozedUntil > SystemClock.elapsedRealtime()) {
+ if (snoozedUntil > mClock.currentTimeMillis()) {
if (DEBUG) Log.v(TAG, key + " snoozed");
return true;
}
@@ -347,39 +270,71 @@
return false;
}
+ /**
+ * Snoozes all current Heads Up Notifications.
+ */
public void snooze() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
String packageName = entry.entry.notification.getPackageName();
mSnoozedPackages.put(snoozeKey(packageName, mUser),
- SystemClock.elapsedRealtime() + mSnoozeLengthMs);
+ mClock.currentTimeMillis() + mSnoozeLengthMs);
}
- mReleaseOnExpandFinish = true;
}
- private static String snoozeKey(String packageName, int user) {
+ @NonNull
+ private static String snoozeKey(@NonNull String packageName, int user) {
return user + "," + packageName;
}
- private HeadsUpEntry getHeadsUpEntry(String key) {
+ @Nullable
+ protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
return mHeadsUpEntries.get(key);
}
- public NotificationData.Entry getEntry(String key) {
- return mHeadsUpEntries.get(key).entry;
+ /**
+ * Returns the entry of given Heads Up Notification.
+ *
+ * @param key Key of heads up notification
+ */
+ @Nullable
+ public NotificationData.Entry getEntry(@NonNull String key) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ return entry != null ? entry.entry : null;
}
- public Collection<HeadsUpEntry> getAllEntries() {
- return mHeadsUpEntries.values();
+ /**
+ * Returns the stream of all current Heads Up Notifications.
+ */
+ @NonNull
+ public Stream<NotificationData.Entry> getAllEntries() {
+ return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
}
- public HeadsUpEntry getTopEntry() {
+ /**
+ * Returns the top Heads Up Notification, which appeares to show at first.
+ */
+ @Nullable
+ public NotificationData.Entry getTopEntry() {
+ HeadsUpEntry topEntry = getTopHeadsUpEntry();
+ return (topEntry != null) ? topEntry.entry : null;
+ }
+
+ /**
+ * Returns if any heads up notification is available or not.
+ */
+ public boolean hasHeadsUpNotifications() {
+ return !mHeadsUpEntries.isEmpty();
+ }
+
+ @Nullable
+ protected HeadsUpEntry getTopHeadsUpEntry() {
if (mHeadsUpEntries.isEmpty()) {
return null;
}
HeadsUpEntry topEntry = null;
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
- if (topEntry == null || entry.compareTo(topEntry) == -1) {
+ if (topEntry == null || entry.compareTo(topEntry) < 0) {
topEntry = entry;
}
}
@@ -387,56 +342,22 @@
}
/**
- * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
- * that a user might have consciously clicked on it.
- *
- * @param key the key of the touched notification
- * @return whether the touch is invalid and should be discarded
+ * Sets the current user.
*/
- public boolean shouldSwallowClick(String key) {
- HeadsUpEntry entry = mHeadsUpEntries.get(key);
- if (entry != null && mClock.currentTimeMillis() < entry.postTime) {
- return true;
- }
- return false;
- }
-
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (mIsExpanded || mBar.isBouncerShowing()) {
- // The touchable region is always the full area when expanded
- return;
- }
- if (mHasPinnedNotification) {
- ExpandableNotificationRow topEntry = getTopEntry().entry.row;
- if (topEntry.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification());
- if (groupSummary != null) {
- topEntry = groupSummary;
- }
- }
- topEntry.getLocationOnScreen(mTmpTwoArray);
- int minX = mTmpTwoArray[0];
- int maxX = mTmpTwoArray[0] + topEntry.getWidth();
- int maxY = topEntry.getIntrinsicHeight();
-
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(minX, 0, maxX, maxY);
- } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
- }
- }
-
public void setUser(int user) {
mUser = user;
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("HeadsUpManager state:");
+ dumpInternal(fd, pw, args);
+ }
+
+ protected void dumpInternal(
+ @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
- pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" now="); pw.println(mClock.currentTimeMillis());
pw.print(" mUser="); pw.println(mUser);
for (HeadsUpEntry entry: mHeadsUpEntries.values()) {
pw.print(" HeadsUpEntry="); pw.println(entry.entry);
@@ -449,6 +370,9 @@
}
}
+ /**
+ * Returns if there are any pinned Heads Up Notifications or not.
+ */
public boolean hasPinnedHeadsUp() {
return mHasPinnedNotification;
}
@@ -464,14 +388,8 @@
}
/**
- * Notifies that a notification was swiped out and will be removed.
- *
- * @param key the notification key
+ * Unpins all pinned Heads Up Notifications.
*/
- public void addSwipedOutNotification(String key) {
- mSwipedOutKeys.add(key);
- }
-
public void unpinAll() {
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
@@ -481,60 +399,13 @@
}
}
- public void onExpandingFinished() {
- if (mReleaseOnExpandFinish) {
- releaseAllImmediately();
- mReleaseOnExpandFinish = false;
- } else {
- for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- }
- mEntriesToRemoveAfterExpand.clear();
- }
-
- public void setTrackingHeadsUp(boolean trackingHeadsUp) {
- mTrackingHeadsUp = trackingHeadsUp;
- }
-
- public boolean isTrackingHeadsUp() {
- return mTrackingHeadsUp;
- }
-
- public void setIsExpanded(boolean isExpanded) {
- if (isExpanded != mIsExpanded) {
- mIsExpanded = isExpanded;
- if (isExpanded) {
- // make sure our state is sane
- mWaitingOnCollapseWhenGoingAway = false;
- mHeadsUpGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- }
-
/**
- * @return the height of the top heads up notification when pinned. This is different from the
- * intrinsic height, which also includes whether the notification is system expanded and
- * is mainly used when dragging down from a heads up notification.
+ * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+ * well.
*/
- public int getTopHeadsUpPinnedHeight() {
- HeadsUpEntry topEntry = getTopEntry();
- if (topEntry == null || topEntry.entry == null) {
- return 0;
- }
- ExpandableNotificationRow row = topEntry.entry.row;
- if (row.isChildInGroup()) {
- final ExpandableNotificationRow groupSummary
- = mGroupManager.getGroupSummary(row.getStatusBarNotification());
- if (groupSummary != null) {
- row = groupSummary;
- }
- }
- return row.getPinnedHeadsUpHeight();
+ public boolean isTrackingHeadsUp() {
+ // Might be implemented in subclass.
+ return false;
}
/**
@@ -543,7 +414,7 @@
* @return -1 if the first argument should be ranked higher than the second, 1 if the second
* one should be ranked higher and 0 if they are equal.
*/
- public int compare(NotificationData.Entry a, NotificationData.Entry b) {
+ public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
if (aEntry == null || bEntry == null) {
@@ -553,147 +424,62 @@
}
/**
- * Set that we are exiting the headsUp pinned mode, but some notifications might still be
- * animating out. This is used to keep the touchable regions in a sane state.
- */
- public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
- if (headsUpGoingAway != mHeadsUpGoingAway) {
- mHeadsUpGoingAway = headsUpGoingAway;
- if (!headsUpGoingAway) {
- waitForStatusBarLayout();
- }
- updateTouchableRegionListener();
- }
- }
-
- /**
- * We need to wait on the whole panel to collapse, before we can remove the touchable region
- * listener.
- */
- private void waitForStatusBarLayout() {
- mWaitingOnCollapseWhenGoingAway = true;
- mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) {
- mStatusBarWindowView.removeOnLayoutChangeListener(this);
- mWaitingOnCollapseWhenGoingAway = false;
- updateTouchableRegionListener();
- }
- }
- });
- }
-
- public static void setIsClickedNotification(View child, boolean clicked) {
- child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
- }
-
- public static boolean isClickedHeadsUpNotification(View child) {
- Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION);
- return clicked != null && clicked;
- }
-
- public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
- headsUpEntry.remoteInputActive = remoteInputActive;
- if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
- }
- }
-
- /**
* Set an entry to be expanded and therefore stick in the heads up area if it's pinned
* until it's collapsed again.
*/
- public void setExpanded(NotificationData.Entry entry, boolean expanded) {
- HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
- if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) {
- headsUpEntry.expanded = expanded;
- if (expanded) {
- headsUpEntry.removeAutoRemovalCallbacks();
- } else {
- headsUpEntry.updateEntry(false /* updatePostTime */);
- }
+ public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
+ HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+ if (headsUpEntry != null && entry.row.isPinned()) {
+ headsUpEntry.expanded(expanded);
}
}
- @Override
- public void onReorderingAllowed() {
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
- for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (isHeadsUp(entry.key)) {
- // Maybe the heads-up was removed already
- removeHeadsUpEntry(entry);
- }
- }
- mEntriesToRemoveWhenReorderingAllowed.clear();
- mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+ @NonNull
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return new HeadsUpEntry();
}
- public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
- mVisualStabilityManager = visualStabilityManager;
- }
-
- public void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
+ protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) {
+ entry.reset();
}
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
*/
- public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
- public NotificationData.Entry entry;
+ protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ @Nullable public NotificationData.Entry entry;
public long postTime;
- public long earliestRemovaltime;
- private Runnable mRemoveHeadsUpRunnable;
public boolean remoteInputActive;
+ public long earliestRemovaltime;
public boolean expanded;
- public void setEntry(final NotificationData.Entry entry) {
+ @Nullable private Runnable mRemoveHeadsUpRunnable;
+
+ public void setEntry(@Nullable final NotificationData.Entry entry) {
+ setEntry(entry, null);
+ }
+
+ public void setEntry(@Nullable final NotificationData.Entry entry,
+ @Nullable Runnable removeHeadsUpRunnable) {
this.entry = entry;
+ this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable;
// The actual post time will be just after the heads-up really slided in
postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay;
- mRemoveHeadsUpRunnable = new Runnable() {
- @Override
- public void run() {
- if (!mVisualStabilityManager.isReorderingAllowed()) {
- mEntriesToRemoveWhenReorderingAllowed.add(entry);
- mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this);
- } else if (!mTrackingHeadsUp) {
- removeHeadsUpEntry(entry);
- } else {
- mEntriesToRemoveAfterExpand.add(entry);
- }
- }
- };
- updateEntry();
- }
-
- public void updateEntry() {
- updateEntry(true);
+ updateEntry(true /* updatePostTime */);
}
public void updateEntry(boolean updatePostTime) {
+ if (DEBUG) Log.v(TAG, "updateEntry");
+
long currentTime = mClock.currentTimeMillis();
earliestRemovaltime = currentTime + mMinimumDisplayTime;
if (updatePostTime) {
postTime = Math.max(postTime, currentTime);
}
removeAutoRemovalCallbacks();
- if (mEntriesToRemoveAfterExpand.contains(entry)) {
- mEntriesToRemoveAfterExpand.remove(entry);
- }
- if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) {
- mEntriesToRemoveWhenReorderingAllowed.remove(entry);
- }
+
if (!isSticky()) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
@@ -707,7 +493,7 @@
}
@Override
- public int compareTo(HeadsUpEntry o) {
+ public int compareTo(@NonNull HeadsUpEntry o) {
boolean isPinned = entry.row.isPinned();
boolean otherPinned = o.entry.row.isPinned();
if (isPinned && !otherPinned) {
@@ -734,26 +520,29 @@
: -1;
}
- public void removeAutoRemovalCallbacks() {
- mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
- }
-
- public boolean wasShownLongEnough() {
- return earliestRemovaltime < mClock.currentTimeMillis();
- }
-
- public void removeAsSoonAsPossible() {
- removeAutoRemovalCallbacks();
- mHandler.postDelayed(mRemoveHeadsUpRunnable,
- earliestRemovaltime - mClock.currentTimeMillis());
+ public void expanded(boolean expanded) {
+ this.expanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks();
entry = null;
- mRemoveHeadsUpRunnable = null;
expanded = false;
remoteInputActive = false;
+ removeAutoRemovalCallbacks();
+ mRemoveHeadsUpRunnable = null;
+ }
+
+ public void removeAutoRemovalCallbacks() {
+ if (mRemoveHeadsUpRunnable != null)
+ mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+ }
+
+ public void removeAsSoonAsPossible() {
+ if (mRemoveHeadsUpRunnable != null) {
+ removeAutoRemovalCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable,
+ earliestRemovaltime - mClock.currentTimeMillis());
+ }
}
}
@@ -762,5 +551,4 @@
return SystemClock.elapsedRealtime();
}
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
new file mode 100644
index 0000000..1e3c123c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 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.policy;
+
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A class of utility static methods for heads up notifications.
+ */
+public final class HeadsUpUtil {
+ private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+
+ /**
+ * Set the given view as clicked or not-clicked.
+ * @param view The view to be set the flag to.
+ * @param clicked True to set as clicked. False to not-clicked.
+ */
+ public static void setIsClickedHeadsUpNotification(View view, boolean clicked) {
+ view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
+ }
+
+ /**
+ * Check if the given view has the flag of "clicked notification"
+ * @param view The view to be checked.
+ * @return True if the view has clicked. False othrewise.
+ */
+ public static boolean isClickedHeadsUpNotification(View view) {
+ Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
+ return clicked != null && clicked;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 077c6c3..ea449c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -28,6 +28,8 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
@@ -45,6 +47,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
@@ -58,12 +61,16 @@
private long mDownTime;
private int mCode;
private int mTouchSlop;
+ private int mTouchDownX;
+ private int mTouchDownY;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
private boolean mGestureAborted;
private boolean mLongClicked;
private OnClickListener mOnClickListener;
private final KeyButtonRipple mRipple;
+ private final OverviewProxyService mOverviewProxyService;
+ private final Vibrator mVibrator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final Runnable mCheckLongPress = new Runnable() {
@@ -110,6 +117,8 @@
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mRipple = new KeyButtonRipple(context, this);
+ mVibrator = mContext.getSystemService(Vibrator.class);
+ mOverviewProxyService = Dependency.get(OverviewProxyService.class);
setBackground(mRipple);
}
@@ -189,6 +198,7 @@
}
public boolean onTouchEvent(MotionEvent ev) {
+ final boolean isProxyConnected = mOverviewProxyService.getProxy() != null;
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
@@ -203,23 +213,34 @@
mDownTime = SystemClock.uptimeMillis();
mLongClicked = false;
setPressed(true);
+ mTouchDownX = (int) ev.getX();
+ mTouchDownY = (int) ev.getY();
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
} else {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
- playSoundEffect(SoundEffectConstants.CLICK);
+ if (isProxyConnected) {
+ // Provide small vibration for quick step or immediate down feedback
+ AsyncTask.execute(() ->
+ mVibrator.vibrate(VibrationEffect
+ .get(VibrationEffect.EFFECT_TICK, false)));
+ } else {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
removeCallbacks(mCheckLongPress);
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
x = (int)ev.getX();
y = (int)ev.getY();
- setPressed(x >= -mTouchSlop
- && x < getWidth() + mTouchSlop
- && y >= -mTouchSlop
- && y < getHeight() + mTouchSlop);
+ boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > mTouchSlop;
+ boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > mTouchSlop;
+ if (exceededTouchSlopX || exceededTouchSlopY) {
+ setPressed(false);
+ removeCallbacks(mCheckLongPress);
+ }
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
@@ -231,9 +252,14 @@
case MotionEvent.ACTION_UP:
final boolean doIt = isPressed() && !mLongClicked;
setPressed(false);
- // Always send a release ourselves because it doesn't seem to be sent elsewhere
- // and it feels weird to sometimes get a release haptic and other times not.
- if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ if (isProxyConnected) {
+ if (doIt) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ } else if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) {
+ // Always send a release ourselves because it doesn't seem to be sent elsewhere
+ // and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
}
if (mCode != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 424858a..d7a810e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -64,7 +64,7 @@
private boolean mPanelTracking;
private boolean mExpansionChanging;
private boolean mPanelFullWidth;
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mUnlockHintRunning;
private boolean mQsCustomizerShowing;
private int mIntrinsicPadding;
@@ -315,23 +315,18 @@
}
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) {
+ public void setPulsing(boolean hasPulsing) {
mPulsing = hasPulsing;
}
public boolean isPulsing(NotificationData.Entry entry) {
- if (mPulsing == null) {
+ if (!mPulsing || mHeadsUpManager == null) {
return false;
}
- for (HeadsUpManager.HeadsUpEntry e : mPulsing) {
- if (e.entry == entry) {
- return true;
- }
- }
- return false;
+ return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry));
}
public boolean isPanelTracking() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index c114a6f..1b55a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -92,10 +92,11 @@
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import android.support.v4.graphics.ColorUtils;
@@ -288,7 +289,7 @@
private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
= new HashSet<>();
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private boolean mTrackingHeadsUp;
private ScrimController mScrimController;
private boolean mForceNoOverlappingRendering;
@@ -358,7 +359,7 @@
}
};
private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
- private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
+ private boolean mPulsing;
private boolean mDrawBackgroundAsSrc;
private boolean mFadingOut;
private boolean mParentNotFullyVisible;
@@ -690,7 +691,7 @@
}
private void updateAlgorithmHeightAndPadding() {
- if (mPulsing != null) {
+ if (mPulsing) {
mTopPadding = mClockBottom;
} else {
mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding;
@@ -920,6 +921,27 @@
}
/**
+ * @return the height of the top heads up notification when pinned. This is different from the
+ * intrinsic height, which also includes whether the notification is system expanded and
+ * is mainly used when dragging down from a heads up notification.
+ */
+ private int getTopHeadsUpPinnedHeight() {
+ NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ if (topEntry == null) {
+ return 0;
+ }
+ ExpandableNotificationRow row = topEntry.row;
+ if (row.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null) {
+ row = groupSummary;
+ }
+ }
+ return row.getPinnedHeadsUpHeight();
+ }
+
+ /**
* @return the position from where the appear transition ends when expanding.
* Measured in absolute height.
*/
@@ -930,7 +952,7 @@
int minNotificationsForShelf = 1;
if (mTrackingHeadsUp
|| (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
- appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+ appearPosition = getTopHeadsUpPinnedHeight();
minNotificationsForShelf = 2;
} else {
appearPosition = 0;
@@ -1198,9 +1220,9 @@
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
- && mHeadsUpManager.getTopEntry().entry.row != row
+ && mHeadsUpManager.getTopEntry().row != row
&& mGroupManager.getGroupSummary(
- mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
+ mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
!= row) {
continue;
}
@@ -2120,7 +2142,7 @@
@Override
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
private void updateScrollability() {
@@ -2753,7 +2775,7 @@
}
private boolean isClickedHeadsUp(View child) {
- return HeadsUpManager.isClickedHeadsUpNotification(child);
+ return HeadsUpUtil.isClickedHeadsUpNotification(child);
}
/**
@@ -4258,7 +4280,7 @@
mAnimationFinishedRunnables.add(runnable);
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
mAmbientState.setHeadsUpManager(headsUpManager);
}
@@ -4326,8 +4348,8 @@
return mIsExpanded;
}
- public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) {
- if (mPulsing == null && pulsing == null) {
+ public void setPulsing(boolean pulsing, int clockBottom) {
+ if (!mPulsing && !pulsing) {
return;
}
mPulsing = pulsing;
@@ -4466,7 +4488,7 @@
pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
+ " alpha:%f scrollY:%d]",
this.getClass().getSimpleName(),
- mPulsing != null ?"T":"f",
+ mPulsing ? "T":"f",
mAmbientState.isQsCustomizerShowing() ? "T":"f",
getVisibility() == View.VISIBLE ? "visible"
: getVisibility() == View.GONE ? "gone"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 682b849..04a7bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -30,7 +30,7 @@
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
/**
* A state of a view. This can be used to apply a set of view properties to a view with
@@ -582,7 +582,7 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- HeadsUpManager.setIsClickedNotification(child, false);
+ HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
child.setTag(TAG_START_TRANSLATION_Y, null);
child.setTag(TAG_END_TRANSLATION_Y, null);
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 936ff51..59a7da6 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -65,7 +65,6 @@
LOCAL_JAVA_LIBRARIES := \
android.test.runner \
telephony-common \
- android.car \
android.test.base \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 6e7477f..f3c1171 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationInflaterTest;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -51,7 +52,7 @@
public NotificationTestHelper(Context context) {
mContext = context;
mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager);
+ mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null);
}
public ExpandableNotificationRow createRow() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
new file mode 100644
index 0000000..aa991cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2018 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.phone;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.Notification;
+import android.os.UserHandle;
+import android.view.View;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HeadsUpManagerPhoneTest extends SysuiTestCase {
+ @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+ private static final String TEST_PACKAGE_NAME = "test";
+ private static final int TEST_UID = 0;
+
+ private HeadsUpManagerPhone mHeadsUpManager;
+
+ private NotificationData.Entry mEntry;
+ private StatusBarNotification mSbn;
+
+ @Mock private NotificationGroupManager mGroupManager;
+ @Mock private View mStatusBarWindowView;
+ @Mock private StatusBar mBar;
+ @Mock private ExpandableNotificationRow mRow;
+ @Mock private VisualStabilityManager mVSManager;
+
+ @Before
+ public void setUp() {
+ when(mVSManager.isReorderingAllowed()).thenReturn(true);
+
+ mHeadsUpManager = new HeadsUpManagerPhone(
+ mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager);
+
+ Notification.Builder n = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+ 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+
+ mEntry = new NotificationData.Entry(mSbn);
+ mEntry.row = mRow;
+ mEntry.expandedIcon = mock(StatusBarIconView.class);
+ }
+
+ @Test
+ public void testBasicOperations() {
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Update the notification.
+ mHeadsUpManager.updateNotification(mEntry, false);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Try to remove but defer, since the notification is currenlt visible on display.
+ mHeadsUpManager.removeNotification(mEntry.key, false /* ignoreEarliestRemovalTime */);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Remove forcibly with ignoreEarliestRemovalTime = true.
+ mHeadsUpManager.removeNotification(mEntry.key, true /* ignoreEarliestRemovalTime */);
+
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+
+ @Test
+ public void testsTimeoutRemoval() {
+ mHeadsUpManager.removeMinimumDisplayTimeForTesting();
+
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // Run the code on the main thready, not to run an async operations.
+ instrumentation.runOnMainSync(() -> {
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ // Ensure the head up is visible before timeout.
+ assertNotNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNotNull(mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+ });
+ // Wait for the async operations, which removes the heads up notification.
+ waitForIdleSync();
+
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+
+ @Test
+ public void releaseImmediately() {
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Remove but defer, since the notification is visible on display.
+ mHeadsUpManager.releaseImmediately(mEntry.key);
+
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+
+ @Test
+ public void releaseAllImmediately() {
+ // Check the initial state.
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Add a notification.
+ mHeadsUpManager.showNotification(mEntry);
+
+ assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key));
+ assertEquals(mEntry, mHeadsUpManager.getTopEntry());
+ assertEquals(1, mHeadsUpManager.getAllEntries().count());
+ assertTrue(mHeadsUpManager.hasHeadsUpNotifications());
+
+ // Remove but defer, since the notification is visible on display.
+ mHeadsUpManager.releaseAllImmediately();
+
+ assertNull(mHeadsUpManager.getEntry(mEntry.key));
+ assertNull(mHeadsUpManager.getTopEntry());
+ assertEquals(0, mHeadsUpManager.getAllEntries().count());
+ assertFalse(mHeadsUpManager.hasHeadsUpNotifications());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index bdf9b1f..31442af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -86,8 +86,8 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -110,7 +110,7 @@
@Mock private UnlockMethodCache mUnlockMethodCache;
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@Mock private NotificationStackScrollLayout mStackScroller;
- @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private SystemServicesProxy mSystemServicesProxy;
@Mock private NotificationPanelView mNotificationPanelView;
@Mock private IStatusBarService mBarService;
@@ -588,7 +588,7 @@
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
- NotificationStackScrollLayout stack, HeadsUpManager hum,
+ NotificationStackScrollLayout stack, HeadsUpManagerPhone hum,
PowerManager pm, NotificationPanelView panelView,
IStatusBarService barService, NotificationListener notificationListener,
NotificationLogger notificationLogger,
@@ -650,7 +650,7 @@
public void setUpForTest(NotificationPresenter presenter,
NotificationListContainer listContainer,
Callback callback,
- HeadsUpManager headsUpManager,
+ HeadsUpManagerPhone headsUpManager,
NotificationData notificationData) {
super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
mNotificationData = notificationData;
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 72c3c94..d5a722b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -306,6 +306,7 @@
*
* @return service instance.
*/
+ @GuardedBy("mLock")
@NonNull
AutofillManagerServiceImpl getServiceForUserLocked(int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -325,6 +326,7 @@
*
* @return service instance or {@code null} if not already present
*/
+ @GuardedBy("mLock")
@Nullable
AutofillManagerServiceImpl peekServiceForUserLocked(int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -474,6 +476,7 @@
/**
* Removes a cached service for a given user.
*/
+ @GuardedBy("mLock")
private void removeCachedServiceLocked(int userId) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
@@ -485,6 +488,7 @@
/**
* Updates a cached service for a given user.
*/
+ @GuardedBy("mLock")
private void updateCachedServiceLocked(int userId) {
updateCachedServiceLocked(userId, mDisabledUsers.get(userId));
}
@@ -492,6 +496,7 @@
/**
* Updates a cached service for a given user.
*/
+ @GuardedBy("mLock")
private void updateCachedServiceLocked(int userId, boolean disabled) {
AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 989a7b5..2dcc6da 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -211,6 +211,7 @@
}
}
+ @GuardedBy("mLock")
private int getServiceUidLocked() {
if (mInfo == null) {
Slog.w(TAG, "getServiceUidLocked(): no mInfo");
@@ -248,6 +249,7 @@
mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
}
+ @GuardedBy("mLock")
void updateLocked(boolean disabled) {
final boolean wasEnabled = isEnabledLocked();
if (sVerbose) {
@@ -300,6 +302,7 @@
}
}
+ @GuardedBy("mLock")
boolean addClientLocked(IAutoFillManagerClient client) {
if (mClients == null) {
mClients = new RemoteCallbackList<>();
@@ -308,12 +311,14 @@
return isEnabledLocked();
}
+ @GuardedBy("mLock")
void removeClientLocked(IAutoFillManagerClient client) {
if (mClients != null) {
mClients.unregister(client);
}
}
+ @GuardedBy("mLock")
void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
if (!isEnabledLocked()) {
return;
@@ -336,6 +341,7 @@
}
}
+ @GuardedBy("mLock")
int startSessionLocked(@NonNull IBinder activityToken, int uid,
@NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
@NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
@@ -389,6 +395,7 @@
/**
* Remove abandoned sessions if needed.
*/
+ @GuardedBy("mLock")
private void pruneAbandonedSessionsLocked() {
long now = System.currentTimeMillis();
if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) {
@@ -400,6 +407,7 @@
}
}
+ @GuardedBy("mLock")
void finishSessionLocked(int sessionId, int uid) {
if (!isEnabledLocked()) {
return;
@@ -423,6 +431,7 @@
}
}
+ @GuardedBy("mLock")
void cancelSessionLocked(int sessionId, int uid) {
if (!isEnabledLocked()) {
return;
@@ -436,6 +445,7 @@
session.removeSelfLocked();
}
+ @GuardedBy("mLock")
void disableOwnedAutofillServicesLocked(int uid) {
Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
if (mInfo == null) return;
@@ -468,6 +478,7 @@
}
}
+ @GuardedBy("mLock")
private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
@NonNull IBinder appCallbackToken, boolean hasCallback,
@NonNull ComponentName componentName, int flags) {
@@ -546,6 +557,7 @@
/**
* Updates a session and returns whether it should be restarted.
*/
+ @GuardedBy("mLock")
boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
AutofillValue value, int action, int flags) {
final Session session = mSessions.get(sessionId);
@@ -568,6 +580,7 @@
return false;
}
+ @GuardedBy("mLock")
void removeSessionLocked(int sessionId) {
mSessions.remove(sessionId);
}
@@ -603,6 +616,7 @@
}
}
+ @GuardedBy("mLock")
void destroyLocked() {
if (sVerbose) Slog.v(TAG, "destroyLocked()");
@@ -655,6 +669,7 @@
}
}
+ @GuardedBy("mLock")
private boolean isValidEventLocked(String method, int sessionId) {
if (mEventHistory == null) {
Slog.w(TAG, method + ": not logging event because history is null");
@@ -726,6 +741,7 @@
/**
* Updates the last fill response when an autofill context is committed.
*/
+ @GuardedBy("mLock")
void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
@Nullable ArrayList<String> selectedDatasets,
@Nullable ArraySet<String> ignoredDatasets,
@@ -739,6 +755,7 @@
manuallyFilledDatasetIds, null, null, appPackageName);
}
+ @GuardedBy("mLock")
void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState,
@Nullable ArrayList<String> selectedDatasets,
@Nullable ArraySet<String> ignoredDatasets,
@@ -847,6 +864,7 @@
}
}
+ @GuardedBy("mLock")
private boolean isCalledByServiceLocked(String methodName, int callingUid) {
if (getServiceUidLocked() != callingUid) {
Slog.w(TAG, methodName + "() called by UID " + callingUid
@@ -856,6 +874,7 @@
return true;
}
+ @GuardedBy("mLock")
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
@@ -965,6 +984,7 @@
mFieldClassificationStrategy.dump(prefix2, pw);
}
+ @GuardedBy("mLock")
void destroySessionsLocked() {
if (mSessions.size() == 0) {
mUi.destroyAll(null, null, false);
@@ -976,6 +996,7 @@
}
// TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
+ @GuardedBy("mLock")
void destroyFinishedSessionsLocked() {
final int sessionCount = mSessions.size();
for (int i = sessionCount - 1; i >= 0; i--) {
@@ -987,6 +1008,7 @@
}
}
+ @GuardedBy("mLock")
void listSessionsLocked(ArrayList<String> output) {
final int numSessions = mSessions.size();
for (int i = 0; i < numSessions; i++) {
@@ -995,6 +1017,7 @@
}
}
+ @GuardedBy("mLock")
boolean isCompatibilityModeRequestedLocked(@NonNull String packageName,
long versionCode) {
if (mInfo == null || !mInfo.isCompatibilityModeRequested(packageName, versionCode)) {
@@ -1060,6 +1083,7 @@
}
}
+ @GuardedBy("mLock")
private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) {
final int sessionCount = mSessions.size();
for (int i = 0; i < sessionCount; i++) {
@@ -1071,6 +1095,7 @@
return true;
}
+ @GuardedBy("mLock")
boolean isEnabledLocked() {
return mSetupComplete && mInfo != null && !mDisabled;
}
@@ -1123,6 +1148,7 @@
/**
* Checks if autofill is disabled by service to the given activity.
*/
+ @GuardedBy("mLock")
private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
// Check activities first.
long elapsedTime = 0;
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index aea9ad0..fe6d4c4 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -475,6 +475,7 @@
return true;
}
+ @GuardedBy("mLock")
protected boolean isCancelledLocked() {
return mCancelled;
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 18f49ec..4a24704 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -300,6 +300,7 @@
/**
* Returns the ids of all entries in {@link #mViewStates} in the same order.
*/
+ @GuardedBy("mLock")
private AutofillId[] getIdsOfAllViewStatesLocked() {
final int numViewState = mViewStates.size();
final AutofillId[] ids = new AutofillId[numViewState];
@@ -346,6 +347,7 @@
* <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts},
* or {@code null} when not found on either of them.
*/
+ @GuardedBy("mLock")
private AutofillValue findValueLocked(@NonNull AutofillId id) {
final ViewState state = mViewStates.get(id);
if (state == null) {
@@ -369,6 +371,7 @@
* @param fillContext The context to be filled
* @param flags The flags that started the session
*/
+ @GuardedBy("mLock")
private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
final ViewNode[] nodes = fillContext
.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
@@ -409,6 +412,7 @@
/**
* Cancels the last request sent to the {@link #mRemoteFillService}.
*/
+ @GuardedBy("mLock")
private void cancelCurrentRequestLocked() {
final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
@@ -430,6 +434,7 @@
/**
* Reads a new structure and then request a new fill response from the fill service.
*/
+ @GuardedBy("mLock")
private void requestNewFillResponseLocked(int flags) {
int requestId;
@@ -497,6 +502,7 @@
*
* @return The activity token
*/
+ @GuardedBy("mLock")
@NonNull IBinder getActivityTokenLocked() {
return mActivityToken;
}
@@ -663,6 +669,7 @@
*
* @return The context or {@code null} if there is no context
*/
+ @GuardedBy("mLock")
@Nullable private FillContext getFillContextByRequestIdLocked(int requestId) {
if (mContexts == null) {
return null;
@@ -820,6 +827,7 @@
});
}
+ @GuardedBy("mLock")
void setAuthenticationResultLocked(Bundle data, int authenticationId) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: "
@@ -882,6 +890,7 @@
}
}
+ @GuardedBy("mLock")
void setHasCallbackLocked(boolean hasIt) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: "
@@ -891,6 +900,7 @@
mHasCallback = hasIt;
}
+ @GuardedBy("mLock")
@Nullable
private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
if (mContexts == null) {
@@ -923,6 +933,7 @@
return response;
}
+ @GuardedBy("mLock")
@Nullable
private SaveInfo getSaveInfoLocked() {
final FillResponse response = getLastResponseLocked(null);
@@ -941,6 +952,7 @@
});
}
+ @GuardedBy("mLock")
private void logContextCommittedLocked() {
final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
if (lastResponse == null) return;
@@ -1241,6 +1253,7 @@
*
* @return {@code true} if session is done, or {@code false} if it's pending user action.
*/
+ @GuardedBy("mLock")
public boolean showSaveLocked() {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
@@ -1510,6 +1523,7 @@
/**
* Returns whether the session is currently showing the save UI
*/
+ @GuardedBy("mLock")
boolean isSavingLocked() {
return mIsSaving;
}
@@ -1517,6 +1531,7 @@
/**
* Gets the latest non-empty value for the given id in the autofill contexts.
*/
+ @GuardedBy("mLock")
@Nullable
private AutofillValue getValueFromContextsLocked(AutofillId id) {
final int numContexts = mContexts.size();
@@ -1539,6 +1554,7 @@
/**
* Gets the latest autofill options for the given id in the autofill contexts.
*/
+ @GuardedBy("mLock")
@Nullable
private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) {
final int numContexts = mContexts.size();
@@ -1556,6 +1572,7 @@
/**
* Calls service when user requested save.
*/
+ @GuardedBy("mLock")
void callSaveLocked() {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: "
@@ -1646,6 +1663,7 @@
* @param viewState The view that is entered.
* @param flags The flag that was passed by the AutofillManager.
*/
+ @GuardedBy("mLock")
private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
@NonNull ViewState viewState, int flags) {
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
@@ -1673,6 +1691,7 @@
*
* @return {@code true} iff a new partition should be started
*/
+ @GuardedBy("mLock")
private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
if (mResponses == null) {
return true;
@@ -1721,6 +1740,7 @@
return true;
}
+ @GuardedBy("mLock")
void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
int flags) {
if (mDestroyed) {
@@ -1830,6 +1850,7 @@
/**
* Checks whether a view should be ignored.
*/
+ @GuardedBy("mLock")
private boolean isIgnoredLocked(AutofillId id) {
// Always check the latest response only
final FillResponse response = getLastResponseLocked(null);
@@ -1910,6 +1931,7 @@
}
}
+ @GuardedBy("mLock")
private void updateTrackedIdsLocked() {
// Only track the views of the last response as only those are reported back to the
// service, see #showSaveLocked
@@ -1982,6 +2004,7 @@
}
}
+ @GuardedBy("mLock")
private void replaceResponseLocked(@NonNull FillResponse oldResponse,
@NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
// Disassociate view states with the old response
@@ -2005,6 +2028,7 @@
removeSelf();
}
+ @GuardedBy("mLock")
private void processResponseLocked(@NonNull FillResponse newResponse,
@Nullable Bundle newClientState, int flags) {
// Make sure we are hiding the UI which will be shown
@@ -2042,6 +2066,7 @@
/**
* Sets the state of all views in the given response.
*/
+ @GuardedBy("mLock")
private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse) {
final List<Dataset> datasets = response.getDatasets();
if (datasets != null) {
@@ -2090,6 +2115,7 @@
/**
* Sets the state of all views in the given dataset and response.
*/
+ @GuardedBy("mLock")
private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
int state, boolean clearResponse) {
final ArrayList<AutofillId> ids = dataset.getFieldIds();
@@ -2110,6 +2136,7 @@
}
}
+ @GuardedBy("mLock")
private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state,
@Nullable AutofillValue value) {
ViewState viewState = mViewStates.get(id);
@@ -2171,6 +2198,7 @@
}
// TODO: this should never be null, but we got at least one occurrence, probably due to a race.
+ @GuardedBy("mLock")
@Nullable
private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
final Intent fillInIntent = new Intent();
@@ -2203,6 +2231,7 @@
return "Session: [id=" + id + ", component=" + mComponentName + "]";
}
+ @GuardedBy("mLock")
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("id: "); pw.println(id);
@@ -2332,6 +2361,7 @@
* disabled it).
* </ul>
*/
+ @GuardedBy("mLock")
RemoteFillService destroyLocked() {
if (mDestroyed) {
return null;
@@ -2347,6 +2377,7 @@
* Cleans up this session and remove it from the service always, even if it does have a pending
* Save UI.
*/
+ @GuardedBy("mLock")
void forceRemoveSelfLocked() {
if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
@@ -2376,6 +2407,7 @@
* Cleans up this session and remove it from the service, but but only if it does not have a
* pending Save UI.
*/
+ @GuardedBy("mLock")
void removeSelfLocked() {
if (sVerbose) Slog.v(TAG, "removeSelfLocked(): " + mPendingSaveUi);
if (mDestroyed) {
@@ -2404,6 +2436,7 @@
* a specific {@code token} created by
* {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
*/
+ @GuardedBy("mLock")
boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
}
@@ -2411,10 +2444,12 @@
/**
* Checks whether this session is hiding the Save UI to handle a custom description link.
*/
+ @GuardedBy("mLock")
private boolean isSaveUiPendingLocked() {
return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
}
+ @GuardedBy("mLock")
private int getLastResponseIndexLocked() {
// The response ids are monotonically increasing so
// we just find the largest id which is the last. We
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 355da2d..45b2118 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -88,6 +88,7 @@
import java.util.function.Predicate;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -98,8 +99,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.LocalLog;
-import com.android.internal.util.Preconditions;
-import com.android.server.AppStateTracker.Listener;
+import com.android.server.ForceAppStandbyTracker.Listener;
/**
* Alarm manager implementaion.
@@ -250,7 +250,7 @@
private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
new SparseArray<>();
- private AppStateTracker mAppStateTracker;
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
private boolean mAppStandbyParole;
private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>();
@@ -708,6 +708,9 @@
super(context);
mConstants = new Constants(mHandler);
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
+
publishLocalService(AlarmManagerInternal.class, new LocalService());
}
@@ -1009,7 +1012,7 @@
// Recurring alarms may have passed several alarm intervals while the
// alarm was kept pending. Send the appropriate trigger count.
if (alarm.repeatInterval > 0) {
- alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval;
+ alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
final long nextElapsed = alarm.whenElapsed + delta;
@@ -1327,15 +1330,13 @@
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ mForceAppStandbyTracker.start();
mConstants.start(getContext().getContentResolver());
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
mUsageStatsManagerInternal.addAppIdleStateChangeListener(new AppStandbyTracker());
-
- mAppStateTracker = LocalServices.getService(AppStateTracker.class);
- mAppStateTracker.addListener(mForceAppStandbyListener);
}
}
@@ -1507,24 +1508,19 @@
* Adjusts the alarm delivery time based on the current app standby bucket.
* @param alarm The alarm to adjust
* @return true if the alarm delivery time was updated.
- * TODO: Reduce the number of calls to getAppStandbyBucket by batching the calls per
- * {package, user} pairs
*/
private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
- if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) {
- return false;
- }
- // TODO: short term fix for b/72816079, remove after a proper fix is in place
- if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+ if (isExemptFromAppStandby(alarm)) {
return false;
}
if (mAppStandbyParole) {
- if (alarm.whenElapsed > alarm.requestedWhenElapsed) {
+ if (alarm.whenElapsed > alarm.expectedWhenElapsed) {
// We did defer this alarm earlier, restore original requirements
- alarm.whenElapsed = alarm.requestedWhenElapsed;
- alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed;
+ alarm.whenElapsed = alarm.expectedWhenElapsed;
+ alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
+ return true;
}
- return true;
+ return false;
}
final long oldWhenElapsed = alarm.whenElapsed;
final long oldMaxWhenElapsed = alarm.maxWhenElapsed;
@@ -1538,13 +1534,13 @@
final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
if (lastElapsed > 0) {
final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
- if (alarm.requestedWhenElapsed < minElapsed) {
+ if (alarm.expectedWhenElapsed < minElapsed) {
alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
} else {
// app is now eligible to run alarms at the originally requested window.
// Restore original requirements in case they were changed earlier.
- alarm.whenElapsed = alarm.requestedWhenElapsed;
- alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed;
+ alarm.whenElapsed = alarm.expectedWhenElapsed;
+ alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
}
}
return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
@@ -1728,9 +1724,8 @@
// This means we will allow these alarms to go off as normal even while idle, with no
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
- || callingUid == mSystemUiUid
- || (mAppStateTracker != null
- && mAppStateTracker.isUidPowerSaveWhitelisted(callingUid)))) {
+ || UserHandle.isSameApp(callingUid, mSystemUiUid)
+ || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
}
@@ -1813,10 +1808,8 @@
mConstants.dump(pw);
pw.println();
- if (mAppStateTracker != null) {
- mAppStateTracker.dump(pw, " ");
- pw.println();
- }
+ mForceAppStandbyTracker.dump(pw, " ");
+ pw.println();
pw.println(" App Standby Parole: " + mAppStandbyParole);
pw.println();
@@ -2164,10 +2157,8 @@
mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
- if (mAppStateTracker != null) {
- mAppStateTracker.dumpProto(proto,
- AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
- }
+ mForceAppStandbyTracker.dumpProto(proto,
+ AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
if (!mInteractive) {
@@ -2947,7 +2938,7 @@
}
final String sourcePackage = alarm.sourcePackage;
final int sourceUid = alarm.creatorUid;
- return mAppStateTracker.areAlarmsRestricted(sourceUid, sourcePackage,
+ return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage,
allowWhileIdle);
}
@@ -2960,7 +2951,7 @@
private long getWhileIdleMinIntervalLocked(int uid) {
final boolean dozing = mPendingIdleUntil != null;
- final boolean ebs = mAppStateTracker.isForceAllAppsStandbyEnabled();
+ final boolean ebs = mForceAppStandbyTracker.isForceAllAppsStandbyEnabled();
if (!dozing && !ebs) {
return mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
}
@@ -3005,10 +2996,11 @@
// Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
// alarm went off for this app. Reschedule the alarm to be in the
// correct time period.
- alarm.whenElapsed = minTime;
+ alarm.expectedWhenElapsed = alarm.whenElapsed = minTime;
if (alarm.maxWhenElapsed < minTime) {
alarm.maxWhenElapsed = minTime;
}
+ alarm.expectedMaxWhenElapsed = alarm.maxWhenElapsed;
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
ent.uid = alarm.uid;
@@ -3058,7 +3050,7 @@
if (alarm.repeatInterval > 0) {
// this adjustment will be zero if we're late by
// less than one full repeat interval
- alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval;
+ alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
@@ -3133,8 +3125,9 @@
public long windowLength;
public long whenElapsed; // 'when' in the elapsed time base
public long maxWhenElapsed; // also in the elapsed time base
- public final long requestedWhenElapsed; // original expiry time requested by the app
- public final long requestedMaxWhenElapsed;
+ // Expected alarm expiry time before app standby deferring is applied.
+ public long expectedWhenElapsed;
+ public long expectedMaxWhenElapsed;
public long repeatInterval;
public PriorityClass priorityClass;
@@ -3148,10 +3141,10 @@
|| _type == AlarmManager.RTC_WAKEUP;
when = _when;
whenElapsed = _whenElapsed;
- requestedWhenElapsed = _whenElapsed;
+ expectedWhenElapsed = _whenElapsed;
windowLength = _windowLength;
maxWhenElapsed = _maxWhen;
- requestedMaxWhenElapsed = _maxWhen;
+ expectedMaxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
listener = _rec;
@@ -3210,8 +3203,10 @@
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
pw.print(prefix); pw.print("tag="); pw.println(statsTag);
pw.print(prefix); pw.print("type="); pw.print(type);
- pw.print(" requestedWhenElapsed="); TimeUtils.formatDuration(
- requestedWhenElapsed, nowELAPSED, pw);
+ pw.print(" expectedWhenElapsed="); TimeUtils.formatDuration(
+ expectedWhenElapsed, nowELAPSED, pw);
+ pw.print(" expectedMaxWhenElapsed="); TimeUtils.formatDuration(
+ expectedMaxWhenElapsed, nowELAPSED, pw);
pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
nowELAPSED, pw);
pw.print(" when=");
@@ -3349,6 +3344,11 @@
}
}
+ private boolean isExemptFromAppStandby(Alarm a) {
+ return a.alarmClock != null || UserHandle.isCore(a.creatorUid)
+ || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
+ }
+
private class AlarmThread extends Thread
{
public AlarmThread()
@@ -3467,7 +3467,7 @@
new ArraySet<>();
for (int i = 0; i < triggerList.size(); i++) {
final Alarm a = triggerList.get(i);
- if (!UserHandle.isCore(a.creatorUid)) {
+ if (!isExemptFromAppStandby(a)) {
triggerPackages.add(Pair.create(
a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
}
@@ -4138,7 +4138,7 @@
if (allowWhileIdle) {
// Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
- if (mAppStateTracker.isUidInForeground(alarm.creatorUid)) {
+ if (mForceAppStandbyTracker.isUidInForeground(alarm.creatorUid)) {
mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
} else {
mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
@@ -4153,7 +4153,7 @@
mAllowWhileIdleDispatches.add(ent);
}
}
- if (!UserHandle.isCore(alarm.creatorUid)) {
+ if (!isExemptFromAppStandby(alarm)) {
final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid));
mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED);
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 2b3c585..44974ff 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -82,7 +82,6 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -129,7 +128,6 @@
private Intent mIdleIntent;
private Intent mLightIdleIntent;
private AnyMotionDetector mAnyMotionDetector;
- private final AppStateTracker mAppStateTracker;
private boolean mLightEnabled;
private boolean mDeepEnabled;
private boolean mForceIdle;
@@ -1373,8 +1371,6 @@
super(context);
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
- mAppStateTracker = new AppStateTracker(context, FgThread.get().getLooper());
- LocalServices.addService(AppStateTracker.class, mAppStateTracker);
}
boolean isAppOnWhitelistInternal(int appid) {
@@ -1505,8 +1501,6 @@
(PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
mHandler, mSensorManager, this, angleThreshold);
- mAppStateTracker.onSystemServicesReady();
-
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
@@ -2621,7 +2615,7 @@
}
private void passWhiteListToForceAppStandbyTrackerLocked() {
- mAppStateTracker.setPowerSaveWhitelistAppIds(
+ ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
mPowerSaveWhitelistExceptIdleAppIdArray,
mTempWhitelistAppIdArray);
}
diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
similarity index 95%
rename from services/core/java/com/android/server/AppStateTracker.java
rename to services/core/java/com/android/server/ForceAppStandbyTracker.java
index 51dff56..100680d 100644
--- a/services/core/java/com/android/server/AppStateTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -54,6 +54,7 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.DeviceIdleController.LocalService;
import com.android.server.ForceAppStandbyTrackerProto.ExemptedPackage;
import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
@@ -72,14 +73,14 @@
* TODO: Make it a LocalService.
*
* Test:
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
*/
-public class AppStateTracker {
+public class ForceAppStandbyTracker {
private static final String TAG = "ForceAppStandbyTracker";
private static final boolean DEBUG = true;
- @GuardedBy("AppStateTracker.class")
- private static AppStateTracker sInstance;
+ @GuardedBy("ForceAppStandbyTracker.class")
+ private static ForceAppStandbyTracker sInstance;
private final Object mLock = new Object();
private final Context mContext;
@@ -88,7 +89,6 @@
static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
IActivityManager mIActivityManager;
- ActivityManagerInternal mActivityManagerInternal;
AppOpsManager mAppOpsManager;
IAppOpsService mAppOpsService;
PowerManagerInternal mPowerManagerInternal;
@@ -172,9 +172,6 @@
int EXEMPT_CHANGED = 6;
int FORCE_ALL_CHANGED = 7;
int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
-
- int IS_UID_ACTIVE_CACHED = 9;
- int IS_UID_ACTIVE_RAW = 10;
}
private final StatLogger mStatLogger = new StatLogger(new String[] {
@@ -187,9 +184,6 @@
"EXEMPT_CHANGED",
"FORCE_ALL_CHANGED",
"FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED",
-
- "IS_UID_ACTIVE_CACHED",
- "IS_UID_ACTIVE_RAW",
});
@VisibleForTesting
@@ -255,7 +249,7 @@
/**
* This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
*/
- private void onRunAnyAppOpsChanged(AppStateTracker sender,
+ private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
int uid, @NonNull String packageName) {
updateJobsForUidPackage(uid, packageName);
@@ -270,14 +264,14 @@
/**
* This is called when the foreground state changed for a UID.
*/
- private void onUidForegroundStateChanged(AppStateTracker sender, int uid) {
+ private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
onUidForeground(uid, sender.isUidInForeground(uid));
}
/**
* This is called when the active/idle state changed for a UID.
*/
- private void onUidActiveStateChanged(AppStateTracker sender, int uid) {
+ private void onUidActiveStateChanged(ForceAppStandbyTracker sender, int uid) {
updateJobsForUid(uid);
if (sender.isUidActive(uid)) {
@@ -288,7 +282,7 @@
/**
* This is called when an app-id(s) is removed from the power save whitelist.
*/
- private void onPowerSaveUnwhitelisted(AppStateTracker sender) {
+ private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
updateAllJobs();
unblockAllUnrestrictedAlarms();
}
@@ -297,14 +291,14 @@
* This is called when the power save whitelist changes, excluding the
* {@link #onPowerSaveUnwhitelisted} case.
*/
- private void onPowerSaveWhitelistedChanged(AppStateTracker sender) {
+ private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
updateAllJobs();
}
/**
* This is called when the temp whitelist changes.
*/
- private void onTempPowerSaveWhitelistChanged(AppStateTracker sender) {
+ private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
// TODO This case happens rather frequently; consider optimizing and update jobs
// only for affected app-ids.
@@ -317,7 +311,7 @@
/**
* This is called when the EXEMPT bucket is updated.
*/
- private void onExemptChanged(AppStateTracker sender) {
+ private void onExemptChanged(ForceAppStandbyTracker sender) {
// This doesn't happen very often, so just re-evaluate all jobs / alarms.
updateAllJobs();
unblockAllUnrestrictedAlarms();
@@ -326,7 +320,7 @@
/**
* This is called when the global "force all apps standby" flag changes.
*/
- private void onForceAllAppsStandbyChanged(AppStateTracker sender) {
+ private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
updateAllJobs();
if (!sender.isForceAllAppsStandbyEnabled()) {
@@ -383,15 +377,30 @@
}
}
- public AppStateTracker(Context context, Looper looper) {
+ @VisibleForTesting
+ ForceAppStandbyTracker(Context context, Looper looper) {
mContext = context;
mHandler = new MyHandler(looper);
}
+ private ForceAppStandbyTracker(Context context) {
+ this(context, FgThread.get().getLooper());
+ }
+
+ /**
+ * Get the singleton instance.
+ */
+ public static synchronized ForceAppStandbyTracker getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new ForceAppStandbyTracker(context);
+ }
+ return sInstance;
+ }
+
/**
* Call it when the system is ready.
*/
- public void onSystemServicesReady() {
+ public void start() {
synchronized (mLock) {
if (mStarted) {
return;
@@ -399,7 +408,6 @@
mStarted = true;
mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
- mActivityManagerInternal = Preconditions.checkNotNull(injectActivityManagerInternal());
mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
@@ -467,11 +475,6 @@
}
@VisibleForTesting
- ActivityManagerInternal injectActivityManagerInternal() {
- return LocalServices.getService(ActivityManagerInternal.class);
- }
-
- @VisibleForTesting
PowerManagerInternal injectPowerManagerInternal() {
return LocalServices.getService(PowerManagerInternal.class);
}
@@ -796,7 +799,7 @@
return;
}
}
- final AppStateTracker sender = AppStateTracker.this;
+ final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
long start = mStatLogger.getTime();
switch (msg.what) {
@@ -1086,11 +1089,11 @@
}
/**
- * @return whether a UID is in active or not *based on cached information.*
+ * @return whether a UID is in active or not.
*
* Note this information is based on the UID proc state callback, meaning it's updated
* asynchronously and may subtly be stale. If the fresh data is needed, use
- * {@link #isUidActiveSynced} instead.
+ * {@link ActivityManagerInternal#getUidProcessState} instead.
*/
public boolean isUidActive(int uid) {
if (UserHandle.isCore(uid)) {
@@ -1102,23 +1105,6 @@
}
/**
- * @return whether a UID is in active or not *right now.*
- *
- * This gives the fresh information, but may access the activity manager so is slower.
- */
- public boolean isUidActiveSynced(int uid) {
- if (isUidActive(uid)) { // Use the cached one first.
- return true;
- }
- final long start = mStatLogger.getTime();
-
- final boolean ret = mActivityManagerInternal.isUidActive(uid);
- mStatLogger.logDurationStat(Stats.IS_UID_ACTIVE_RAW, start);
-
- return ret;
- }
-
- /**
* @return whether a UID is in the foreground or not.
*
* Note this information is based on the UID proc state callback, meaning it's updated
diff --git a/services/core/java/com/android/server/StatLogger.java b/services/core/java/com/android/server/StatLogger.java
index 0e6f5e2..f211731 100644
--- a/services/core/java/com/android/server/StatLogger.java
+++ b/services/core/java/com/android/server/StatLogger.java
@@ -17,7 +17,6 @@
package com.android.server;
import android.os.SystemClock;
-import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
@@ -34,8 +33,6 @@
* @hide
*/
public class StatLogger {
- private static final String TAG = "StatLogger";
-
private final Object mLock = new Object();
private final int SIZE;
@@ -69,12 +66,8 @@
*/
public void logDurationStat(int eventId, long start) {
synchronized (mLock) {
- if (eventId >= 0 && eventId < SIZE) {
- mCountStats[eventId]++;
- mDurationStats[eventId] += (getTime() - start);
- } else {
- Slog.wtf(TAG, "Invalid event ID: " + eventId);
- }
+ mCountStats[eventId]++;
+ mDurationStats[eventId] += (getTime() - start);
}
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 48b5a58..8d22eca 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -55,6 +55,7 @@
import android.view.InputDevice;
import android.media.AudioAttributes;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
@@ -111,6 +112,7 @@
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+ @GuardedBy("mLock")
private Vibration mCurrentVibration;
private int mCurVibUid = -1;
private boolean mLowPowerMode;
@@ -435,45 +437,45 @@
// If our current vibration is longer than the new vibration and is the same amplitude,
// then just let the current one finish.
- if (effect instanceof VibrationEffect.OneShot
- && mCurrentVibration != null
- && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
- VibrationEffect.OneShot currentOneShot =
- (VibrationEffect.OneShot) mCurrentVibration.effect;
- if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
- && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ synchronized (mLock) {
+ if (effect instanceof VibrationEffect.OneShot
+ && mCurrentVibration != null
+ && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+ VibrationEffect.OneShot currentOneShot =
+ (VibrationEffect.OneShot) mCurrentVibration.effect;
+ if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
+ && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
+ }
+ return;
+ }
+ }
+
+ // If the current vibration is repeating and the incoming one is non-repeating, then
+ // ignore the non-repeating vibration. This is so that we don't cancel vibrations that
+ // are meant to grab the attention of the user, like ringtones and alarms, in favor of
+ // one-shot vibrations that are likely quite short.
+ if (!isRepeatingVibration(effect)
+ && mCurrentVibration != null
+ && isRepeatingVibration(mCurrentVibration.effect)) {
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
+ Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
}
return;
}
- }
- // If the current vibration is repeating and the incoming one is non-repeating, then ignore
- // the non-repeating vibration. This is so that we don't cancel vibrations that are meant
- // to grab the attention of the user, like ringtones and alarms, in favor of one-shot
- // vibrations that are likely quite short.
- if (!isRepeatingVibration(effect)
- && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.effect)) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
- }
- return;
- }
-
- Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
- linkVibration(vib);
-
- long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
+ Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+ linkVibration(vib);
+ long ident = Binder.clearCallingIdentity();
+ try {
doCancelVibrateLocked();
startVibrationLocked(vib);
addToPreviousVibrationsLocked(vib);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
}
@@ -516,6 +518,7 @@
}
};
+ @GuardedBy("mLock")
private void doCancelVibrateLocked() {
mH.removeCallbacks(mVibrationEndRunnable);
if (mThread != null) {
@@ -538,6 +541,7 @@
}
}
+ @GuardedBy("mLock")
private void startVibrationLocked(final Vibration vib) {
if (!isAllowedToVibrateLocked(vib)) {
return;
@@ -568,6 +572,7 @@
startVibrationInnerLocked(vib);
}
+ @GuardedBy("mLock")
private void startVibrationInnerLocked(Vibration vib) {
mCurrentVibration = vib;
if (vib.effect instanceof VibrationEffect.OneShot) {
@@ -701,6 +706,7 @@
return mode;
}
+ @GuardedBy("mLock")
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
@@ -880,6 +886,7 @@
}
}
+ @GuardedBy("mLock")
private long doVibratorPrebakedEffectLocked(Vibration vib) {
final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
final boolean usingInputDeviceVibrators;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e278e38..cee1ff9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4084,9 +4084,9 @@
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
- if (app.info.isAllowedToUseHiddenApi()) {
- // This app is allowed to use undocumented and private APIs. Set
- // up its runtime with the appropriate flag.
+ if (app.info.isAllowedToUseHiddenApi() || app.instr != null) {
+ // This app is allowed to use undocumented and private APIs or is
+ // being instrumented. Set up its runtime with the appropriate flag.
runtimeFlags |= Zygote.DISABLE_HIDDEN_API_CHECKS;
}
@@ -7218,7 +7218,7 @@
handleAppDiedLocked(app, willRestart, allowRestart);
if (willRestart) {
removeLruProcessLocked(app);
- addAppLocked(app.info, null, false, null /* ABI override */);
+ addAppLocked(app.info, null, false, null /* ABI override */, app.instr);
}
} else {
mRemovedProcesses.add(app);
@@ -12490,7 +12490,8 @@
.getPersistentApplications(STOCK_PM_FLAGS | matchFlags).getList();
for (ApplicationInfo app : apps) {
if (!"android".equals(app.packageName)) {
- addAppLocked(app, null, false, null /* ABI override */);
+ addAppLocked(app, null, false, null /* ABI override */,
+ null /* instrumentation */);
}
}
} catch (RemoteException ex) {
@@ -12706,7 +12707,7 @@
}
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
- String abiOverride) {
+ String abiOverride, ActiveInstrumentation instrumentation) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@@ -12735,6 +12736,9 @@
app.persistent = true;
app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
}
+
+ app.instr = instrumentation;
+
if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
startProcessLocked(app, "added application",
@@ -14798,8 +14802,6 @@
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
BinderInternal.nSetBinderProxyCountEnabled(true);
- //STOPSHIP: Temporary BinderProxy Threshold for b/71353150
- BinderInternal.nSetBinderProxyCountWatermarks(1500, 1200);
BinderInternal.setBinderProxyCountCallback(
new BinderInternal.BinderProxyLimitListener() {
@Override
@@ -21541,8 +21543,7 @@
mUsageStatsService.reportEvent(ii.targetPackage, userId,
UsageEvents.Event.SYSTEM_INTERACTION);
}
- ProcessRecord app = addAppLocked(ai, defProcess, false, abiOverride);
- app.instr = activeInstr;
+ ProcessRecord app = addAppLocked(ai, defProcess, false, abiOverride, activeInstr);
activeInstr.mFinished = false;
activeInstr.mRunningProcesses.add(app);
if (!mActiveInstrumentation.contains(activeInstr)) {
@@ -24941,7 +24942,7 @@
mRemovedProcesses.remove(i);
if (app.persistent) {
- addAppLocked(app.info, null, false, null /* ABI override */);
+ addAppLocked(app.info, null, false, null /* ABI override */, app.instr);
}
}
}
@@ -25882,14 +25883,6 @@
public boolean isCallerRecents(int callingUid) {
return getRecentTasks().isCallerRecents(callingUid);
}
-
- @Override
- public boolean isUidActive(int uid) {
- synchronized (ActivityManagerService.this) {
- final UidRecord uidRec = mActiveUids.get(uid);
- return (uidRec != null) && !uidRec.idle;
- }
- }
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b061ba4..055b89b 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -790,7 +790,7 @@
// Instead, launch the ephemeral installer. Once the installer is finished, it
// starts either the intent we resolved here [on install error] or the ephemeral
// app [on install success].
- if (rInfo != null && rInfo.isInstantAppAvailable) {
+ if (rInfo != null && rInfo.auxiliaryInfo != null) {
intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent,
callingPackage, verificationBundle, resolvedType, userId);
resolvedType = null;
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 47a4fb2..401c05e 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -40,6 +40,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.Intent.UriFlags;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -78,7 +79,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleController;
import com.android.server.FgThread;
-import com.android.server.AppStateTracker;
+import com.android.server.ForceAppStandbyTracker;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
@@ -183,7 +184,7 @@
ActivityManagerInternal mActivityManagerInternal;
IBatteryStats mBatteryStats;
DeviceIdleController.LocalService mLocalDeviceIdleController;
- AppStateTracker mAppStateTracker;
+ final ForceAppStandbyTracker mForceAppStandbyTracker;
/**
* Set to true once we are allowed to run third party apps.
@@ -786,13 +787,20 @@
}
/**
- * Return whether an UID is active or idle.
+ * Return whether an UID is in the foreground or not.
*/
- private boolean isUidActive(int uid) {
- return mAppStateTracker.isUidActiveSynced(uid);
+ private boolean isUidInForeground(int uid) {
+ synchronized (mLock) {
+ if (mUidPriorityOverride.get(uid, 0) > 0) {
+ return true;
+ }
+ }
+ // Note UID observer may not be called in time, so we always check with the AM.
+ return mActivityManagerInternal.getUidProcessState(uid)
+ <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
}
- private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
+ private final Predicate<Integer> mIsUidInForegroundPredicate = this::isUidInForeground;
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
@@ -818,7 +826,7 @@
// If any of work item is enqueued when the source is in the foreground,
// exempt the entire job.
- toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
+ toCancel.maybeAddForegroundExemption(mIsUidInForegroundPredicate);
return JobScheduler.RESULT_SUCCESS;
}
@@ -830,7 +838,7 @@
// Note if it's a sync job, this method is called on the handler so it's not exactly
// the state when requestSync() was called, but that should be fine because of the
// 1 minute foreground grace period.
- jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);
+ jobStatus.maybeAddForegroundExemption(mIsUidInForegroundPredicate);
if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
// Jobs on behalf of others don't apply to the per-app job cap
@@ -1115,6 +1123,8 @@
mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
mControllers.add(mDeviceIdleJobsController);
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
if (!mJobs.jobTimesInflatedValid()) {
@@ -1175,8 +1185,7 @@
if (PHASE_SYSTEM_SERVICES_READY == phase) {
mConstants.start(getContext().getContentResolver());
- mAppStateTracker = Preconditions.checkNotNull(
- LocalServices.getService(AppStateTracker.class));
+ mForceAppStandbyTracker.start();
// Register br for package removals and user removals.
final IntentFilter filter = new IntentFilter();
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index e8057fb..5eb7700 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -22,10 +22,8 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.util.Preconditions;
-import com.android.server.AppStateTracker;
-import com.android.server.AppStateTracker.Listener;
-import com.android.server.LocalServices;
+import com.android.server.ForceAppStandbyTracker;
+import com.android.server.ForceAppStandbyTracker.Listener;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
import com.android.server.job.StateControllerProto;
@@ -44,7 +42,8 @@
private final JobSchedulerService mJobSchedulerService;
- private final AppStateTracker mAppStateTracker;
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
public static BackgroundJobsController get(JobSchedulerService service) {
synchronized (sCreationLock) {
@@ -60,9 +59,10 @@
super(service, context, lock);
mJobSchedulerService = service;
- mAppStateTracker = Preconditions.checkNotNull(
- LocalServices.getService(AppStateTracker.class));
- mAppStateTracker.addListener(mForceAppStandbyListener);
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
+ mForceAppStandbyTracker.start();
}
@Override
@@ -79,7 +79,7 @@
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("BackgroundJobsController");
- mAppStateTracker.dump(pw, "");
+ mForceAppStandbyTracker.dump(pw, "");
pw.println("Job state:");
mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -92,16 +92,16 @@
jobStatus.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, uid);
- pw.print(mAppStateTracker.isUidActive(uid) ? " active" : " idle");
- if (mAppStateTracker.isUidPowerSaveWhitelisted(uid) ||
- mAppStateTracker.isUidTempPowerSaveWhitelisted(uid)) {
+ pw.print(mForceAppStandbyTracker.isUidActive(uid) ? " active" : " idle");
+ if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+ mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
pw.print(", whitelisted");
}
pw.print(": ");
pw.print(sourcePkg);
pw.print(" [RUN_ANY_IN_BACKGROUND ");
- pw.print(mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg)
+ pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg)
? "allowed]" : "disallowed]");
if ((jobStatus.satisfiedConstraints
@@ -118,7 +118,7 @@
final long token = proto.start(fieldId);
final long mToken = proto.start(StateControllerProto.BACKGROUND);
- mAppStateTracker.dumpProto(proto,
+ mForceAppStandbyTracker.dumpProto(proto,
StateControllerProto.BackgroundJobsController.FORCE_APP_STANDBY_TRACKER);
mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -136,14 +136,14 @@
proto.write(TrackedJob.SOURCE_PACKAGE_NAME, sourcePkg);
proto.write(TrackedJob.IS_IN_FOREGROUND,
- mAppStateTracker.isUidActive(sourceUid));
+ mForceAppStandbyTracker.isUidActive(sourceUid));
proto.write(TrackedJob.IS_WHITELISTED,
- mAppStateTracker.isUidPowerSaveWhitelisted(sourceUid) ||
- mAppStateTracker.isUidTempPowerSaveWhitelisted(sourceUid));
+ mForceAppStandbyTracker.isUidPowerSaveWhitelisted(sourceUid) ||
+ mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(sourceUid));
proto.write(
TrackedJob.CAN_RUN_ANY_IN_BACKGROUND,
- mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(
+ mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
sourceUid, sourcePkg));
proto.write(
@@ -197,7 +197,7 @@
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
+ final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName,
(jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
!= 0);
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
index b0b07ea..671d7a6 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
@@ -363,7 +363,7 @@
indent = indent + " ";
pw.print(indent);
- pw.println("Battery Saver: Off On");
+ pw.println("Battery Saver: w/Off w/On");
dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
DozeState.NOT_DOZING, "NonDoze");
dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr",
diff --git a/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
rename to services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
index 90db2a3..a499472 100644
--- a/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -15,7 +15,7 @@
*/
package com.android.server;
-import static com.android.server.AppStateTracker.TARGET_OP;
+import static com.android.server.ForceAppStandbyTracker.TARGET_OP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -33,7 +33,6 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
@@ -64,7 +63,7 @@
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
-import com.android.server.AppStateTracker.Listener;
+import com.android.server.ForceAppStandbyTracker.Listener;
import org.junit.Before;
import org.junit.Test;
@@ -83,17 +82,17 @@
import java.util.function.Consumer;
/**
- * Tests for {@link AppStateTracker}
+ * Tests for {@link ForceAppStandbyTracker}
*
* Run with:
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class AppStateTrackerTest {
+public class ForceAppStandbyTrackerTest {
- private class AppStateTrackerTestable extends AppStateTracker {
- AppStateTrackerTestable() {
+ private class ForceAppStandbyTrackerTestable extends ForceAppStandbyTracker {
+ ForceAppStandbyTrackerTestable() {
super(mMockContext, Looper.getMainLooper());
}
@@ -113,11 +112,6 @@
}
@Override
- ActivityManagerInternal injectActivityManagerInternal() {
- return mMockIActivityManagerInternal;
- }
-
- @Override
PowerManagerInternal injectPowerManagerInternal() {
return mMockPowerManagerInternal;
}
@@ -158,9 +152,6 @@
private IActivityManager mMockIActivityManager;
@Mock
- private ActivityManagerInternal mMockIActivityManagerInternal;
-
- @Mock
private AppOpsManager mMockAppOpsManager;
@Mock
@@ -204,7 +195,7 @@
return new PowerSaveState.Builder().setBatterySaverEnabled(mPowerSaveMode).build();
}
- private AppStateTrackerTestable newInstance() throws Exception {
+ private ForceAppStandbyTrackerTestable newInstance() throws Exception {
MockitoAnnotations.initMocks(this);
when(mMockIAppOpsService.checkOperation(eq(TARGET_OP), anyInt(), anyString()))
@@ -214,12 +205,12 @@
AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
});
- final AppStateTrackerTestable instance = new AppStateTrackerTestable();
+ final ForceAppStandbyTrackerTestable instance = new ForceAppStandbyTrackerTestable();
return instance;
}
- private void callStart(AppStateTrackerTestable instance) throws RemoteException {
+ private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException {
// Set up functions that start() calls.
when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY)))
@@ -232,7 +223,7 @@
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
// Call start.
- instance.onSystemServicesReady();
+ instance.start();
// Capture the listeners.
ArgumentCaptor<IUidObserver> uidObserverArgumentCaptor =
@@ -296,7 +287,7 @@
private static final int JOBS_ONLY = 1 << 1;
private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY;
- private void areRestricted(AppStateTrackerTestable instance, int uid, String packageName,
+ private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
int restrictionTypes, boolean exemptFromBatterySaver) {
assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver));
@@ -304,13 +295,13 @@
instance.areAlarmsRestricted(uid, packageName, exemptFromBatterySaver));
}
- private void areRestricted(AppStateTrackerTestable instance, int uid, String packageName,
+ private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
int restrictionTypes) {
areRestricted(instance, uid, packageName, restrictionTypes,
/*exemptFromBatterySaver=*/ false);
}
- private void areRestrictedWithExemption(AppStateTrackerTestable instance,
+ private void areRestrictedWithExemption(ForceAppStandbyTrackerTestable instance,
int uid, String packageName, int restrictionTypes) {
areRestricted(instance, uid, packageName, restrictionTypes,
/*exemptFromBatterySaver=*/ true);
@@ -318,7 +309,7 @@
@Test
public void testAll() throws Exception {
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
assertFalse(instance.isForceAllAppsStandbyEnabled());
@@ -475,7 +466,7 @@
@Test
public void testUidStateForeground() throws Exception {
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
mIUidObserver.onUidActive(UID_1);
@@ -485,10 +476,6 @@
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
- assertTrue(instance.isUidActiveSynced(UID_1));
- assertFalse(instance.isUidActiveSynced(UID_2));
- assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
-
assertFalse(instance.isUidInForeground(UID_1));
assertFalse(instance.isUidInForeground(UID_2));
assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
@@ -502,10 +489,6 @@
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
- assertTrue(instance.isUidActiveSynced(UID_1));
- assertFalse(instance.isUidActiveSynced(UID_2));
- assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
-
assertFalse(instance.isUidInForeground(UID_1));
assertTrue(instance.isUidInForeground(UID_2));
assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
@@ -565,34 +548,14 @@
assertFalse(instance.isUidActive(UID_2));
assertTrue(instance.isUidActive(Process.SYSTEM_UID));
- assertFalse(instance.isUidActiveSynced(UID_1));
- assertFalse(instance.isUidActiveSynced(UID_2));
- assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
-
assertFalse(instance.isUidInForeground(UID_1));
assertFalse(instance.isUidInForeground(UID_2));
assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
- // The result from AMI.isUidActive() only affects isUidActiveSynced().
- when(mMockIActivityManagerInternal.isUidActive(anyInt())).thenReturn(true);
-
- assertFalse(instance.isUidActive(UID_1));
- assertFalse(instance.isUidActive(UID_2));
- assertTrue(instance.isUidActive(Process.SYSTEM_UID));
-
- assertTrue(instance.isUidActiveSynced(UID_1));
- assertTrue(instance.isUidActiveSynced(UID_2));
- assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
-
- assertFalse(instance.isUidInForeground(UID_1));
- assertFalse(instance.isUidInForeground(UID_2));
- assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
}
@Test
public void testExempt() throws Exception {
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
assertFalse(instance.isForceAllAppsStandbyEnabled());
@@ -658,7 +621,7 @@
}
public void loadPersistedAppOps() throws Exception {
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
final List<PackageOps> ops = new ArrayList<>();
@@ -668,7 +631,7 @@
AppOpsManager.OP_ACCESS_NOTIFICATIONS,
AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
entries.add(new AppOpsManager.OpEntry(
- AppStateTracker.TARGET_OP,
+ ForceAppStandbyTracker.TARGET_OP,
AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
ops.add(new PackageOps(PACKAGE_1, UID_1, entries));
@@ -676,7 +639,7 @@
//--------------------------------------------------
entries = new ArrayList<>();
entries.add(new AppOpsManager.OpEntry(
- AppStateTracker.TARGET_OP,
+ ForceAppStandbyTracker.TARGET_OP,
AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
ops.add(new PackageOps(PACKAGE_2, UID_2, entries));
@@ -684,7 +647,7 @@
//--------------------------------------------------
entries = new ArrayList<>();
entries.add(new AppOpsManager.OpEntry(
- AppStateTracker.TARGET_OP,
+ ForceAppStandbyTracker.TARGET_OP,
AppOpsManager.MODE_ALLOWED, 0, 0, 0, 0, null));
ops.add(new PackageOps(PACKAGE_1, UID_10_1, entries));
@@ -692,7 +655,7 @@
//--------------------------------------------------
entries = new ArrayList<>();
entries.add(new AppOpsManager.OpEntry(
- AppStateTracker.TARGET_OP,
+ ForceAppStandbyTracker.TARGET_OP,
AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
entries.add(new AppOpsManager.OpEntry(
AppOpsManager.OP_ACCESS_NOTIFICATIONS,
@@ -725,10 +688,10 @@
@Test
public void testPowerSaveListener() throws Exception {
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
- AppStateTracker.Listener l = mock(AppStateTracker.Listener.class);
+ ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
instance.addListener(l);
// Power save on.
@@ -768,10 +731,10 @@
@Test
public void testAllListeners() throws Exception {
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
- AppStateTracker.Listener l = mock(AppStateTracker.Listener.class);
+ ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
instance.addListener(l);
// -------------------------------------------------------------------------
@@ -1079,7 +1042,7 @@
@Test
public void testUserRemoved() throws Exception {
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
mIUidObserver.onUidActive(UID_1);
@@ -1114,7 +1077,7 @@
// This is a small battery device
mIsSmallBatteryDevice = true;
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
assertFalse(instance.isForceAllAppsStandbyEnabled());
@@ -1140,7 +1103,7 @@
// Not a small battery device, so plugged in status should not affect forced app standby
mIsSmallBatteryDevice = false;
- final AppStateTrackerTestable instance = newInstance();
+ final ForceAppStandbyTrackerTestable instance = newInstance();
callStart(instance);
assertFalse(instance.isForceAllAppsStandbyEnabled());
@@ -1189,7 +1152,7 @@
private void checkAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray, boolean expected) {
assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
- expected, AppStateTracker.isAnyAppIdUnwhitelisted(prevArray, newArray));
+ expected, ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(prevArray, newArray));
// Also test isAnyAppIdUnwhitelistedSlow.
assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
@@ -1221,7 +1184,7 @@
final int[] array2 = makeRandomArray();
final boolean expected = isAnyAppIdUnwhitelistedSlow(array1, array2);
- final boolean actual = AppStateTracker.isAnyAppIdUnwhitelisted(array1, array2);
+ final boolean actual = ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(array1, array2);
assertEquals("Input: " + Arrays.toString(array1) + " " + Arrays.toString(array2),
expected, actual);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 0abb48f..98483a8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -294,7 +294,7 @@
null /*disabledPkg*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
- null /*resourcePath*/,
+ UPDATED_CODE_PATH /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
"armeabi" /*secondaryCpuAbi*/,
@@ -328,7 +328,7 @@
null /*disabledPkg*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
- null /*resourcePath*/,
+ UPDATED_CODE_PATH /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
"armeabi" /*secondaryCpuAbi*/,
@@ -561,34 +561,6 @@
false /*notLaunched*/, false /*stopped*/, true /*installed*/);
}
- @Test
- public void testInsertPackageSetting() {
- final PackageSetting ps = createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
- final PackageParser.Package pkg = new PackageParser.Package(PACKAGE_NAME);
- pkg.applicationInfo.setCodePath(ps.codePathString);
- pkg.applicationInfo.setResourcePath(ps.resourcePathString);
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
- final Settings settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
- pkg.usesStaticLibraries = new ArrayList<>(
- Arrays.asList("foo.bar1", "foo.bar2", "foo.bar3"));
- pkg.usesStaticLibrariesVersions = new long[] {2, 4, 6};
- settings.insertPackageSettingLPw(ps, pkg);
- assertEquals(pkg, ps.pkg);
- assertArrayEquals(pkg.usesStaticLibraries.toArray(new String[0]), ps.usesStaticLibraries);
- assertArrayEquals(pkg.usesStaticLibrariesVersions, ps.usesStaticLibrariesVersions);
-
- pkg.usesStaticLibraries = null;
- pkg.usesStaticLibrariesVersions = null;
- settings.insertPackageSettingLPw(ps, pkg);
- assertEquals(pkg, ps.pkg);
- assertNull("Actual: " + Arrays.toString(ps.usesStaticLibraries), ps.usesStaticLibraries);
- assertNull("Actual: " + Arrays.toString(ps.usesStaticLibrariesVersions),
- ps.usesStaticLibrariesVersions);
- }
-
private <T> void assertArrayEquals(T[] a, T[] b) {
assertTrue("Expected: " + Arrays.toString(a) + ", actual: " + Arrays.toString(b),
Arrays.equals(a, b));
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 6799417..8c18518 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -352,8 +352,11 @@
*/
public static final int CAPABILITY_CAN_PULL_CALL = 0x00800000;
+ /** Call supports the deflect feature. */
+ public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
+
//******************************************************************************************
- // Next CAPABILITY value: 0x01000000
+ // Next CAPABILITY value: 0x02000000
//******************************************************************************************
/**
@@ -528,6 +531,9 @@
if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
builder.append(" CAPABILITY_CAN_PULL_CALL");
}
+ if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
+ builder.append(" CAPABILITY_SUPPORT_DEFLECT");
+ }
builder.append("]");
return builder.toString();
}
@@ -1235,6 +1241,15 @@
}
/**
+ * Instructs this {@link #STATE_RINGING} {@code Call} to deflect.
+ *
+ * @param address The address to which the call will be deflected.
+ */
+ public void deflect(Uri address) {
+ mInCallAdapter.deflectCall(mTelecomCallId, address);
+ }
+
+ /**
* Instructs this {@link #STATE_RINGING} {@code Call} to reject.
*
* @param rejectWithMessage Whether to reject with a text message.
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index e0b0bbf..24184e0 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -328,8 +328,11 @@
*/
public static final int CAPABILITY_CAN_PULL_CALL = 0x01000000;
+ /** Call supports the deflect feature. */
+ public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
+
//**********************************************************************************************
- // Next CAPABILITY value: 0x02000000
+ // Next CAPABILITY value: 0x04000000
//**********************************************************************************************
/**
@@ -726,6 +729,9 @@
if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
builder.append(isLong ? " CAPABILITY_CAN_PULL_CALL" : " pull");
}
+ if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
+ builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
+ }
builder.append("]");
return builder.toString();
@@ -2752,6 +2758,12 @@
/**
* Notifies this Connection, which is in {@link #STATE_RINGING}, of
+ * a request to deflect.
+ */
+ public void onDeflect(Uri address) {}
+
+ /**
+ * Notifies this Connection, which is in {@link #STATE_RINGING}, of
* a request to reject.
*/
public void onReject() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index c1040ad..211699e 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -124,6 +124,7 @@
private static final String SESSION_ABORT = "CS.ab";
private static final String SESSION_ANSWER = "CS.an";
private static final String SESSION_ANSWER_VIDEO = "CS.anV";
+ private static final String SESSION_DEFLECT = "CS.def";
private static final String SESSION_REJECT = "CS.r";
private static final String SESSION_REJECT_MESSAGE = "CS.rWM";
private static final String SESSION_SILENCE = "CS.s";
@@ -181,6 +182,7 @@
private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31;
private static final int MSG_HANDOVER_FAILED = 32;
private static final int MSG_HANDOVER_COMPLETE = 33;
+ private static final int MSG_DEFLECT = 34;
private static Connection sNullConnection;
@@ -353,6 +355,20 @@
}
@Override
+ public void deflect(String callId, Uri address, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_DEFLECT);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = address;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_DEFLECT, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void reject(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_REJECT);
try {
@@ -847,6 +863,17 @@
}
break;
}
+ case MSG_DEFLECT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_DEFLECT);
+ try {
+ deflect((String) args.arg1, (Uri) args.arg2);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
case MSG_REJECT: {
SomeArgs args = (SomeArgs) msg.obj;
Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT);
@@ -1605,6 +1632,11 @@
findConnectionForAction(callId, "answer").onAnswer();
}
+ private void deflect(String callId, Uri address) {
+ Log.d(this, "deflect %s", callId);
+ findConnectionForAction(callId, "deflect").onDeflect(address);
+ }
+
private void reject(String callId) {
Log.d(this, "reject %s", callId);
findConnectionForAction(callId, "reject").onReject();
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 658685f..8678e33 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.net.Uri;
import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.os.RemoteException;
@@ -61,6 +62,19 @@
}
/**
+ * Instructs Telecom to deflect the specified call.
+ *
+ * @param callId The identifier of the call to deflect.
+ * @param address The address to deflect.
+ */
+ public void deflectCall(String callId, Uri address) {
+ try {
+ mAdapter.deflectCall(callId, address);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Instructs Telecom to reject the specified call.
*
* @param callId The identifier of the call to reject.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 3a84f00..e35093c 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telecom;
+import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.telecom.CallAudioState;
@@ -58,6 +59,8 @@
void answer(String callId, in Session.Info sessionInfo);
+ void deflect(String callId, in Uri address, in Session.Info sessionInfo);
+
void reject(String callId, in Session.Info sessionInfo);
void rejectWithMessage(String callId, String message, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 87ccd3e..57df5c1 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telecom;
+import android.net.Uri;
import android.os.Bundle;
import android.telecom.PhoneAccountHandle;
@@ -29,6 +30,8 @@
oneway interface IInCallAdapter {
void answerCall(String callId, int videoState);
+ void deflectCall(String callId, in Uri address);
+
void rejectCall(String callId, boolean rejectWithMessage, String textMessage);
void disconnectCall(String callId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7eb691b..c8c898a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1376,6 +1376,12 @@
*/
public static final String KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL = "allow_hold_in_ims_call";
+ /**
+ * Flag indicating whether the carrier supports call deflection for an incoming IMS call.
+ * @hide
+ */
+ public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL =
+ "carrier_allow_deflect_ims_call_bool";
/**
* Flag indicating whether the carrier always wants to play an "on-hold" tone when a call has
@@ -1844,6 +1850,7 @@
static {
sDefaults = new PersistableBundle();
sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL, false);
sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true);
sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false);
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index c3d103f..a20d4f5 100644
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -754,6 +754,22 @@
}
/**
+ * Deflects an incoming call.
+ *
+ * @param number number to be deflected to
+ */
+ public void deflect(String number) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ miSession.deflect(number);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
index 00cb1e2..e5ed825 100644
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -182,6 +182,15 @@
}
/**
+ * Deflects an incoming call.
+ *
+ * @param deflectNumber number to deflect the call
+ */
+ @Override
+ public void deflect(String deflectNumber) {
+ }
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index c6ca6fd..7b9fe2b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -174,6 +174,11 @@
}
@Override
+ public void deflect(String deflectNumber) {
+ ImsCallSessionImplBase.this.deflect(deflectNumber);
+ }
+
+ @Override
public void reject(int reason) {
ImsCallSessionImplBase.this.reject(reason);
}
@@ -395,6 +400,14 @@
}
/**
+ * Deflects an incoming call.
+ *
+ * @param deflectNumber number to deflect the call
+ */
+ public void deflect(String deflectNumber) {
+ }
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index 203e6cf..15234e5 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -136,6 +136,13 @@
void accept(int callType, in ImsStreamMediaProfile profile);
/**
+ * Deflects an incoming call.
+ *
+ * @param deflectNumber number to deflect the call
+ */
+ void deflect(String deflectNumber);
+
+ /**
* Rejects an incoming call or session update.
*
* @param reason reason code to reject an incoming call
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 0b81dd2..3fb4fae 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -837,8 +837,8 @@
}
dest.writeByte(result.LCR.id);
if (result.LCR.id != (byte) 0xFF) {
- dest.writeInt((byte) result.LCR.data.length);
- dest.writeByte(result.LCR.id);
+ dest.writeByte((byte) result.LCR.data.length);
+ dest.writeByteArray(result.LCR.data);
}
dest.writeByte(result.secure ? (byte) 1 : 0);
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index c46789c..8024bf0 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -273,27 +273,6 @@
public RadioChainInfo[] radioChainInfos;
/**
- * @hide
- * Update RSSI of the scan result
- * @param previousRssi
- * @param previousSeen
- * @param maxAge
- */
- public void averageRssi(int previousRssi, long previousSeen, int maxAge) {
-
- if (seen == 0) {
- seen = System.currentTimeMillis();
- }
- long age = seen - previousSeen;
-
- if (previousSeen > 0 && age > 0 && age < maxAge/2) {
- // Average the RSSI with previously seen instances of this scan result
- double alpha = 0.5 - (double) age / (double) maxAge;
- level = (int) ((double) level * (1 - alpha) + (double) previousRssi * alpha);
- }
- }
-
- /**
* Status indicating the scan result does not correspond to a user's saved configuration
* @hide
* @removed