diff options
40 files changed, 774 insertions, 216 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 2ef20253e950..f7bfeaeb02b0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -204,6 +204,10 @@ package android.app.backup { package android.app.usage { + public class NetworkStatsManager { + method public void setPollForce(boolean); + } + public class StorageStatsManager { method public boolean isQuotaSupported(java.util.UUID); method public boolean isReservedSupported(java.util.UUID); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 919f71472d33..569c2bd37b6a 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -575,6 +575,11 @@ interface IActivityManager { void resizeDockedStack(in Rect dockedBounds, in Rect tempDockedTaskBounds, in Rect tempDockedTaskInsetBounds, in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds); + /** + * Sets whether we are currently in an interactive split screen resize operation where we + * are changing the docked stack size. + */ + void setSplitScreenResizing(boolean resizing); int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName); // Gets the URI permissions granted to an arbitrary package (or all packages if null) // NOTE: this is different from getPersistedUriPermissions(), which returns the URIs the package diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 0b21196f5d0f..9f46f207d645 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.Nullable; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.usage.NetworkStats.Bucket; import android.content.Context; import android.net.ConnectivityManager; @@ -111,7 +112,9 @@ public class NetworkStatsManager { /** @hide */ public static final int FLAG_POLL_ON_OPEN = 1 << 0; /** @hide */ - public static final int FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN = 1 << 1; + public static final int FLAG_POLL_FORCE = 1 << 1; + /** @hide */ + public static final int FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN = 1 << 2; private int mFlags; @@ -141,6 +144,16 @@ public class NetworkStatsManager { } /** @hide */ + @TestApi + public void setPollForce(boolean pollForce) { + if (pollForce) { + mFlags |= FLAG_POLL_FORCE; + } else { + mFlags &= ~FLAG_POLL_FORCE; + } + } + + /** @hide */ public void setAugmentWithSubscriptionPlan(boolean augmentWithSubscriptionPlan) { if (augmentWithSubscriptionPlan) { mFlags |= FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN; diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 435bcb020a91..90d407ca3a96 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -498,7 +498,7 @@ public final class ProgramSelector implements Parcelable { @Override public int hashCode() { // secondaryIds and vendorIds are ignored for equality/hashing - return Objects.hash(mProgramType, mPrimaryId); + return mPrimaryId.hashCode(); } @Override @@ -507,7 +507,8 @@ public final class ProgramSelector implements Parcelable { if (!(obj instanceof ProgramSelector)) return false; ProgramSelector other = (ProgramSelector) obj; // secondaryIds and vendorIds are ignored for equality/hashing - return other.getProgramType() == mProgramType && mPrimaryId.equals(other.getPrimaryId()); + // programType can be inferred from primaryId, thus not checked + return mPrimaryId.equals(other.getPrimaryId()); } private ProgramSelector(Parcel in) { diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 32737c54bea1..a7d70d01b04a 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -807,7 +807,8 @@ public abstract class NotificationListenerService extends Service { * @return An array of active notifications, sorted in natural order. */ public StatusBarNotification[] getActiveNotifications() { - return getActiveNotifications(null, TRIM_FULL); + StatusBarNotification[] activeNotifications = getActiveNotifications(null, TRIM_FULL); + return activeNotifications != null ? activeNotifications : new StatusBarNotification[0]; } /** @@ -842,7 +843,8 @@ public abstract class NotificationListenerService extends Service { */ @SystemApi public StatusBarNotification[] getActiveNotifications(int trim) { - return getActiveNotifications(null, trim); + StatusBarNotification[] activeNotifications = getActiveNotifications(null, trim); + return activeNotifications != null ? activeNotifications : new StatusBarNotification[0]; } /** @@ -858,7 +860,8 @@ public abstract class NotificationListenerService extends Service { * same order as the key list. */ public StatusBarNotification[] getActiveNotifications(String[] keys) { - return getActiveNotifications(keys, TRIM_FULL); + StatusBarNotification[] activeNotifications = getActiveNotifications(keys, TRIM_FULL); + return activeNotifications != null ? activeNotifications : new StatusBarNotification[0]; } /** @@ -890,6 +893,9 @@ public abstract class NotificationListenerService extends Service { private StatusBarNotification[] cleanUpNotificationList( ParceledListSlice<StatusBarNotification> parceledList) { + if (parceledList == null || parceledList.getList() == null) { + return new StatusBarNotification[0]; + } List<StatusBarNotification> list = parceledList.getList(); ArrayList<StatusBarNotification> corruptNotifications = null; int N = list.size(); diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java index e60377b46e7d..549f8b3953dd 100644 --- a/core/java/android/text/method/LinkMovementMethod.java +++ b/core/java/android/text/method/LinkMovementMethod.java @@ -25,6 +25,7 @@ import android.text.style.ClickableSpan; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.textclassifier.TextLinks.TextLinkSpan; import android.widget.TextView; /** @@ -130,64 +131,70 @@ public class LinkMovementMethod extends ScrollingMovementMethod { selStart = selEnd = -1; switch (what) { - case CLICK: - if (selStart == selEnd) { - return false; - } + case CLICK: + if (selStart == selEnd) { + return false; + } - ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class); + ClickableSpan[] links = buffer.getSpans(selStart, selEnd, ClickableSpan.class); - if (link.length != 1) - return false; + if (links.length != 1) { + return false; + } - link[0].onClick(widget); - break; + ClickableSpan link = links[0]; + if (link instanceof TextLinkSpan) { + ((TextLinkSpan) link).onClick(widget, TextLinkSpan.INVOCATION_METHOD_KEYBOARD); + } else { + link.onClick(widget); + } + break; - case UP: - int bestStart, bestEnd; + case UP: + int bestStart, bestEnd; - bestStart = -1; - bestEnd = -1; + bestStart = -1; + bestEnd = -1; - for (int i = 0; i < candidates.length; i++) { - int end = buffer.getSpanEnd(candidates[i]); + for (int i = 0; i < candidates.length; i++) { + int end = buffer.getSpanEnd(candidates[i]); - if (end < selEnd || selStart == selEnd) { - if (end > bestEnd) { - bestStart = buffer.getSpanStart(candidates[i]); - bestEnd = end; + if (end < selEnd || selStart == selEnd) { + if (end > bestEnd) { + bestStart = buffer.getSpanStart(candidates[i]); + bestEnd = end; + } } } - } - if (bestStart >= 0) { - Selection.setSelection(buffer, bestEnd, bestStart); - return true; - } + if (bestStart >= 0) { + Selection.setSelection(buffer, bestEnd, bestStart); + return true; + } - break; + break; - case DOWN: - bestStart = Integer.MAX_VALUE; - bestEnd = Integer.MAX_VALUE; + case DOWN: + bestStart = Integer.MAX_VALUE; + bestEnd = Integer.MAX_VALUE; - for (int i = 0; i < candidates.length; i++) { - int start = buffer.getSpanStart(candidates[i]); + for (int i = 0; i < candidates.length; i++) { + int start = buffer.getSpanStart(candidates[i]); - if (start > selStart || selStart == selEnd) { - if (start < bestStart) { - bestStart = start; - bestEnd = buffer.getSpanEnd(candidates[i]); + if (start > selStart || selStart == selEnd) { + if (start < bestStart) { + bestStart = start; + bestEnd = buffer.getSpanEnd(candidates[i]); + } } } - } - if (bestEnd < Integer.MAX_VALUE) { - Selection.setSelection(buffer, bestStart, bestEnd); - return true; - } + if (bestEnd < Integer.MAX_VALUE) { + Selection.setSelection(buffer, bestStart, bestEnd); + return true; + } - break; + break; } return false; @@ -215,8 +222,14 @@ public class LinkMovementMethod extends ScrollingMovementMethod { ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class); if (links.length != 0) { + ClickableSpan link = links[0]; if (action == MotionEvent.ACTION_UP) { - links[0].onClick(widget); + if (link instanceof TextLinkSpan) { + ((TextLinkSpan) link).onClick( + widget, TextLinkSpan.INVOCATION_METHOD_TOUCH); + } else { + link.onClick(widget); + } } else if (action == MotionEvent.ACTION_DOWN) { if (widget.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) { @@ -225,8 +238,8 @@ public class LinkMovementMethod extends ScrollingMovementMethod { widget.hideFloatingToolbar(HIDE_FLOATING_TOOLBAR_DELAY_MS); } Selection.setSelection(buffer, - buffer.getSpanStart(links[0]), - buffer.getSpanEnd(links[0])); + buffer.getSpanStart(link), + buffer.getSpanEnd(link)); } return true; } else { diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 6486230f8e3a..8395681f0139 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -342,12 +342,6 @@ interface IWindowManager int getDockedStackSide(); /** - * Sets whether we are currently in a drag resize operation where we are changing the docked - * stack size. - */ - void setDockedStackResizing(boolean resizing); - - /** * Sets the region the user can touch the divider. This region will be excluded from the region * which is used to cause a focus switch when dispatching touch. */ diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 851b2c9be29b..e7faf142c55d 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -503,6 +503,22 @@ public final class TextLinks implements Parcelable { */ public static class TextLinkSpan extends ClickableSpan { + /** + * How the clickspan is triggered. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({INVOCATION_METHOD_UNSPECIFIED, INVOCATION_METHOD_TOUCH, + INVOCATION_METHOD_KEYBOARD}) + public @interface InvocationMethod {} + + /** @hide */ + public static final int INVOCATION_METHOD_UNSPECIFIED = -1; + /** @hide */ + public static final int INVOCATION_METHOD_TOUCH = 0; + /** @hide */ + public static final int INVOCATION_METHOD_KEYBOARD = 1; + private final TextLink mTextLink; public TextLinkSpan(@NonNull TextLink textLink) { @@ -511,16 +527,24 @@ public final class TextLinks implements Parcelable { @Override public void onClick(View widget) { + onClick(widget, INVOCATION_METHOD_UNSPECIFIED); + } + + /** @hide */ + public final void onClick(View widget, @InvocationMethod int invocationMethod) { if (widget instanceof TextView) { final TextView textView = (TextView) widget; final Context context = textView.getContext(); if (TextClassificationManager.getSettings(context).isSmartLinkifyEnabled()) { - if (textView.requestFocus()) { - textView.requestActionMode(this); - } else { - // If textView can not take focus, then simply handle the click as it will - // be difficult to get rid of the floating action mode. - textView.handleClick(this); + switch (invocationMethod) { + case INVOCATION_METHOD_TOUCH: + textView.requestActionMode(this); + break; + case INVOCATION_METHOD_KEYBOARD:// fall though + case INVOCATION_METHOD_UNSPECIFIED: // fall through + default: + textView.handleClick(this); + break; } } else { if (mTextLink.mUrlSpan != null) { diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index 2ce5a0be44e0..63c2e96e173c 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -1176,6 +1176,9 @@ public final class FloatingToolbar { final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist; final View menuItemButton = createMenuItemButton( mContext, menuItem, mIconTextSpacing, showIcon); + if (!showIcon && menuItemButton instanceof LinearLayout) { + ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER); + } // Adding additional start padding for the first button to even out button spacing. if (isFirstItem) { @@ -1200,57 +1203,21 @@ public final class FloatingToolbar { final int menuItemButtonWidth = Math.min( menuItemButton.getMeasuredWidth(), toolbarWidth); - final boolean isNewGroup = !isFirstItem && lastGroupId != menuItem.getGroupId(); - final int extraPadding = isNewGroup ? menuItemButton.getPaddingEnd() * 2 : 0; - // Check if we can fit an item while reserving space for the overflowButton. final boolean canFitWithOverflow = menuItemButtonWidth <= - availableWidth - mOverflowButtonSize.getWidth() - extraPadding; + availableWidth - mOverflowButtonSize.getWidth(); final boolean canFitNoOverflow = - isLastItem && menuItemButtonWidth <= availableWidth - extraPadding; + isLastItem && menuItemButtonWidth <= availableWidth; if (canFitWithOverflow || canFitNoOverflow) { - if (isNewGroup) { - final View divider = createDivider(mContext); - final int dividerWidth = divider.getLayoutParams().width; - - // Add extra padding to the end of the previous button. - // Half of the extra padding (less borderWidth) goes to the previous button. - final View previousButton = mMainPanel.getChildAt( - mMainPanel.getChildCount() - 1); - final int prevPaddingEnd = previousButton.getPaddingEnd() - + extraPadding / 2 - dividerWidth; - previousButton.setPaddingRelative( - previousButton.getPaddingStart(), - previousButton.getPaddingTop(), - prevPaddingEnd, - previousButton.getPaddingBottom()); - final ViewGroup.LayoutParams prevParams = previousButton.getLayoutParams(); - prevParams.width += extraPadding / 2 - dividerWidth; - previousButton.setLayoutParams(prevParams); - - // Add extra padding to the start of this button. - // Other half of the extra padding goes to this button. - final int paddingStart = menuItemButton.getPaddingStart() - + extraPadding / 2; - menuItemButton.setPaddingRelative( - paddingStart, - menuItemButton.getPaddingTop(), - menuItemButton.getPaddingEnd(), - menuItemButton.getPaddingBottom()); - - // Include a divider. - mMainPanel.addView(divider); - } - setButtonTagAndClickListener(menuItemButton, menuItem); // Set tooltips for main panel items, but not overflow items (b/35726766). menuItemButton.setTooltipText(menuItem.getTooltipText()); mMainPanel.addView(menuItemButton); final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams(); - params.width = menuItemButtonWidth + extraPadding / 2; + params.width = menuItemButtonWidth; menuItemButton.setLayoutParams(params); - availableWidth -= menuItemButtonWidth + extraPadding; + availableWidth -= menuItemButtonWidth; remainingMenuItems.pop(); } else { break; @@ -1726,30 +1693,6 @@ public final class FloatingToolbar { return popupWindow; } - private static View createDivider(Context context) { - // TODO: Inflate this instead. - View divider = new View(context); - - int _1dp = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics()); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - _1dp, ViewGroup.LayoutParams.MATCH_PARENT); - params.setMarginsRelative(0, _1dp * 10, 0, _1dp * 10); - divider.setLayoutParams(params); - - TypedArray a = context.obtainStyledAttributes( - new TypedValue().data, new int[] { R.attr.floatingToolbarDividerColor }); - divider.setBackgroundColor(a.getColor(0, 0)); - a.recycle(); - - divider.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - divider.setEnabled(false); - divider.setFocusable(false); - divider.setContentDescription(null); - - return divider; - } - /** * Creates an "appear" animation for the specified view. * diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 2aa40fdf9fd5..a135b28c196a 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -536,7 +536,7 @@ <dimen name="floating_toolbar_menu_image_width">24dp</dimen> <dimen name="floating_toolbar_menu_image_button_width">56dp</dimen> <dimen name="floating_toolbar_menu_image_button_vertical_padding">12dp</dimen> - <dimen name="floating_toolbar_menu_button_side_padding">11dp</dimen> + <dimen name="floating_toolbar_menu_button_side_padding">8dp</dimen> <dimen name="floating_toolbar_overflow_image_button_width">60dp</dimen> <dimen name="floating_toolbar_overflow_side_padding">18dp</dimen> <dimen name="floating_toolbar_text_size">14sp</dimen> diff --git a/core/tests/coretests/src/android/graphics/RectTest.java b/core/tests/coretests/src/android/graphics/RectTest.java new file mode 100644 index 000000000000..d31d7d54940c --- /dev/null +++ b/core/tests/coretests/src/android/graphics/RectTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import static android.graphics.Rect.copyOrNull; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class RectTest { + + @Test + public void copyOrNull_passesThroughNull() { + assertNull(copyOrNull(null)); + } + + @Test + public void copyOrNull_copiesNonNull() { + final Rect orig = new Rect(1, 2, 3, 4); + final Rect copy = copyOrNull(orig); + + assertEquals(orig, copy); + assertNotSame(orig, copy); + } +} diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index aff942da78d1..3843cb91154c 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -17,6 +17,7 @@ package android.graphics; import android.annotation.CheckResult; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -99,6 +100,16 @@ public final class Rect implements Parcelable { } } + /** + * Returns a copy of {@code r} if {@code r} is not {@code null}, or {@code null} otherwise. + * + * @hide + */ + @Nullable + public static Rect copyOrNull(@Nullable Rect r) { + return r == null ? null : new Rect(r); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/packages/SettingsLib/src/com/android/settingslib/SliceBroadcastRelay.java b/packages/SettingsLib/src/com/android/settingslib/SliceBroadcastRelay.java new file mode 100644 index 000000000000..e92b36a45645 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/SliceBroadcastRelay.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.settingslib; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Process; +import android.os.UserHandle; + +/** + * Utility class that allows Settings to use SystemUI to relay broadcasts related to pinned slices. + */ +public class SliceBroadcastRelay { + + public static final String ACTION_REGISTER + = "com.android.settingslib.action.REGISTER_SLICE_RECEIVER"; + public static final String ACTION_UNREGISTER + = "com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER"; + public static final String SYSTEMUI_PACKAGE = "com.android.systemui"; + + public static final String EXTRA_URI = "uri"; + public static final String EXTRA_RECEIVER = "receiver"; + public static final String EXTRA_FILTER = "filter"; + + public static void registerReceiver(Context context, Uri registerKey, + Class<? extends BroadcastReceiver> receiver, IntentFilter filter) { + Intent registerBroadcast = new Intent(ACTION_REGISTER); + registerBroadcast.setPackage(SYSTEMUI_PACKAGE); + registerBroadcast.putExtra(EXTRA_URI, ContentProvider.maybeAddUserId(registerKey, + Process.myUserHandle().getIdentifier())); + registerBroadcast.putExtra(EXTRA_RECEIVER, + new ComponentName(context.getPackageName(), receiver.getName())); + registerBroadcast.putExtra(EXTRA_FILTER, filter); + + context.sendBroadcastAsUser(registerBroadcast, UserHandle.SYSTEM); + } + + public static void unregisterReceivers(Context context, Uri registerKey) { + Intent registerBroadcast = new Intent(ACTION_UNREGISTER); + registerBroadcast.setPackage(SYSTEMUI_PACKAGE); + registerBroadcast.putExtra(EXTRA_URI, ContentProvider.maybeAddUserId(registerKey, + Process.myUserHandle().getIdentifier())); + + context.sendBroadcastAsUser(registerBroadcast, UserHandle.SYSTEM); + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java index ad300f43d88d..53f7e44bc25a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java @@ -50,6 +50,4 @@ public abstract class QSTileView extends LinearLayout { public abstract void onStateChanged(State state); public abstract int getDetailY(); - - public void setExpansion(float expansion) {} } diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 74c22b06838d..49d142a1e6d6 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -76,7 +76,7 @@ android:layout_below="@id/label_group" android:clickable="false" android:ellipsize="marquee" - android:maxLines="1" + android:singleLine="true" android:padding="0dp" android:visibility="gone" android:gravity="center" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f49d3de4a6a4..7eb08c4c3582 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -350,6 +350,7 @@ <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.ScreenDecorations</item> <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item> + <item>com.android.systemui.SliceBroadcastRelayHandler</item> </string-array> <!-- SystemUI vender service, used in config_systemUIServiceComponents. --> diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java new file mode 100644 index 000000000000..68f583695596 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.SliceBroadcastRelay; + +/** + * Allows settings to register certain broadcasts to launch the settings app for pinned slices. + * @see SliceBroadcastRelay + */ +public class SliceBroadcastRelayHandler extends SystemUI { + private static final String TAG = "SliceBroadcastRelay"; + private static final boolean DEBUG = false; + + private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>(); + + @Override + public void start() { + if (DEBUG) Log.d(TAG, "Start"); + IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER); + filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER); + mContext.registerReceiver(mReceiver, filter); + } + + @VisibleForTesting + void handleIntent(Intent intent) { + if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) { + Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI); + ComponentName receiverClass = + intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER); + IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER); + if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter); + getOrCreateRelay(uri).register(mContext, receiverClass, filter); + } else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) { + Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI); + if (DEBUG) Log.d(TAG, "Unregister " + uri); + getAndRemoveRelay(uri).unregister(mContext); + } + } + + private BroadcastRelay getOrCreateRelay(Uri uri) { + BroadcastRelay ret = mRelays.get(uri); + if (ret == null) { + ret = new BroadcastRelay(uri); + mRelays.put(uri, ret); + } + return ret; + } + + private BroadcastRelay getAndRemoveRelay(Uri uri) { + return mRelays.remove(uri); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleIntent(intent); + } + }; + + private static class BroadcastRelay extends BroadcastReceiver { + + private final ArraySet<ComponentName> mReceivers = new ArraySet<>(); + private final UserHandle mUserId; + + public BroadcastRelay(Uri uri) { + mUserId = new UserHandle(ContentProvider.getUserIdFromUri(uri)); + } + + public void register(Context context, ComponentName receiver, IntentFilter filter) { + mReceivers.add(receiver); + context.registerReceiver(this, filter); + } + + public void unregister(Context context) { + context.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + for (ComponentName receiver : mReceivers) { + intent.setComponent(receiver); + if (DEBUG) Log.d(TAG, "Forwarding " + receiver + " " + intent + " " + mUserId); + context.sendBroadcastAsUser(intent, mUserId); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 03a76dab2072..b7ff98402081 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -114,6 +114,8 @@ public class DozeUi implements DozeMachine.Part { mHost.dozeTimeTick(); // The first frame may arrive when the display isn't ready yet. mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 100); + // The the delayed frame may arrive when the display isn't ready yet either. + mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 1000); } scheduleTimeTick(); break; diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index d8d07c01fc96..1fd60239ee98 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -56,6 +56,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private AnimatorSet mBounceAnimatorSet; private int mAnimatingToPage = -1; + private float mLastExpansion; public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -172,8 +173,19 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override public void setExpansion(float expansion) { - for (TileRecord tr : mTiles) { - tr.tileView.setExpansion(expansion); + mLastExpansion = expansion; + updateSelected(); + } + + private void updateSelected() { + // Start the marquee when fully expanded and stop when fully collapsed. Leave as is for + // other expansion ratios since there is no way way to pause the marquee. + if (mLastExpansion > 0f && mLastExpansion < 1f) { + return; + } + boolean selected = mLastExpansion == 1f; + for (int i = 0; i < mPages.size(); i++) { + mPages.get(i).setSelected(i == getCurrentItem() ? selected : false); } } @@ -323,6 +335,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { + updateSelected(); if (mPageIndicator == null) return; if (mPageListener != null) { mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index d21b06f02f16..5649f7f5d764 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -107,15 +107,6 @@ public class QSTileView extends QSTileBaseView { } @Override - public void setExpansion(float expansion) { - // Start the marquee when fully expanded and stop when fully collapsed. Leave as is for - // other expansion ratios since there is no way way to pause the marquee. - boolean selected = expansion == 1f ? true : expansion == 0f ? false : mLabel.isSelected(); - mLabel.setSelected(selected); - mSecondLine.setSelected(selected); - } - - @Override protected void handleStateChanged(QSTile.State state) { super.handleStateChanged(state); if (!Objects.equals(mLabel.getText(), state.label) || mState != state.state) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index 85a60624c275..1e5b37c9af50 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -180,7 +180,7 @@ public class WindowManagerProxy { @Override public void run() { try { - WindowManagerGlobal.getWindowManagerService().setDockedStackResizing(resizing); + ActivityManager.getService().setSplitScreenResizing(resizing); } catch (RemoteException e) { Log.w(TAG, "Error calling setDockedStackResizing: " + e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java index dbd1cd482305..f1e2302ceda2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java @@ -20,7 +20,9 @@ import android.content.Context; import android.content.pm.UserInfo; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.graphics.Bitmap; import android.os.AsyncTask; +import android.os.UserHandle; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -31,6 +33,7 @@ import android.widget.TextView; import androidx.car.widget.PagedListView; +import com.android.internal.util.UserIcons; import com.android.settingslib.users.UserManagerHelper; import com.android.systemui.R; @@ -180,13 +183,7 @@ public class UserGridRecyclerView extends PagedListView implements @Override public void onBindViewHolder(UserAdapterViewHolder holder, int position) { UserRecord userRecord = mUsers.get(position); - if (!userRecord.mIsAddUser) { - holder.mUserAvatarImageView.setImageBitmap(mUserManagerHelper - .getUserIcon(userRecord.mInfo)); - } else { - holder.mUserAvatarImageView.setImageDrawable(mContext - .getDrawable(R.drawable.car_add_circle_round)); - } + holder.mUserAvatarImageView.setImageBitmap(getUserRecordIcon(userRecord)); holder.mUserNameTextView.setText(userRecord.mInfo.name); holder.mView.setOnClickListener(v -> { if (userRecord == null) { @@ -219,6 +216,20 @@ public class UserGridRecyclerView extends PagedListView implements } + private Bitmap getUserRecordIcon(UserRecord userRecord) { + if (userRecord.mIsStartGuestSession) { + return UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon( + mContext.getResources(), UserHandle.USER_NULL, false)); + } + + if (userRecord.mIsAddUser) { + return UserIcons.convertToBitmap(mContext + .getDrawable(R.drawable.car_add_circle_round)); + } + + return mUserManagerHelper.getUserIcon(userRecord.mInfo); + } + private class AddNewUserTask extends AsyncTask<String, Void, UserInfo> { @Override diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 767a24a27a26..1be83229cf22 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -57,6 +57,12 @@ <service android:name="com.android.systemui.qs.external.TileLifecycleManagerTests$FakeTileService" android:process=":killable" /> + + <receiver android:name="com.android.systemui.SliceBroadcastRelayHandlerTest$Receiver"> + <intent-filter> + <action android:name="com.android.systemui.action.TEST_ACTION" /> + </intent-filter> + </receiver> </application> <instrumentation android:name="android.testing.TestableInstrumentation" diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java new file mode 100644 index 000000000000..4abac56d7455 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; + +import com.android.settingslib.SliceBroadcastRelay; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { + + private static final String TEST_ACTION = "com.android.systemui.action.TEST_ACTION"; + + @Test + public void testRegister() { + Uri testUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority("something") + .path("test") + .build(); + SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler(); + relayHandler.mContext = spy(mContext); + + Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER); + intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); + intent.putExtra(SliceBroadcastRelay.EXTRA_RECEIVER, + new ComponentName(mContext.getPackageName(), Receiver.class.getName())); + IntentFilter value = new IntentFilter(TEST_ACTION); + intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value); + + relayHandler.handleIntent(intent); + verify(relayHandler.mContext).registerReceiver(any(), eq(value)); + } + + @Test + public void testUnregister() { + Uri testUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority("something") + .path("test") + .build(); + SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler(); + relayHandler.mContext = spy(mContext); + + Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER); + intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); + intent.putExtra(SliceBroadcastRelay.EXTRA_RECEIVER, + new ComponentName(mContext.getPackageName(), Receiver.class.getName())); + IntentFilter value = new IntentFilter(TEST_ACTION); + intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value); + + relayHandler.handleIntent(intent); + ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value)); + + intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER); + intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); + relayHandler.handleIntent(intent); + verify(relayHandler.mContext).unregisterReceiver(eq(relay.getValue())); + } + + @Test + public void testRelay() { + Receiver.sReceiver = mock(BroadcastReceiver.class); + Uri testUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority("something") + .path("test") + .build(); + SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler(); + relayHandler.mContext = spy(mContext); + + Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER); + intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); + intent.putExtra(SliceBroadcastRelay.EXTRA_RECEIVER, + new ComponentName(mContext.getPackageName(), Receiver.class.getName())); + IntentFilter value = new IntentFilter(TEST_ACTION); + intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value); + + relayHandler.handleIntent(intent); + ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value)); + relay.getValue().onReceive(relayHandler.mContext, new Intent(TEST_ACTION)); + + verify(Receiver.sReceiver, timeout(2000)).onReceive(any(), any()); + } + + public static class Receiver extends BroadcastReceiver { + private static BroadcastReceiver sReceiver; + + @Override + public void onReceive(Context context, Intent intent) { + if (sReceiver != null) sReceiver.onReceive(context, intent); + } + } + +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6718d9558ffb..62a055d330c8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -11451,6 +11451,19 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public void setSplitScreenResizing(boolean resizing) { + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setSplitScreenResizing()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (this) { + mStackSupervisor.setSplitScreenResizing(resizing); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()"); final long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index e20356ff12db..95bae2eb24a8 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1743,6 +1743,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return getDisplay().isTopStack(this); } + boolean isTopActivityVisible() { + final ActivityRecord topActivity = getTopActivity(); + return topActivity != null && topActivity.visible; + } + /** * Returns true if the stack should be visible. * diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 6a3587c69dfb..0dc2445109ea 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -43,6 +43,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.graphics.Rect.copyOrNull; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; @@ -245,6 +246,20 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // the activity callback indicating that it has completed pausing static final boolean PAUSE_IMMEDIATELY = true; + /** True if the docked stack is currently being resized. */ + private boolean mDockedStackResizing; + + /** + * True if there are pending docked bounds that need to be applied after + * {@link #mDockedStackResizing} is reset to false. + */ + private boolean mHasPendingDockedBounds; + private Rect mPendingDockedBounds; + private Rect mPendingTempDockedTaskBounds; + private Rect mPendingTempDockedTaskInsetBounds; + private Rect mPendingTempOtherTaskBounds; + private Rect mPendingTempOtherTaskInsetBounds; + /** * The modes which affect which tasks are returned when calling * {@link ActivityStackSupervisor#anyTaskForIdLocked(int)}. @@ -2708,6 +2723,28 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D moveTasksToFullscreenStackInSurfaceTransaction(fromStack, toDisplayId, onTop)); } + void setSplitScreenResizing(boolean resizing) { + if (resizing == mDockedStackResizing) { + return; + } + + mDockedStackResizing = resizing; + mWindowManager.setDockedStackResizing(resizing); + + if (!resizing && mHasPendingDockedBounds) { + resizeDockedStackLocked(mPendingDockedBounds, mPendingTempDockedTaskBounds, + mPendingTempDockedTaskInsetBounds, mPendingTempOtherTaskBounds, + mPendingTempOtherTaskInsetBounds, PRESERVE_WINDOWS); + + mHasPendingDockedBounds = false; + mPendingDockedBounds = null; + mPendingTempDockedTaskBounds = null; + mPendingTempDockedTaskInsetBounds = null; + mPendingTempOtherTaskBounds = null; + mPendingTempOtherTaskInsetBounds = null; + } + } + void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds, Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds, boolean preserveWindows) { @@ -2731,6 +2768,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return; } + if (mDockedStackResizing) { + mHasPendingDockedBounds = true; + mPendingDockedBounds = copyOrNull(dockedBounds); + mPendingTempDockedTaskBounds = copyOrNull(tempDockedTaskBounds); + mPendingTempDockedTaskInsetBounds = copyOrNull(tempDockedTaskInsetBounds); + mPendingTempOtherTaskBounds = copyOrNull(tempOtherTaskBounds); + mPendingTempOtherTaskInsetBounds = copyOrNull(tempOtherTaskInsetBounds); + } + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack"); mWindowManager.deferSurfaceLayout(); try { @@ -2765,6 +2811,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (!current.affectedBySplitScreenResize()) { continue; } + if (mDockedStackResizing && !current.isTopActivityVisible()) { + // Non-visible stacks get resized once we're done with the resize + // interaction. + continue; + } // Need to set windowing mode here before we try to get the dock bounds. current.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); current.getStackDockedModeBounds( diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index ae7058d42e73..9ef6c66b070a 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -544,7 +544,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final int usedFlags = isRateLimitedForPoll(callingUid) ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN) : flags; - if ((usedFlags & NetworkStatsManager.FLAG_POLL_ON_OPEN) != 0) { + if ((usedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN + | NetworkStatsManager.FLAG_POLL_FORCE)) != 0) { final long ident = Binder.clearCallingIdentity(); try { performPoll(FLAG_PERSIST_ALL); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 4c8b91baad0a..477b062ac90f 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -926,7 +926,11 @@ abstract public class ManagedServices { Slog.v(TAG, " disconnecting old " + getCaption() + ": " + info.service); removeServiceLocked(i); if (info.connection != null) { - mContext.unbindService(info.connection); + try { + mContext.unbindService(info.connection); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "failed to unbind " + name, e); + } } } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 608d0aa5875f..68be50c2689a 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -811,36 +811,35 @@ final class AccessibilityController { return; } mInvalidated = false; - Canvas canvas = null; - try { - // Empty dirty rectangle means unspecified. - if (mDirtyRect.isEmpty()) { - mBounds.getBounds(mDirtyRect); + if (mAlpha > 0) { + Canvas canvas = null; + try { + // Empty dirty rectangle means unspecified. + if (mDirtyRect.isEmpty()) { + mBounds.getBounds(mDirtyRect); + } + mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); + canvas = mSurface.lockCanvas(mDirtyRect); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); + } + } catch (IllegalArgumentException iae) { + /* ignore */ + } catch (Surface.OutOfResourcesException oore) { + /* ignore */ + } + if (canvas == null) { + return; } - mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth); - canvas = mSurface.lockCanvas(mDirtyRect); if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); + Slog.i(LOG_TAG, "Bounds: " + mBounds); } - } catch (IllegalArgumentException iae) { - /* ignore */ - } catch (Surface.OutOfResourcesException oore) { - /* ignore */ - } - if (canvas == null) { - return; - } - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Bounds: " + mBounds); - } - canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); - mPaint.setAlpha(mAlpha); - Path path = mBounds.getBoundaryPath(); - canvas.drawPath(path, mPaint); + canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + mPaint.setAlpha(mAlpha); + Path path = mBounds.getBoundaryPath(); + canvas.drawPath(path, mPaint); - mSurface.unlockCanvasAndPost(canvas); - - if (mAlpha > 0) { + mSurface.unlockCanvasAndPost(canvas); mSurfaceControl.show(); } else { mSurfaceControl.hide(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b1b026ed55cc..09e43f843983 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6864,7 +6864,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override public void setDockedStackResizing(boolean resizing) { synchronized (mWindowMap) { getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6af730f9abdc..6c2821d9d740 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2405,6 +2405,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void binderDied() { try { + boolean resetSplitScreenResizing = false; synchronized(mService.mWindowMap) { final WindowState win = mService.windowForClientLocked(mSession, mClient, false); Slog.i(TAG, "WIN DEATH: " + win); @@ -2424,13 +2425,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (stack != null) { stack.resetDockedStackToMiddle(); } - mService.setDockedStackResizing(false); + resetSplitScreenResizing = true; } } else if (mHasSurface) { Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid."); WindowState.this.removeIfPossible(); } } + if (resetSplitScreenResizing) { + try { + // Note: this calls into ActivityManager, so we must *not* hold the window + // manager lock while calling this. + mService.mActivityManager.setSplitScreenResizing(false); + } catch (RemoteException e) { + // Local call, shouldn't return RemoteException. + throw e.rethrowAsRuntimeException(); + } + } } catch (IllegalArgumentException ex) { // This will happen if the window has already been removed. } @@ -4706,16 +4717,38 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outPoint.offset(-mAttrs.surfaceInsets.left, -mAttrs.surfaceInsets.top); } + boolean needsRelativeLayeringToIme() { + // We only use the relative layering mode in split screen, as part of elevating the IME + // and windows above it's target above the docked divider. + if (!inSplitScreenWindowingMode()) { + return false; + } + + if (isChildWindow()) { + // If we are a child of the input method target we need this promotion. + if (getParentWindow().isInputMethodTarget()) { + return true; + } + } else if (mAppToken != null) { + // Likewise if we share a token with the Input method target and are ordered + // above it but not necessarily a child (e.g. a Dialog) then we also need + // this promotion. + final WindowState imeTarget = mService.mInputMethodTarget; + boolean inTokenWithAndAboveImeTarget = imeTarget != null && imeTarget != this + && imeTarget.mToken == mToken && imeTarget.compareTo(this) <= 0; + return inTokenWithAndAboveImeTarget; + } + return false; + } + @Override void assignLayer(Transaction t, int layer) { // See comment in assignRelativeLayerForImeTargetChild - if (!isChildWindow() - || (!getParentWindow().isInputMethodTarget()) - || !inSplitScreenWindowingMode()) { - super.assignLayer(t, layer); + if (needsRelativeLayeringToIme()) { + getDisplayContent().assignRelativeLayerForImeTargetChild(t, this); return; } - getDisplayContent().assignRelativeLayerForImeTargetChild(t, this); + super.assignLayer(t, layer); } @Override diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/services/net/java/android/net/apf/ApfGenerator.java index fcfb19ce5a68..99b2fc6db472 100644 --- a/services/net/java/android/net/apf/ApfGenerator.java +++ b/services/net/java/android/net/apf/ApfGenerator.java @@ -841,30 +841,30 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load 32 bits from the data memory into - * {@code register}. The source address is computed by adding @{code offset} to the other - * register. + * {@code register}. The source address is computed by adding the signed immediate + * @{code offset} to the other register. * Requires APF v3 or greater. */ public ApfGenerator addLoadData(Register destinationRegister, int offset) throws IllegalInstructionException { requireApfVersion(3); Instruction instruction = new Instruction(Opcodes.LDDW, destinationRegister); - instruction.setUnsignedImm(offset); + instruction.setSignedImm(offset); addInstruction(instruction); return this; } /** * Add an instruction to the end of the program to store 32 bits from {@code register} into the - * data memory. The destination address is computed by adding @{code offset} to the other - * register. + * data memory. The destination address is computed by adding the signed immediate + * @{code offset} to the other register. * Requires APF v3 or greater. */ public ApfGenerator addStoreData(Register sourceRegister, int offset) throws IllegalInstructionException { requireApfVersion(3); Instruction instruction = new Instruction(Opcodes.STDW, sourceRegister); - instruction.setUnsignedImm(offset); + instruction.setSignedImm(offset); addInstruction(instruction); return this; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 181fcebbb536..ef9ba78b8263 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -24,7 +24,13 @@ import static android.service.notification.NotificationListenerService.Ranking .USER_SENTIMENT_POSITIVE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import android.app.INotificationManager; import android.app.NotificationChannel; import android.content.Intent; import android.os.Binder; @@ -34,6 +40,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationRankingUpdate; import android.service.notification.SnoozeCriterion; +import android.service.notification.StatusBarNotification; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; @@ -52,6 +59,19 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { private String[] mKeys = new String[] { "key", "key1", "key2", "key3"}; @Test + public void testGetActiveNotifications_notNull() throws Exception { + TestListenerService service = new TestListenerService(); + INotificationManager noMan = service.getNoMan(); + when(noMan.getActiveNotificationsFromListener(any(), any(), anyInt())).thenReturn(null); + + assertNotNull(service.getActiveNotifications()); + assertNotNull(service.getActiveNotifications(NotificationListenerService.TRIM_FULL)); + assertNotNull(service.getActiveNotifications(new String[0])); + assertNotNull(service.getActiveNotifications( + new String[0], NotificationListenerService.TRIM_LIGHT)); + } + + @Test public void testRanking() throws Exception { TestListenerService service = new TestListenerService(); service.applyUpdateLocked(generateUpdate()); @@ -180,7 +200,12 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { private final IBinder binder = new LocalBinder(); public TestListenerService() { + mWrapper = mock(NotificationListenerWrapper.class); + mNoMan = mock(INotificationManager.class); + } + INotificationManager getNoMan() { + return mNoMan; } @Override diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java index 082f3108ed0a..f8a413226bd2 100644 --- a/tests/net/java/android/net/apf/ApfTest.java +++ b/tests/net/java/android/net/apf/ApfTest.java @@ -160,7 +160,7 @@ public class ApfTest { throw new Exception( "program: " + HexDump.toHexString(program) + "\ndata memory: " + HexDump.toHexString(data) + - "\nexpected: " + HexDump.toHexString(expected_data)); + "\nexpected: " + HexDump.toHexString(expected_data)); } } @@ -625,18 +625,19 @@ public class ApfTest { @Test public void testApfDataWrite() throws IllegalInstructionException, Exception { byte[] packet = new byte[MIN_PKT_SIZE]; - byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; + byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; byte[] expected_data = data.clone(); // No memory access instructions: should leave the data segment untouched. ApfGenerator gen = new ApfGenerator(3); assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); - // Expect value 0x87654321 to be stored starting from address 3 + 2, in big-endian order. + // Expect value 0x87654321 to be stored starting from address -11 from the end of the + // data buffer, in big-endian order. gen = new ApfGenerator(3); gen.addLoadImmediate(Register.R0, 0x87654321); - gen.addLoadImmediate(Register.R1, 2); - gen.addStoreData(Register.R0, 3); + gen.addLoadImmediate(Register.R1, -5); + gen.addStoreData(Register.R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16) expected_data[5] = (byte)0x87; expected_data[6] = (byte)0x65; expected_data[7] = (byte)0x43; @@ -646,16 +647,16 @@ public class ApfTest { @Test public void testApfDataRead() throws IllegalInstructionException, Exception { - // Program that DROPs if address 11 (7 + 3) contains 0x87654321. + // Program that DROPs if address 10 (-6) contains 0x87654321. ApfGenerator gen = new ApfGenerator(3); - gen.addLoadImmediate(Register.R1, 3); - gen.addLoadData(Register.R0, 7); + gen.addLoadImmediate(Register.R1, 10); + gen.addLoadData(Register.R0, -16); // 10 + -16 = -6 (offset +10 with data_len=16) gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL); byte[] program = gen.generate(); byte[] packet = new byte[MIN_PKT_SIZE]; // Content is incorrect (last byte does not match) -> PASS - byte[] data = new byte[32]; + byte[] data = new byte[16]; data[10] = (byte)0x87; data[11] = (byte)0x65; data[12] = (byte)0x43; @@ -672,10 +673,10 @@ public class ApfTest { @Test public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception { ApfGenerator gen = new ApfGenerator(3); - gen.addLoadImmediate(Register.R1, 3); - gen.addLoadData(Register.R0, 7); // Load from address 7 + 3 = 10 + gen.addLoadImmediate(Register.R1, -22); + gen.addLoadData(Register.R0, 0); // Load from address 32 -22 + 0 = 10 gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733 - gen.addStoreData(Register.R0, 11); // Write back to address 11 + 3 = 14 + gen.addStoreData(Register.R0, 4); // Write back to address 32 -22 + 4 = 14 byte[] packet = new byte[MIN_PKT_SIZE]; byte[] data = new byte[32]; @@ -718,10 +719,17 @@ public class ApfTest { gen.addJump(gen.DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); - // ...but underflowing isn't allowed. + // ...and underflowing simply wraps around to the end of the buffer... gen = new ApfGenerator(3); gen.addLoadImmediate(Register.R0, 20); gen.addLoadData(Register.R1, -30); + gen.addJump(gen.DROP_LABEL); + assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); + + // ...but doesn't allow accesses before the start of the buffer + gen = new ApfGenerator(3); + gen.addLoadImmediate(Register.R0, 20); + gen.addLoadData(Register.R1, -1000); gen.addJump(gen.DROP_LABEL); // Not reached. assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); } diff --git a/tests/net/jni/apf_jni.cpp b/tests/net/jni/apf_jni.cpp index 79444e3f5ba8..1ea9e274ab9e 100644 --- a/tests/net/jni/apf_jni.cpp +++ b/tests/net/jni/apf_jni.cpp @@ -36,11 +36,20 @@ static jint com_android_server_ApfTest_apfSimulate( uint32_t program_len = env->GetArrayLength(program); uint32_t packet_len = env->GetArrayLength(packet); uint32_t data_len = data ? env->GetArrayLength(data) : 0; - jint result = accept_packet(program_raw, program_len, packet_raw, - packet_len, data_raw, data_len, filter_age); + + // Merge program and data into a single buffer. + uint8_t* program_and_data = (uint8_t*)malloc(program_len + data_len); + memcpy(program_and_data, program_raw, program_len); + memcpy(program_and_data + program_len, data_raw, data_len); + + jint result = + accept_packet(program_and_data, program_len, program_len + data_len, + packet_raw, packet_len, filter_age); if (data) { + memcpy(data_raw, program_and_data + program_len, data_len); env->ReleaseByteArrayElements(data, (jbyte*)data_raw, 0 /* copy back */); } + free(program_and_data); env->ReleaseByteArrayElements(packet, (jbyte*)packet_raw, JNI_ABORT); env->ReleaseByteArrayElements(program, (jbyte*)program_raw, JNI_ABORT); return result; @@ -109,8 +118,8 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js jstring jpcap_filename, jbyteArray japf_program) { ScopedUtfChars filter(env, jfilter); ScopedUtfChars pcap_filename(env, jpcap_filename); - const uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL); - const uint32_t apf_program_len = env->GetArrayLength(japf_program); + uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL); + uint32_t apf_program_len = env->GetArrayLength(japf_program); // Open pcap file for BPF filtering ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb")); @@ -152,8 +161,8 @@ static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, js do { apf_packet = pcap_next(apf_pcap.get(), &apf_header); } while (apf_packet != NULL && !accept_packet( - apf_program, apf_program_len, apf_packet, apf_header.len, - nullptr, 0, 0)); + apf_program, apf_program_len, 0 /* data_len */, + apf_packet, apf_header.len, 0 /* filter_age */)); // Make sure both filters matched the same packet. if (apf_packet == NULL && bpf_packet == NULL) diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index df483b2bdb1b..d7a377176fc5 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -306,6 +306,7 @@ message FileReference { } // A value that represents a primitive data type (float, int, boolean, etc.). +// Refer to Res_value in ResourceTypes.h for info on types and formatting message Primitive { message NullType { } @@ -315,8 +316,8 @@ message Primitive { NullType null_value = 1; EmptyType empty_value = 2; float float_value = 3; - float dimension_value = 4; - float fraction_value = 5; + uint32 dimension_value = 13; + uint32 fraction_value = 14; int32 int_decimal_value = 6; uint32 int_hexadecimal_value = 7; bool boolean_value = 8; @@ -324,6 +325,8 @@ message Primitive { uint32 color_rgb8_value = 10; uint32 color_argb4_value = 11; uint32 color_rgb4_value = 12; + float dimension_value_deprecated = 4 [deprecated=true]; + float fraction_value_deprecated = 5 [deprecated=true]; } } diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index f1eb95289053..3b101b7152be 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -780,13 +780,11 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, } break; case pb::Primitive::kDimensionValue: { val.dataType = android::Res_value::TYPE_DIMENSION; - float dimen_val = pb_prim.dimension_value(); - val.data = *(uint32_t*)&dimen_val; + val.data = pb_prim.dimension_value(); } break; case pb::Primitive::kFractionValue: { val.dataType = android::Res_value::TYPE_FRACTION; - float fraction_val = pb_prim.fraction_value(); - val.data = *(uint32_t*)&fraction_val; + val.data = pb_prim.fraction_value(); } break; case pb::Primitive::kIntDecimalValue: { val.dataType = android::Res_value::TYPE_INT_DEC; @@ -816,6 +814,16 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, val.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; val.data = pb_prim.color_rgb4_value(); } break; + case pb::Primitive::kDimensionValueDeprecated: { // DEPRECATED + val.dataType = android::Res_value::TYPE_DIMENSION; + float dimen_val = pb_prim.dimension_value_deprecated(); + val.data = *(uint32_t*)&dimen_val; + } break; + case pb::Primitive::kFractionValueDeprecated: { // DEPRECATED + val.dataType = android::Res_value::TYPE_FRACTION; + float fraction_val = pb_prim.fraction_value_deprecated(); + val.data = *(uint32_t*)&fraction_val; + } break; default: { LOG(FATAL) << "Unexpected Primitive type: " << static_cast<uint32_t>(pb_prim.oneof_value_case()); diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 2e5635956a0d..411cc29630fe 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -452,10 +452,10 @@ class ValueSerializer : public ConstValueVisitor { pb_prim->set_float_value(*(float*)&val.data); } break; case android::Res_value::TYPE_DIMENSION: { - pb_prim->set_dimension_value(*(float*)&val.data); + pb_prim->set_dimension_value(val.data); } break; case android::Res_value::TYPE_FRACTION: { - pb_prim->set_fraction_value(*(float*)&val.data); + pb_prim->set_fraction_value(val.data); } break; case android::Res_value::TYPE_INT_DEC: { pb_prim->set_int_decimal_value(static_cast<int32_t>(val.data)); diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 6366a3dd4e68..21fdbd8a4c1e 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -271,6 +271,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) { .AddValue("android:integer/hex_int_abcd", ResourceUtils::TryParseInt("0xABCD")) .AddValue("android:dimen/dimen_1.39mm", ResourceUtils::TryParseFloat("1.39mm")) .AddValue("android:fraction/fraction_27", ResourceUtils::TryParseFloat("27%")) + .AddValue("android:dimen/neg_2.3in", ResourceUtils::TryParseFloat("-2.3in")) .AddValue("android:integer/null", ResourceUtils::MakeEmpty()) .Build(); @@ -353,6 +354,12 @@ TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) { EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_FRACTION)); EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseFloat("27%")->value.data)); + bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:dimen/neg_2.3in", + ConfigDescription::DefaultConfig(), ""); + ASSERT_THAT(bp, NotNull()); + EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_DIMENSION)); + EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseFloat("-2.3in")->value.data)); + bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "android:integer/null", ConfigDescription::DefaultConfig(), ""); ASSERT_THAT(bp, NotNull()); |