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 }