Home | History | Annotate | Download | only in views
      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.systemui.recents.views;
     18 
     19 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
     20 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     21 import static android.view.WindowManager.DOCKED_BOTTOM;
     22 import static android.view.WindowManager.DOCKED_INVALID;
     23 import static android.view.WindowManager.DOCKED_LEFT;
     24 import static android.view.WindowManager.DOCKED_RIGHT;
     25 import static android.view.WindowManager.DOCKED_TOP;
     26 
     27 import android.animation.Animator;
     28 import android.animation.AnimatorSet;
     29 import android.animation.ObjectAnimator;
     30 import android.animation.PropertyValuesHolder;
     31 import android.annotation.IntDef;
     32 import android.content.Context;
     33 import android.content.res.Configuration;
     34 import android.content.res.Resources;
     35 import android.graphics.Canvas;
     36 import android.graphics.Color;
     37 import android.graphics.Paint;
     38 import android.graphics.Point;
     39 import android.graphics.Rect;
     40 import android.graphics.RectF;
     41 import android.graphics.drawable.ColorDrawable;
     42 import android.util.IntProperty;
     43 import android.view.animation.Interpolator;
     44 
     45 import com.android.internal.policy.DockedDividerUtils;
     46 import com.android.systemui.Interpolators;
     47 import com.android.systemui.R;
     48 import com.android.systemui.recents.Recents;
     49 import com.android.systemui.shared.recents.utilities.Utilities;
     50 
     51 import java.lang.annotation.Retention;
     52 import java.lang.annotation.RetentionPolicy;
     53 import java.util.ArrayList;
     54 
     55 /**
     56  * The various possible dock states when dragging and dropping a task.
     57  */
     58 public class DockState implements DropTarget {
     59 
     60     public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
     61     public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
     62 
     63     // The rotation to apply to the hint text
     64     @Retention(RetentionPolicy.SOURCE)
     65     @IntDef({HORIZONTAL, VERTICAL})
     66     public @interface TextOrientation {}
     67     private static final int HORIZONTAL = 0;
     68     private static final int VERTICAL = 1;
     69 
     70     private static final int DOCK_AREA_ALPHA = 80;
     71     public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
     72             null, null, null);
     73     public static final DockState LEFT = new DockState(DOCKED_LEFT,
     74             SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
     75             new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
     76             new RectF(0, 0, 0.5f, 1));
     77     public static final DockState TOP = new DockState(DOCKED_TOP,
     78             SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
     79             new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
     80             new RectF(0, 0, 1, 0.5f));
     81     public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
     82             SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
     83             new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
     84             new RectF(0.5f, 0, 1, 1));
     85     public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
     86             SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
     87             new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
     88             new RectF(0, 0.5f, 1, 1));
     89 
     90     @Override
     91     public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
     92             boolean isCurrentTarget) {
     93         if (isCurrentTarget) {
     94             getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
     95             return mTmpRect.contains(x, y);
     96         } else {
     97             getMappedRect(touchArea, width, height, mTmpRect);
     98             updateBoundsWithSystemInsets(mTmpRect, insets);
     99             return mTmpRect.contains(x, y);
    100         }
    101     }
    102 
    103     // Represents the view state of this dock state
    104     public static class ViewState {
    105         private static final IntProperty<ViewState> HINT_ALPHA =
    106                 new IntProperty<ViewState>("drawableAlpha") {
    107                     @Override
    108                     public void setValue(ViewState object, int alpha) {
    109                         object.mHintTextAlpha = alpha;
    110                         object.dockAreaOverlay.invalidateSelf();
    111                     }
    112 
    113                     @Override
    114                     public Integer get(ViewState object) {
    115                         return object.mHintTextAlpha;
    116                     }
    117                 };
    118 
    119         public final int dockAreaAlpha;
    120         public final ColorDrawable dockAreaOverlay;
    121         public final int hintTextAlpha;
    122         public final int hintTextOrientation;
    123 
    124         private final int mHintTextResId;
    125         private String mHintText;
    126         private Paint mHintTextPaint;
    127         private Point mHintTextBounds = new Point();
    128         private int mHintTextAlpha = 255;
    129         private AnimatorSet mDockAreaOverlayAnimator;
    130         private Rect mTmpRect = new Rect();
    131 
    132         private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
    133                 int hintTextResId) {
    134             dockAreaAlpha = areaAlpha;
    135             dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
    136                     ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
    137             dockAreaOverlay.setAlpha(0);
    138             hintTextAlpha = hintAlpha;
    139             hintTextOrientation = hintOrientation;
    140             mHintTextResId = hintTextResId;
    141             mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    142             mHintTextPaint.setColor(Color.WHITE);
    143         }
    144 
    145         /**
    146          * Updates the view state with the given context.
    147          */
    148         public void update(Context context) {
    149             Resources res = context.getResources();
    150             mHintText = context.getString(mHintTextResId);
    151             mHintTextPaint.setTextSize(res.getDimensionPixelSize(
    152                     R.dimen.recents_drag_hint_text_size));
    153             mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
    154             mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
    155         }
    156 
    157         /**
    158          * Draws the current view state.
    159          */
    160         public void draw(Canvas canvas) {
    161             // Draw the overlay background
    162             if (dockAreaOverlay.getAlpha() > 0) {
    163                 dockAreaOverlay.draw(canvas);
    164             }
    165 
    166             // Draw the hint text
    167             if (mHintTextAlpha > 0) {
    168                 Rect bounds = dockAreaOverlay.getBounds();
    169                 int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
    170                 int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
    171                 mHintTextPaint.setAlpha(mHintTextAlpha);
    172                 if (hintTextOrientation == VERTICAL) {
    173                     canvas.save();
    174                     canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
    175                 }
    176                 canvas.drawText(mHintText, x, y, mHintTextPaint);
    177                 if (hintTextOrientation == VERTICAL) {
    178                     canvas.restore();
    179                 }
    180             }
    181         }
    182 
    183         /**
    184          * Creates a new bounds and alpha animation.
    185          */
    186         public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
    187                 Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
    188             if (mDockAreaOverlayAnimator != null) {
    189                 mDockAreaOverlayAnimator.cancel();
    190             }
    191 
    192             ObjectAnimator anim;
    193             ArrayList<Animator> animators = new ArrayList<>();
    194             if (dockAreaOverlay.getAlpha() != areaAlpha) {
    195                 if (animateAlpha) {
    196                     anim = ObjectAnimator.ofInt(dockAreaOverlay,
    197                             Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
    198                     anim.setDuration(duration);
    199                     anim.setInterpolator(interpolator);
    200                     animators.add(anim);
    201                 } else {
    202                     dockAreaOverlay.setAlpha(areaAlpha);
    203                 }
    204             }
    205             if (mHintTextAlpha != hintAlpha) {
    206                 if (animateAlpha) {
    207                     anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
    208                             hintAlpha);
    209                     anim.setDuration(150);
    210                     anim.setInterpolator(hintAlpha > mHintTextAlpha
    211                             ? Interpolators.ALPHA_IN
    212                             : Interpolators.ALPHA_OUT);
    213                     animators.add(anim);
    214                 } else {
    215                     mHintTextAlpha = hintAlpha;
    216                     dockAreaOverlay.invalidateSelf();
    217                 }
    218             }
    219             if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
    220                 if (animateBounds) {
    221                     PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
    222                             Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
    223                             new Rect(dockAreaOverlay.getBounds()), bounds);
    224                     anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
    225                     anim.setDuration(duration);
    226                     anim.setInterpolator(interpolator);
    227                     animators.add(anim);
    228                 } else {
    229                     dockAreaOverlay.setBounds(bounds);
    230                 }
    231             }
    232             if (!animators.isEmpty()) {
    233                 mDockAreaOverlayAnimator = new AnimatorSet();
    234                 mDockAreaOverlayAnimator.playTogether(animators);
    235                 mDockAreaOverlayAnimator.start();
    236             }
    237         }
    238     }
    239 
    240     public final int dockSide;
    241     public final int createMode;
    242     public final ViewState viewState;
    243     private final RectF touchArea;
    244     private final RectF dockArea;
    245     private final RectF expandedTouchDockArea;
    246     private static final Rect mTmpRect = new Rect();
    247 
    248     /**
    249      * @param createMode used to pass to ActivityManager to dock the task
    250      * @param touchArea the area in which touch will initiate this dock state
    251      * @param dockArea the visible dock area
    252      * @param expandedTouchDockArea the area in which touch will continue to dock after entering
    253      *                              the initial touch area.  This is also the new dock area to
    254      *                              draw.
    255      */
    256     DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
    257             @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
    258             RectF expandedTouchDockArea) {
    259         this.dockSide = dockSide;
    260         this.createMode = createMode;
    261         this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
    262                 R.string.recents_drag_hint_message);
    263         this.dockArea = dockArea;
    264         this.touchArea = touchArea;
    265         this.expandedTouchDockArea = expandedTouchDockArea;
    266     }
    267 
    268     /**
    269      * Updates the dock state with the given context.
    270      */
    271     public void update(Context context) {
    272         viewState.update(context);
    273     }
    274 
    275     /**
    276      * Returns the docked task bounds with the given {@param width} and {@param height}.
    277      */
    278     public Rect getPreDockedBounds(int width, int height, Rect insets) {
    279         getMappedRect(dockArea, width, height, mTmpRect);
    280         return updateBoundsWithSystemInsets(mTmpRect, insets);
    281     }
    282 
    283     /**
    284      * Returns the expanded docked task bounds with the given {@param width} and
    285      * {@param height}.
    286      */
    287     public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
    288             Resources res) {
    289         // Calculate the docked task bounds
    290         boolean isHorizontalDivision =
    291                 res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
    292         int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
    293                 insets, width, height, dividerSize);
    294         Rect newWindowBounds = new Rect();
    295         DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
    296                 width, height, dividerSize);
    297         return newWindowBounds;
    298     }
    299 
    300     /**
    301      * Returns the task stack bounds with the given {@param width} and
    302      * {@param height}.
    303      */
    304     public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
    305             int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
    306             Resources res, Rect windowRectOut) {
    307         // Calculate the inverse docked task bounds
    308         boolean isHorizontalDivision =
    309                 res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
    310         int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
    311                 insets, width, height, dividerSize);
    312         DockedDividerUtils.calculateBoundsForPosition(position,
    313                 DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
    314                 dividerSize);
    315 
    316         // Calculate the task stack bounds from the new window bounds
    317         Rect taskStackBounds = new Rect();
    318         // If the task stack bounds is specifically under the dock area, then ignore the top
    319         // inset
    320         int top = dockArea.bottom < 1f
    321                 ? 0
    322                 : insets.top;
    323         // For now, ignore the left insets since we always dock on the left and show Recents
    324         // on the right
    325         layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
    326                 taskStackBounds);
    327         return taskStackBounds;
    328     }
    329 
    330     /**
    331      * Returns the expanded bounds in certain dock sides such that the bounds account for the
    332      * system insets (namely the vertical nav bar).  This call modifies and returns the given
    333      * {@param bounds}.
    334      */
    335     private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
    336         if (dockSide == DOCKED_LEFT) {
    337             bounds.right += insets.left;
    338         } else if (dockSide == DOCKED_RIGHT) {
    339             bounds.left -= insets.right;
    340         }
    341         return bounds;
    342     }
    343 
    344     /**
    345      * Returns the mapped rect to the given dimensions.
    346      */
    347     private void getMappedRect(RectF bounds, int width, int height, Rect out) {
    348         out.set((int) (bounds.left * width), (int) (bounds.top * height),
    349                 (int) (bounds.right * width), (int) (bounds.bottom * height));
    350     }
    351 }