blob: 3af427139e0c4cafe173e1d989569a838d19427e [file] [log] [blame]
Winson Chung82f55532011-08-09 14:14:23 -07001/*
2 * Copyright (C) 2011 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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
Winson Chung82f55532011-08-09 14:14:23 -070018
Winson Chungaf40f202013-09-18 18:26:31 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Winson Chung3a6e7f32013-10-09 15:50:52 -070021import android.content.ComponentName;
Michael Jurka34c2e6c2013-12-13 16:07:45 +010022import android.content.Context;
Winson Chung3a6e7f32013-10-09 15:50:52 -070023import android.content.Intent;
Winson Chung82f55532011-08-09 14:14:23 -070024import android.content.res.Resources;
25import android.content.res.TypedArray;
Winson Chung3a6e7f32013-10-09 15:50:52 -070026import android.graphics.*;
Winson Chung82f55532011-08-09 14:14:23 -070027import android.graphics.drawable.Drawable;
28import android.util.AttributeSet;
29import android.util.DisplayMetrics;
Svetoslav Ganov55d225d2012-05-22 15:19:14 -070030import android.view.FocusFinder;
31import android.view.MotionEvent;
32import android.view.View;
Winson Chungaf40f202013-09-18 18:26:31 -070033import android.view.animation.AccelerateInterpolator;
Winson Chung82f55532011-08-09 14:14:23 -070034import android.widget.FrameLayout;
Winson Chung3a6e7f32013-10-09 15:50:52 -070035import android.widget.TextView;
Winson Chung82f55532011-08-09 14:14:23 -070036
Winson Chung3a6e7f32013-10-09 15:50:52 -070037public class Cling extends FrameLayout implements Insettable, View.OnClickListener,
38 View.OnLongClickListener, View.OnTouchListener {
Winson Chung82f55532011-08-09 14:14:23 -070039
Winson Chungaf40f202013-09-18 18:26:31 -070040 static final String FIRST_RUN_CLING_DISMISSED_KEY = "cling_gel.first_run.dismissed";
41 static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
42 static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed";
43
44 private static String FIRST_RUN_PORTRAIT = "first_run_portrait";
45 private static String FIRST_RUN_LANDSCAPE = "first_run_landscape";
Winson Chung7d7541e2011-09-16 20:14:36 -070046
Winson Chung82f55532011-08-09 14:14:23 -070047 private static String WORKSPACE_PORTRAIT = "workspace_portrait";
48 private static String WORKSPACE_LANDSCAPE = "workspace_landscape";
Andrew Flynn2f5f9452012-05-12 17:00:35 -070049 private static String WORKSPACE_LARGE = "workspace_large";
50 private static String WORKSPACE_CUSTOM = "workspace_custom";
51
Winson Chung7d7541e2011-09-16 20:14:36 -070052 private static String FOLDER_PORTRAIT = "folder_portrait";
53 private static String FOLDER_LANDSCAPE = "folder_landscape";
Winson Chungd0160152011-11-15 15:04:42 -080054 private static String FOLDER_LARGE = "folder_large";
Winson Chung82f55532011-08-09 14:14:23 -070055
Winson Chunge6eabff2013-09-24 11:01:23 -070056 private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60;
Winson Chungaf40f202013-09-18 18:26:31 -070057 private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50;
58 private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60;
59 private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30;
60
Winson Chung82f55532011-08-09 14:14:23 -070061 private Launcher mLauncher;
62 private boolean mIsInitialized;
63 private String mDrawIdentifier;
Winson Chung7d7541e2011-09-16 20:14:36 -070064 private Drawable mBackground;
Winson Chung82f55532011-08-09 14:14:23 -070065
Winson Chung3a6e7f32013-10-09 15:50:52 -070066 private int[] mTouchDownPt = new int[2];
67
68 private Drawable mFocusedHotseatApp;
69 private ComponentName mFocusedHotseatAppComponent;
70 private Rect mFocusedHotseatAppBounds;
71
Winson Chung7d7541e2011-09-16 20:14:36 -070072 private Paint mErasePaint;
Winson Chungaf40f202013-09-18 18:26:31 -070073 private Paint mBubblePaint;
74 private Paint mDotPaint;
75
Winson Chungfa545132013-09-26 17:45:32 -070076 private View mScrimView;
77 private int mBackgroundColor;
78
Winson Chungaf40f202013-09-18 18:26:31 -070079 private final Rect mInsets = new Rect();
Winson Chung7d7541e2011-09-16 20:14:36 -070080
Winson Chung82f55532011-08-09 14:14:23 -070081 public Cling(Context context) {
82 this(context, null, 0);
83 }
84
85 public Cling(Context context, AttributeSet attrs) {
86 this(context, attrs, 0);
87 }
88
89 public Cling(Context context, AttributeSet attrs, int defStyle) {
90 super(context, attrs, defStyle);
91
92 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Cling, defStyle, 0);
93 mDrawIdentifier = a.getString(R.styleable.Cling_drawIdentifier);
94 a.recycle();
Michael Jurka9bc8eba2012-05-21 20:36:44 -070095
96 setClickable(true);
Winson Chungaf40f202013-09-18 18:26:31 -070097
Winson Chung82f55532011-08-09 14:14:23 -070098 }
99
Winson Chungfa545132013-09-26 17:45:32 -0700100 void init(Launcher l, View scrim) {
Winson Chung82f55532011-08-09 14:14:23 -0700101 if (!mIsInitialized) {
102 mLauncher = l;
Winson Chungfa545132013-09-26 17:45:32 -0700103 mScrimView = scrim;
104 mBackgroundColor = 0xdd000000;
Winson Chungaf40f202013-09-18 18:26:31 -0700105 setOnLongClickListener(this);
Winson Chung3a6e7f32013-10-09 15:50:52 -0700106 setOnClickListener(this);
107 setOnTouchListener(this);
Winson Chung82f55532011-08-09 14:14:23 -0700108
Winson Chung7d7541e2011-09-16 20:14:36 -0700109 mErasePaint = new Paint();
110 mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
111 mErasePaint.setColor(0xFFFFFF);
112 mErasePaint.setAlpha(0);
Winson Chungaf40f202013-09-18 18:26:31 -0700113 mErasePaint.setAntiAlias(true);
114
Winson Chunge6eabff2013-09-24 11:01:23 -0700115 int circleColor = getResources().getColor(
116 R.color.first_run_cling_circle_background_color);
Winson Chungaf40f202013-09-18 18:26:31 -0700117 mBubblePaint = new Paint();
Winson Chunge6eabff2013-09-24 11:01:23 -0700118 mBubblePaint.setColor(circleColor);
Winson Chungaf40f202013-09-18 18:26:31 -0700119 mBubblePaint.setAntiAlias(true);
120
121 mDotPaint = new Paint();
122 mDotPaint.setColor(0x72BBED);
123 mDotPaint.setAntiAlias(true);
Winson Chung7d7541e2011-09-16 20:14:36 -0700124
Winson Chung82f55532011-08-09 14:14:23 -0700125 mIsInitialized = true;
126 }
127 }
128
Winson Chung3a6e7f32013-10-09 15:50:52 -0700129 void setFocusedHotseatApp(int drawableId, int appRank, ComponentName cn, String title,
130 String description) {
131 // Get the app to draw
132 Resources r = getResources();
133 int appIconId = drawableId;
134 Hotseat hotseat = mLauncher.getHotseat();
135 if (hotseat != null && appIconId > -1 && appRank > -1 && !title.isEmpty() &&
136 !description.isEmpty()) {
137 // Set the app bounds
138 int x = hotseat.getCellXFromOrder(appRank);
139 int y = hotseat.getCellYFromOrder(appRank);
140 Rect pos = hotseat.getCellCoordinates(x, y);
141 LauncherAppState app = LauncherAppState.getInstance();
142 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
143 mFocusedHotseatApp = getResources().getDrawable(appIconId);
144 mFocusedHotseatAppComponent = cn;
145 mFocusedHotseatAppBounds = new Rect(pos.left, pos.top,
146 pos.left + Utilities.sIconTextureWidth,
147 pos.top + Utilities.sIconTextureHeight);
148 Utilities.scaleRectAboutCenter(mFocusedHotseatAppBounds,
Winson Chung6e1c0d32013-10-25 15:24:24 -0700149 ((float) grid.hotseatIconSizePx / grid.iconSizePx));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700150
151 // Set the title
152 TextView v = (TextView) findViewById(R.id.focused_hotseat_app_title);
153 if (v != null) {
154 v.setText(title);
155 }
156
157 // Set the description
158 v = (TextView) findViewById(R.id.focused_hotseat_app_description);
159 if (v != null) {
160 v.setText(description);
161 }
162
163 // Show the bubble
164 View bubble = findViewById(R.id.focused_hotseat_app_bubble);
165 bubble.setVisibility(View.VISIBLE);
166 }
167 }
168
Winson Chungaf40f202013-09-18 18:26:31 -0700169 void show(boolean animate, int duration) {
170 setVisibility(View.VISIBLE);
171 setLayerType(View.LAYER_TYPE_HARDWARE, null);
172 if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
173 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
174 mDrawIdentifier.equals(WORKSPACE_LARGE) ||
175 mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
176 View content = getContent();
177 content.setAlpha(0f);
178 content.animate()
179 .alpha(1f)
180 .setDuration(duration)
Winson Chungfa545132013-09-26 17:45:32 -0700181 .setListener(null)
Winson Chungaf40f202013-09-18 18:26:31 -0700182 .start();
183 setAlpha(1f);
184 } else {
185 if (animate) {
186 buildLayer();
187 setAlpha(0f);
188 animate()
189 .alpha(1f)
190 .setInterpolator(new AccelerateInterpolator())
191 .setDuration(duration)
Winson Chungfa545132013-09-26 17:45:32 -0700192 .setListener(null)
Winson Chungaf40f202013-09-18 18:26:31 -0700193 .start();
194 } else {
195 setAlpha(1f);
196 }
197 }
Winson Chungfa545132013-09-26 17:45:32 -0700198
199 // Show the scrim if necessary
200 if (mScrimView != null) {
201 mScrimView.setVisibility(View.VISIBLE);
202 mScrimView.setAlpha(0f);
203 mScrimView.animate()
204 .alpha(1f)
205 .setDuration(duration)
206 .setListener(null)
207 .start();
208 }
209
Winson Chungaf40f202013-09-18 18:26:31 -0700210 setFocusableInTouchMode(true);
211 post(new Runnable() {
212 public void run() {
213 setFocusable(true);
214 requestFocus();
215 }
216 });
217 }
218
219 void hide(final int duration, final Runnable postCb) {
220 if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
221 mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
222 View content = getContent();
Winson Chungfa545132013-09-26 17:45:32 -0700223 content.animate()
224 .alpha(0f)
225 .setDuration(duration)
226 .setListener(new AnimatorListenerAdapter() {
227 public void onAnimationEnd(Animator animation) {
228 // We are about to trigger the workspace cling, so don't do anything else
229 setVisibility(View.GONE);
230 postCb.run();
231 };
232 })
233 .start();
Winson Chungaf40f202013-09-18 18:26:31 -0700234 } else {
Winson Chungfa545132013-09-26 17:45:32 -0700235 animate()
236 .alpha(0f)
237 .setDuration(duration)
238 .setListener(new AnimatorListenerAdapter() {
239 public void onAnimationEnd(Animator animation) {
240 // We are about to trigger the workspace cling, so don't do anything else
241 setVisibility(View.GONE);
242 postCb.run();
243 };
244 })
245 .start();
246 }
247
248 // Show the scrim if necessary
249 if (mScrimView != null) {
250 mScrimView.animate()
251 .alpha(0f)
252 .setDuration(duration)
253 .setListener(new AnimatorListenerAdapter() {
254 public void onAnimationEnd(Animator animation) {
255 mScrimView.setVisibility(View.GONE);
256 };
257 })
258 .start();
Winson Chungaf40f202013-09-18 18:26:31 -0700259 }
260 }
261
Winson Chung82f55532011-08-09 14:14:23 -0700262 void cleanup() {
Winson Chung7d7541e2011-09-16 20:14:36 -0700263 mBackground = null;
Winson Chung7d7541e2011-09-16 20:14:36 -0700264 mIsInitialized = false;
265 }
266
Winson Chungfa545132013-09-26 17:45:32 -0700267 void bringScrimToFront() {
268 if (mScrimView != null) {
269 mScrimView.bringToFront();
270 }
271 }
272
Winson Chungaf40f202013-09-18 18:26:31 -0700273 @Override
274 public void setInsets(Rect insets) {
275 mInsets.set(insets);
276 setPadding(insets.left, insets.top, insets.right, insets.bottom);
Michael Jurka974c3862012-05-22 22:00:31 -0700277 }
278
Winson Chungaf40f202013-09-18 18:26:31 -0700279 View getContent() {
280 return findViewById(R.id.content);
281 }
282
283 String getDrawIdentifier() {
284 return mDrawIdentifier;
Winson Chung82f55532011-08-09 14:14:23 -0700285 }
286
287 @Override
Svetoslav Ganov55d225d2012-05-22 15:19:14 -0700288 public View focusSearch(int direction) {
Michael Jurka9bc8eba2012-05-21 20:36:44 -0700289 return this.focusSearch(this, direction);
Svetoslav Ganov55d225d2012-05-22 15:19:14 -0700290 }
291
292 @Override
293 public View focusSearch(View focused, int direction) {
294 return FocusFinder.getInstance().findNextFocus(this, focused, direction);
295 }
296
297 @Override
298 public boolean onHoverEvent(MotionEvent event) {
299 return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT)
300 || mDrawIdentifier.equals(WORKSPACE_LANDSCAPE)
301 || mDrawIdentifier.equals(WORKSPACE_LARGE)
Svetoslav Ganov55d225d2012-05-22 15:19:14 -0700302 || mDrawIdentifier.equals(WORKSPACE_CUSTOM));
303 }
304
305 @Override
Winson Chung82f55532011-08-09 14:14:23 -0700306 public boolean onTouchEvent(android.view.MotionEvent event) {
Winson Chungaf40f202013-09-18 18:26:31 -0700307 if (mDrawIdentifier.equals(FOLDER_PORTRAIT) ||
Winson Chungd0160152011-11-15 15:04:42 -0800308 mDrawIdentifier.equals(FOLDER_LANDSCAPE) ||
309 mDrawIdentifier.equals(FOLDER_LARGE)) {
Winson Chung7d7541e2011-09-16 20:14:36 -0700310 Folder f = mLauncher.getWorkspace().getOpenFolder();
311 if (f != null) {
312 Rect r = new Rect();
313 f.getHitRect(r);
314 if (r.contains((int) event.getX(), (int) event.getY())) {
315 return false;
316 }
317 }
318 }
Winson Chungaf40f202013-09-18 18:26:31 -0700319 return super.onTouchEvent(event);
Winson Chung82f55532011-08-09 14:14:23 -0700320 };
321
322 @Override
Winson Chung3a6e7f32013-10-09 15:50:52 -0700323 public boolean onTouch(View v, MotionEvent ev) {
324 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
325 mTouchDownPt[0] = (int) ev.getX();
326 mTouchDownPt[1] = (int) ev.getY();
327 }
328 return false;
329 }
330
331 @Override
332 public void onClick(View v) {
333 if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
334 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
335 mDrawIdentifier.equals(WORKSPACE_LARGE)) {
336 if (mFocusedHotseatAppBounds != null &&
337 mFocusedHotseatAppBounds.contains(mTouchDownPt[0], mTouchDownPt[1])) {
338 // Launch the activity that is being highlighted
339 Intent intent = new Intent(Intent.ACTION_MAIN);
340 intent.setComponent(mFocusedHotseatAppComponent);
341 intent.addCategory(Intent.CATEGORY_LAUNCHER);
342 mLauncher.startActivity(intent, null);
343 mLauncher.dismissWorkspaceCling(this);
344 }
345 }
346 }
347
348 @Override
Winson Chungaf40f202013-09-18 18:26:31 -0700349 public boolean onLongClick(View v) {
350 if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
351 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
352 mDrawIdentifier.equals(WORKSPACE_LARGE)) {
353 mLauncher.dismissWorkspaceCling(null);
354 return true;
355 }
356 return false;
357 }
358
359 @Override
Winson Chung82f55532011-08-09 14:14:23 -0700360 protected void dispatchDraw(Canvas canvas) {
Winson Chung82f55532011-08-09 14:14:23 -0700361 if (mIsInitialized) {
Winson Chungaf40f202013-09-18 18:26:31 -0700362 canvas.save();
Winson Chung82f55532011-08-09 14:14:23 -0700363
Winson Chungaf40f202013-09-18 18:26:31 -0700364 // Get the background override if there is one
Winson Chung7d7541e2011-09-16 20:14:36 -0700365 if (mBackground == null) {
Winson Chungaf40f202013-09-18 18:26:31 -0700366 if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
Andrew Flynna743d352012-05-18 13:38:52 -0700367 mBackground = getResources().getDrawable(R.drawable.bg_cling5);
Winson Chung7d7541e2011-09-16 20:14:36 -0700368 }
369 }
Winson Chungaf40f202013-09-18 18:26:31 -0700370 // Draw the background
371 Bitmap eraseBg = null;
372 Canvas eraseCanvas = null;
Winson Chungfa545132013-09-26 17:45:32 -0700373 if (mScrimView != null) {
374 // Skip drawing the background
375 mScrimView.setBackgroundColor(mBackgroundColor);
376 } else if (mBackground != null) {
Winson Chung7d7541e2011-09-16 20:14:36 -0700377 mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
Winson Chungaf40f202013-09-18 18:26:31 -0700378 mBackground.draw(canvas);
Winson Chungaf40f202013-09-18 18:26:31 -0700379 } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
380 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
381 mDrawIdentifier.equals(WORKSPACE_LARGE)) {
382 // Initialize the draw buffer (to allow punching through)
383 eraseBg = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
384 Bitmap.Config.ARGB_8888);
385 eraseCanvas = new Canvas(eraseBg);
Winson Chungfa545132013-09-26 17:45:32 -0700386 eraseCanvas.drawColor(mBackgroundColor);
Winson Chung7d7541e2011-09-16 20:14:36 -0700387 } else {
Winson Chungfa545132013-09-26 17:45:32 -0700388 canvas.drawColor(mBackgroundColor);
Winson Chung7d7541e2011-09-16 20:14:36 -0700389 }
Winson Chung82f55532011-08-09 14:14:23 -0700390
Winson Chungaf40f202013-09-18 18:26:31 -0700391 // Draw everything else
392 DisplayMetrics metrics = new DisplayMetrics();
393 mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics);
394 float alpha = getAlpha();
395 View content = getContent();
396 if (content != null) {
397 alpha *= content.getAlpha();
398 }
399 if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
400 mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
Winson Chung45cab392013-10-02 17:45:32 -0700401 // Draw the circle
Winson Chungaf40f202013-09-18 18:26:31 -0700402 View bubbleContent = findViewById(R.id.bubble_content);
403 Rect bubbleRect = new Rect();
404 bubbleContent.getGlobalVisibleRect(bubbleRect);
405 mBubblePaint.setAlpha((int) (255 * alpha));
406 float buffer = DynamicGrid.pxFromDp(FIRST_RUN_CIRCLE_BUFFER_DPS, metrics);
407 canvas.drawCircle(metrics.widthPixels / 2,
408 bubbleRect.centerY(),
409 (bubbleContent.getMeasuredWidth() + buffer) / 2,
410 mBubblePaint);
411 } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
412 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
413 mDrawIdentifier.equals(WORKSPACE_LARGE)) {
414 int offset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics);
415 mErasePaint.setAlpha((int) (128));
416 eraseCanvas.drawCircle(metrics.widthPixels / 2,
417 metrics.heightPixels / 2 - offset,
418 DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics),
419 mErasePaint);
420 mErasePaint.setAlpha(0);
421 eraseCanvas.drawCircle(metrics.widthPixels / 2,
422 metrics.heightPixels / 2 - offset,
423 DynamicGrid.pxFromDp(WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics),
424 mErasePaint);
425 canvas.drawBitmap(eraseBg, 0, 0, null);
426 eraseCanvas.setBitmap(null);
427 eraseBg = null;
Winson Chung7d7541e2011-09-16 20:14:36 -0700428
Winson Chung3a6e7f32013-10-09 15:50:52 -0700429 // Draw the focused hotseat app icon
430 if (mFocusedHotseatAppBounds != null && mFocusedHotseatApp != null) {
431 mFocusedHotseatApp.setBounds(mFocusedHotseatAppBounds.left,
432 mFocusedHotseatAppBounds.top, mFocusedHotseatAppBounds.right,
433 mFocusedHotseatAppBounds.bottom);
434 mFocusedHotseatApp.setAlpha((int) (255 * alpha));
435 mFocusedHotseatApp.draw(canvas);
436 }
437 }
Winson Chung7d7541e2011-09-16 20:14:36 -0700438
Winson Chungaf40f202013-09-18 18:26:31 -0700439 canvas.restore();
Winson Chung82f55532011-08-09 14:14:23 -0700440 }
Winson Chung7d7541e2011-09-16 20:14:36 -0700441
442 // Draw the rest of the cling
443 super.dispatchDraw(canvas);
Winson Chung82f55532011-08-09 14:14:23 -0700444 };
445}