Simplifying app icon popup

> Using a single linearLayout instead of multiple nested views
> Using clipToOutline for rounded corners instead of using canvas.saveLayer
> Removing nested view elevations and overdraw
> Using LayoutTransition for animating layout changes, instead of manually creating animators

Change-Id: I8e57092f52ca5a032a2756594fdd39788acc5a0d
This commit is contained in:
Sunny Goyal 2017-11-09 15:24:06 -08:00
parent ea529083bd
commit 00ac920241
26 changed files with 1071 additions and 1995 deletions

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?attr/popupColorTertiary" />
<item android:height="3dp" android:top="0dp">
<shape>
<gradient
android:angle="270"
android:endColor="@android:color/transparent"
android:startColor="#33000000"
android:type="linear" />
</shape>
</item>
</layer-list>

View File

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.notification.NotificationItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notification_view"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
android:theme="@style/PopupItem"
android:elevation="@dimen/deep_shortcuts_elevation">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipChildren="false">
<View
android:id="@+id/gutter_top"
android:layout_width="match_parent"
android:layout_height="4dp"
android:theme="@style/PopupGutter"
android:visibility="gone" />
<FrameLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_header_height"
android:paddingStart="@dimen/notification_padding_start"
android:paddingEnd="@dimen/notification_padding_end"
android:background="?attr/popupColorPrimary"
android:elevation="@dimen/notification_elevation"
android:layout_below="@id/gutter_top" >
<TextView
android:id="@+id/notification_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:gravity="center_vertical"
android:text="@string/notifications_header"
android:textSize="@dimen/notification_header_text_size"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/notification_count"
android:layout_width="@dimen/notification_icon_size"
android:layout_height="match_parent"
android:layout_gravity="end"
android:gravity="center"
android:textSize="@dimen/notification_header_count_text_size"
android:fontFamily="sans-serif-medium"
android:textColor="?android:attr/textColorPrimary" />
</FrameLayout>
<include layout="@layout/notification_main"
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_main_height"
android:layout_below="@id/header" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="@dimen/popup_item_divider_height"
android:background="?attr/popupColorTertiary"
android:layout_below="@id/main_view"
android:visibility="gone" />
<include layout="@layout/notification_footer"
android:id="@+id/footer"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_footer_height"
android:layout_below="@id/divider" />
<View
android:id="@+id/gutter_bottom"
android:layout_width="match_parent"
android:layout_height="4dp"
android:theme="@style/PopupGutter"
android:visibility="gone"
android:layout_below="@id/footer" />
</RelativeLayout>
</com.android.launcher3.notification.NotificationItemView>

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- header -->
<FrameLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_header_height"
android:paddingEnd="@dimen/notification_padding_end"
android:paddingStart="@dimen/notification_padding_start">
<TextView
android:id="@+id/notification_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:gravity="center_vertical"
android:text="@string/notifications_header"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/notification_header_text_size" />
<TextView
android:id="@+id/notification_count"
android:layout_width="@dimen/notification_icon_size"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/notification_header_count_text_size" />
</FrameLayout>
<!-- Main view -->
<com.android.launcher3.notification.NotificationMainView
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_main_height"
android:background="@drawable/bg_notification_content"
android:focusable="true" >
<LinearLayout
android:id="@+id/text_and_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/popupColorPrimary"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingBottom="14dp"
android:paddingEnd="@dimen/notification_main_text_padding_end"
android:paddingStart="@dimen/notification_padding_start">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="sans-serif"
android:lines="1"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/notification_main_title_size" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="sans-serif"
android:lines="1"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/notification_main_text_size" />
</LinearLayout>
<View
android:id="@+id/popup_item_icon"
android:layout_width="@dimen/notification_icon_size"
android:layout_height="@dimen/notification_icon_size"
android:layout_gravity="center_vertical|end"
android:layout_marginBottom="7dp"
android:layout_marginEnd="@dimen/notification_padding_end" />
</com.android.launcher3.notification.NotificationMainView>
<!-- Divider -->
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="@dimen/popup_item_divider_height"
android:layout_below="@id/main_view"
android:background="?attr/popupColorTertiary" />
<!-- Footer -->
<com.android.launcher3.notification.NotificationFooterLayout
android:id="@+id/footer"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_footer_height"
android:layout_gravity="center_vertical"
android:clipChildren="false">
<LinearLayout
android:id="@+id/icon_row"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="end|center_vertical"
android:orientation="horizontal"
android:padding="@dimen/notification_footer_icon_row_padding"/>
<View
android:id="@+id/overflow"
android:layout_width="@dimen/horizontal_ellipsis_size"
android:layout_height="@dimen/horizontal_ellipsis_size"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
android:background="@drawable/horizontal_ellipsis" />
</com.android.launcher3.notification.NotificationFooterLayout>
</merge>

View File

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.notification.NotificationFooterLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:elevation="@dimen/notification_elevation"
android:clipChildren="false"
android:layout_gravity="center_vertical"
android:background="?attr/popupColorPrimary">
<LinearLayout
android:id="@+id/icon_row"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="end|center_vertical"
android:padding="@dimen/notification_footer_icon_row_padding"
android:clipToPadding="false"
android:clipChildren="false"/>
<View
android:id="@+id/overflow"
android:layout_width="@dimen/horizontal_ellipsis_size"
android:layout_height="@dimen/horizontal_ellipsis_size"
android:background="@drawable/horizontal_ellipsis"
android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
android:layout_gravity="start|center_vertical" />
</com.android.launcher3.notification.NotificationFooterLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginTop="4dp"
android:background="@drawable/bg_notification_content" />

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.notification.NotificationMainView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:elevation="@dimen/notification_elevation" >
<LinearLayout
android:id="@+id/text_and_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical"
android:background="?attr/popupColorPrimary"
android:paddingStart="@dimen/notification_padding_start"
android:paddingEnd="@dimen/notification_main_text_padding_end"
android:paddingBottom="14dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:fontFamily="sans-serif"
android:textSize="@dimen/notification_main_title_size"
android:textColor="?android:attr/textColorPrimary"
android:lines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textSize="@dimen/notification_main_text_size"
android:textColor="?android:attr/textColorSecondary"
android:lines="1"
android:ellipsize="end" />
</LinearLayout>
<View
android:id="@+id/popup_item_icon"
android:layout_width="@dimen/notification_icon_size"
android:layout_height="@dimen/notification_icon_size"
android:layout_marginEnd="@dimen/notification_padding_end"
android:layout_marginBottom="7dp"
android:layout_gravity="center_vertical|end" />
</com.android.launcher3.notification.NotificationMainView>

View File

@ -19,11 +19,8 @@
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:background="?attr/popupColorPrimary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="@dimen/deep_shortcuts_elevation"
android:orientation="vertical">
</com.android.launcher3.popup.PopupContainerWithArrow>
android:orientation="vertical" />

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.shortcuts.ShortcutsItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/shortcuts_view"
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="wrap_content"
android:elevation="@dimen/deep_shortcuts_elevation">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- The shortcuts header is added at runtime when necessary. -->
<LinearLayout
android:id="@+id/shortcuts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>
</com.android.launcher3.shortcuts.ShortcutsItemView>

View File

@ -22,6 +22,4 @@
android:orientation="horizontal"
android:gravity="end|center_vertical"
android:background="?attr/popupColorSecondary"
android:elevation="1dp"
android:outlineProvider="none" />
<!-- We have elevation so this is drawn on top, but no outline provider to remove shadow -->
android:clipToPadding="true" />

View File

@ -179,6 +179,7 @@
<dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
<dimen name="popup_padding_start">10dp</dimen>
<dimen name="popup_padding_end">16dp</dimen>
<dimen name="popup_vertical_padding">4dp</dimen>
<dimen name="popup_arrow_width">10dp</dimen>
<dimen name="popup_arrow_height">8dp</dimen>
<dimen name="popup_arrow_vertical_offset">-2dp</dimen>
@ -226,7 +227,6 @@
<dimen name="notification_footer_icon_size">18dp</dimen>
<!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
<dimen name="notification_main_text_padding_end">52dp</dimen>
<dimen name="notification_elevation">2dp</dimen>
<dimen name="horizontal_ellipsis_size">18dp</dimen>
<!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
<dimen name="horizontal_ellipsis_offset">19dp</dimen>

View File

@ -136,13 +136,6 @@
<style name="PopupItem">
<item name="android:colorControlHighlight">?attr/popupColorTertiary</item>
</style>
<style name="PopupGutter">
<item name="android:backgroundTintMode">multiply</item>
<item name="android:backgroundTint">?attr/popupColorSecondary</item>
<item name="android:background">@drawable/gutter_horizontal</item>
<item name="android:elevation">@dimen/notification_elevation</item>
<item name="android:outlineProvider">none</item>
</style>
<!-- Drop targets -->
<style name="DropTargetButtonBase">

View File

@ -118,7 +118,6 @@ import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.popup.BaseActionPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.shortcuts.DeepShortcutManager;
@ -1257,9 +1256,9 @@ public class Launcher extends BaseActivity
mWorkspace.updateIconBadges(updatedBadges);
mAppsView.updateIconBadges(updatedBadges);
BaseActionPopup popup = BaseActionPopup.getOpen(Launcher.this);
if (popup instanceof PopupContainerWithArrow) {
((PopupContainerWithArrow) popup).updateNotificationHeader(updatedBadges);
PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
if (popup != null) {
popup.updateNotificationHeader(updatedBadges);
}
}
};
@ -3178,7 +3177,7 @@ public class Launcher extends BaseActivity
&& mAccessibilityDelegate.performAction(focusedView,
(ItemInfo) focusedView.getTag(),
LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
BaseActionPopup.getOpen(this).requestFocus();
PopupContainerWithArrow.getOpen(this).requestFocus();
return true;
}
break;

View File

@ -28,10 +28,6 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
/** Sets the progress, from 0 to 1, of the reveal animation. */
abstract void setProgress(float progress);
public ValueAnimator createRevealAnimator(final View revealView) {
return createRevealAnimator(revealView, false);
}
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
ValueAnimator va =
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
@ -39,8 +35,13 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
va.addListener(new AnimatorListenerAdapter() {
private boolean mWasCanceled = false;
private boolean mIsClippedToOutline;
private ViewOutlineProvider mOldOutlineProvider;
public void onAnimationStart(Animator animation) {
mIsClippedToOutline = revealView.getClipToOutline();
mOldOutlineProvider = revealView.getOutlineProvider();
revealView.setOutlineProvider(RevealOutlineAnimation.this);
revealView.setClipToOutline(true);
if (shouldRemoveElevationDuringAnimation()) {
@ -55,8 +56,8 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
public void onAnimationEnd(Animator animation) {
if (!mWasCanceled) {
revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
revealView.setClipToOutline(false);
revealView.setOutlineProvider(mOldOutlineProvider);
revealView.setClipToOutline(mIsClippedToOutline);
if (shouldRemoveElevationDuringAnimation()) {
revealView.setTranslationZ(0);
}

View File

@ -18,11 +18,6 @@ package com.android.launcher3.anim;
import android.graphics.Rect;
import com.android.launcher3.popup.PopupContainerWithArrow;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
/**
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
* and two {@link Rect}s.
@ -37,21 +32,12 @@ public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
private final Rect mStartRect;
private final Rect mEndRect;
private final @PopupContainerWithArrow.RoundedCornerFlags int mRoundedCorners;
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
Rect endRect) {
this(startRadius, endRadius, startRect, endRect,
ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
}
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
Rect endRect, int roundedCorners) {
mStartRadius = startRadius;
mEndRadius = endRadius;
mStartRect = startRect;
mEndRect = endRect;
mRoundedCorners = roundedCorners;
}
@Override
@ -65,13 +51,7 @@ public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
if ((mRoundedCorners & ROUNDED_TOP_CORNERS) == 0) {
mOutline.top -= mOutlineRadius;
}
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) == 0) {
mOutline.bottom += mOutlineRadius;
}
}
}

View File

@ -27,7 +27,7 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.popup.BaseActionPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import java.util.ArrayList;
import java.util.Collections;
@ -46,7 +46,7 @@ public class CustomActionsPopup implements OnMenuItemClickListener {
public CustomActionsPopup(Launcher launcher, View icon) {
mLauncher = launcher;
mIcon = icon;
BaseActionPopup container = BaseActionPopup.getOpen(launcher);
PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
if (container != null) {
mDelegate = container.getAccessibilityDelegate();
} else {

View File

@ -23,22 +23,18 @@ import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.popup.BaseActionPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.util.Themes;
import java.util.ArrayList;
import java.util.Iterator;
@ -61,11 +57,12 @@ public class NotificationFooterLayout extends FrameLayout {
private final List<NotificationInfo> mNotifications = new ArrayList<>();
private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
private final boolean mRtl;
private final int mBackgroundColor;
FrameLayout.LayoutParams mIconLayoutParams;
private View mOverflowEllipsis;
private LinearLayout mIconRow;
private int mBackgroundColor;
private NotificationItemView mContainer;
public NotificationFooterLayout(Context context) {
this(context, null, 0);
@ -93,14 +90,19 @@ public class NotificationFooterLayout extends FrameLayout {
int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
- iconSize * MAX_FOOTER_NOTIFICATIONS;
mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mOverflowEllipsis = findViewById(R.id.overflow);
mIconRow = (LinearLayout) findViewById(R.id.icon_row);
mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
mIconRow = findViewById(R.id.icon_row);
}
void setContainer(NotificationItemView container) {
mContainer = container;
}
/**
@ -198,25 +200,8 @@ public class NotificationFooterLayout extends FrameLayout {
updateOverflowEllipsisVisibility();
if (mIconRow.getChildCount() == 0) {
// There are no more icons in the footer, so hide it.
BaseActionPopup popup = BaseActionPopup.getOpen(
Launcher.getLauncher(getContext()));
if (popup instanceof PopupContainerWithArrow) {
final int newHeight = getResources().getDimensionPixelSize(
R.dimen.notification_empty_footer_height);
Animator collapseFooter = ((PopupContainerWithArrow) popup)
.reduceNotificationViewHeight(getHeight() - newHeight,
getResources().getInteger(R.integer.config_removeNotificationViewDuration));
collapseFooter.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
((ViewGroup) getParent()).findViewById(R.id.divider).setVisibility(GONE);
// Keep view around because gutter is aligned to it, but remove height to
// both hide the view and keep calculations correct for last dismissal.
getLayoutParams().height = newHeight;
requestLayout();
}
});
collapseFooter.start();
if (mContainer != null) {
mContainer.removeFooter();
}
}
}

View File

@ -16,117 +16,102 @@
package com.android.launcher3.notification;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Notification;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
import com.android.launcher3.popup.PopupItemView;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.Themes;
import java.util.List;
import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
/**
* A {@link FrameLayout} that contains a header, main view and a footer.
* The main view contains the icon and text (title + subtext) of the first notification.
* The footer contains: A list of just the icons of all the notifications past the first one.
* @see NotificationFooterLayout
* Utility class to manage notification UI
*/
public class NotificationItemView extends PopupItemView implements LogContainerProvider {
public class NotificationItemView {
private static final Rect sTempRect = new Rect();
private TextView mHeaderText;
private TextView mHeaderCount;
private NotificationMainView mMainView;
private NotificationFooterLayout mFooter;
private SwipeDetector mSwipeDetector;
private final Context mContext;
private final PopupContainerWithArrow mContainer;
private final TextView mHeaderText;
private final TextView mHeaderCount;
private final NotificationMainView mMainView;
private final NotificationFooterLayout mFooter;
private final SwipeDetector mSwipeDetector;
private final View mIconView;
private final View mHeader;
private final View mDivider;
private View mGutter;
private boolean mIgnoreTouch = false;
private boolean mAnimatingNextIcon;
private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
public NotificationItemView(Context context) {
this(context, null, 0);
}
public NotificationItemView(PopupContainerWithArrow container) {
mContainer = container;
mContext = container.getContext();
public NotificationItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
mHeaderText = container.findViewById(R.id.notification_text);
mHeaderCount = container.findViewById(R.id.notification_count);
mMainView = container.findViewById(R.id.main_view);
mFooter = container.findViewById(R.id.footer);
mIconView = container.findViewById(R.id.popup_item_icon);
public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
mHeader = container.findViewById(R.id.header);
mDivider = container.findViewById(R.id.divider);
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mHeaderText = findViewById(R.id.notification_text);
mHeaderCount = findViewById(R.id.notification_count);
mMainView = findViewById(R.id.main_view);
mFooter = findViewById(R.id.footer);
mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL);
mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
mMainView.setSwipeDetector(mSwipeDetector);
mFooter.setContainer(this);
}
public NotificationMainView getMainView() {
return mMainView;
public void addGutter() {
if (mGutter == null) {
mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter);
}
}
/**
* This method is used to calculate the height to remove when dismissing the last notification.
* We subtract the height of the footer in this case since the footer should be gone or in the
* process of being removed.
* @return The height of the entire notification item, minus the footer if it still exists.
*/
public int getHeightMinusFooter() {
if (mFooter.getParent() == null) {
return getHeight();
public void removeFooter() {
if (mContainer.indexOfChild(mFooter) >= 0) {
mContainer.removeView(mFooter);
mContainer.removeView(mDivider);
}
int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize(
R.dimen.notification_empty_footer_height);
return getHeight() - excessFooterHeight;
}
public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) {
AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
public void inverseGutterMargin() {
MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
int top = lp.topMargin;
lp.topMargin = lp.bottomMargin;
lp.bottomMargin = top;
}
Rect startRect = new Rect(mPillRect);
Rect endRect = new Rect(mPillRect);
if (shouldRemoveFromTop) {
endRect.top += heightToRemove;
} else {
endRect.bottom -= heightToRemove;
}
anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(),
startRect, endRect, mRoundedCorners).createRevealAnimator(this, false));
public void removeAllViews() {
mContainer.removeView(mMainView);
mContainer.removeView(mHeader);
View bottomGutter = findViewById(R.id.gutter_bottom);
if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) {
Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y,
-heightToRemove);
translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f));
anim.play(translateGutter);
if (mContainer.indexOfChild(mFooter) >= 0) {
mContainer.removeView(mFooter);
mContainer.removeView(mDivider);
}
return anim;
if (mGutter != null) {
mContainer.removeView(mGutter);
}
}
public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
@ -134,32 +119,44 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
if (palette != null) {
if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
mNotificationHeaderTextColor =
IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
Themes.getAttrColor(getContext(), R.attr.popupColorPrimary));
IconPalette.resolveContrastColor(mContext, palette.dominantColor,
Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
}
mHeaderText.setTextColor(mNotificationHeaderTextColor);
mHeaderCount.setTextColor(mNotificationHeaderTextColor);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
mMainView.getRight(), mMainView.getBottom());
mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
if (!mIgnoreTouch) {
mContainer.getParent().requestDisallowInterceptTouchEvent(true);
}
}
if (mIgnoreTouch) {
return false;
}
if (mMainView.getNotificationInfo() == null) {
// The notification hasn't been populated yet.
return false;
}
getParent().requestDisallowInterceptTouchEvent(true);
mSwipeDetector.onTouchEvent(ev);
return mSwipeDetector.isDraggingOrSettling();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mIgnoreTouch) {
return false;
}
if (mMainView.getNotificationInfo() == null) {
// The notification hasn't been populated yet.
return false;
}
return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
return mSwipeDetector.onTouchEvent(ev);
}
public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
@ -168,7 +165,7 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
}
NotificationInfo mainNotification = notificationInfos.get(0);
mMainView.applyNotificationInfo(mainNotification, mIconView);
mMainView.applyNotificationInfo(mainNotification, false);
for (int i = 1; i < notificationInfos.size(); i++) {
mFooter.addNotificationInfo(notificationInfos.get(i));
@ -182,29 +179,18 @@ public class NotificationItemView extends PopupItemView implements LogContainerP
if (dismissedMainNotification && !mAnimatingNextIcon) {
// Animate the next icon into place as the new main notification.
mAnimatingNextIcon = true;
mMainView.setVisibility(INVISIBLE);
mMainView.setTranslationX(0);
mMainView.setContentVisibility(View.INVISIBLE);
mMainView.setContentTranslation(0);
mIconView.getGlobalVisibleRect(sTempRect);
mFooter.animateFirstNotificationTo(sTempRect,
new NotificationFooterLayout.IconAnimationEndListener() {
@Override
public void onIconAnimationEnd(NotificationInfo newMainNotification) {
if (newMainNotification != null) {
mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
mMainView.setVisibility(VISIBLE);
}
mAnimatingNextIcon = false;
mFooter.animateFirstNotificationTo(sTempRect, (newMainNotification) -> {
if (newMainNotification != null) {
mMainView.applyNotificationInfo(newMainNotification, true);
mMainView.setContentVisibility(View.VISIBLE);
}
mAnimatingNextIcon = false;
});
} else {
mFooter.trimNotifications(notificationKeys);
}
}
@Override
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
LauncherLogProto.Target targetParent) {
target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
}
}

View File

@ -18,13 +18,17 @@ package com.android.launcher3.notification;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@ -33,6 +37,7 @@ import android.widget.TextView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@ -42,13 +47,33 @@ import com.android.launcher3.util.Themes;
* A {@link android.widget.FrameLayout} that contains a single notification,
* e.g. icon + title + text.
*/
@TargetApi(Build.VERSION_CODES.N)
public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
new FloatProperty<NotificationMainView>("contentTranslation") {
@Override
public void setValue(NotificationMainView view, float v) {
view.setContentTranslation(v);
}
@Override
public Float get(NotificationMainView view) {
return view.mTextAndBackground.getTranslationX();
}
};
// This is used only to track the notification view, so that it can be properly logged.
public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo();
private final ObjectAnimator mContentTranslateAnimator;
private NotificationInfo mNotificationInfo;
private ViewGroup mTextAndBackground;
private int mBackgroundColor;
private TextView mTitleView;
private TextView mTextView;
private View mIconView;
private SwipeDetector mSwipeDetector;
@ -62,25 +87,24 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContentTranslateAnimator = ObjectAnimator.ofFloat(this, CONTENT_TRANSLATION, 0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
mTextAndBackground = findViewById(R.id.text_and_background);
ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
mBackgroundColor = colorBackground.getColor();
RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
colorBackground, null);
mTextAndBackground.setBackground(rippleBackground);
mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
}
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
applyNotificationInfo(mainNotification, iconView, false);
mTitleView = mTextAndBackground.findViewById(R.id.title);
mTextView = mTextAndBackground.findViewById(R.id.text);
mIconView = findViewById(R.id.popup_item_icon);
}
public void setSwipeDetector(SwipeDetector swipeDetector) {
@ -90,8 +114,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
/**
* Sets the content of this view, animating it after a new icon shifts up if necessary.
*/
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
boolean animate) {
public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
mNotificationInfo = mainNotification;
CharSequence title = mNotificationInfo.title;
CharSequence text = mNotificationInfo.text;
@ -103,20 +126,30 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
mTitleView.setText(TextUtils.isEmpty(title) ? text.toString() : title.toString());
mTextView.setVisibility(GONE);
}
iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
mBackgroundColor));
if (mNotificationInfo.intent != null) {
setOnClickListener(mNotificationInfo);
}
setTranslationX(0);
setContentTranslation(0);
// Add a dummy ItemInfo so that logging populates the correct container and item types
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
setTag(new ItemInfo());
setTag(NOTIFICATION_ITEM_INFO);
if (animate) {
ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
}
}
public void setContentTranslation(float translation) {
mTextAndBackground.setTranslationX(translation);
mIconView.setTranslationX(translation);
}
public void setContentVisibility(int visibility) {
mTextAndBackground.setVisibility(visibility);
mIconView.setVisibility(visibility);
}
public NotificationInfo getNotificationInfo() {
return mNotificationInfo;
}
@ -143,9 +176,9 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
@Override
public boolean onDrag(float displacement, float velocity) {
setTranslationX(canChildBeDismissed()
setContentTranslation(canChildBeDismissed()
? displacement : OverScroll.dampedScroll(displacement, getWidth()));
animate().cancel();
mContentTranslateAnimator.cancel();
return true;
}
@ -153,6 +186,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
public void onDragEnd(float velocity, boolean fling) {
final boolean willExit;
final float endTranslation;
final float startTranslation = mTextAndBackground.getTranslationX();
if (!canChildBeDismissed()) {
willExit = false;
@ -160,28 +194,30 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
} else if (fling) {
willExit = true;
endTranslation = velocity < 0 ? - getWidth() : getWidth();
} else if (Math.abs(getTranslationX()) > getWidth() / 2) {
} else if (Math.abs(startTranslation) > getWidth() / 2) {
willExit = true;
endTranslation = (getTranslationX() < 0 ? -getWidth() : getWidth());
endTranslation = (startTranslation < 0 ? -getWidth() : getWidth());
} else {
willExit = false;
endTranslation = 0;
}
long duration = SwipeDetector.calculateDuration(velocity,
(endTranslation - getTranslationX()) / getWidth());
animate()
.setDuration(duration)
.setInterpolator(scrollInterpolatorForVelocity(velocity))
.translationX(endTranslation)
.withEndAction(new Runnable() {
@Override
public void run() {
mSwipeDetector.finishedScrolling();
if (willExit) {
onChildDismissed();
}
}
}).start();
(endTranslation - startTranslation) / getWidth());
mContentTranslateAnimator.removeAllListeners();
mContentTranslateAnimator.setDuration(duration)
.setInterpolator(scrollInterpolatorForVelocity(velocity));
mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mSwipeDetector.finishedScrolling();
if (willExit) {
onChildDismissed();
}
}
});
mContentTranslateAnimator.start();
}
}

View File

@ -1,599 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.popup;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.CornerPathEffect;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.TriangleShape;
import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutsItemView;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Themes;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;
/**
* Base popup container for showing shortcuts to deep links within apps.
*/
@TargetApi(Build.VERSION_CODES.N)
public class BaseActionPopup<V extends TextView> extends AbstractFloatingView {
public static final int ROUNDED_TOP_CORNERS = 1 << 0;
public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1;
@IntDef(flag = true, value = {
ROUNDED_TOP_CORNERS,
ROUNDED_BOTTOM_CORNERS
})
@Retention(RetentionPolicy.SOURCE)
public @interface RoundedCornerFlags {}
protected final Launcher mLauncher;
protected final LauncherAccessibilityDelegate mAccessibilityDelegate;
private final boolean mIsRtl;
public ShortcutsItemView mShortcutsItemView;
protected V mOriginalIcon;
private final Rect mTempRect = new Rect();
private PointF mInterceptTouchDown = new PointF();
private boolean mIsLeftAligned;
protected boolean mIsAboveIcon;
protected View mArrow;
private int mGravity;
protected Animator mOpenCloseAnimator;
protected boolean mDeferContainerRemoval;
private final Rect mStartRect = new Rect();
private final Rect mEndRect = new Rect();
public BaseActionPopup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
mIsRtl = Utilities.isRtl(getResources());
}
public BaseActionPopup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseActionPopup(Context context) {
this(context, null, 0);
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
return mAccessibilityDelegate;
}
protected PopupItemView getItemViewAt(int index) {
if (!mIsAboveIcon) {
// Opening down, so arrow is the first view.
index++;
}
return (PopupItemView) getChildAt(index);
}
protected int getItemCount() {
// All children except the arrow are items.
return getChildCount() - 1;
}
protected void animateOpen() {
setVisibility(View.VISIBLE);
mIsOpen = true;
final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
final Resources res = getResources();
final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
// Rectangular reveal.
int itemsTotalHeight = 0;
for (int i = 0; i < getItemCount(); i++) {
itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
}
Point startPoint = computeAnimStartPoint(itemsTotalHeight);
int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
float radius = getItemViewAt(0).getBackgroundRadius();
mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider
(radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false);
revealAnim.setDuration(revealDuration);
revealAnim.setInterpolator(revealInterpolator);
Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
fadeIn.setDuration(revealDuration);
fadeIn.setInterpolator(revealInterpolator);
openAnim.play(fadeIn);
// Animate the arrow.
mArrow.setScaleX(0);
mArrow.setScaleY(0);
Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger(
R.integer.config_popupArrowOpenDuration));
openAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator = null;
Utilities.sendCustomAccessibilityEvent(
BaseActionPopup.this,
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
getContext().getString(R.string.action_deep_shortcut));
}
});
mOpenCloseAnimator = openAnim;
openAnim.playSequentially(revealAnim, arrowScale);
openAnim.start();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
enforceContainedWithinScreen(l, r);
}
private void enforceContainedWithinScreen(int left, int right) {
DragLayer dragLayer = mLauncher.getDragLayer();
if (getTranslationX() + left < 0 ||
getTranslationX() + right > dragLayer.getWidth()) {
// If we are still off screen, center horizontally too.
mGravity |= Gravity.CENTER_HORIZONTAL;
}
if (Gravity.isHorizontal(mGravity)) {
setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
}
if (Gravity.isVertical(mGravity)) {
setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
}
}
/**
* Returns the point at which the center of the arrow merges with the first popup item.
*/
private Point computeAnimStartPoint(int itemsTotalHeight) {
int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
R.dimen.popup_arrow_horizontal_center_start:
R.dimen.popup_arrow_horizontal_center_end);
if (!mIsLeftAligned) {
arrowCenterX = getMeasuredWidth() - arrowCenterX;
}
int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
- itemsTotalHeight;
// The y-coordinate of edge between the arrow and the first popup item.
int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight);
return new Point(arrowCenterX, arrowEdge);
}
/**
* Orients this container above or below the given icon, aligning with the left or right.
*
* These are the preferred orientations, in order (RTL prefers right-aligned over left):
* - Above and left-aligned
* - Above and right-aligned
* - Below and left-aligned
* - Below and right-aligned
*
* So we always align left if there is enough horizontal space
* and align above if there is enough vertical space.
*/
protected void orientAboutIcon(int arrowHeight) {
int width = getMeasuredWidth();
int height = getMeasuredHeight() + arrowHeight;
DragLayer dragLayer = mLauncher.getDragLayer();
dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
Rect insets = dragLayer.getInsets();
// Align left (right in RTL) if there is room.
int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
int x = leftAlignedX;
boolean canBeLeftAligned = leftAlignedX + width + insets.left
< dragLayer.getRight() - insets.right;
boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
x = rightAlignedX;
}
mIsLeftAligned = x == leftAlignedX;
if (mIsRtl) {
x -= dragLayer.getWidth() - width;
}
// Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
int iconWidth = mOriginalIcon.getWidth()
- mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
iconWidth *= mOriginalIcon.getScaleX();
Resources resources = getResources();
int xOffset;
if (isAlignedWithStart()) {
// Aligning with the shortcut icon.
int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
int shortcutPaddingStart = resources.getDimensionPixelSize(
R.dimen.popup_padding_start);
xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
} else {
// Aligning with the drag handle.
int shortcutDragHandleWidth = resources.getDimensionPixelSize(
R.dimen.deep_shortcut_drag_handle_size);
int shortcutPaddingEnd = resources.getDimensionPixelSize(
R.dimen.popup_padding_end);
xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
}
x += mIsLeftAligned ? xOffset : -xOffset;
// Open above icon if there is room.
int iconHeight = getIconHeightForPopupPlacement();
int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
mIsAboveIcon = y > dragLayer.getTop() + insets.top;
if (!mIsAboveIcon) {
y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight;
}
// Insets are added later, so subtract them now.
if (mIsRtl) {
x += insets.right;
} else {
x -= insets.left;
}
y -= insets.top;
mGravity = 0;
if (y + height > dragLayer.getBottom() - insets.bottom) {
// The container is opening off the screen, so just center it in the drag layer instead.
mGravity = Gravity.CENTER_VERTICAL;
// Put the container next to the icon, preferring the right side in ltr (left in rtl).
int rightSide = leftAlignedX + iconWidth - insets.left;
int leftSide = rightAlignedX - iconWidth - insets.left;
if (!mIsRtl) {
if (rightSide + width < dragLayer.getRight()) {
x = rightSide;
mIsLeftAligned = true;
} else {
x = leftSide;
mIsLeftAligned = false;
}
} else {
if (leftSide > dragLayer.getLeft()) {
x = leftSide;
mIsLeftAligned = false;
} else {
x = rightSide;
mIsLeftAligned = true;
}
}
mIsAboveIcon = true;
}
setX(x);
setY(y);
}
protected int getIconHeightForPopupPlacement() {
return mOriginalIcon.getHeight();
}
protected boolean isAlignedWithStart() {
return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
}
/**
* Adds an arrow view pointing at the original icon.
* @param horizontalOffset the horizontal offset of the arrow, so that it
* points at the center of the original icon
*/
protected View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
LayoutParams layoutParams = new LayoutParams(width, height);
if (mIsLeftAligned) {
layoutParams.gravity = Gravity.LEFT;
layoutParams.leftMargin = horizontalOffset;
} else {
layoutParams.gravity = Gravity.RIGHT;
layoutParams.rightMargin = horizontalOffset;
}
if (mIsAboveIcon) {
layoutParams.topMargin = verticalOffset;
} else {
layoutParams.bottomMargin = verticalOffset;
}
View arrowView = new View(getContext());
if (Gravity.isVertical(mGravity)) {
// This is only true if there wasn't room for the container next to the icon,
// so we centered it instead. In that case we don't want to show the arrow.
arrowView.setVisibility(INVISIBLE);
} else {
ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
width, height, !mIsAboveIcon));
Paint arrowPaint = arrowDrawable.getPaint();
arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
// The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
arrowPaint.setPathEffect(new CornerPathEffect(radius));
arrowView.setBackground(arrowDrawable);
arrowView.setElevation(getElevation());
}
addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
return arrowView;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mInterceptTouchDown.set(ev.getX(), ev.getY());
return false;
}
// Stop sending touch events to deep shortcut views if user moved beyond touch slop.
return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
> ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
protected ObjectAnimator createArrowScaleAnim(float scale) {
return LauncherAnimUtils.ofPropertyValuesHolder(
mArrow, new PropertyListBuilder().scale(scale).build());
}
@Override
protected void handleClose(boolean animate) {
if (animate) {
animateClose();
} else {
closeComplete();
}
}
protected void animateClose() {
if (!mIsOpen) {
return;
}
mEndRect.setEmpty();
if (mOpenCloseAnimator != null) {
Outline outline = new Outline();
getOutlineProvider().getOutline(this, outline);
outline.getRect(mEndRect);
mOpenCloseAnimator.cancel();
}
mIsOpen = false;
final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
prepareCloseAnimator(closeAnim);
closeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator = null;
if (mDeferContainerRemoval) {
setVisibility(INVISIBLE);
} else {
closeComplete();
}
}
});
mOpenCloseAnimator = closeAnim;
closeAnim.start();
}
protected void prepareCloseAnimator(AnimatorSet closeAnim) {
final Resources res = getResources();
final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
// Rectangular reveal (reversed).
int itemsTotalHeight = 0;
for (int i = 0; i < getItemCount(); i++) {
itemsTotalHeight += getItemViewAt(i).getMeasuredHeight();
}
Point startPoint = computeAnimStartPoint(itemsTotalHeight);
int top = mIsAboveIcon ? getPaddingTop() : startPoint.y;
float radius = getItemViewAt(0).getBackgroundRadius();
mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y);
if (mEndRect.isEmpty()) {
mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight);
}
final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider(
radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true);
revealAnim.setInterpolator(revealInterpolator);
closeAnim.play(revealAnim);
Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
fadeOut.setInterpolator(revealInterpolator);
closeAnim.play(fadeOut);
closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
}
/**
* Closes the folder without animation.
*/
protected void closeComplete() {
if (mOpenCloseAnimator != null) {
mOpenCloseAnimator.cancel();
mOpenCloseAnimator = null;
}
mIsOpen = false;
mDeferContainerRemoval = false;
mLauncher.getDragLayer().removeView(this);
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ACTION_POPUP) != 0;
}
/**
* Returns a DeepShortcutsContainer which is already open or null
*/
public static BaseActionPopup getOpen(Launcher launcher) {
return getOpenView(launcher, TYPE_ACTION_POPUP);
}
@Override
public void logActionCommand(int command) {
mLauncher.getUserEventDispatcher().logActionCommand(
command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
DragLayer dl = mLauncher.getDragLayer();
if (!dl.isEventOverView(this, ev)) {
mLauncher.getUserEventDispatcher().logActionTapOutside(
LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
close(true);
// We let touches on the original icon go through so that users can launch
// the app with one tap if they don't find a shortcut they want.
return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
}
}
return false;
}
public void populateAndShow(V originalIcon, PopupPopulator.Item[] itemsToPopulate) {
setVisibility(View.INVISIBLE);
mLauncher.getDragLayer().addView(this);
final Resources resources = getResources();
final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
final int arrowVerticalOffset = resources.getDimensionPixelSize(
R.dimen.popup_arrow_vertical_offset);
mOriginalIcon = originalIcon;
// Add dummy views first, and populate with real info when ready.
addDummyViews(itemsToPopulate);
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
orientAboutIcon(arrowHeight + arrowVerticalOffset);
boolean reverseOrder = mIsAboveIcon;
if (reverseOrder) {
removeAllViews();
mShortcutsItemView = null;
itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
addDummyViews(itemsToPopulate);
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
orientAboutIcon(arrowHeight + arrowVerticalOffset);
}
// Add the arrow.
final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ?
R.dimen.popup_arrow_horizontal_offset_start :
R.dimen.popup_arrow_horizontal_offset_end);
mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
mArrow.setPivotX(arrowWidth / 2);
mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
animateOpen();
}
protected void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate) {
final LayoutInflater inflater = mLauncher.getLayoutInflater();
int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
int numItems = itemTypesToPopulate.length;
for (int i = 0; i < numItems; i++) {
PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
PopupPopulator.Item prevItemTypeToPopulate =
i > 0 ? itemTypesToPopulate[i - 1] : null;
PopupPopulator.Item nextItemTypeToPopulate =
i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
&& itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
&& itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
onViewInflated(item, itemTypeToPopulate,
shouldUnroundTopCorners, shouldUnroundBottomCorners);
if (itemTypeToPopulate.isShortcut) {
if (mShortcutsItemView == null) {
mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
R.layout.shortcuts_item, this, false);
addView(mShortcutsItemView);
if (shouldUnroundTopCorners) {
shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
}
}
mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
if (shouldUnroundBottomCorners) {
shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
}
} else {
addView(item);
}
}
int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
}
protected void onViewInflated(View view, PopupPopulator.Item itemType,
boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) {
}
}

File diff suppressed because it is too large Load Diff

View File

@ -148,9 +148,9 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
}
private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
BaseActionPopup openContainer = BaseActionPopup.getOpen(mLauncher);
if (openContainer instanceof PopupContainerWithArrow) {
((PopupContainerWithArrow) openContainer).trimNotifications(updatedBadges);
PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
if (openContainer != null) {
openContainer.trimNotifications(updatedBadges);
}
}

View File

@ -1,145 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.popup;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.popup.BaseActionPopup.RoundedCornerFlags;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS;
import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS;
/**
* An abstract {@link FrameLayout} that contains content for {@link PopupContainerWithArrow}.
*/
public abstract class PopupItemView extends FrameLayout {
protected final Rect mPillRect;
protected @RoundedCornerFlags int mRoundedCorners;
protected final boolean mIsRtl;
protected View mIconView;
private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private final Matrix mMatrix = new Matrix();
private Bitmap mRoundedCornerBitmap;
public PopupItemView(Context context) {
this(context, null, 0);
}
public PopupItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mPillRect = new Rect();
// Initialize corner clipping Bitmap and Paint.
int radius = (int) getBackgroundRadius();
mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
Canvas canvas = new Canvas();
canvas.setBitmap(mRoundedCornerBitmap);
canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mIsRtl = Utilities.isRtl(getResources());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconView = findViewById(R.id.popup_item_icon);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mRoundedCorners == 0) {
super.dispatchDraw(canvas);
return;
}
int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
super.dispatchDraw(canvas);
// Clip children to this item's rounded corners.
int cornerWidth = mRoundedCornerBitmap.getWidth();
int cornerHeight = mRoundedCornerBitmap.getHeight();
int cornerCenterX = Math.round(cornerWidth / 2f);
int cornerCenterY = Math.round(cornerHeight / 2f);
if ((mRoundedCorners & ROUNDED_TOP_CORNERS) != 0) {
// Clip top left corner.
mMatrix.reset();
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
// Clip top right corner.
mMatrix.setRotate(90, cornerCenterX, cornerCenterY);
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
}
if ((mRoundedCorners & ROUNDED_BOTTOM_CORNERS) != 0) {
// Clip bottom right corner.
mMatrix.setRotate(180, cornerCenterX, cornerCenterY);
mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
// Clip bottom left corner.
mMatrix.setRotate(270, cornerCenterX, cornerCenterY);
mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
}
canvas.restoreToCount(saveCount);
}
/**
* Creates a round rect drawable (with the specified corners unrounded)
* and sets it as this View's background.
*/
public void setBackgroundWithCorners(int color, @RoundedCornerFlags int roundedCorners) {
mRoundedCorners = roundedCorners;
float rTop = (roundedCorners & ROUNDED_TOP_CORNERS) == 0 ? 0 : getBackgroundRadius();
float rBot = (roundedCorners & ROUNDED_BOTTOM_CORNERS) == 0 ? 0 : getBackgroundRadius();
float[] radii = new float[] {rTop, rTop, rTop, rTop, rBot, rBot, rBot, rBot};
ShapeDrawable roundRectBackground = new ShapeDrawable(new RoundRectShape(radii, null, null));
roundRectBackground.getPaint().setColor(color);
setBackground(roundRectBackground);
}
protected float getBackgroundRadius() {
return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
}
}

View File

@ -17,23 +17,17 @@
package com.android.launcher3.popup;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.view.View;
import android.widget.ImageView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
@ -56,55 +50,6 @@ public class PopupPopulator {
@VisibleForTesting static final int NUM_DYNAMIC = 2;
public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
public enum Item {
SHORTCUT(R.layout.deep_shortcut, true),
NOTIFICATION(R.layout.notification, false),
SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
public final int layoutId;
public final boolean isShortcut;
Item(int layoutId, boolean isShortcut) {
this.layoutId = layoutId;
this.isShortcut = isShortcut;
}
}
public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
@NonNull List<NotificationKeyData> notificationKeys,
@NonNull List<SystemShortcut> systemShortcuts) {
boolean hasNotifications = notificationKeys.size() > 0;
int numNotificationItems = hasNotifications ? 1 : 0;
int numShortcuts = shortcutIds.size();
int numItems = Math.min(MAX_SHORTCUTS, numShortcuts) + numNotificationItems
+ systemShortcuts.size();
Item[] items = new Item[numItems];
for (int i = 0; i < numItems; i++) {
items[i] = Item.SHORTCUT;
}
if (hasNotifications) {
// The notification layout is always first.
items[0] = Item.NOTIFICATION;
}
// The system shortcuts are always last.
boolean iconsOnly = !shortcutIds.isEmpty();
for (int i = 0; i < systemShortcuts.size(); i++) {
items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
}
return items;
}
public static Item[] reverseItems(Item[] items) {
if (items == null) return null;
int numItems = items.length;
Item[] reversedArray = new Item[numItems];
for (int i = 0; i < numItems; i++) {
reversedArray[i] = items[numItems - i - 1];
}
return reversedArray;
}
/**
* Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
*/
@ -179,137 +124,42 @@ public class PopupPopulator {
public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
final List<NotificationKeyData> notificationKeys,
final NotificationItemView notificationView, final List<SystemShortcut> systemShortcuts,
final List<View> systemShortcutViews) {
final List<NotificationKeyData> notificationKeys) {
final ComponentName activity = originalInfo.getTargetComponent();
final UserHandle user = originalInfo.user;
return new Runnable() {
@Override
public void run() {
if (notificationView != null) {
List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
.getStatusBarNotificationsForKeys(notificationKeys);
List<NotificationInfo> infos = new ArrayList<>(notifications.size());
for (int i = 0; i < notifications.size(); i++) {
StatusBarNotification notification = notifications.get(i);
infos.add(new NotificationInfo(launcher, notification));
}
uiHandler.post(new UpdateNotificationChild(notificationView, infos));
return () -> {
if (!notificationKeys.isEmpty()) {
List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
.getStatusBarNotificationsForKeys(notificationKeys);
List<NotificationInfo> infos = new ArrayList<>(notifications.size());
for (int i = 0; i < notifications.size(); i++) {
StatusBarNotification notification = notifications.get(i);
infos.add(new NotificationInfo(launcher, notification));
}
List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
.queryForShortcutsContainer(activity, shortcutIds, user);
String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
: notificationKeys.get(0).shortcutId;
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
final ShortcutInfoCompat shortcut = shortcuts.get(i);
ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
// Use unbadged icon for the menu.
si.iconBitmap = LauncherIcons.createShortcutIcon(
shortcut, launcher, false /* badged */);
si.rank = i;
uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
si, shortcut));
}
// This ensures that mLauncher.getWidgetsForPackageUser()
// doesn't return null (it puts all the widgets in memory).
for (int i = 0; i < systemShortcuts.size(); i++) {
final SystemShortcut systemShortcut = systemShortcuts.get(i);
uiHandler.post(new UpdateSystemShortcutChild(container,
systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
}
uiHandler.post(new Runnable() {
@Override
public void run() {
launcher.refreshAndBindWidgetsForPackageUser(
PackageUserKey.fromItemInfo(originalInfo));
}
});
uiHandler.post(() -> container.applyNotificationInfos(infos));
}
List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
.queryForShortcutsContainer(activity, shortcutIds, user);
String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
: notificationKeys.get(0).shortcutId;
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
final ShortcutInfoCompat shortcut = shortcuts.get(i);
final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
// Use unbadged icon for the menu.
si.iconBitmap = LauncherIcons.createShortcutIcon(
shortcut, launcher, false /* badged */);
si.rank = i;
final DeepShortcutView view = shortcutViews.get(i);
uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
}
// This ensures that mLauncher.getWidgetsForPackageUser()
// doesn't return null (it puts all the widgets in memory).
uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
PackageUserKey.fromItemInfo(originalInfo)));
};
}
/** Updates the shortcut child of this container based on the given shortcut info. */
private static class UpdateShortcutChild implements Runnable {
private final PopupContainerWithArrow mContainer;
private final DeepShortcutView mShortcutChild;
private final ShortcutInfo mShortcutChildInfo;
private final ShortcutInfoCompat mDetail;
public UpdateShortcutChild(PopupContainerWithArrow container, DeepShortcutView shortcutChild,
ShortcutInfo shortcutChildInfo, ShortcutInfoCompat detail) {
mContainer = container;
mShortcutChild = shortcutChild;
mShortcutChildInfo = shortcutChildInfo;
mDetail = detail;
}
@Override
public void run() {
mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
mContainer.mShortcutsItemView);
}
}
/** Updates the notification child based on the given notification info. */
private static class UpdateNotificationChild implements Runnable {
private NotificationItemView mNotificationView;
private List<NotificationInfo> mNotificationInfos;
public UpdateNotificationChild(NotificationItemView notificationView,
List<NotificationInfo> notificationInfos) {
mNotificationView = notificationView;
mNotificationInfos = notificationInfos;
}
@Override
public void run() {
mNotificationView.applyNotificationInfos(mNotificationInfos);
}
}
/** Updates the system shortcut child based on the given shortcut info. */
private static class UpdateSystemShortcutChild implements Runnable {
private final PopupContainerWithArrow mContainer;
private final View mSystemShortcutChild;
private final SystemShortcut mSystemShortcutInfo;
private final Launcher mLauncher;
private final ItemInfo mItemInfo;
public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
mContainer = container;
mSystemShortcutChild = systemShortcutChild;
mSystemShortcutInfo = systemShortcut;
mLauncher = launcher;
mItemInfo = originalInfo;
}
@Override
public void run() {
final Context context = mSystemShortcutChild.getContext();
initializeSystemShortcut(context, mSystemShortcutChild, mSystemShortcutInfo);
mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
.getOnClickListener(mLauncher, mItemInfo));
}
}
public static void initializeSystemShortcut(Context context, View view, SystemShortcut info) {
if (view instanceof DeepShortcutView) {
// Expanded system shortcut, with both icon and text shown on white background.
final DeepShortcutView shortcutView = (DeepShortcutView) view;
shortcutView.getIconView().setBackground(info.getIcon(context));
shortcutView.getBubbleText().setText(info.getLabel(context));
} else if (view instanceof ImageView) {
// Only the system shortcut icon shows on a gray background header.
final ImageView shortcutIcon = (ImageView) view;
shortcutIcon.setImageDrawable(info.getIcon(context));
shortcutIcon.setContentDescription(info.getLabel(context));
}
view.setTag(info);
}
}

View File

@ -1,9 +1,7 @@
package com.android.launcher3.popup;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
@ -31,20 +29,12 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
*/
public abstract class SystemShortcut extends ItemInfo {
private final int mIconResId;
private final int mLabelResId;
public final int iconResId;
public final int labelResId;
public SystemShortcut(int iconResId, int labelResId) {
mIconResId = iconResId;
mLabelResId = labelResId;
}
public Drawable getIcon(Context context) {
return context.getResources().getDrawable(mIconResId, context.getTheme());
}
public String getLabel(Context context) {
return context.getString(mLabelResId);
this.iconResId = iconResId;
this.labelResId = labelResId;
}
public abstract View.OnClickListener getOnClickListener(final Launcher launcher,

View File

@ -29,6 +29,7 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.popup.PopupContainerWithArrow;
/**
* A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
@ -42,6 +43,7 @@ public class DeepShortcutView extends FrameLayout {
private BubbleTextView mBubbleText;
private View mIconView;
private View mDivider;
private ShortcutInfo mInfo;
private ShortcutInfoCompat mDetail;
@ -65,6 +67,11 @@ public class DeepShortcutView extends FrameLayout {
super.onFinishInflate();
mBubbleText = findViewById(R.id.bubble_text);
mIconView = findViewById(R.id.icon);
mDivider = findViewById(R.id.divider);
}
public void setDividerVisibility(int visibility) {
mDivider.setVisibility(visibility);
}
public BubbleTextView getBubbleText() {
@ -98,7 +105,7 @@ public class DeepShortcutView extends FrameLayout {
/** package private **/
public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
ShortcutsItemView container) {
PopupContainerWithArrow container) {
mInfo = info;
mDetail = detail;
mBubbleText.applyFromShortcutInfo(info);

View File

@ -1,340 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.shortcuts;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupItemView;
import com.android.launcher3.popup.PopupPopulator;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
* as well as the system shortcuts such as Widgets and App Info.
*/
public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
View.OnTouchListener, LogContainerProvider {
private static final String TAG = "ShortcutsItem";
private Launcher mLauncher;
private LinearLayout mContent;
private LinearLayout mShortcutsLayout;
private LinearLayout mSystemShortcutIcons;
private final Point mIconShift = new Point();
private final Point mIconLastTouchPos = new Point();
private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
private final List<View> mSystemShortcutViews = new ArrayList<>();
private int mHiddenShortcutsHeight;
public ShortcutsItemView(Context context) {
this(context, null, 0);
}
public ShortcutsItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mLauncher = Launcher.getLauncher(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.content);
mShortcutsLayout = findViewById(R.id.shortcuts);
}
@Override
public boolean onTouch(View v, MotionEvent ev) {
// Touched a shortcut, update where it was touched so we can drag from there on long click.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
break;
}
return false;
}
@Override
public boolean onLongClick(View v) {
// Return early if not the correct view
if (!(v.getParent() instanceof DeepShortcutView)) return false;
// Return early if global dragging is not enabled
if (!mLauncher.isDraggingEnabled()) return false;
// Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
if (mLauncher.getDragController().isDragging()) return false;
// Long clicked on a shortcut.
DeepShortcutView sv = (DeepShortcutView) v.getParent();
sv.setWillDrawIcon(false);
// Move the icon to align with the center-top of the touch point
mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
(PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
dv.animateShift(-mIconShift.x, -mIconShift.y);
// TODO: support dragging from within folder without having to close it
AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
return false;
}
public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
addShortcutView(shortcutView, shortcutType, -1);
}
private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
if (shortcutType == PopupPopulator.Item.SHORTCUT) {
mDeepShortcutViews.add((DeepShortcutView) shortcutView);
} else {
mSystemShortcutViews.add(shortcutView);
}
if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
// System shortcut icons are added to a header that is separate from the full shortcuts.
if (mSystemShortcutIcons == null) {
mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
R.layout.system_shortcut_icons, mContent, false);
boolean iconsAreBelowShortcuts = mShortcutsLayout.getChildCount() > 0;
mContent.addView(mSystemShortcutIcons, iconsAreBelowShortcuts ? -1 : 0);
}
mSystemShortcutIcons.addView(shortcutView, index);
} else {
if (mShortcutsLayout.getChildCount() > 0) {
View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
if (prevChild instanceof DeepShortcutView) {
prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
}
}
mShortcutsLayout.addView(shortcutView, index);
}
}
public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
if (reverseOrder) {
Collections.reverse(mDeepShortcutViews);
}
return mDeepShortcutViews;
}
public List<View> getSystemShortcutViews(boolean reverseOrder) {
// Always reverse system shortcut icons (in the header)
// so they are in priority order from right to left.
if (reverseOrder || mSystemShortcutIcons != null) {
Collections.reverse(mSystemShortcutViews);
}
return mSystemShortcutViews;
}
/**
* Hides shortcuts until only {@param maxShortcuts} are showing. Also sets
* {@link #mHiddenShortcutsHeight} to be the amount of extra space that shortcuts will
* require when {@link #showAllShortcuts(boolean)} is called.
*/
public void hideShortcuts(boolean hideFromTop, int maxShortcuts) {
// When shortcuts are shown, they get more space allocated to them.
final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
mHiddenShortcutsHeight = (newHeight - oldHeight) * mShortcutsLayout.getChildCount();
int numToHide = mShortcutsLayout.getChildCount() - maxShortcuts;
if (numToHide <= 0) {
return;
}
final int numShortcuts = mShortcutsLayout.getChildCount();
final int dir = hideFromTop ? 1 : -1;
for (int i = hideFromTop ? 0 : numShortcuts - 1; 0 <= i && i < numShortcuts; i += dir) {
View child = mShortcutsLayout.getChildAt(i);
if (child instanceof DeepShortcutView) {
mHiddenShortcutsHeight += child.getLayoutParams().height;
child.setVisibility(GONE);
int prev = i + dir;
if (!hideFromTop && 0 <= prev && prev < numShortcuts) {
// When hiding views from the bottom, make sure to hide the last divider.
mShortcutsLayout.getChildAt(prev).findViewById(R.id.divider).setVisibility(GONE);
}
numToHide--;
if (numToHide == 0) {
break;
}
}
}
}
public int getHiddenShortcutsHeight() {
return mHiddenShortcutsHeight;
}
/**
* Sets all shortcuts in {@link #mShortcutsLayout} to VISIBLE, then creates an
* animation to reveal the newly shown shortcuts.
*
* @see #hideShortcuts(boolean, int)
*/
public Animator showAllShortcuts(boolean showFromTop) {
// First set all the shortcuts to VISIBLE.
final int numShortcuts = mShortcutsLayout.getChildCount();
if (numShortcuts == 0) {
Log.w(TAG, "Tried to show all shortcuts but there were no shortcuts to show");
return null;
}
final int oldHeight = mShortcutsLayout.getChildAt(0).getLayoutParams().height;
final int newHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
for (int i = 0; i < numShortcuts; i++) {
DeepShortcutView view = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
view.getLayoutParams().height = newHeight;
view.requestLayout();
view.setVisibility(VISIBLE);
if (i < numShortcuts - 1) {
view.findViewById(R.id.divider).setVisibility(VISIBLE);
}
}
// Now reveal the newly shown shortcuts.
AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
if (showFromTop) {
// The new shortcuts pushed the original shortcuts down, but we want to animate them
// to that position. So we revert the translation and animate to the new.
animation.play(translateYFrom(mShortcutsLayout, -mHiddenShortcutsHeight));
} else if (mSystemShortcutIcons != null) {
// When adding the shortcuts from the bottom, things are a little trickier, since
// that means they push the icons header down. To account for this, we do the same
// translation trick as above, but on the header. Since this means leaving behind
// a blank area where the header was, we also need to clip the background.
animation.play(translateYFrom(mSystemShortcutIcons, -mHiddenShortcutsHeight));
// mPillRect is the bounds of this view before the new shortcuts were shown.
Rect backgroundStartRect = new Rect(mPillRect);
Rect backgroundEndRect = new Rect(mPillRect);
backgroundEndRect.bottom += mHiddenShortcutsHeight;
animation.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(),
getBackgroundRadius(), backgroundStartRect, backgroundEndRect, mRoundedCorners)
.createRevealAnimator(this, false));
}
for (int i = 0; i < numShortcuts; i++) {
// Animate each shortcut to its new height.
DeepShortcutView shortcut = (DeepShortcutView) mShortcutsLayout.getChildAt(i);
int heightDiff = newHeight - oldHeight;
int heightAdjustmentIndex = showFromTop ? numShortcuts - i - 1 : i;
int fromDir = showFromTop ? 1 : -1;
animation.play(translateYFrom(shortcut, heightDiff * heightAdjustmentIndex * fromDir));
// Make sure the text and icon stay centered in the shortcut.
animation.play(translateYFrom(shortcut.getBubbleText(), heightDiff / 2 * fromDir));
animation.play(translateYFrom(shortcut.getIconView(), heightDiff / 2 * fromDir));
// Scale icons back up to full size.
animation.play(LauncherAnimUtils.ofPropertyValuesHolder(shortcut.getIconView(),
new PropertyListBuilder().scale(1f).build()));
}
return animation;
}
/**
* Animates the translationY of the view from the given offset to the view's current translation
* @return an Animator, which should be started by the caller.
*/
private Animator translateYFrom(View v, int diff) {
float finalY = v.getTranslationY();
return ObjectAnimator.ofFloat(v, TRANSLATION_Y, finalY + diff, finalY);
}
/**
* Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
*/
public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
SystemShortcut widgetInfo = new SystemShortcut.Widgets();
View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
View widgetsView = null;
for (View systemShortcutView : mSystemShortcutViews) {
if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
widgetsView = systemShortcutView;
break;
}
}
final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
? PopupPopulator.Item.SYSTEM_SHORTCUT
: PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
if (onClickListener != null && widgetsView == null) {
// We didn't have any widgets cached but now there are some, so enable the shortcut.
widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
widgetsView.setOnClickListener(onClickListener);
if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
addShortcutView(widgetsView, widgetsItem, 0);
} else {
// If using the expanded system shortcut (as opposed to just the icon), we need to
// reopen the container to ensure measurements etc. all work out. While this could
// be quite janky, in practice the user would typically see a small flicker as the
// animation restarts partway through, and this is a very rare edge case anyway.
((PopupContainerWithArrow) getParent()).close(false);
PopupContainerWithArrow.showForIcon(originalIcon);
}
} else if (onClickListener == null && widgetsView != null) {
// No widgets exist, but we previously added the shortcut so remove it.
if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
mSystemShortcutViews.remove(widgetsView);
mSystemShortcutIcons.removeView(widgetsView);
} else {
((PopupContainerWithArrow) getParent()).close(false);
PopupContainerWithArrow.showForIcon(originalIcon);
}
}
}
@Override
public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
LauncherLogProto.Target targetParent) {
target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
target.rank = info.rank;
targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
}
}