Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3.widget;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.content.Context;
     23 import android.graphics.Rect;
     24 import android.support.v4.view.animation.FastOutSlowInInterpolator;
     25 import android.util.AttributeSet;
     26 import android.view.ContextThemeWrapper;
     27 import android.view.Gravity;
     28 import android.view.LayoutInflater;
     29 import android.view.MotionEvent;
     30 import android.view.View;
     31 import android.view.ViewGroup;
     32 import android.view.animation.Interpolator;
     33 import android.widget.TextView;
     34 
     35 import com.android.launcher3.AbstractFloatingView;
     36 import com.android.launcher3.DropTarget;
     37 import com.android.launcher3.Insettable;
     38 import com.android.launcher3.ItemInfo;
     39 import com.android.launcher3.Launcher;
     40 import com.android.launcher3.LauncherAnimUtils;
     41 import com.android.launcher3.LauncherAppState;
     42 import com.android.launcher3.R;
     43 import com.android.launcher3.Utilities;
     44 import com.android.launcher3.allapps.VerticalPullDetector;
     45 import com.android.launcher3.anim.PropertyListBuilder;
     46 import com.android.launcher3.dragndrop.DragController;
     47 import com.android.launcher3.dragndrop.DragOptions;
     48 import com.android.launcher3.model.WidgetItem;
     49 import com.android.launcher3.userevent.nano.LauncherLogProto;
     50 import com.android.launcher3.util.PackageUserKey;
     51 import com.android.launcher3.util.TouchController;
     52 
     53 import java.util.List;
     54 
     55 /**
     56  * Bottom sheet for the "Widgets" system shortcut in the long-press popup.
     57  */
     58 public class WidgetsBottomSheet extends AbstractFloatingView implements Insettable, TouchController,
     59         VerticalPullDetector.Listener, View.OnClickListener, View.OnLongClickListener,
     60         DragController.DragListener {
     61 
     62     private int mTranslationYOpen;
     63     private int mTranslationYClosed;
     64     private float mTranslationYRange;
     65 
     66     private Launcher mLauncher;
     67     private ItemInfo mOriginalItemInfo;
     68     private ObjectAnimator mOpenCloseAnimator;
     69     private Interpolator mFastOutSlowInInterpolator;
     70     private VerticalPullDetector.ScrollInterpolator mScrollInterpolator;
     71     private Rect mInsets;
     72     private boolean mWasNavBarLight;
     73     private VerticalPullDetector mVerticalPullDetector;
     74 
     75     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
     76         this(context, attrs, 0);
     77     }
     78 
     79     public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
     80         super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme), attrs, defStyleAttr);
     81         setWillNotDraw(false);
     82         mLauncher = Launcher.getLauncher(context);
     83         mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
     84         mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
     85         mScrollInterpolator = new VerticalPullDetector.ScrollInterpolator();
     86         mInsets = new Rect();
     87         mVerticalPullDetector = new VerticalPullDetector(context);
     88         mVerticalPullDetector.setListener(this);
     89     }
     90 
     91     @Override
     92     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     93         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     94         mTranslationYOpen = 0;
     95         mTranslationYClosed = getMeasuredHeight();
     96         mTranslationYRange = mTranslationYClosed - mTranslationYOpen;
     97     }
     98 
     99     public void populateAndShow(ItemInfo itemInfo) {
    100         mOriginalItemInfo = itemInfo;
    101         ((TextView) findViewById(R.id.title)).setText(getContext().getString(
    102                 R.string.widgets_bottom_sheet_title, mOriginalItemInfo.title));
    103 
    104         onWidgetsBound();
    105 
    106         mWasNavBarLight = (mLauncher.getWindow().getDecorView().getSystemUiVisibility()
    107                 & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
    108         mLauncher.getDragLayer().addView(this);
    109         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    110         setTranslationY(mTranslationYClosed);
    111         mIsOpen = false;
    112         open(true);
    113     }
    114 
    115     @Override
    116     protected void onWidgetsBound() {
    117         List<WidgetItem> widgets = mLauncher.getWidgetsForPackageUser(new PackageUserKey(
    118                 mOriginalItemInfo.getTargetComponent().getPackageName(), mOriginalItemInfo.user));
    119 
    120         ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets);
    121         ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list);
    122 
    123         widgetCells.removeAllViews();
    124 
    125         for (int i = 0; i < widgets.size(); i++) {
    126             WidgetCell widget = addItemCell(widgetCells);
    127             widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher)
    128                     .getWidgetCache());
    129             widget.ensurePreview();
    130             widget.setVisibility(View.VISIBLE);
    131             if (i < widgets.size() - 1) {
    132                 addDivider(widgetCells);
    133             }
    134         }
    135 
    136         if (widgets.size() == 1) {
    137             // If there is only one widget, we want to center it instead of left-align.
    138             WidgetsBottomSheet.LayoutParams params = (WidgetsBottomSheet.LayoutParams)
    139                     widgetRow.getLayoutParams();
    140             params.gravity = Gravity.CENTER_HORIZONTAL;
    141         } else {
    142             // Otherwise, add an empty view to the start as padding (but still scroll edge to edge).
    143             View leftPaddingView = LayoutInflater.from(getContext()).inflate(
    144                     R.layout.widget_list_divider, widgetRow, false);
    145             leftPaddingView.getLayoutParams().width = Utilities.pxFromDp(
    146                     16, getResources().getDisplayMetrics());
    147             widgetCells.addView(leftPaddingView, 0);
    148         }
    149     }
    150 
    151     private void addDivider(ViewGroup parent) {
    152         LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
    153     }
    154 
    155     private WidgetCell addItemCell(ViewGroup parent) {
    156         WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
    157                 R.layout.widget_cell, parent, false);
    158 
    159         widget.setOnClickListener(this);
    160         widget.setOnLongClickListener(this);
    161         widget.setAnimatePreview(false);
    162 
    163         parent.addView(widget);
    164         return widget;
    165     }
    166 
    167     @Override
    168     public void onClick(View view) {
    169         mLauncher.getWidgetsView().handleClick();
    170     }
    171 
    172     @Override
    173     public boolean onLongClick(View view) {
    174         mLauncher.getDragController().addDragListener(this);
    175         return mLauncher.getWidgetsView().handleLongClick(view);
    176     }
    177 
    178     private void open(boolean animate) {
    179         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
    180             return;
    181         }
    182         mIsOpen = true;
    183         setLightNavBar(true);
    184         if (animate) {
    185             mOpenCloseAnimator.setValues(new PropertyListBuilder()
    186                     .translationY(mTranslationYOpen).build());
    187             mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
    188                 @Override
    189                 public void onAnimationEnd(Animator animation) {
    190                     mVerticalPullDetector.finishedScrolling();
    191                 }
    192             });
    193             mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
    194             mOpenCloseAnimator.start();
    195         } else {
    196             setTranslationY(mTranslationYOpen);
    197         }
    198     }
    199 
    200     @Override
    201     protected void handleClose(boolean animate) {
    202         if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
    203             return;
    204         }
    205         if (animate) {
    206             mOpenCloseAnimator.setValues(new PropertyListBuilder()
    207                     .translationY(mTranslationYClosed).build());
    208             mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
    209                 @Override
    210                 public void onAnimationEnd(Animator animation) {
    211                     mIsOpen = false;
    212                     mVerticalPullDetector.finishedScrolling();
    213                     ((ViewGroup) getParent()).removeView(WidgetsBottomSheet.this);
    214                     setLightNavBar(mWasNavBarLight);
    215                 }
    216             });
    217             mOpenCloseAnimator.setInterpolator(mVerticalPullDetector.isIdleState()
    218                     ? mFastOutSlowInInterpolator : mScrollInterpolator);
    219             mOpenCloseAnimator.start();
    220         } else {
    221             setTranslationY(mTranslationYClosed);
    222             setLightNavBar(mWasNavBarLight);
    223             mIsOpen = false;
    224         }
    225     }
    226 
    227     private void setLightNavBar(boolean lightNavBar) {
    228         mLauncher.activateLightSystemBars(lightNavBar, false /* statusBar */, true /* navBar */);
    229     }
    230 
    231     @Override
    232     protected boolean isOfType(@FloatingViewType int type) {
    233         return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0;
    234     }
    235 
    236     @Override
    237     public int getLogContainerType() {
    238         return LauncherLogProto.ContainerType.WIDGETS; // TODO: be more specific
    239     }
    240 
    241     /**
    242      * Returns a {@link WidgetsBottomSheet} which is already open or null
    243      */
    244     public static WidgetsBottomSheet getOpen(Launcher launcher) {
    245         return getOpenView(launcher, TYPE_WIDGETS_BOTTOM_SHEET);
    246     }
    247 
    248     @Override
    249     public void setInsets(Rect insets) {
    250         // Extend behind left, right, and bottom insets.
    251         int leftInset = insets.left - mInsets.left;
    252         int rightInset = insets.right - mInsets.right;
    253         int bottomInset = insets.bottom - mInsets.bottom;
    254         mInsets.set(insets);
    255         setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
    256                 getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
    257     }
    258 
    259     /* VerticalPullDetector.Listener */
    260 
    261     @Override
    262     public void onDragStart(boolean start) {
    263     }
    264 
    265     @Override
    266     public boolean onDrag(float displacement, float velocity) {
    267         setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen,
    268                 mTranslationYClosed));
    269         return true;
    270     }
    271 
    272     @Override
    273     public void onDragEnd(float velocity, boolean fling) {
    274         if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
    275             mScrollInterpolator.setVelocityAtZero(velocity);
    276             mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
    277                     (mTranslationYClosed - getTranslationY()) / mTranslationYRange));
    278             close(true);
    279         } else {
    280             mIsOpen = false;
    281             mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
    282                     (getTranslationY() - mTranslationYOpen) / mTranslationYRange));
    283             open(true);
    284         }
    285     }
    286 
    287     @Override
    288     public boolean onControllerTouchEvent(MotionEvent ev) {
    289         return mVerticalPullDetector.onTouchEvent(ev);
    290     }
    291 
    292     @Override
    293     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
    294         int directionsToDetectScroll = mVerticalPullDetector.isIdleState() ?
    295                 VerticalPullDetector.DIRECTION_DOWN : 0;
    296         mVerticalPullDetector.setDetectableScrollConditions(
    297                 directionsToDetectScroll, false);
    298         mVerticalPullDetector.onTouchEvent(ev);
    299         return mVerticalPullDetector.isDraggingOrSettling();
    300     }
    301 
    302     /* DragListener */
    303 
    304     @Override
    305     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
    306         // A widget or custom shortcut was dragged.
    307         close(true);
    308     }
    309 
    310     @Override
    311     public void onDragEnd() {
    312     }
    313 }
    314