summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apex/statsd/Android.bp1
-rw-r--r--apex/statsd/tests/libstatspull/Android.bp2
-rw-r--r--cmds/statsd/Android.bp6
-rw-r--r--core/java/android/content/pm/FileSystemControlParcel.aidl3
-rw-r--r--core/java/android/os/incremental/IIncrementalService.aidl7
-rw-r--r--core/java/android/os/incremental/IIncrementalServiceConnector.aidl27
-rw-r--r--core/java/android/os/incremental/IncrementalManager.java19
-rw-r--r--core/java/android/service/dataloader/DataLoaderService.java22
-rw-r--r--core/proto/android/stats/dnsresolver/dns_resolver.proto21
-rw-r--r--packages/SystemUI/res/layout/bubble_dismiss_target.xml2
-rw-r--r--packages/SystemUI/res/layout/pip_dismiss_view.xml35
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/Interpolators.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java188
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java253
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt8
-rw-r--r--packages/Tethering/tests/unit/AndroidManifest.xml9
-rw-r--r--packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java56
-rw-r--r--packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java194
-rw-r--r--packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java7
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java131
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java419
-rw-r--r--services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java403
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java36
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java4
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java20
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/Task.java8
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java28
-rw-r--r--services/incremental/BinderIncrementalService.cpp5
-rw-r--r--services/incremental/BinderIncrementalService.h1
-rw-r--r--services/incremental/IncrementalService.cpp8
-rw-r--r--services/incremental/IncrementalService.h16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java15
-rw-r--r--telephony/common/android/telephony/LocationAccessPolicy.java2
-rw-r--r--tests/net/common/java/android/net/DependenciesTest.java113
-rw-r--r--tests/net/common/java/android/net/NetworkCapabilitiesTest.java241
-rw-r--r--tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt124
-rw-r--r--tools/stats_log_api_gen/Collation.h2
58 files changed, 1969 insertions, 1000 deletions
diff --git a/Android.bp b/Android.bp
index 874d76fe8d00..d4ca7066781a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -911,6 +911,7 @@ cc_library {
filegroup {
name: "incremental_aidl",
srcs: [
+ "core/java/android/os/incremental/IIncrementalServiceConnector.aidl",
"core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
],
path: "core/java",
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 32e13e31eebe..15d74951019d 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -67,7 +67,6 @@ cc_library_shared {
"liblog", // Has a stable abi - should not be copied into apex.
"libstatssocket",
],
- //TODO: is libc++_static correct?
stl: "libc++_static",
cflags: [
"-Wall",
diff --git a/apex/statsd/tests/libstatspull/Android.bp b/apex/statsd/tests/libstatspull/Android.bp
index 2d64f190839c..0df96e149d4f 100644
--- a/apex/statsd/tests/libstatspull/Android.bp
+++ b/apex/statsd/tests/libstatspull/Android.bp
@@ -32,7 +32,7 @@ android_test {
"protos/**/*.proto",
],
test_suites: [
- "general-tests",
+ "device-tests",
],
platform_apis: true,
privileged: true,
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index d3d7e1d483e8..65061d0c9bda 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -113,17 +113,18 @@ cc_defaults {
static_libs: [
"libbase",
"libcutils",
+ "libgtest_prod",
"libprotoutil",
"libstatsmetadata",
"libstatslog_statsd",
"libsysutils",
"libutils",
+ "statsd-aidl-ndk_platform",
],
shared_libs: [
"libbinder_ndk",
"libincident",
"liblog",
- "statsd-aidl-ndk_platform",
],
}
@@ -268,10 +269,11 @@ cc_binary {
proto: {
type: "lite",
+ static: true,
},
+ stl: "libc++_static",
shared_libs: [
- "libgtest_prod",
"libstatssocket",
],
diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl
index f00feaeb2f5a..92df16ced8a3 100644
--- a/core/java/android/content/pm/FileSystemControlParcel.aidl
+++ b/core/java/android/content/pm/FileSystemControlParcel.aidl
@@ -17,6 +17,7 @@
package android.content.pm;
import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.os.incremental.IIncrementalServiceConnector;
import android.os.incremental.IncrementalFileSystemControlParcel;
/**
@@ -26,6 +27,8 @@ import android.os.incremental.IncrementalFileSystemControlParcel;
parcelable FileSystemControlParcel {
// Incremental FS control descriptors.
@nullable IncrementalFileSystemControlParcel incremental;
+ // Incremental FS service.
+ @nullable IIncrementalServiceConnector service;
// Callback-based installation connector.
@nullable IPackageInstallerSessionFileSystemConnector callback;
}
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index d8308c7c3362..2dbaea860e2a 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -38,13 +38,6 @@ interface IIncrementalService {
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
- * Changes storage params. Returns 0 on success, and -errno on failure.
- * Use enableReadLogs to switch pages read logs reporting on and off.
- * Returns 0 on success, and - errno on failure: permission check or remount.
- */
- int setStorageParams(int storageId, boolean enableReadLogs);
-
- /**
* Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
*/
const int BIND_TEMPORARY = 0;
diff --git a/core/java/android/os/incremental/IIncrementalServiceConnector.aidl b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl
new file mode 100644
index 000000000000..5800ecf63a1e
--- /dev/null
+++ b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+/** @hide */
+interface IIncrementalServiceConnector {
+ /**
+ * Changes storage params. Returns 0 on success, and -errno on failure.
+ * Use enableReadLogs to switch pages read logs reporting on and off.
+ * Returns 0 on success, and - errno on failure: permission check or remount.
+ */
+ int setStorageParams(boolean enableReadLogs);
+}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 5f01408944e8..35518db32829 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -19,13 +19,11 @@ package android.os.incremental;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.DataLoaderParams;
import android.content.pm.IDataLoaderStatusListener;
import android.os.RemoteException;
-import android.system.ErrnoException;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -321,23 +319,6 @@ public final class IncrementalManager {
return nativeUnsafeGetFileSignature(path);
}
- /**
- * Sets storage parameters.
- *
- * @param enableReadLogs - enables or disables read logs. Caller has to have a permission.
- */
- @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS)
- public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException {
- try {
- int res = mService.setStorageParams(storageId, enableReadLogs);
- if (res < 0) {
- throw new ErrnoException("setStorageParams", -res);
- }
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
/* Native methods */
private static native boolean nativeIsEnabled();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index 05877a59368a..c047dc0d07c7 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.Service;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.DataLoaderParams;
import android.content.pm.DataLoaderParamsParcel;
@@ -32,8 +31,6 @@ import android.content.pm.InstallationFile;
import android.content.pm.InstallationFileParcel;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import android.os.incremental.IncrementalManager;
-import android.system.ErrnoException;
import android.util.ExceptionUtils;
import android.util.Slog;
@@ -211,25 +208,6 @@ public abstract class DataLoaderService extends Service {
private final long mNativeInstance;
}
- /* Used by native FileSystemConnector. */
- private boolean setStorageParams(int storageId, boolean enableReadLogs) {
- IncrementalManager incrementalManager = (IncrementalManager) getSystemService(
- Context.INCREMENTAL_SERVICE);
- if (incrementalManager == null) {
- Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId);
- return false;
- }
- try {
- // This has to be done directly in incrementalManager as the storage
- // might be missing still.
- incrementalManager.setStorageParams(storageId, enableReadLogs);
- } catch (ErrnoException e) {
- Slog.e(TAG, "Failed to set params for storage: " + storageId, e);
- return false;
- }
- return true;
- }
-
/* Native methods */
private native boolean nativeCreateDataLoader(int storageId,
@NonNull FileSystemControlParcel control,
diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto
index 76f8f0febf59..61b9b25fe7cf 100644
--- a/core/proto/android/stats/dnsresolver/dns_resolver.proto
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -62,6 +62,13 @@ enum NsRcode {
NS_R_NOTAUTH = 9; // Not authoritative for zone
NS_R_NOTZONE = 10; // Zone of record different from zone section
NS_R_MAX = 11;
+ // Define rcode=12~15(UNASSIGNED) in rcode enum type.
+ // Some DNS Servers might return undefined code to devices.
+ // Without the enum definition, that would be noise for our dashboard.
+ NS_R_UNASSIGNED12 = 12; // Unassigned
+ NS_R_UNASSIGNED13 = 13; // Unassigned
+ NS_R_UNASSIGNED14 = 14; // Unassigned
+ NS_R_UNASSIGNED15 = 15; // Unassigned
// The following are EDNS extended rcodes
NS_R_BADVERS = 16;
// The following are TSIG errors
@@ -170,12 +177,22 @@ enum NetworkType {
NT_BLUETOOTH = 3;
// Indicates this network uses an Ethernet transport.
NT_ETHERNET = 4;
- // Indicates this network uses a VPN transport.
- NT_VPN = 5;
+ // Indicates this network uses a VPN transport, now deprecated.
+ NT_VPN = 5 [deprecated=true];
// Indicates this network uses a Wi-Fi Aware transport.
NT_WIFI_AWARE = 6;
// Indicates this network uses a LoWPAN transport.
NT_LOWPAN = 7;
+ // Indicates this network uses a Cellular+VPN transport.
+ NT_CELLULAR_VPN = 8;
+ // Indicates this network uses a Wi-Fi+VPN transport.
+ NT_WIFI_VPN = 9;
+ // Indicates this network uses a Bluetooth+VPN transport.
+ NT_BLUETOOTH_VPN = 10;
+ // Indicates this network uses an Ethernet+VPN transport.
+ NT_ETHERNET_VPN = 11;
+ // Indicates this network uses a Wi-Fi+Cellular+VPN transport.
+ NT_WIFI_CELLULAR_VPN = 12;
}
enum CacheStatus{
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
index ca085b69c35d..f5cd727a6d03 100644
--- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -17,7 +17,7 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="@dimen/pip_dismiss_gradient_height"
+ android:layout_height="@dimen/floating_dismiss_gradient_height"
android:layout_gravity="bottom|center_horizontal">
<FrameLayout
diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml
deleted file mode 100644
index 2cc4b220fe2b..000000000000
--- a/packages/SystemUI/res/layout/pip_dismiss_view.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2016 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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/pip_dismiss_gradient_height"
- android:alpha="0">
-
- <TextView
- android:id="@+id/pip_dismiss_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center_horizontal"
- android:text="@string/pip_phone_dismiss_hint"
- android:textColor="#FFFFFFFF"
- android:textSize="14sp"
- android:shadowColor="@android:color/black"
- android:shadowDx="-2"
- android:shadowDy="2"
- android:shadowRadius="0.01" />
-
-</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9b9fbed0d904..bce5fac76cfc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -955,7 +955,7 @@
<dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen>
<!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
- <dimen name="pip_dismiss_gradient_height">176dp</dimen>
+ <dimen name="floating_dismiss_gradient_height">176dp</dimen>
<!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. -->
<dimen name="pip_dismiss_text_bottom_margin">24dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
index d1544346a25a..af5196f92bcb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
@@ -24,6 +24,8 @@ import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
@@ -40,6 +42,7 @@ import androidx.palette.graphics.Palette;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.MediaControllerFactory;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
@@ -71,10 +74,11 @@ public class KeyguardMediaPlayer {
private KeyguardMediaObserver mObserver;
@Inject
- public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) {
+ public KeyguardMediaPlayer(Context context, MediaControllerFactory factory,
+ @Background Executor backgroundExecutor) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
- mViewModel = new KeyguardMediaViewModel(context);
+ mViewModel = new KeyguardMediaViewModel(context, factory);
}
/** Binds media controls to a view hierarchy. */
@@ -139,14 +143,16 @@ public class KeyguardMediaPlayer {
private static final class KeyguardMediaViewModel {
private final Context mContext;
+ private final MediaControllerFactory mMediaControllerFactory;
private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>();
private final Object mActionsLock = new Object();
private List<PendingIntent> mActions;
private float mAlbumArtRadius;
private int mAlbumArtSize;
- KeyguardMediaViewModel(Context context) {
+ KeyguardMediaViewModel(Context context, MediaControllerFactory factory) {
mContext = context;
+ mMediaControllerFactory = factory;
loadDimens();
}
@@ -162,6 +168,17 @@ public class KeyguardMediaPlayer {
public void updateControls(NotificationEntry entry, Icon appIcon,
MediaMetadata mediaMetadata) {
+ // Check the playback state of the media controller. If it is null, then the session was
+ // probably destroyed. Don't update in this case.
+ final MediaSession.Token token = entry.getSbn().getNotification().extras
+ .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+ final MediaController controller = token != null
+ ? mMediaControllerFactory.create(token) : null;
+ if (controller != null && controller.getPlaybackState() == null) {
+ clearControls();
+ return;
+ }
+
// Foreground and Background colors computed from album art
Notification notif = entry.getSbn().getNotification();
int fgColor = notif.color;
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 6923079dd5c4..13d6a9bef266 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -49,6 +49,8 @@ public class Interpolators {
public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
public static final Interpolator HEADS_UP_APPEAR = new HeadsUpAppearInterpolator();
public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+ public static final Interpolator SHADE_ANIMATION =
+ new PathInterpolator(0.6f, 0.02f, 0.4f, 0.98f);
public static final Interpolator ICON_OVERSHOT_LESS
= new PathInterpolator(0.4f, 0f, 0.2f, 1.1f);
public static final Interpolator PANEL_CLOSE_ACCELERATED
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index eff693436451..044feaa117c8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -514,7 +514,7 @@ public class BubbleStackView extends FrameLayout {
mDismissTargetContainer = new FrameLayout(context);
mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
MATCH_PARENT,
- getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
+ getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
Gravity.BOTTOM));
mDismissTargetContainer.setClipChildren(false);
mDismissTargetContainer.addView(targetView);
@@ -523,7 +523,7 @@ public class BubbleStackView extends FrameLayout {
// Start translated down so the target springs up.
targetView.setTranslationY(
- getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height));
+ getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
// MagnetizedObjects.
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index c16dce12041d..3f88f252bfe7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -40,7 +40,7 @@ public class DozeDockHandler implements DozeMachine.Part {
private int mDockState = DockManager.STATE_NONE;
- public DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine,
+ DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine,
DockManager dockManager) {
mMachine = machine;
mConfig = config;
@@ -74,8 +74,13 @@ public class DozeDockHandler implements DozeMachine.Part {
@Override
public void onEvent(int dockState) {
if (DEBUG) Log.d(TAG, "dock event = " + dockState);
- final DozeMachine.State nextState;
+
mDockState = dockState;
+ if (isPulsing()) {
+ return;
+ }
+
+ DozeMachine.State nextState;
switch (mDockState) {
case DockManager.STATE_DOCKED:
nextState = State.DOZE_AOD_DOCKED;
@@ -90,10 +95,15 @@ public class DozeDockHandler implements DozeMachine.Part {
default:
return;
}
-
mMachine.requestState(nextState);
}
+ private boolean isPulsing() {
+ DozeMachine.State state = mMachine.getState();
+ return state == State.DOZE_REQUEST_PULSE || state == State.DOZE_PULSING
+ || state == State.DOZE_PULSING_BRIGHT;
+ }
+
void register() {
if (mRegistered) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index f7f9afdd2928..18bfd899a4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -339,8 +339,8 @@ public class DozeMachine {
return State.DOZE;
}
if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
- || mState == State.DOZE_AOD || mState == State.DOZE)
- && requestedState == State.DOZE_PULSE_DONE) {
+ || mState == State.DOZE_AOD || mState == State.DOZE
+ || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) {
Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
return mState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
new file mode 100644
index 000000000000..71bc7c20c026
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link MediaController} constructor.
+ */
+public class MediaControllerFactory {
+
+ private final Context mContext;
+
+ @Inject
+ public MediaControllerFactory(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Creates a new MediaController from a session's token.
+ *
+ * @param token The token for the session. This value must never be null.
+ */
+ public MediaController create(MediaSession.Token token) {
+ return new MediaController(mContext, token);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 1868536dca98..e24d29f1aedf 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -231,7 +231,12 @@ public class PipBoundsHandler {
/**
* @return {@link Rect} of the destination PiP window bounds.
*/
- Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) {
+ Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
+ Size minimalSize) {
+ if (!componentName.equals(mLastPipComponentName)) {
+ onResetReentryBoundsUnchecked();
+ mLastPipComponentName = componentName;
+ }
final Rect destinationBounds;
if (bounds == null) {
final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
@@ -246,11 +251,7 @@ public class PipBoundsHandler {
transformBoundsToAspectRatio(destinationBounds, aspectRatio,
false /* useCurrentMinEdgeSize */);
}
- if (destinationBounds.equals(bounds)) {
- return bounds;
- }
mAspectRatio = aspectRatio;
- onResetReentryBoundsUnchecked();
mLastDestinationBounds.set(destinationBounds);
return destinationBounds;
}
@@ -483,6 +484,7 @@ public class PipBoundsHandler {
pw.println(prefix + TAG);
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
+ pw.println(innerPrefix + "mReentrySize=" + mReentrySize);
pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index d9872d7dcf17..b10dd93fa695 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -247,7 +247,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
public void onTaskAppeared(ActivityManager.RunningTaskInfo info) {
Objects.requireNonNull(info, "Requires RunningTaskInfo");
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(info.pictureInPictureParams),
+ info.topActivity, getAspectRatioOrDefault(info.pictureInPictureParams),
null /* bounds */, getMinimalSize(info.topActivityInfo));
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
mTaskInfo = info;
@@ -303,7 +303,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
return;
}
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(newParams),
+ info.topActivity, getAspectRatioOrDefault(newParams),
null /* bounds */, getMinimalSize(info.topActivityInfo));
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
@@ -335,7 +335,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
}
final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams),
+ mTaskInfo.topActivity, getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams),
null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
if (newDestinationBounds.equals(currentDestinationBounds)) return;
if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
deleted file mode 100644
index b7258117c48c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2016 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.pip.phone;
-
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.widget.FrameLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-/**
- * Displays the dismiss UI and target for floating objects.
- */
-public class PipDismissViewController {
-
- // This delay controls how long to wait before we show the target when the user first moves
- // the PIP, to prevent the target from animating if the user just wants to fling the PIP
- public static final int SHOW_TARGET_DELAY = 100;
- private static final int SHOW_TARGET_DURATION = 350;
- private static final int HIDE_TARGET_DURATION = 225;
-
- private Context mContext;
- private WindowManager mWindowManager;
- private View mDismissView;
-
- // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss
- private View mTargetView;
- private int mTargetSlop;
- private Point mWindowSize;
- private int[] mLoc = new int[2];
- private boolean mIntersecting;
- private Vibrator mVibe;
-
- public PipDismissViewController(Context context) {
- mContext = context;
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- }
-
- /**
- * Creates the dismiss target for showing via {@link #showDismissTarget()}.
- */
- public void createDismissTarget() {
- if (mDismissView == null) {
- // Determine sizes for the view
- final Rect stableInsets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
- mWindowSize = new Point();
- mWindowManager.getDefaultDisplay().getRealSize(mWindowSize);
- final int gradientHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_dismiss_gradient_height);
- final int bottomMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_dismiss_text_bottom_margin);
- mTargetSlop = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_dismiss_slop);
-
- // Create a new view for the dismiss target
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null);
- mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- mDismissView.forceHasOverlappingRendering(false);
-
- // Set the gradient background
- Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
- gradient.setAlpha((int) (255 * 0.85f));
- mDismissView.setBackground(gradient);
-
- // Adjust bottom margins of the text
- mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text);
- FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams();
- tlp.bottomMargin = stableInsets.bottom + bottomMargin;
- mTargetView.setLayoutParams(tlp);
-
- // Add the target to the window
- LayoutParams lp = new LayoutParams(
- LayoutParams.MATCH_PARENT, gradientHeight,
- 0, mWindowSize.y - gradientHeight,
- LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | LayoutParams.FLAG_NOT_TOUCHABLE
- | LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- lp.setTitle("pip-dismiss-overlay");
- lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.setFitInsetsTypes(0 /* types */);
- mWindowManager.addView(mDismissView, lp);
- }
- mDismissView.animate().cancel();
- }
-
-
- /**
- * Updates the dismiss target based on location of the view, only used for bubbles not for PIP.
- *
- * @return whether the view is within the dismiss target.
- */
- public boolean updateTarget(View view) {
- if (mDismissView == null) {
- return false;
- }
- if (mDismissView.getAlpha() > 0) {
- view.getLocationOnScreen(mLoc);
- Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(),
- mLoc[1] + view.getHeight());
- mTargetView.getLocationOnScreen(mLoc);
- Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(),
- mLoc[1] + mTargetView.getHeight());
- expandRect(targetRect, mTargetSlop);
- boolean intersecting = targetRect.intersect(viewRect);
- if (intersecting != mIntersecting) {
- // TODO: is this the right effect?
- mVibe.vibrate(VibrationEffect.get(intersecting
- ? VibrationEffect.EFFECT_CLICK
- : VibrationEffect.EFFECT_TICK));
- }
- mIntersecting = intersecting;
- return intersecting;
- }
- return false;
- }
-
- /**
- * Shows the dismiss target.
- */
- public void showDismissTarget() {
- mDismissView.animate()
- .alpha(1f)
- .setInterpolator(Interpolators.LINEAR)
- .setStartDelay(SHOW_TARGET_DELAY)
- .setDuration(SHOW_TARGET_DURATION)
- .start();
- }
-
- /**
- * Hides and destroys the dismiss target.
- */
- public void destroyDismissTarget() {
- if (mDismissView != null) {
- mDismissView.animate()
- .alpha(0f)
- .setInterpolator(Interpolators.LINEAR)
- .setStartDelay(0)
- .setDuration(HIDE_TARGET_DURATION)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mWindowManager.removeViewImmediate(mDismissView);
- mDismissView = null;
- }
- })
- .start();
- }
- }
-
- private void expandRect(Rect outRect, int expandAmount) {
- outRect.left = Math.max(0, outRect.left - expandAmount);
- outRect.top = Math.max(0, outRect.top - expandAmount);
- outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount);
- outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 8397c65dbdb0..a192afceddb9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -24,8 +24,6 @@ import android.annotation.Nullable;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityTaskManager;
import android.content.Context;
-import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
import android.os.RemoteException;
@@ -40,6 +38,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.animation.FloatProperties;
import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -61,9 +60,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/** Friction to use for PIP when it moves via physics fling animations. */
private static final float DEFAULT_FRICTION = 2f;
- // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
- private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f;
-
private final Context mContext;
private final IActivityTaskManager mActivityTaskManager;
private final PipTaskOrganizer mPipTaskOrganizer;
@@ -103,7 +99,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/**
* Update listener that resizes the PIP to {@link #mAnimatedBounds}.
*/
- private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
+ final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
(target, values) -> resizePipUnchecked(mAnimatedBounds);
/** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
@@ -122,6 +118,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set;
+ /**
+ * Whether we're springing to the touch event location (vs. moving it to that position
+ * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
+ * 'stuck' in the target and needs to catch up to the touch location.
+ */
+ private boolean mSpringingToTouch = false;
+
public PipMotionHelper(Context context, IActivityTaskManager activityTaskManager,
PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils,
@@ -211,9 +214,35 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mFloatingContentCoordinator.onContentMoved(this);
}
- cancelAnimations();
- resizePipUnchecked(toBounds);
- mBounds.set(toBounds);
+ if (!mSpringingToTouch) {
+ // If we are moving PIP directly to the touch event locations, cancel any animations and
+ // move PIP to the given bounds.
+ cancelAnimations();
+ resizePipUnchecked(toBounds);
+ mBounds.set(toBounds);
+ } else {
+ // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
+ // to spring towards the new touch location.
+ mAnimatedBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
+ .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig)
+ .withEndActions(() -> mSpringingToTouch = false);
+
+ startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
+ }
+ }
+
+ /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
+ void setSpringingToTouch(boolean springingToTouch) {
+ if (springingToTouch) {
+ mAnimatedBounds.set(mBounds);
+ }
+
+ mSpringingToTouch = springingToTouch;
+ }
+
+ void prepareForAnimation() {
+ mAnimatedBounds.set(mBounds);
}
/**
@@ -278,24 +307,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
/**
- * @return whether the PiP at the current bounds should be dismissed.
- */
- boolean shouldDismissPip() {
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- final int y = displaySize.y - mStableInsets.bottom;
- if (mBounds.bottom > y) {
- float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height();
- return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION;
- }
- return false;
- }
-
- /**
* Flings the PiP to the closest snap target.
*/
void flingToSnapTarget(
- float velocityX, float velocityY, Runnable updateAction, @Nullable Runnable endAction) {
+ float velocityX, float velocityY,
+ @Nullable Runnable updateAction, @Nullable Runnable endAction) {
mAnimatedBounds.set(mBounds);
mAnimatedBoundsPhysicsAnimator
.flingThenSpring(
@@ -303,9 +319,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
true /* flingMustReachMinOrMax */)
.flingThenSpring(
FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
- .addUpdateListener((target, values) -> updateAction.run())
.withEndActions(endAction);
+ if (updateAction != null) {
+ mAnimatedBoundsPhysicsAnimator.addUpdateListener(
+ (target, values) -> updateAction.run());
+ }
+
final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right;
final float estimatedFlingYEndValue =
PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
@@ -338,16 +358,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* Animates the dismissal of the PiP off the edge of the screen.
*/
void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) {
- final float velocity = PointF.length(velocityX, velocityY);
- final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
- final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling);
-
mAnimatedBounds.set(mBounds);
- // Animate to the dismiss end point, and then dismiss PIP.
+ // Animate off the bottom of the screen, then dismiss PIP.
mAnimatedBoundsPhysicsAnimator
- .spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig)
- .spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig)
+ .spring(FloatProperties.RECT_Y,
+ mBounds.bottom + mBounds.height(),
+ velocityY,
+ mSpringConfig)
.withEndActions(this::dismissPip);
// If we were provided with an update action, run it whenever there's an update.
@@ -356,7 +374,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
(target, values) -> updateAction.run());
}
- startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */);
+ startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */);
}
/**
@@ -408,6 +426,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private void cancelAnimations() {
mAnimatedBoundsPhysicsAnimator.cancel();
mAnimatingToBounds.setEmpty();
+ mSpringingToTouch = false;
}
/** Set new fling configs whose min/max values respect the given movement bounds. */
@@ -426,7 +445,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* the 'real' bounds to equal the final animated bounds.
*/
private void startBoundsAnimator(float toX, float toY) {
- cancelAnimations();
+ if (!mSpringingToTouch) {
+ cancelAnimations();
+ }
// Set animatingToBounds directly to avoid allocating a new Rect, but then call
// setAnimatingToBounds to run the normal logic for changing animatingToBounds.
@@ -484,47 +505,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
/**
- * @return the coordinates the PIP should animate to based on the direction of velocity when
- * dismissing.
+ * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
+ * magnetic dismiss target so it can calculate PIP's size and position.
*/
- private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) {
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- final float bottomBound = displaySize.y + pipBounds.height() * .1f;
- if (isFling && velX != 0 && velY != 0) {
- // Line is defined by: y = mx + b, m = slope, b = y-intercept
- // Find the slope
- final float slope = velY / velX;
- // Sub in slope and PiP position to solve for y-intercept: b = y - mx
- final float yIntercept = pipBounds.top - slope * pipBounds.left;
- // Now find the point on this line when y = bottom bound: x = (y - b) / m
- final float x = (bottomBound - yIntercept) / slope;
- return new Point((int) x, (int) bottomBound);
- } else {
- // If it wasn't a fling the velocity on 'up' is not reliable for direction of movement,
- // just animate downwards.
- return new Point(pipBounds.left, (int) bottomBound);
- }
- }
+ MagnetizedObject<Rect> getMagnetizedPip() {
+ return new MagnetizedObject<Rect>(
+ mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+ @Override
+ public float getWidth(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.width();
+ }
- /**
- * @return whether the gesture it towards the dismiss area based on the velocity when
- * dismissing.
- */
- public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY,
- boolean isFling) {
- Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling);
- // Center the point
- endpoint.x += pipBounds.width() / 2;
- endpoint.y += pipBounds.height() / 2;
-
- // The dismiss area is the middle third of the screen, half the PIP's height from the bottom
- Point size = new Point();
- mContext.getDisplay().getRealSize(size);
- final int left = size.x / 3;
- Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2,
- size.y + pipBounds.height());
- return dismissArea.contains(endpoint.x, endpoint.y);
+ @Override
+ public float getHeight(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.height();
+ }
+
+ @Override
+ public void getLocationOnScreen(
+ @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
+ loc[0] = animatedPipBounds.left;
+ loc[1] = animatedPipBounds.top;
+ }
+ };
}
public void dump(PrintWriter pw, String prefix) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 7cc2759ad59a..bbb493966533 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -20,11 +20,13 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STAT
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import android.annotation.SuppressLint;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -33,14 +35,23 @@ import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
+import android.view.Gravity;
import android.view.IPinnedStackController;
import android.view.InputEvent;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.logging.MetricsLoggerWrapper;
@@ -51,7 +62,10 @@ import com.android.systemui.pip.PipTaskOrganizer;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
import java.io.PrintWriter;
@@ -62,9 +76,6 @@ import java.io.PrintWriter;
public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
- // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed.
- private static final boolean ENABLE_FLING_DISMISS = false;
-
private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225;
private static final int BOTTOM_OFFSET_BUFFER_DP = 1;
@@ -73,17 +84,45 @@ public class PipTouchHandler {
// Allow PIP to resize to a slightly bigger state upon touch
private final boolean mEnableResize;
private final Context mContext;
+ private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final PipBoundsHandler mPipBoundsHandler;
private PipResizeGestureHandler mPipResizeGestureHandler;
private IPinnedStackController mPinnedStackController;
private final PipMenuActivityController mMenuController;
- private final PipDismissViewController mDismissViewController;
private final PipSnapAlgorithm mSnapAlgorithm;
private final AccessibilityManager mAccessibilityManager;
private boolean mShowPipMenuOnAnimationEnd = false;
+ /**
+ * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+ * PIP.
+ */
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Container for the dismiss circle, so that it can be animated within the container via
+ * translation rather than within the WindowManager via slow layout animations.
+ */
+ private ViewGroup mTargetViewContainer;
+
+ /** Circle view used to render the dismiss target. */
+ private DismissCircleView mTargetView;
+
+ /**
+ * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+ */
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ /** PhysicsAnimator instance for animating the dismiss target in/out. */
+ private PhysicsAnimator<View> mMagneticTargetAnimator;
+
+ /** Default configuration to use for springing the dismiss target in/out. */
+ private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
+ new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
// The current movement bounds
private Rect mMovementBounds = new Rect();
// The current resized bounds, changed by user resize.
@@ -104,21 +143,20 @@ public class PipTouchHandler {
private int mDeferResizeToNormalBoundsUntilRotation = -1;
private int mDisplayRotation;
+ /**
+ * Runnable that can be posted delayed to show the target. This needs to be saved as a member
+ * variable so we can pass it to removeCallbacks.
+ */
+ private Runnable mShowTargetAction = this::showDismissTargetMaybe;
+
private Handler mHandler = new Handler();
- private Runnable mShowDismissAffordance = new Runnable() {
- @Override
- public void run() {
- if (mEnableDismissDragToEdge) {
- mDismissViewController.showDismissTarget();
- }
- }
- };
// Behaviour states
private int mMenuState = MENU_STATE_NONE;
private boolean mIsImeShowing;
private int mImeHeight;
private int mImeOffset;
+ private int mDismissAreaHeight;
private boolean mIsShelfShowing;
private int mShelfHeight;
private int mMovementBoundsExtraOffsets;
@@ -168,6 +206,7 @@ public class PipTouchHandler {
}
}
+ @SuppressLint("InflateParams")
public PipTouchHandler(Context context, IActivityManager activityManager,
IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
InputConsumerController inputConsumerController,
@@ -180,9 +219,9 @@ public class PipTouchHandler {
mContext = context;
mActivityManager = activityManager;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mMenuController = menuController;
mMenuController.addListener(new PipMenuListener());
- mDismissViewController = new PipDismissViewController(context);
mSnapAlgorithm = pipSnapAlgorithm;
mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
2.5f);
@@ -200,6 +239,7 @@ public class PipTouchHandler {
mExpandedShortestEdgeSize = res.getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
@@ -212,6 +252,56 @@ public class PipTouchHandler {
mFloatingContentCoordinator = floatingContentCoordinator;
mConnection = new PipAccessibilityInteractionConnection(mMotionHelper,
this::onAccessibilityShowMenu, mHandler);
+
+ final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ mTargetView = new DismissCircleView(context);
+ final FrameLayout.LayoutParams newParams =
+ new FrameLayout.LayoutParams(targetSize, targetSize);
+ newParams.gravity = Gravity.CENTER;
+ mTargetView.setLayoutParams(newParams);
+
+ mTargetViewContainer = new FrameLayout(context);
+ mTargetViewContainer.setClipChildren(false);
+ mTargetViewContainer.addView(mTargetView);
+
+ mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+ mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+ mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener);
+ mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mMotionHelper.prepareForAnimation();
+
+ // Show the dismiss target, in case the initial touch event occurred within the
+ // magnetic field radius.
+ showDismissTargetMaybe();
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ if (wasFlungOut) {
+ mMotionHelper.flingToSnapTarget(velX, velY, null, null);
+ hideDismissTarget();
+ } else {
+ mMotionHelper.setSpringingToTouch(true);
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mHandler.post(() -> {
+ mMotionHelper.animateDismiss(0, 0, null);
+ hideDismissTarget();
+ });
+
+
+ MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
+ PipUtils.getTopPipActivity(mContext, mActivityManager));
+ }
+ });
+
+ mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
}
public void setTouchGesture(PipTouchGesture gesture) {
@@ -231,7 +321,8 @@ public class PipTouchHandler {
}
public void onActivityPinned() {
- cleanUpDismissTarget();
+ createDismissTargetMaybe();
+
mShowPipMenuOnAnimationEnd = true;
mPipResizeGestureHandler.onActivityPinned();
mFloatingContentCoordinator.onContentAdded(mMotionHelper);
@@ -264,6 +355,10 @@ public class PipTouchHandler {
public void onConfigurationChanged() {
mMotionHelper.onConfigurationChanged();
mMotionHelper.synchronizePinnedStackBounds();
+
+ // Recreate the dismiss target for the new orientation.
+ cleanUpDismissTarget();
+ createDismissTargetMaybe();
}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -351,6 +446,74 @@ public class PipTouchHandler {
}
}
+ /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+ private void createDismissTargetMaybe() {
+ if (!mTargetViewContainer.isAttachedToWindow()) {
+ mHandler.removeCallbacks(mShowTargetAction);
+ mMagneticTargetAnimator.cancel();
+
+ final Point windowSize = new Point();
+ mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ mDismissAreaHeight,
+ 0, windowSize.y - mDismissAreaHeight,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.setTitle("pip-dismiss-overlay");
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setFitInsetsTypes(0 /* types */);
+
+ mTargetViewContainer.setVisibility(View.INVISIBLE);
+ mWindowManager.addView(mTargetViewContainer, lp);
+ }
+ }
+
+ /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+ private void showDismissTargetMaybe() {
+ createDismissTargetMaybe();
+
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+
+ mTargetView.setTranslationY(mTargetViewContainer.getHeight());
+ mTargetViewContainer.setVisibility(View.VISIBLE);
+
+ // Set the magnetic field radius to half of PIP's width.
+ mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width());
+
+ // Cancel in case we were in the middle of animating it out.
+ mMagneticTargetAnimator.cancel();
+ mMagneticTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
+ .start();
+ }
+ }
+
+ /** Animates the magnetic dismiss target out and then sets it to GONE. */
+ private void hideDismissTarget() {
+ mHandler.removeCallbacks(mShowTargetAction);
+ mMagneticTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y,
+ mTargetViewContainer.getHeight(),
+ mTargetSpringConfig)
+ .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
+ .start();
+ }
+
+ /**
+ * Removes the dismiss target and cancels any pending callbacks to show it.
+ */
+ private void cleanUpDismissTarget() {
+ mHandler.removeCallbacks(mShowTargetAction);
+
+ if (mTargetViewContainer.isAttachedToWindow()) {
+ mWindowManager.removeView(mTargetViewContainer);
+ }
+ }
+
private void onRegistrationChanged(boolean isRegistered) {
mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
? mConnection : null);
@@ -375,8 +538,24 @@ public class PipTouchHandler {
if (mPinnedStackController == null) {
return true;
}
+
MotionEvent ev = (MotionEvent) inputEvent;
+ if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
+ // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
+ // to the touch state. Touch state needs a DOWN event in order to later process MOVE
+ // events it'll receive if the object is dragged out of the magnetic field.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchState.onTouchEvent(ev);
+ }
+
+ // Continue tracking velocity when the object is in the magnetic field, since we want to
+ // respect touch input velocity if the object is dragged out and then flung.
+ mTouchState.addMovementToVelocityTracker(ev);
+
+ return true;
+ }
+
// Update the touch state
mTouchState.onTouchEvent(ev);
@@ -600,17 +779,13 @@ public class PipTouchHandler {
mDelta.set(0f, 0f);
mStartPosition.set(bounds.left, bounds.top);
mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
+ mMotionHelper.setSpringingToTouch(false);
// If the menu is still visible then just poke the menu
// so that it will timeout after the user stops touching it
if (mMenuState != MENU_STATE_NONE) {
mMenuController.pokeMenu();
}
-
- if (mEnableDismissDragToEdge) {
- mDismissViewController.createDismissTarget();
- mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
- }
}
@Override
@@ -623,8 +798,10 @@ public class PipTouchHandler {
mSavedSnapFraction = -1f;
if (mEnableDismissDragToEdge) {
- mHandler.removeCallbacks(mShowDismissAffordance);
- mDismissViewController.showDismissTarget();
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+ mHandler.removeCallbacks(mShowTargetAction);
+ showDismissTargetMaybe();
+ }
}
}
@@ -644,10 +821,6 @@ public class PipTouchHandler {
mTmpBounds.offsetTo((int) left, (int) top);
mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
- if (mEnableDismissDragToEdge) {
- updateDismissFraction();
- }
-
final PointF curPos = touchState.getLastTouchPosition();
if (mMovementWithinDismiss) {
// Track if movement remains near the bottom edge to identify swipe to dismiss
@@ -661,9 +834,7 @@ public class PipTouchHandler {
@Override
public boolean onUp(PipTouchState touchState) {
if (mEnableDismissDragToEdge) {
- // Clean up the dismiss target regardless of the touch state in case the touch
- // enabled state changes while the user is interacting
- cleanUpDismissTarget();
+ hideDismissTarget();
}
if (!touchState.isUserInteracting()) {
@@ -671,26 +842,8 @@ public class PipTouchHandler {
}
final PointF vel = touchState.getVelocity();
- final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
final float velocity = PointF.length(vel.x, vel.y);
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
- final boolean isUpWithinDimiss = ENABLE_FLING_DISMISS
- && touchState.getLastTouchPosition().y >= mMovementBounds.bottom
- && mMotionHelper.isGestureToDismissArea(mMotionHelper.getBounds(), vel.x,
- vel.y, isFling);
- final boolean isFlingToBot = isFling && vel.y > 0 && !isHorizontal
- && (mMovementWithinDismiss || isUpWithinDimiss);
- if (mEnableDismissDragToEdge) {
- // Check if the user dragged or flung the PiP offscreen to dismiss it
- if (mMotionHelper.shouldDismissPip() || isFlingToBot) {
- MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
- PipUtils.getTopPipActivity(mContext, mActivityManager));
- mMotionHelper.animateDismiss(
- vel.x, vel.y,
- PipTouchHandler.this::updateDismissFraction /* updateAction */);
- return true;
- }
- }
if (touchState.isDragging()) {
Runnable endAction = null;
@@ -749,14 +902,6 @@ public class PipTouchHandler {
}
/**
- * Removes the dismiss target and cancels any pending callbacks to show it.
- */
- private void cleanUpDismissTarget() {
- mHandler.removeCallbacks(mShowDismissAffordance);
- mDismissViewController.destroyDismissTarget();
- }
-
- /**
* @return whether the menu will resize as a part of showing the full menu.
*/
private boolean willResizeMenu() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index e3f65ef812fb..dc286c1c2de5 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -92,7 +92,7 @@ public class PipTouchState {
// Initialize the velocity tracker
initOrResetVelocityTracker();
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
mActivePointerId = ev.getPointerId(0);
if (DEBUG) {
@@ -120,7 +120,7 @@ public class PipTouchState {
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
@@ -151,7 +151,7 @@ public class PipTouchState {
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
int pointerIndex = ev.getActionIndex();
int pointerId = ev.getPointerId(pointerIndex);
@@ -174,7 +174,7 @@ public class PipTouchState {
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
mVelocityTracker.computeCurrentVelocity(1000,
mViewConfig.getScaledMaximumFlingVelocity());
mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
@@ -318,6 +318,20 @@ public class PipTouchState {
return -1;
}
+ void addMovementToVelocityTracker(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ return;
+ }
+
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
@@ -333,16 +347,6 @@ public class PipTouchState {
}
}
- private void addMovement(MotionEvent event) {
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- float deltaX = event.getRawX() - event.getX();
- float deltaY = event.getRawY() - event.getY();
- event.offsetLocation(deltaX, deltaY);
- mVelocityTracker.addMovement(event);
- event.offsetLocation(-deltaX, -deltaY);
- }
-
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index 9c175bc2b756..8efeef1ffa0a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -30,6 +30,14 @@ import com.android.systemui.R;
*/
public class PipControlsView extends LinearLayout {
+ public PipControlsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index d2da2628276a..55d3e8372b01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.WallpaperManager
import android.util.Log
+import android.util.MathUtils
import android.view.Choreographer
import android.view.View
import androidx.annotation.VisibleForTesting
@@ -225,7 +226,10 @@ class NotificationShadeDepthController @Inject constructor(
private fun updateShadeBlur() {
var newBlur = 0
if (statusBarStateController.state == StatusBarState.SHADE) {
- newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
+ val animatedBlur =
+ Interpolators.SHADE_ANIMATION.getInterpolation(
+ MathUtils.constrain(shadeExpansion / 0.15f, 0f, 1f))
+ newBlur = blurUtils.blurRadiusOfRatio(0.35f * animatedBlur + 0.65f * shadeExpansion)
}
shadeSpring.animateTo(newBlur)
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
index 812a1e4bc121..f27bdbfbeda0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -160,6 +160,18 @@ abstract class MagnetizedObject<T : Any>(
lateinit var magnetListener: MagnetizedObject.MagnetListener
/**
+ * Optional update listener to provide to the PhysicsAnimator that is used to spring the object
+ * into the target.
+ */
+ var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null
+
+ /**
+ * Optional end listener to provide to the PhysicsAnimator that is used to spring the object
+ * into the target.
+ */
+ var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null
+
+ /**
* Sets whether forcefully flinging the object vertically towards a target causes it to be
* attracted to the target and then released immediately, despite never being dragged within the
* magnetic field.
@@ -479,6 +491,14 @@ abstract class MagnetizedObject<T : Any>(
.spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY,
springConfig)
+ if (physicsAnimatorUpdateListener != null) {
+ animator.addUpdateListener(physicsAnimatorUpdateListener!!)
+ }
+
+ if (physicsAnimatorEndListener != null) {
+ animator.addEndListener(physicsAnimatorEndListener!!)
+ }
+
if (after != null) {
animator.withEndActions(after)
}
@@ -560,13 +580,15 @@ abstract class MagnetizedObject<T : Any>(
private val tempLoc = IntArray(2)
fun updateLocationOnScreen() {
- targetView.getLocationOnScreen(tempLoc)
-
- // Add half of the target size to get the center, and subtract translation since the
- // target could be animating in while we're doing this calculation.
- centerOnScreen.set(
- tempLoc[0] + targetView.width / 2f - targetView.translationX,
- tempLoc[1] + targetView.height / 2f - targetView.translationY)
+ targetView.post {
+ targetView.getLocationOnScreen(tempLoc)
+
+ // Add half of the target size to get the center, and subtract translation since the
+ // target could be animating in while we're doing this calculation.
+ centerOnScreen.set(
+ tempLoc[0] + targetView.width / 2f - targetView.translationX,
+ tempLoc[1] + targetView.height / 2f - targetView.translationY)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
index 072bc446fd21..4bcf917fa95d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
@@ -16,8 +16,12 @@
package com.android.keyguard
+import android.app.Notification
import android.graphics.drawable.Icon
import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -28,7 +32,9 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.media.MediaControllerFactory
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -38,6 +44,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -48,9 +55,12 @@ import org.mockito.Mockito.`when` as whenever
public class KeyguardMediaPlayerTest : SysuiTestCase() {
private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
+ @Mock private lateinit var mockMediaFactory: MediaControllerFactory
+ @Mock private lateinit var mockMediaController: MediaController
+ private lateinit var playbackState: PlaybackState
private lateinit var fakeExecutor: FakeExecutor
private lateinit var mediaMetadata: MediaMetadata.Builder
- private lateinit var entry: NotificationEntryBuilder
+ private lateinit var entry: NotificationEntry
@Mock private lateinit var mockView: View
private lateinit var songView: TextView
private lateinit var artistView: TextView
@@ -70,8 +80,16 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
@Before
public fun setup() {
+ playbackState = PlaybackState.Builder().run {
+ build()
+ }
+ mockMediaController = mock(MediaController::class.java)
+ whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState)
+ mockMediaFactory = mock(MediaControllerFactory::class.java)
+ whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController)
+
fakeExecutor = FakeExecutor(FakeSystemClock())
- keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor)
+ keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor)
mockIcon = mock(Icon::class.java)
mockView = mock(View::class.java)
@@ -81,7 +99,9 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
mediaMetadata = MediaMetadata.Builder()
- entry = NotificationEntryBuilder()
+ entry = NotificationEntryBuilder().build()
+ entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token(1, null))
ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
@@ -109,7 +129,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
@Test
public fun testUpdateControls() {
- keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
FakeExecutor.exhaustExecutors(fakeExecutor)
verify(mockView).setVisibility(View.VISIBLE)
}
@@ -122,11 +142,22 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
}
@Test
+ public fun testUpdateControlsNullPlaybackState() {
+ // GIVEN that the playback state is null (ie. the media session was destroyed)
+ whenever(mockMediaController.getPlaybackState()).thenReturn(null)
+ // WHEN updated
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
+ FakeExecutor.exhaustExecutors(fakeExecutor)
+ // THEN the controls are cleared (ie. visibility is set to GONE)
+ verify(mockView).setVisibility(View.GONE)
+ }
+
+ @Test
public fun testSongName() {
val song: String = "Song"
mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song)
- keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
assertThat(songView.getText()).isEqualTo(song)
@@ -137,7 +168,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
val artist: String = "Artist"
mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist)
- keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+ keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
assertThat(artistView.getText()).isEqualTo(artist)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index c9bb4016c7bf..9985d21e8515 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -20,8 +20,10 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
@@ -56,6 +58,7 @@ public class DozeDockHandlerTest extends SysuiTestCase {
mDockManagerFake = spy(new DockManagerFake());
mDockHandler = new DozeDockHandler(mConfig, mMachine, mDockManagerFake);
+ when(mMachine.getState()).thenReturn(State.DOZE_AOD);
doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
}
@@ -101,4 +104,31 @@ public class DozeDockHandlerTest extends SysuiTestCase {
verify(mMachine).requestState(eq(State.DOZE));
}
+
+ @Test
+ public void onEvent_dockedWhilePulsing_wontRequestStateChange() {
+ when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED);
+
+ verify(mMachine, never()).requestState(any(State.class));
+ }
+
+ @Test
+ public void onEvent_noneWhilePulsing_wontRequestStateChange() {
+ when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_NONE);
+
+ verify(mMachine, never()).requestState(any(State.class));
+ }
+
+ @Test
+ public void onEvent_hideWhilePulsing_wontRequestStateChange() {
+ when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+ mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
+
+ verify(mMachine, never()).requestState(any(State.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index c483314918fc..1f07f46bf764 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -254,6 +254,17 @@ public class DozeMachineTest extends SysuiTestCase {
}
@Test
+ public void testPulseDone_whileDockedAoD_staysDockedAod() {
+ when(mDockManager.isDocked()).thenReturn(true);
+ mMachine.requestState(INITIALIZED);
+ mMachine.requestState(DOZE_AOD_DOCKED);
+
+ mMachine.requestState(DOZE_PULSE_DONE);
+
+ verify(mPartMock, never()).transitionTo(DOZE_AOD_DOCKED, DOZE_PULSE_DONE);
+ }
+
+ @Test
public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() {
when(mHost.isDozeSuppressed()).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index 0bf0f04d2d43..425bf88ebec0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.pip;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
@@ -56,11 +57,15 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
private PipBoundsHandler mPipBoundsHandler;
private DisplayInfo mDefaultDisplayInfo;
+ private ComponentName mTestComponentName1;
+ private ComponentName mTestComponentName2;
@Before
public void setUp() throws Exception {
initializeMockResources();
mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext));
+ mTestComponentName1 = new ComponentName(mContext, "component1");
+ mTestComponentName2 = new ComponentName(mContext, "component2");
mPipBoundsHandler.onDisplayInfoChanged(mDefaultDisplayInfo);
}
@@ -121,7 +126,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
};
for (float aspectRatio : aspectRatios) {
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+ mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
assertEquals("Destination bounds matches the given aspect ratio",
@@ -137,7 +142,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
};
for (float aspectRatio : invalidAspectRatios) {
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+ mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
assertEquals("Destination bounds fallbacks to default aspect ratio",
@@ -153,7 +158,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
+ mTestComponentName1, aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
@@ -177,7 +182,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
final float aspectRatio = aspectRatios[i];
final Size minimalSize = minimalSizes[i];
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
+ mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
assertTrue("Destination bounds is no smaller than minimal requirement",
(destinationBounds.width() == minimalSize.getWidth()
&& destinationBounds.height() >= minimalSize.getHeight())
@@ -198,7 +203,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2);
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, currentBounds, minSize);
+ mTestComponentName1, aspectRatio, currentBounds, minSize);
assertTrue("Destination bounds ignores minimal size",
destinationBounds.width() > minSize.getWidth()
@@ -206,81 +211,92 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
}
@Test
+ public void getDestinationBounds_withDifferentComponentName_ignoreLastPosition() {
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+ oldPosition.offset(0, -100);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
+
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName2,
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+ assertNonBoundsInclusionWithMargin("ignore saved bounds", oldPosition, newPosition);
+ }
+
+ @Test
public void setShelfHeight_offsetBounds() {
final int shelfHeight = 100;
- final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
mPipBoundsHandler.setShelfHeight(true, shelfHeight);
- final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -shelfHeight);
- assertBoundsWithMargin("offsetBounds by shelf", oldPosition, newPosition);
+ assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition);
}
@Test
public void onImeVisibilityChanged_offsetBounds() {
final int imeHeight = 100;
- final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight);
- final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -imeHeight);
- assertBoundsWithMargin("offsetBounds by IME", oldPosition, newPosition);
+ assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition);
}
@Test
public void onSaveReentryBounds_restoreLastPosition() {
- final ComponentName componentName = new ComponentName(mContext, "component1");
- final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -100);
- mPipBoundsHandler.onSaveReentryBounds(componentName, oldPosition);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
- final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+ final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
- assertBoundsWithMargin("restoreLastPosition", oldPosition, newPosition);
+ assertBoundsInclusionWithMargin("restoreLastPosition", oldPosition, newPosition);
}
@Test
public void onResetReentryBounds_useDefaultBounds() {
- final ComponentName componentName = new ComponentName(mContext, "component1");
- final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
+ final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final Rect newBounds = new Rect(defaultBounds);
newBounds.offset(0, -100);
- mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
- mPipBoundsHandler.onResetReentryBounds(componentName);
- final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
+ mPipBoundsHandler.onResetReentryBounds(mTestComponentName1);
+ final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
- assertBoundsWithMargin("useDefaultBounds", defaultBounds, actualBounds);
+ assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
}
@Test
public void onResetReentryBounds_componentMismatch_restoreLastPosition() {
- final ComponentName componentName = new ComponentName(mContext, "component1");
- final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
+ final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final Rect newBounds = new Rect(defaultBounds);
newBounds.offset(0, -100);
- mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
+ mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
- mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2"));
- final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
+ mPipBoundsHandler.onResetReentryBounds(mTestComponentName2);
+ final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
- assertBoundsWithMargin("restoreLastPosition", newBounds, actualBounds);
+ assertBoundsInclusionWithMargin("restoreLastPosition", newBounds, actualBounds);
}
- private void assertBoundsWithMargin(String from, Rect expected, Rect actual) {
+ private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
final Rect expectedWithMargin = new Rect(expected);
expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
assertTrue(from + ": expect " + expected
@@ -288,4 +304,13 @@ public class PipBoundsHandlerTest extends SysuiTestCase {
+ " with error margin " + ROUNDING_ERROR_MARGIN,
expectedWithMargin.contains(actual));
}
+
+ private void assertNonBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
+ final Rect expectedWithMargin = new Rect(expected);
+ expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
+ assertFalse(from + ": expect " + expected
+ + " not contains " + actual
+ + " with error margin " + ROUNDING_ERROR_MARGIN,
+ expectedWithMargin.contains(actual));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
index f1672b1c644d..f6b7b74d4bfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
@@ -106,6 +106,10 @@ class MagnetizedObjectTest : SysuiTestCase() {
location[1] = targetCenterY - targetSize / 2 // y = 800
}
}.`when`(targetView).getLocationOnScreen(ArgumentMatchers.any())
+ doAnswer { invocation ->
+ (invocation.arguments[0] as Runnable).run()
+ true
+ }.`when`(targetView).post(ArgumentMatchers.any())
`when`(targetView.context).thenReturn(context)
magneticTarget = MagnetizedObject.MagneticTarget(targetView, magneticFieldRadius)
@@ -408,6 +412,10 @@ class MagnetizedObjectTest : SysuiTestCase() {
`when`(secondTargetView.width).thenReturn(targetSize) // width = 200
`when`(secondTargetView.height).thenReturn(targetSize) // height = 200
doAnswer { invocation ->
+ (invocation.arguments[0] as Runnable).run()
+ true
+ }.`when`(secondTargetView).post(ArgumentMatchers.any())
+ doAnswer { invocation ->
(invocation.arguments[0] as IntArray).also { location ->
// Return the top left of the target.
location[0] = secondTargetCenterX - targetSize / 2 // x = 0
diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml
index 530bc0788a78..4ff1d3777f25 100644
--- a/packages/Tethering/tests/unit/AndroidManifest.xml
+++ b/packages/Tethering/tests/unit/AndroidManifest.xml
@@ -20,7 +20,16 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <service
+ android:name="com.android.server.connectivity.tethering.MockTetheringService"
+ android:permission="android.permission.TETHER_PRIVILEGED"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.server.connectivity.tethering.TetheringService"/>
+ </intent-filter>
+ </service>
</application>
+
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.networkstack.tethering.tests.unit"
android:label="Tethering service tests">
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
new file mode 100644
index 000000000000..355ece9a44a1
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.connectivity.tethering;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Intent;
+import android.net.ITetheringConnector;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class MockTetheringService extends TetheringService {
+ private final Tethering mTethering = mock(Tethering.class);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new MockTetheringConnector(super.onBind(intent));
+ }
+
+ @Override
+ public Tethering makeTethering(TetheringDependencies deps) {
+ return mTethering;
+ }
+
+ public Tethering getTethering() {
+ return mTethering;
+ }
+
+ public class MockTetheringConnector extends Binder {
+ final IBinder mBase;
+ MockTetheringConnector(IBinder base) {
+ mBase = base;
+ }
+
+ public ITetheringConnector getTetheringConnector() {
+ return ITetheringConnector.Stub.asInterface(mBase);
+ }
+
+ public MockTetheringService getService() {
+ return MockTetheringService.this;
+ }
+ }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
new file mode 100644
index 000000000000..d9d3e73eb4e3
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.net.IIntResultListener;
+import android.net.ITetheringConnector;
+import android.net.ITetheringEventCallback;
+import android.net.TetheringRequestParcel;
+import android.os.ResultReceiver;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.connectivity.tethering.MockTetheringService.MockTetheringConnector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TetheringServiceTest {
+ private static final String TEST_IFACE_NAME = "test_wlan0";
+ private static final String TEST_CALLER_PKG = "test_pkg";
+ @Mock private ITetheringEventCallback mITetheringEventCallback;
+ @Rule public ServiceTestRule mServiceTestRule;
+ private Tethering mTethering;
+ private Intent mMockServiceIntent;
+ private ITetheringConnector mTetheringConnector;
+
+ private class TestTetheringResult extends IIntResultListener.Stub {
+ private int mResult = -1; // Default value that does not match any result code.
+ @Override
+ public void onResult(final int resultCode) {
+ mResult = resultCode;
+ }
+
+ public void assertResult(final int expected) {
+ assertEquals(expected, mResult);
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mServiceTestRule = new ServiceTestRule();
+ mMockServiceIntent = new Intent(
+ InstrumentationRegistry.getTargetContext(),
+ MockTetheringService.class);
+ final MockTetheringConnector mockConnector =
+ (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent);
+ mTetheringConnector = mockConnector.getTetheringConnector();
+ final MockTetheringService service = mockConnector.getService();
+ mTethering = service.getTethering();
+ verify(mTethering).startStateMachineUpdaters();
+ when(mTethering.hasTetherableConfiguration()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mServiceTestRule.unbindService();
+ }
+
+ @Test
+ public void testTether() throws Exception {
+ when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).tether(TEST_IFACE_NAME);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testUntether() throws Exception {
+ when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).untether(TEST_IFACE_NAME);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testSetUsbTethering() throws Exception {
+ when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).setUsbTethering(true /* enable */);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testStartTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ final TetheringRequestParcel request = new TetheringRequestParcel();
+ request.tetheringType = TETHERING_WIFI;
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).startTethering(eq(request), eq(result));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testStopTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).stopTethering(TETHERING_WIFI);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testRequestLatestTetheringEntitlementResult() throws Exception {
+ final ResultReceiver result = new ResultReceiver(null);
+ mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
+ true /* showEntitlementUi */, TEST_CALLER_PKG);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI),
+ eq(result), eq(true) /* showEntitlementUi */);
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testRegisterTetheringEventCallback() throws Exception {
+ mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback,
+ TEST_CALLER_PKG);
+ verify(mTethering).registerTetheringEventCallback(eq(mITetheringEventCallback));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testUnregisterTetheringEventCallback() throws Exception {
+ mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback,
+ TEST_CALLER_PKG);
+ verify(mTethering).unregisterTetheringEventCallback(
+ eq(mITetheringEventCallback));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testStopAllTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).untetherAll();
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testIsTetheringSupported() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index a59c6fd9e193..3a580dd8e5bd 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -140,7 +140,9 @@ import com.android.networkstack.tethering.R;
import com.android.testutils.MiscAssertsKt;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -439,6 +441,18 @@ public class TetheringTest {
return buildMobileUpstreamState(false, true, true);
}
+ // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and
+ // after use.
+ @BeforeClass
+ public static void setupOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2100c1a8be44..7230b00f87ad 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -413,7 +413,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
&& component.getPackageName().equals(packageName))
|| userState.mCrashedServices.removeIf(component -> component != null
&& component.getPackageName().equals(packageName));
- if (reboundAService) {
+ // Reloads the installed services info to make sure the rebound service could
+ // get a new one.
+ userState.mInstalledServices.clear();
+ final boolean configurationChanged =
+ readConfigurationForUserStateLocked(userState);
+ if (reboundAService || configurationChanged) {
onUserStateChangedLocked(userState);
}
migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
new file mode 100644
index 000000000000..3612e093c8bd
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+
+/**
+ * Controls the interaction with the IME for the inline suggestion sessions.
+ */
+final class AutofillInlineSessionController {
+ @NonNull
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+ private final int mUserId;
+ @NonNull
+ private final ComponentName mComponentName;
+ @NonNull
+ private final Object mLock;
+ @NonNull
+ private final Handler mHandler;
+
+ @GuardedBy("mLock")
+ private AutofillInlineSuggestionsRequestSession mSession;
+
+ AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal,
+ int userId, ComponentName componentName, Handler handler, Object lock) {
+ mInputMethodManagerInternal = inputMethodManagerInternal;
+ mUserId = userId;
+ mComponentName = componentName;
+ mHandler = handler;
+ mLock = lock;
+ }
+
+
+ /**
+ * Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}.
+ *
+ * @param autofillId the Id of the field for which the request is for.
+ * @param requestConsumer the callback which will be invoked when IME responded or if it times
+ * out waiting for IME response.
+ */
+ @GuardedBy("mLock")
+ void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId,
+ @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+ // TODO(b/151123764): rename the method to better reflect what it does.
+ if (mSession != null) {
+ // Send an empty response to IME and destroy the existing session.
+ mSession.onInlineSuggestionsResponseLocked(mSession.getAutofillIdLocked(),
+ new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+ mSession.destroySessionLocked();
+ }
+ // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the
+ // same field.
+ mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId,
+ mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras);
+ mSession.onCreateInlineSuggestionsRequestLocked();
+
+ }
+
+ /**
+ * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request.
+ *
+ * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+ * this method, using the {@code requestConsumer} provided when calling {@link
+ * #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}.
+ */
+ @GuardedBy("mLock")
+ Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+ if (mSession != null) {
+ return mSession.getInlineSuggestionsRequestLocked();
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Requests the IME to hide the current suggestions, if any. Returns true if the message is sent
+ * to the IME.
+ */
+ @GuardedBy("mLock")
+ boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) {
+ if (mSession != null) {
+ return mSession.onInlineSuggestionsResponseLocked(autofillId,
+ new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+ }
+ return false;
+ }
+
+ /**
+ * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+ * on the {@code autofillId}.
+ *
+ * @return false if there is no session, or if the IME callback is not available in the session.
+ */
+ @GuardedBy("mLock")
+ boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+ @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+ // TODO(b/151123764): rename the method to better reflect what it does.
+ if (mSession != null) {
+ return mSession.onInlineSuggestionsResponseLocked(autofillId,
+ inlineSuggestionsResponse);
+ }
+ return false;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
new file mode 100644
index 000000000000..ca230b6936ff
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.lang.ref.WeakReference;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Maintains an inline suggestion session with the IME.
+ *
+ * <p> Each session corresponds to one request from the Autofill manager service to create an
+ * {@link InlineSuggestionsRequest}. It's responsible for receiving callbacks from the IME and
+ * sending {@link android.view.inputmethod.InlineSuggestionsResponse} to IME.
+ */
+final class AutofillInlineSuggestionsRequestSession {
+
+ private static final String TAG = AutofillInlineSuggestionsRequestSession.class.getSimpleName();
+ private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
+
+ @NonNull
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+ private final int mUserId;
+ @NonNull
+ private final ComponentName mComponentName;
+ @NonNull
+ private final Object mLock;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final Bundle mUiExtras;
+
+ @GuardedBy("mLock")
+ @NonNull
+ private AutofillId mAutofillId;
+ @GuardedBy("mLock")
+ @Nullable
+ private Consumer<InlineSuggestionsRequest> mImeRequestConsumer;
+
+ @GuardedBy("mLock")
+ private boolean mImeRequestReceived;
+ @GuardedBy("mLock")
+ @Nullable
+ private InlineSuggestionsRequest mImeRequest;
+ @GuardedBy("mLock")
+ @Nullable
+ private IInlineSuggestionsResponseCallback mResponseCallback;
+ @GuardedBy("mLock")
+ @Nullable
+ private Runnable mTimeoutCallback;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private AutofillId mImeCurrentFieldId;
+ @GuardedBy("mLock")
+ private boolean mImeInputStarted;
+ @GuardedBy("mLock")
+ private boolean mImeInputViewStarted;
+ @GuardedBy("mLock")
+ @Nullable
+ private InlineSuggestionsResponse mInlineSuggestionsResponse;
+ @GuardedBy("mLock")
+ private boolean mPreviousResponseIsNotEmpty;
+
+ @GuardedBy("mLock")
+ private boolean mDestroyed = false;
+
+ AutofillInlineSuggestionsRequestSession(
+ @NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId,
+ @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock,
+ @NonNull AutofillId autofillId,
+ @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+ mInputMethodManagerInternal = inputMethodManagerInternal;
+ mUserId = userId;
+ mComponentName = componentName;
+ mHandler = handler;
+ mLock = lock;
+ mUiExtras = uiExtras;
+
+ mAutofillId = autofillId;
+ mImeRequestConsumer = requestConsumer;
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ AutofillId getAutofillIdLocked() {
+ return mAutofillId;
+ }
+
+ /**
+ * Returns the {@link InlineSuggestionsRequest} provided by IME.
+ *
+ * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+ * this method, using the {@link #mImeRequestConsumer}.
+ */
+ @GuardedBy("mLock")
+ Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+ if (mDestroyed) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(mImeRequest);
+ }
+
+ /**
+ * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+ * on the {@code autofillId}.
+ *
+ * @return false if the IME callback is not available.
+ */
+ @GuardedBy("mLock")
+ boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+ @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+ if (mDestroyed) {
+ return false;
+ }
+ if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId);
+ if (mImeRequest == null || mResponseCallback == null) {
+ return false;
+ }
+ // TODO(b/151123764): each session should only correspond to one field.
+ mAutofillId = autofillId;
+ mInlineSuggestionsResponse = inlineSuggestionsResponse;
+ maybeUpdateResponseToImeLocked();
+ return true;
+ }
+
+ /**
+ * This method must be called when the session is destroyed, to avoid further callbacks from/to
+ * the IME.
+ */
+ @GuardedBy("mLock")
+ void destroySessionLocked() {
+ mDestroyed = true;
+ }
+
+ /**
+ * Requests the IME to create an {@link InlineSuggestionsRequest}.
+ *
+ * <p> This method should only be called once per session.
+ */
+ @GuardedBy("mLock")
+ void onCreateInlineSuggestionsRequestLocked() {
+ if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId);
+ if (mDestroyed) {
+ return;
+ }
+ mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId,
+ new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras),
+ new InlineSuggestionsRequestCallbackImpl(this));
+ mTimeoutCallback = () -> {
+ Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
+ handleOnReceiveImeRequest(null, null);
+ };
+ mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
+ }
+
+ /**
+ * Optionally sends inline response to the IME, depending on the current state.
+ */
+ @GuardedBy("mLock")
+ private void maybeUpdateResponseToImeLocked() {
+ if (sDebug) Log.d(TAG, "maybeUpdateResponseToImeLocked called");
+ if (mDestroyed || mResponseCallback == null) {
+ return;
+ }
+ if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) {
+ // 1. if previous response is not empty, and IME just become invisible, then send
+ // empty response to make sure existing responses don't stick around on the IME.
+ // Although the inline suggestions should disappear when IME hides which removes them
+ // from the view hierarchy, but we still send an empty response to be extra safe.
+
+ // TODO(b/149945531): clear the existing suggestions when IME is hide, once the bug is
+ // fixed.
+ //if (sDebug) Log.d(TAG, "Send empty inline response");
+ //updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections
+ // .EMPTY_LIST));
+ //mPreviousResponseIsNotEmpty = false;
+ } else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId,
+ mImeCurrentFieldId)) {
+ // 2. if IME is visible, and response is not null, send the response
+ boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty();
+ if (isEmptyResponse && !mPreviousResponseIsNotEmpty) {
+ // No-op if both the previous response and current response are empty.
+ return;
+ }
+ if (sDebug) {
+ Log.d(TAG, "Send inline response: "
+ + mInlineSuggestionsResponse.getInlineSuggestions().size());
+ }
+ updateResponseToImeUncheckLocked(mInlineSuggestionsResponse);
+ // TODO(b/149945531): don't set the response to null so it's cached, once the bug is
+ // fixed.
+ mInlineSuggestionsResponse = null;
+ mPreviousResponseIsNotEmpty = !isEmptyResponse;
+ }
+ }
+
+ /**
+ * Sends the {@code response} to the IME, assuming all the relevant checks are already done.
+ */
+ @GuardedBy("mLock")
+ private void updateResponseToImeUncheckLocked(InlineSuggestionsResponse response) {
+ if (mDestroyed) {
+ return;
+ }
+ try {
+ mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
+ }
+ }
+
+ /**
+ * Handles the {@code request} and {@code callback} received from the IME.
+ *
+ * <p> Should only invoked in the {@link #mHandler} thread.
+ */
+ private void handleOnReceiveImeRequest(@Nullable InlineSuggestionsRequest request,
+ @Nullable IInlineSuggestionsResponseCallback callback) {
+ synchronized (mLock) {
+ if (mDestroyed || mImeRequestReceived) {
+ return;
+ }
+ mImeRequestReceived = true;
+
+ if (mTimeoutCallback != null) {
+ if (sDebug) Log.d(TAG, "removing timeout callback");
+ mHandler.removeCallbacks(mTimeoutCallback);
+ mTimeoutCallback = null;
+ }
+ if (request != null && callback != null) {
+ mImeRequest = request;
+ mResponseCallback = callback;
+ handleOnReceiveImeStatusUpdated(mAutofillId, true, false);
+ }
+ if (mImeRequestConsumer != null) {
+ // Note that mImeRequest is only set if both request and callback are non-null.
+ mImeRequestConsumer.accept(mImeRequest);
+ mImeRequestConsumer = null;
+ }
+ }
+ }
+
+ /**
+ * Handles the IME status updates received from the IME.
+ *
+ * <p> Should only be invoked in the {@link #mHandler} thread.
+ */
+ private void handleOnReceiveImeStatusUpdated(boolean imeInputStarted,
+ boolean imeInputViewStarted) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ if (mImeCurrentFieldId != null) {
+ boolean imeInputStartedChanged = (mImeInputStarted != imeInputStarted);
+ boolean imeInputViewStartedChanged = (mImeInputViewStarted != imeInputViewStarted);
+ mImeInputStarted = imeInputStarted;
+ mImeInputViewStarted = imeInputViewStarted;
+ if (imeInputStartedChanged || imeInputViewStartedChanged) {
+ maybeUpdateResponseToImeLocked();
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles the IME status updates received from the IME.
+ *
+ * <p> Should only be invoked in the {@link #mHandler} thread.
+ */
+ private void handleOnReceiveImeStatusUpdated(@Nullable AutofillId imeFieldId,
+ boolean imeInputStarted, boolean imeInputViewStarted) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ if (imeFieldId != null) {
+ mImeCurrentFieldId = imeFieldId;
+ }
+ handleOnReceiveImeStatusUpdated(imeInputStarted, imeInputViewStarted);
+ }
+ }
+
+ private static final class InlineSuggestionsRequestCallbackImpl extends
+ IInlineSuggestionsRequestCallback.Stub {
+
+ private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession;
+
+ private InlineSuggestionsRequestCallbackImpl(
+ AutofillInlineSuggestionsRequestSession session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @BinderThread
+ @Override
+ public void onInlineSuggestionsUnsupported() throws RemoteException {
+ if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+ null, null));
+ }
+ }
+
+ @BinderThread
+ @Override
+ public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback) {
+ if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+ request, callback));
+ }
+ }
+
+ @Override
+ public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+ if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, imeFieldId, true, false));
+ }
+ }
+
+ @Override
+ public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+ if (sDebug) {
+ Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
+ }
+ }
+
+ @BinderThread
+ @Override
+ public void onInputMethodStartInputView() {
+ if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, true, true));
+ }
+ }
+
+ @BinderThread
+ @Override
+ public void onInputMethodFinishInputView() {
+ if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, true, false));
+ }
+ }
+
+ @Override
+ public void onInputMethodFinishInput() throws RemoteException {
+ if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
+ final AutofillInlineSuggestionsRequestSession session = mSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+ session, false, false));
+ }
+ }
+ }
+
+ private static boolean match(@Nullable AutofillId autofillId,
+ @Nullable AutofillId imeClientFieldId) {
+ // The IME doesn't have information about the virtual view id for the child views in the
+ // web view, so we are only comparing the parent view id here. This means that for cases
+ // where there are two input fields in the web view, they will have the same view id
+ // (although different virtual child id), and we will not be able to distinguish them.
+ return autofillId != null && imeClientFieldId != null
+ && autofillId.getViewId() == imeClientFieldId.getViewId();
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
deleted file mode 100644
index e2d511212a71..000000000000
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill;
-
-import static com.android.server.autofill.Helper.sDebug;
-
-import android.annotation.BinderThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.inputmethod.InlineSuggestionsRequest;
-import android.view.inputmethod.InlineSuggestionsResponse;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.view.IInlineSuggestionsRequestCallback;
-import com.android.internal.view.IInlineSuggestionsResponseCallback;
-import com.android.internal.view.InlineSuggestionsRequestInfo;
-import com.android.server.inputmethod.InputMethodManagerInternal;
-
-import java.util.Collections;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-
-/**
- * Maintains an autofill inline suggestion session that communicates with the IME.
- *
- * <p>
- * The same session may be reused for multiple input fields involved in the same autofill
- * {@link Session}. Therefore, one {@link InlineSuggestionsRequest} and one
- * {@link IInlineSuggestionsResponseCallback} may be used to generate and callback with inline
- * suggestions for different input fields.
- *
- * <p>
- * This class is the sole place in Autofill responsible for directly communicating with the IME. It
- * receives the IME input view start/finish events, with the associated IME field Id. It uses the
- * information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result,
- * some of the response will be cached locally and only be sent when the IME is ready to show them.
- *
- * <p>
- * See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService
- * side flow.
- *
- * <p>
- * This class should hold the same lock as {@link Session} as they call into each other.
- */
-final class InlineSuggestionSession {
-
- private static final String TAG = "AfInlineSuggestionSession";
- private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
-
- @NonNull
- private final InputMethodManagerInternal mInputMethodManagerInternal;
- private final int mUserId;
- @NonNull
- private final ComponentName mComponentName;
- @NonNull
- private final Object mLock;
- @NonNull
- private final ImeStatusListener mImeStatusListener;
- @NonNull
- private final Handler mHandler;
-
- /**
- * To avoid the race condition, one should not access {@code mPendingImeResponse} without
- * holding the {@code mLock}. For consuming the existing value, tt's recommended to use
- * {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call.
- */
- @GuardedBy("mLock")
- @Nullable
- private CompletableFuture<ImeResponse> mPendingImeResponse;
-
- @GuardedBy("mLock")
- @Nullable
- private AutofillResponse mPendingAutofillResponse;
-
- @GuardedBy("mLock")
- private boolean mIsLastResponseNonEmpty = false;
-
- @Nullable
- @GuardedBy("mLock")
- private AutofillId mImeFieldId = null;
-
- @GuardedBy("mLock")
- private boolean mImeInputViewStarted = false;
-
- InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
- int userId, ComponentName componentName, Handler handler, Object lock) {
- mInputMethodManagerInternal = inputMethodManagerInternal;
- mUserId = userId;
- mComponentName = componentName;
- mHandler = handler;
- mLock = lock;
- mImeStatusListener = new ImeStatusListener() {
- @Override
- public void onInputMethodStartInput(AutofillId imeFieldId) {
- synchronized (mLock) {
- mImeFieldId = imeFieldId;
- mImeInputViewStarted = false;
- }
- }
-
- @Override
- public void onInputMethodStartInputView() {
- synchronized (mLock) {
- mImeInputViewStarted = true;
- AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
- if (pendingAutofillResponse != null
- && pendingAutofillResponse.mAutofillId.equalsIgnoreSession(
- mImeFieldId)) {
- mPendingAutofillResponse = null;
- onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId,
- pendingAutofillResponse.mResponse);
- }
- }
- }
-
- @Override
- public void onInputMethodFinishInputView() {
- synchronized (mLock) {
- mImeInputViewStarted = false;
- }
- }
-
- @Override
- public void onInputMethodFinishInput() {
- synchronized (mLock) {
- mImeFieldId = null;
- }
- }
- };
- }
-
- public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId,
- @NonNull Consumer<InlineSuggestionsRequest> requestConsumer) {
- if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId);
-
- synchronized (mLock) {
- // Clean up all the state about the previous request.
- hideInlineSuggestionsUi(autofillId);
- mImeFieldId = null;
- mImeInputViewStarted = false;
- if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) {
- mPendingImeResponse.complete(null);
- }
- mPendingImeResponse = new CompletableFuture<>();
- // TODO(b/146454892): pipe the uiExtras from the ExtServices.
- mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
- mUserId,
- new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
- new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse,
- mImeStatusListener, requestConsumer, mHandler, mLock));
- }
- }
-
- public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() {
- final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
- if (pendingImeResponse == null || !pendingImeResponse.isDone()) {
- return Optional.empty();
- }
- return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest);
- }
-
- public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) {
- synchronized (mLock) {
- if (mIsLastResponseNonEmpty) {
- return onInlineSuggestionsResponseLocked(autofillId,
- new InlineSuggestionsResponse(Collections.EMPTY_LIST));
- }
- return false;
- }
- }
-
- public boolean onInlineSuggestionsResponse(@NonNull AutofillId autofillId,
- @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
- synchronized (mLock) {
- return onInlineSuggestionsResponseLocked(autofillId, inlineSuggestionsResponse);
- }
- }
-
- private boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
- @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
- final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse();
- if (completedImsResponse == null || !completedImsResponse.isDone()) {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request");
- return false;
- }
- // There is no need to wait on the CompletableFuture since it should have been completed.
- ImeResponse imeResponse = completedImsResponse.getNow(null);
- if (imeResponse == null) {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response");
- return false;
- }
-
- // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we
- // only compare the view id for now.
- if (!mImeInputViewStarted || mImeFieldId == null
- || autofillId.getViewId() != mImeFieldId.getViewId()) {
- if (sDebug) {
- Log.d(TAG,
- "onInlineSuggestionsResponseLocked not sent because input view is not "
- + "started for " + autofillId);
- }
- mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse);
- // TODO(b/149442582): Although we are not sending the response to IME right away, we
- // still return true to indicate that the response may be sent eventually, such that
- // the dropdown UI will not be shown. This may not be the desired behavior in the
- // auto-focus case where IME isn't shown after switching back to an activity. We may
- // revisit this.
- return true;
- }
-
- try {
- imeResponse.mCallback.onInlineSuggestionsResponse(autofillId,
- inlineSuggestionsResponse);
- mIsLastResponseNonEmpty = !inlineSuggestionsResponse.getInlineSuggestions().isEmpty();
- if (sDebug) {
- Log.d(TAG, "Autofill sends inline response to IME: "
- + inlineSuggestionsResponse.getInlineSuggestions().size());
- }
- return true;
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
- return false;
- }
- }
-
- @Nullable
- @GuardedBy("mLock")
- private CompletableFuture<ImeResponse> getPendingImeResponse() {
- synchronized (mLock) {
- return mPendingImeResponse;
- }
- }
-
- private static final class InlineSuggestionsRequestCallbackImpl
- extends IInlineSuggestionsRequestCallback.Stub {
-
- private final Object mLock;
- private final AutofillId mAutofillId;
- @GuardedBy("mLock")
- private final CompletableFuture<ImeResponse> mResponse;
- @GuardedBy("mLock")
- private final Consumer<InlineSuggestionsRequest> mRequestConsumer;
- private final ImeStatusListener mImeStatusListener;
- private final Handler mHandler;
- private final Runnable mTimeoutCallback;
-
- private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId,
- CompletableFuture<ImeResponse> response,
- ImeStatusListener imeStatusListener,
- Consumer<InlineSuggestionsRequest> requestConsumer,
- Handler handler, Object lock) {
- mAutofillId = autofillId;
- mResponse = response;
- mImeStatusListener = imeStatusListener;
- mRequestConsumer = requestConsumer;
- mLock = lock;
-
- mHandler = handler;
- mTimeoutCallback = () -> {
- Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
- completeIfNot(null);
- };
- mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
- }
-
- private void completeIfNot(@Nullable ImeResponse response) {
- synchronized (mLock) {
- if (mResponse.isDone()) {
- return;
- }
- mResponse.complete(response);
- mRequestConsumer.accept(response == null ? null : response.mRequest);
- mHandler.removeCallbacks(mTimeoutCallback);
- }
- }
-
- @BinderThread
- @Override
- public void onInlineSuggestionsUnsupported() throws RemoteException {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
- completeIfNot(null);
- }
-
- @BinderThread
- @Override
- public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
- IInlineSuggestionsResponseCallback callback) {
- if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
- mImeStatusListener.onInputMethodStartInput(mAutofillId);
- if (request != null && callback != null) {
- completeIfNot(new ImeResponse(request, callback));
- } else {
- completeIfNot(null);
- }
- }
-
- @Override
- public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
- if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
- mImeStatusListener.onInputMethodStartInput(imeFieldId);
- }
-
- @Override
- public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
- if (sDebug) {
- Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
- }
- // TODO(b/151123764): use this signal to adjust the timeout on Autofill side waiting for
- // IME to show.
- }
-
- @BinderThread
- @Override
- public void onInputMethodStartInputView() {
- if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
- mImeStatusListener.onInputMethodStartInputView();
- }
-
- @BinderThread
- @Override
- public void onInputMethodFinishInputView() {
- if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
- mImeStatusListener.onInputMethodFinishInputView();
- }
-
- @Override
- public void onInputMethodFinishInput() throws RemoteException {
- if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
- mImeStatusListener.onInputMethodFinishInput();
- }
- }
-
- private interface ImeStatusListener {
- void onInputMethodStartInput(AutofillId imeFieldId);
-
- void onInputMethodStartInputView();
-
- void onInputMethodFinishInputView();
-
- void onInputMethodFinishInput();
- }
-
- /**
- * A data class wrapping Autofill responses for the inline suggestion request.
- */
- private static class AutofillResponse {
- @NonNull
- final AutofillId mAutofillId;
-
- @NonNull
- final InlineSuggestionsResponse mResponse;
-
- AutofillResponse(@NonNull AutofillId autofillId,
- @NonNull InlineSuggestionsResponse response) {
- mAutofillId = autofillId;
- mResponse = response;
- }
-
- }
-
- /**
- * A data class wrapping IME responses for the create inline suggestions request.
- */
- private static class ImeResponse {
- @NonNull
- final InlineSuggestionsRequest mRequest;
-
- @NonNull
- final IInlineSuggestionsResponseCallback mCallback;
-
- ImeResponse(@NonNull InlineSuggestionsRequest request,
- @NonNull IInlineSuggestionsResponseCallback callback) {
- mRequest = request;
- mCallback = callback;
- }
-
- InlineSuggestionsRequest getRequest() {
- return mRequest;
- }
- }
-}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 12905696ff98..4ecffd8d29b0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -304,7 +304,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private boolean mForAugmentedAutofillOnly;
@Nullable
- private final InlineSuggestionSession mInlineSuggestionSession;
+ private final AutofillInlineSessionController mInlineSessionController;
/**
* Receiver of assist data from the app's {@link Activity}.
@@ -720,8 +720,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
if (inlineSuggestionsRequestConsumer != null) {
- mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
- inlineSuggestionsRequestConsumer);
+ // TODO(b/146454892): pipe the uiExtras from the ExtServices.
+ mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
+ inlineSuggestionsRequestConsumer, Bundle.EMPTY);
}
} else {
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
@@ -777,8 +778,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
setClientLocked(client);
- mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
- componentName, handler, mLock);
+ mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal,
+ userId, componentName, handler, mLock);
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
@@ -2561,7 +2562,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (sVerbose) Slog.v(TAG, "Exiting view " + id);
mUi.hideFillUi(this);
hideAugmentedAutofillLocked(viewState);
- mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+ mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
mCurrentViewId = null;
}
break;
@@ -2779,7 +2780,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
@Nullable String filterText) {
final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
- mInlineSuggestionSession.getInlineSuggestionsRequest();
+ mInlineSessionController.getInlineSuggestionsRequestLocked();
if (!inlineSuggestionsRequest.isPresent()) {
Log.w(TAG, "InlineSuggestionsRequest unavailable");
return false;
@@ -2801,7 +2802,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
this, () -> {
synchronized (mLock) {
- mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+ mInlineSessionController.hideInlineSuggestionsUiLocked(
+ mCurrentViewId);
}
}, remoteRenderService);
if (inlineSuggestionsResponse == null) {
@@ -2809,7 +2811,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return false;
}
- return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId,
+ return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId,
inlineSuggestionsResponse);
}
@@ -3106,8 +3108,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
focusedId,
currentValue, inlineSuggestionsRequest,
/*inlineSuggestionsCallback=*/
- response -> mInlineSuggestionSession.onInlineSuggestionsResponse(
- mCurrentViewId, response),
+ response -> {
+ synchronized (mLock) {
+ return mInlineSessionController
+ .onInlineSuggestionsResponseLocked(
+ mCurrentViewId, response);
+ }
+ },
/*onErrorCallback=*/ () -> {
synchronized (mLock) {
cancelAugmentedAutofillLocked();
@@ -3125,11 +3132,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
&& (mForAugmentedAutofillOnly
|| !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
- mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
- /*requestConsumer=*/ requestAugmentedAutofill);
+ // TODO(b/146454892): pipe the uiExtras from the ExtServices.
+ mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
+ /*requestConsumer=*/ requestAugmentedAutofill, Bundle.EMPTY);
} else {
requestAugmentedAutofill.accept(
- mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null));
+ mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
}
if (mAugmentedAutofillDestroyer == null) {
mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e6cb37185d71..846d09915fba 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3106,10 +3106,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
MSG_SHOW_SOFT_INPUT, getImeShowFlags(), reason, mCurMethod, resultReceiver,
showInputToken));
mInputShown = true;
+
if (mHaveConnection && !mVisibleBound) {
bindCurrentInputMethodServiceLocked(
mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);
mVisibleBound = true;
+ } else {
+ // Clear the show request after the input shown.
+ mShowRequested = false;
}
res = true;
} else if (mHaveConnection && SystemClock.uptimeMillis()
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index ad3c8a61182f..d8acf0e331af 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -809,11 +809,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
locationRequest.setProvider(provider);
// Ignore location settings if in emergency mode. This is only allowed for
- // isUserEmergency request (introduced in HAL v2.0), or DBH request in HAL v1.1.
+ // isUserEmergency request (introduced in HAL v2.0), or HAL v1.1.
if (mNIHandler.getInEmergency()) {
GnssConfiguration.HalInterfaceVersion halVersion =
mGnssConfiguration.getHalInterfaceVersion();
- if (isUserEmergency || (halVersion.mMajor < 2 && !independentFromGnss)) {
+ if (isUserEmergency || halVersion.mMajor < 2) {
locationRequest.setLocationSettingsIgnored(true);
durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER;
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 52e9d7c67605..193107996d1e 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -808,7 +808,7 @@ class MediaRouter2ServiceImpl {
return;
}
- // Can be null if the session is system's.
+ // Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
@@ -829,7 +829,7 @@ class MediaRouter2ServiceImpl {
return;
}
- // Can be null if the session is system's.
+ // Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
@@ -850,7 +850,7 @@ class MediaRouter2ServiceImpl {
return;
}
- // Can be null if the session is system's.
+ // Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
@@ -1232,7 +1232,7 @@ class MediaRouter2ServiceImpl {
route.getOriginalId(), sessionHints);
}
- // routerRecord can be null if the session is system's.
+ // routerRecord can be null if the session is system's or RCN.
private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
@@ -1250,7 +1250,7 @@ class MediaRouter2ServiceImpl {
route.getOriginalId());
}
- // routerRecord can be null if the session is system's.
+ // routerRecord can be null if the session is system's or RCN.
private void deselectRouteOnHandler(long uniqueRequestId,
@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1270,7 +1270,7 @@ class MediaRouter2ServiceImpl {
route.getOriginalId());
}
- // routerRecord can be null if the session is system's.
+ // routerRecord can be null if the session is system's or RCN.
private void transferToRouteOnHandler(long uniqueRequestId,
@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1289,6 +1289,8 @@ class MediaRouter2ServiceImpl {
route.getOriginalId());
}
+ // routerRecord is null if and only if the session is created without the request, which
+ // includes the system's session and RCN cases.
private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
@NonNull String description) {
@@ -1305,12 +1307,6 @@ class MediaRouter2ServiceImpl {
return true;
}
- //TODO(b/152950479): Handle RCN case.
- if (routerRecord == null) {
- Slog.w(TAG, "Ignoring " + description + " route from unknown router.");
- return false;
- }
-
RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
if (matchingRecord != routerRecord) {
Slog.w(TAG, "Ignoring " + description + " route from non-matching router. "
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index a726d39b8595..e3faffa0699b 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -882,7 +882,7 @@ public final class DefaultPermissionGrantPolicy {
public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) {
Log.i(TAG, "Granting permissions to default browser for user:" + userId);
- grantPermissionsToSystemPackage(packageName, userId, ALWAYS_LOCATION_PERMISSIONS);
+ grantPermissionsToSystemPackage(packageName, userId, FOREGROUND_LOCATION_PERMISSIONS);
}
private String getDefaultSystemHandlerActivityPackage(String intentAction, int userId) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9adacb8c578d..d31939dec509 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4121,20 +4121,18 @@ class Task extends WindowContainer<WindowContainer> {
* Any time any of these conditions are updated, the updating code should call
* sendTaskAppeared.
*/
- private boolean taskAppearedReady() {
+ boolean taskAppearedReady() {
return mSurfaceControl != null && mTaskOrganizer != null && getHasBeenVisible();
}
private void sendTaskAppeared() {
- if (taskAppearedReady() && !mTaskAppearedSent) {
- mTaskAppearedSent = true;
+ if (mTaskOrganizer != null) {
mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
}
}
private void sendTaskVanished() {
- if (mTaskOrganizer != null && mTaskAppearedSent) {
- mTaskAppearedSent = false;
+ if (mTaskOrganizer != null) {
mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 2bbf8dbb274c..22702dd6b566 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -106,19 +106,29 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
void addTask(Task t) {
- mOrganizedTasks.add(t);
- try {
- mOrganizer.onTaskAppeared(t.getTaskInfo());
- } catch (Exception e) {
- Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+ if (t.mTaskAppearedSent) return;
+
+ if (!mOrganizedTasks.contains(t)) {
+ mOrganizedTasks.add(t);
+ }
+ if (t.taskAppearedReady()) {
+ try {
+ t.mTaskAppearedSent = true;
+ mOrganizer.onTaskAppeared(t.getTaskInfo());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+ }
}
}
void removeTask(Task t) {
- try {
- mOrganizer.onTaskVanished(t.getTaskInfo());
- } catch (Exception e) {
- Slog.e(TAG, "Exception sending taskVanished callback" + e);
+ if (t.mTaskAppearedSent) {
+ try {
+ t.mTaskAppearedSent = false;
+ mOrganizer.onTaskVanished(t.getTaskInfo());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskVanished callback" + e);
+ }
}
mOrganizedTasks.remove(t);
}
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 97de1800cae2..2dbbc5ac6806 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -155,11 +155,6 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) {
return ok();
}
-binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) {
- *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs);
- return ok();
-}
-
binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path,
int32_t* _aidl_return) {
*_aidl_return = mImpl.makeDir(storageId, path);
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index d0357d924586..28613e101b7c 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -71,7 +71,6 @@ public:
binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
const std::string& libDirRelativePath,
const std::string& abi, bool* _aidl_return) final;
- binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final;
private:
android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index de24bcf02a7e..d1153e6cf6e7 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -581,6 +581,7 @@ StorageId IncrementalService::findStorageId(std::string_view path) const {
int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
const auto ifs = getIfs(storageId);
if (!ifs) {
+ LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
return -EINVAL;
}
@@ -1153,6 +1154,7 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs,
fsControlParcel.incremental->pendingReads.reset(
base::unique_fd(::dup(ifs.control.pendingReads())));
fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs())));
+ fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId);
sp<IncrementalDataLoaderListener> listener =
new IncrementalDataLoaderListener(*this,
externalListener ? *externalListener
@@ -1451,4 +1453,10 @@ void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) {
incrementalService.onAppOpChanged(packageName);
}
+binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams(
+ bool enableReadLogs, int32_t* _aidl_return) {
+ *_aidl_return = incrementalService.setStorageParams(storage, enableReadLogs);
+ return binder::Status::ok();
+}
+
} // namespace android::incremental
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 9b156464f480..db14a794457e 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -39,6 +39,7 @@
#include "ServiceWrappers.h"
#include "android/content/pm/BnDataLoaderStatusListener.h"
+#include "android/os/incremental/BnIncrementalServiceConnector.h"
#include "incfs.h"
#include "path.h"
@@ -139,7 +140,7 @@ public:
DataLoaderStatusListener externalListener)
: incrementalService(incrementalService), externalListener(externalListener) {}
// Callbacks interface
- binder::Status onStatusChanged(MountId mount, int newStatus) override;
+ binder::Status onStatusChanged(MountId mount, int newStatus) final;
private:
IncrementalService& incrementalService;
@@ -149,13 +150,24 @@ public:
class AppOpsListener : public android::BnAppOpsCallback {
public:
AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {}
- void opChanged(int32_t op, const String16& packageName) override;
+ void opChanged(int32_t op, const String16& packageName) final;
private:
IncrementalService& incrementalService;
const std::string packageName;
};
+ class IncrementalServiceConnector : public BnIncrementalServiceConnector {
+ public:
+ IncrementalServiceConnector(IncrementalService& incrementalService, int32_t storage)
+ : incrementalService(incrementalService), storage(storage) {}
+ binder::Status setStorageParams(bool enableReadLogs, int32_t* _aidl_return) final;
+
+ private:
+ IncrementalService& incrementalService;
+ int32_t const storage;
+ };
+
private:
static const bool sEnablePerfLogging;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 2ce9c2b9ced0..06ca6c110613 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -192,6 +192,21 @@ public class TaskOrganizerTests extends WindowTestsBase {
}
@Test
+ public void testTaskNoDraw() throws RemoteException {
+ final ActivityStack stack = createStack();
+ final Task task = createTask(stack, false /* fakeDraw */);
+ final ITaskOrganizer organizer = registerMockOrganizer();
+
+ stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ verify(organizer, never()).onTaskAppeared(any());
+ assertTrue(stack.isOrganized());
+
+ mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
+ verify(organizer, never()).onTaskVanished(any());
+ assertFalse(stack.isOrganized());
+ }
+
+ @Test
public void testClearOrganizer() throws RemoteException {
final ActivityStack stack = createStack();
final Task task = createTask(stack);
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index f3e9de0d2688..3048ad7c1fb0 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -311,7 +311,7 @@ public final class LocationAccessPolicy {
}
// If the user or profile is current, permission is granted.
// Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
- return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid);
+ return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, pid, uid);
}
private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) {
diff --git a/tests/net/common/java/android/net/DependenciesTest.java b/tests/net/common/java/android/net/DependenciesTest.java
new file mode 100644
index 000000000000..ac1c28a45462
--- /dev/null
+++ b/tests/net/common/java/android/net/DependenciesTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A simple class that tests dependencies to java standard tools from the
+ * Network stack. These tests are not meant to be comprehensive tests of
+ * the relevant APIs : such tests belong in the relevant test suite for
+ * these dependencies. Instead, this just makes sure coverage is present
+ * by calling the methods in the exact way (or a representative way of how)
+ * they are called in the network stack.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DependenciesTest {
+ // Used to in ipmemorystore's RegularMaintenanceJobService to convert
+ // 24 hours into seconds
+ @Test
+ public void testTimeUnit() {
+ final int hours = 24;
+ final long inSeconds = TimeUnit.HOURS.toMillis(hours);
+ assertEquals(inSeconds, hours * 60 * 60 * 1000);
+ }
+
+ private byte[] makeTrivialArray(final int size) {
+ final byte[] src = new byte[size];
+ for (int i = 0; i < size; ++i) {
+ src[i] = (byte) i;
+ }
+ return src;
+ }
+
+ // Used in ApfFilter to find an IP address from a byte array
+ @Test
+ public void testArrays() {
+ final int size = 128;
+ final byte[] src = makeTrivialArray(size);
+
+ // Test copy
+ final int copySize = 16;
+ final int offset = 24;
+ final byte[] expected = new byte[copySize];
+ for (int i = 0; i < copySize; ++i) {
+ expected[i] = (byte) (offset + i);
+ }
+
+ final byte[] copy = Arrays.copyOfRange(src, offset, offset + copySize);
+ assertArrayEquals(expected, copy);
+ assertArrayEquals(new byte[0], Arrays.copyOfRange(src, size, size));
+ }
+
+ // Used mainly in the Dhcp code
+ @Test
+ public void testCopyOf() {
+ final byte[] src = makeTrivialArray(128);
+ final byte[] copy = Arrays.copyOf(src, src.length);
+ assertArrayEquals(src, copy);
+ assertFalse(src == copy);
+
+ assertArrayEquals(new byte[0], Arrays.copyOf(src, 0));
+
+ final int excess = 16;
+ final byte[] biggerCopy = Arrays.copyOf(src, src.length + excess);
+ for (int i = src.length; i < src.length + excess; ++i) {
+ assertEquals(0, biggerCopy[i]);
+ }
+ for (int i = src.length - 1; i >= 0; --i) {
+ assertEquals(src[i], biggerCopy[i]);
+ }
+ }
+
+ // Used mainly in DnsUtils but also various other places
+ @Test
+ public void testAsList() {
+ final int size = 24;
+ final Object[] src = new Object[size];
+ final ArrayList<Object> expected = new ArrayList<>(size);
+ for (int i = 0; i < size; ++i) {
+ final Object o = new Object();
+ src[i] = o;
+ expected.add(o);
+ }
+ assertEquals(expected, Arrays.asList(src));
+ }
+}
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 6e9dc8eaf2dc..12d80fc2c084 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -17,6 +17,8 @@
package android.net;
import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.MIN_TRANSPORT;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
@@ -32,10 +34,12 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVIT
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
@@ -45,10 +49,15 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.wifi.aware.DiscoverySession;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
import android.os.Build;
+import android.os.Process;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
@@ -61,6 +70,7 @@ import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.Arrays;
import java.util.Set;
@@ -74,6 +84,9 @@ public class NetworkCapabilitiesTest {
@Rule
public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+ private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class);
+ private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class);
+
private boolean isAtLeastR() {
// BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R.
// Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after
@@ -685,4 +698,232 @@ public class NetworkCapabilitiesTest {
assertEquals(TRANSPORT_VPN, transportTypes[2]);
assertEquals(TRANSPORT_TEST, transportTypes[3]);
}
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testTelephonyNetworkSpecifier() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setNetworkSpecifier(specifier)
+ .build();
+ assertEquals(specifier, nc1.getNetworkSpecifier());
+ try {
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .setNetworkSpecifier(specifier)
+ .build();
+ fail("Must have a single transport type. Without transport type or multiple transport"
+ + " types is invalid.");
+ } catch (IllegalStateException expected) { }
+ }
+
+ @Test
+ public void testWifiAwareNetworkSpecifier() {
+ final NetworkCapabilities nc = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI_AWARE);
+ // If NetworkSpecifier is not set, the default value is null.
+ assertNull(nc.getNetworkSpecifier());
+ final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder(
+ mDiscoverySession, mPeerHandle).build();
+ nc.setNetworkSpecifier(specifier);
+ assertEquals(specifier, nc.getNetworkSpecifier());
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testAdministratorUidsAndOwnerUid() {
+ // Test default owner uid.
+ // If the owner uid is not set, the default value should be Process.INVALID_UID.
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build();
+ assertEquals(Process.INVALID_UID, nc1.getOwnerUid());
+ // Test setAdministratorUids and getAdministratorUids.
+ final int[] administratorUids = {1001, 10001};
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .setAdministratorUids(administratorUids)
+ .build();
+ assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids()));
+ // Test setOwnerUid and getOwnerUid.
+ // The owner UID must be included in administrator UIDs, or throw IllegalStateException.
+ try {
+ final NetworkCapabilities nc3 = new NetworkCapabilities.Builder()
+ .setOwnerUid(1001)
+ .build();
+ fail("The owner UID must be included in administrator UIDs.");
+ } catch (IllegalStateException expected) { }
+ final NetworkCapabilities nc4 = new NetworkCapabilities.Builder()
+ .setAdministratorUids(administratorUids)
+ .setOwnerUid(1001)
+ .build();
+ assertEquals(1001, nc4.getOwnerUid());
+ try {
+ final NetworkCapabilities nc5 = new NetworkCapabilities.Builder()
+ .setAdministratorUids(null)
+ .build();
+ fail("Should not set null into setAdministratorUids");
+ } catch (NullPointerException expected) { }
+ }
+
+ @Test
+ public void testLinkBandwidthKbps() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED.
+ assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps());
+ assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps());
+ nc.setLinkDownstreamBandwidthKbps(512);
+ nc.setLinkUpstreamBandwidthKbps(128);
+ assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+ assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+ assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+ assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+ }
+
+ @Test
+ public void testSignalStrength() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED.
+ assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength());
+ nc.setSignalStrength(-80);
+ assertEquals(-80, nc.getSignalStrength());
+ assertNotEquals(-50, nc.getSignalStrength());
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testDeduceRestrictedCapability() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ // Default capabilities don't have restricted capability.
+ assertFalse(nc.deduceRestrictedCapability());
+ // If there is a force restricted capability, then the network capabilities is restricted.
+ nc.addCapability(NET_CAPABILITY_OEM_PAID);
+ nc.addCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc.deduceRestrictedCapability());
+ // Except for the force restricted capability, if there is any unrestricted capability in
+ // capabilities, then the network capabilities is not restricted.
+ nc.removeCapability(NET_CAPABILITY_OEM_PAID);
+ nc.addCapability(NET_CAPABILITY_CBS);
+ assertFalse(nc.deduceRestrictedCapability());
+ // Except for the force restricted capability, the network capabilities will only be treated
+ // as restricted when there is no any unrestricted capability.
+ nc.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc.deduceRestrictedCapability());
+ }
+
+ private void assertNoTransport(NetworkCapabilities nc) {
+ for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+ assertFalse(nc.hasTransport(i));
+ }
+ }
+
+ // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all
+ // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence
+ // is true. If positiveSequence is false, then the check sequence is opposite.
+ private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType,
+ boolean positiveSequence) {
+ for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) {
+ if (positiveSequence) {
+ assertTrue(nc.hasTransport(i));
+ } else {
+ assertFalse(nc.hasTransport(i));
+ }
+ }
+ for (int i = MAX_TRANSPORT; i > maxTransportType; i--) {
+ if (positiveSequence) {
+ assertFalse(nc.hasTransport(i));
+ } else {
+ assertTrue(nc.hasTransport(i));
+ }
+ }
+ }
+
+ @Test
+ public void testMultipleTransportTypes() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ assertNoTransport(nc);
+ // Test adding multiple transport types.
+ for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+ nc.addTransportType(i);
+ checkCurrentTransportTypes(nc, i, true /* positiveSequence */);
+ }
+ // Test removing multiple transport types.
+ for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+ nc.removeTransportType(i);
+ checkCurrentTransportTypes(nc, i, false /* positiveSequence */);
+ }
+ assertNoTransport(nc);
+ nc.addTransportType(TRANSPORT_WIFI);
+ assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+ assertFalse(nc.hasTransport(TRANSPORT_VPN));
+ nc.addTransportType(TRANSPORT_VPN);
+ assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+ assertTrue(nc.hasTransport(TRANSPORT_VPN));
+ nc.removeTransportType(TRANSPORT_WIFI);
+ assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+ assertTrue(nc.hasTransport(TRANSPORT_VPN));
+ nc.removeTransportType(TRANSPORT_VPN);
+ assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+ assertFalse(nc.hasTransport(TRANSPORT_VPN));
+ assertNoTransport(nc);
+ }
+
+ @Test
+ public void testAddAndRemoveTransportType() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ try {
+ nc.addTransportType(-1);
+ fail("Should not set invalid transport type into addTransportType");
+ } catch (IllegalArgumentException expected) { }
+ try {
+ nc.removeTransportType(-1);
+ fail("Should not set invalid transport type into removeTransportType");
+ } catch (IllegalArgumentException e) { }
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testBuilder() {
+ final int ownerUid = 1001;
+ final int signalStrength = -80;
+ final int requestUid = 10100;
+ final int[] administratorUids = {ownerUid, 10001};
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+ final String ssid = "TEST_SSID";
+ final String packageName = "com.google.test.networkcapabilities";
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .removeTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_EIMS)
+ .addCapability(NET_CAPABILITY_CBS)
+ .removeCapability(NET_CAPABILITY_CBS)
+ .setAdministratorUids(administratorUids)
+ .setOwnerUid(ownerUid)
+ .setLinkDownstreamBandwidthKbps(512)
+ .setLinkUpstreamBandwidthKbps(128)
+ .setNetworkSpecifier(specifier)
+ .setTransportInfo(null)
+ .setSignalStrength(signalStrength)
+ .setSsid(ssid)
+ .setRequestorUid(requestUid)
+ .setRequestorPackageName(packageName)
+ .build();
+ assertEquals(1, nc.getTransportTypes().length);
+ assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]);
+ assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS));
+ assertFalse(nc.hasCapability(NET_CAPABILITY_CBS));
+ assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids()));
+ assertEquals(ownerUid, nc.getOwnerUid());
+ assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+ assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+ assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+ assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+ assertEquals(specifier, nc.getNetworkSpecifier());
+ assertNull(nc.getTransportInfo());
+ assertEquals(signalStrength, nc.getSignalStrength());
+ assertNotEquals(-50, nc.getSignalStrength());
+ assertEquals(ssid, nc.getSsid());
+ assertEquals(requestUid, nc.getRequestorUid());
+ assertEquals(packageName, nc.getRequestorPackageName());
+ // Cannot assign null into NetworkCapabilities.Builder
+ try {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null);
+ fail("Should not set null into NetworkCapabilities.Builder");
+ } catch (NullPointerException expected) { }
+ assertEquals(nc, new NetworkCapabilities.Builder(nc).build());
+ }
}
diff --git a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
index 9119d62fb023..7b22e45db90a 100644
--- a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -31,7 +31,6 @@ import android.net.NetworkStats.TAG_NONE
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertFieldCountEquals
import com.android.testutils.assertNetworkStatsEquals
import com.android.testutils.assertParcelingIsLossless
@@ -47,70 +46,22 @@ import kotlin.test.assertEquals
class NetworkStatsApiTest {
@Rule
@JvmField
- val ignoreRule = DevSdkIgnoreRule()
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
private val testStatsEmpty = NetworkStats(0L, 0)
+ // Note that these variables need to be initialized outside of constructor, initialize
+ // here with methods that don't exist in Q devices will result in crash.
+
// stats1 and stats2 will have some entries with common keys, which are expected to
// be merged if performing add on these 2 stats.
- private val testStats1 = NetworkStats(0L, 0)
- // Entries which only appear in set1.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4))
- // Entries which are common for set1 and set2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
-
- private val testStats2 = NetworkStats(0L, 0)
- // Entries which are common for set1 and set2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
- // Entry which only appears in set2.
- .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ private lateinit var testStats1: NetworkStats
+ private lateinit var testStats2: NetworkStats
// This is a result of adding stats1 and stats2, while the merging of common key items is
// subject to test later, this should not be initialized with for a loop to add stats1
// and stats2 above.
- private val testStats3 = NetworkStats(0L, 9)
- // Entries which are unique either in stats1 or stats2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
- .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
- // Entries which are common for stats1 and stats2 are being merged.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0))
+ private lateinit var testStats3: NetworkStats
companion object {
private const val TEST_IFACE = "test0"
@@ -120,13 +71,67 @@ class NetworkStatsApiTest {
@Before
fun setUp() {
+ testStats1 = NetworkStats(0L, 0)
+ // Entries which only appear in set1.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4))
+ // Entries which are common for set1 and set2.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0))
+ .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8))
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
assertEquals(8, testStats1.size())
+
+ testStats2 = NetworkStats(0L, 0)
+ // Entries which are common for set1 and set2.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
+ .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
+ // Entry which only appears in set2.
+ .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
assertEquals(5, testStats2.size())
+
+ testStats3 = NetworkStats(0L, 9)
+ // Entries which are unique either in stats1 or stats2.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+ .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ // Entries which are common for stats1 and stats2 are being merged.
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1))
+ .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49))
+ .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15))
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0))
assertEquals(9, testStats3.size())
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testAddEntry() {
val expectedEntriesInStats2 = arrayOf(
Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
@@ -156,7 +161,6 @@ class NetworkStatsApiTest {
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testAdd() {
var stats = NetworkStats(0L, 0)
assertNetworkStatsEquals(testStatsEmpty, stats)
@@ -168,7 +172,6 @@ class NetworkStatsApiTest {
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testParcelUnparcel() {
assertParcelingIsLossless(testStatsEmpty)
assertParcelingIsLossless(testStats1)
@@ -177,7 +180,6 @@ class NetworkStatsApiTest {
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testDescribeContents() {
assertEquals(0, testStatsEmpty.describeContents())
assertEquals(0, testStats1.describeContents())
@@ -186,7 +188,6 @@ class NetworkStatsApiTest {
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testSubtract() {
// STATS3 - STATS2 = STATS1
assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2))
@@ -195,7 +196,6 @@ class NetworkStatsApiTest {
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testMethodsDontModifyReceiver() {
listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach {
val origStats = it.clone()
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index d56f2be7ecb3..b513463ec98f 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -160,7 +160,7 @@ struct AtomDecl {
int exclusiveField = 0;
int defaultState = INT_MAX;
int triggerStateReset = INT_MAX;
- bool nested;
+ bool nested = true;
int uidField = 0;