Home | History | Annotate | Download | only in launcher3
      1 /*
      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 
     17 package com.android.launcher3;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.app.ActivityOptions;
     22 import android.content.Context;
     23 import android.content.ComponentName;
     24 import android.content.Intent;
     25 import android.content.res.Resources;
     26 import android.content.res.TypedArray;
     27 import android.graphics.*;
     28 import android.graphics.drawable.Drawable;
     29 import android.util.AttributeSet;
     30 import android.util.DisplayMetrics;
     31 import android.view.FocusFinder;
     32 import android.view.MotionEvent;
     33 import android.view.View;
     34 import android.view.animation.AccelerateInterpolator;
     35 import android.widget.FrameLayout;
     36 import android.widget.TextView;
     37 
     38 public class Cling extends FrameLayout implements Insettable, View.OnClickListener,
     39         View.OnLongClickListener, View.OnTouchListener {
     40 
     41     static final String FIRST_RUN_CLING_DISMISSED_KEY = "cling_gel.first_run.dismissed";
     42     static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
     43     static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed";
     44 
     45     private static String FIRST_RUN_PORTRAIT = "first_run_portrait";
     46     private static String FIRST_RUN_LANDSCAPE = "first_run_landscape";
     47 
     48     private static String WORKSPACE_PORTRAIT = "workspace_portrait";
     49     private static String WORKSPACE_LANDSCAPE = "workspace_landscape";
     50     private static String WORKSPACE_LARGE = "workspace_large";
     51     private static String WORKSPACE_CUSTOM = "workspace_custom";
     52 
     53     private static String FOLDER_PORTRAIT = "folder_portrait";
     54     private static String FOLDER_LANDSCAPE = "folder_landscape";
     55     private static String FOLDER_LARGE = "folder_large";
     56 
     57     private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60;
     58     private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50;
     59     private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60;
     60     private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30;
     61 
     62     private Launcher mLauncher;
     63     private boolean mIsInitialized;
     64     private String mDrawIdentifier;
     65     private Drawable mBackground;
     66 
     67     private int[] mTouchDownPt = new int[2];
     68 
     69     private Drawable mFocusedHotseatApp;
     70     private ComponentName mFocusedHotseatAppComponent;
     71     private Rect mFocusedHotseatAppBounds;
     72 
     73     private Paint mErasePaint;
     74     private Paint mBubblePaint;
     75     private Paint mDotPaint;
     76 
     77     private View mScrimView;
     78     private int mBackgroundColor;
     79 
     80     private final Rect mInsets = new Rect();
     81 
     82     public Cling(Context context) {
     83         this(context, null, 0);
     84     }
     85 
     86     public Cling(Context context, AttributeSet attrs) {
     87         this(context, attrs, 0);
     88     }
     89 
     90     public Cling(Context context, AttributeSet attrs, int defStyle) {
     91         super(context, attrs, defStyle);
     92 
     93         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Cling, defStyle, 0);
     94         mDrawIdentifier = a.getString(R.styleable.Cling_drawIdentifier);
     95         a.recycle();
     96 
     97         setClickable(true);
     98 
     99     }
    100 
    101     void init(Launcher l, View scrim) {
    102         if (!mIsInitialized) {
    103             mLauncher = l;
    104             mScrimView = scrim;
    105             mBackgroundColor = 0xdd000000;
    106             setOnLongClickListener(this);
    107             setOnClickListener(this);
    108             setOnTouchListener(this);
    109 
    110             mErasePaint = new Paint();
    111             mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
    112             mErasePaint.setColor(0xFFFFFF);
    113             mErasePaint.setAlpha(0);
    114             mErasePaint.setAntiAlias(true);
    115 
    116             int circleColor = getResources().getColor(
    117                     R.color.first_run_cling_circle_background_color);
    118             mBubblePaint = new Paint();
    119             mBubblePaint.setColor(circleColor);
    120             mBubblePaint.setAntiAlias(true);
    121 
    122             mDotPaint = new Paint();
    123             mDotPaint.setColor(0x72BBED);
    124             mDotPaint.setAntiAlias(true);
    125 
    126             mIsInitialized = true;
    127         }
    128     }
    129 
    130     void setFocusedHotseatApp(int drawableId, int appRank, ComponentName cn, String title,
    131                               String description) {
    132         // Get the app to draw
    133         Resources r = getResources();
    134         int appIconId = drawableId;
    135         Hotseat hotseat = mLauncher.getHotseat();
    136         if (hotseat != null && appIconId > -1 && appRank > -1 && !title.isEmpty() &&
    137                 !description.isEmpty()) {
    138             // Set the app bounds
    139             int x = hotseat.getCellXFromOrder(appRank);
    140             int y = hotseat.getCellYFromOrder(appRank);
    141             Rect pos = hotseat.getCellCoordinates(x, y);
    142             LauncherAppState app = LauncherAppState.getInstance();
    143             DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
    144             mFocusedHotseatApp = getResources().getDrawable(appIconId);
    145             mFocusedHotseatAppComponent = cn;
    146             mFocusedHotseatAppBounds = new Rect(pos.left, pos.top,
    147                     pos.left + Utilities.sIconTextureWidth,
    148                     pos.top + Utilities.sIconTextureHeight);
    149             Utilities.scaleRectAboutCenter(mFocusedHotseatAppBounds,
    150                     (grid.hotseatIconSize / grid.iconSize));
    151 
    152             // Set the title
    153             TextView v = (TextView) findViewById(R.id.focused_hotseat_app_title);
    154             if (v != null) {
    155                 v.setText(title);
    156             }
    157 
    158             // Set the description
    159             v = (TextView) findViewById(R.id.focused_hotseat_app_description);
    160             if (v != null) {
    161                 v.setText(description);
    162             }
    163 
    164             // Show the bubble
    165             View bubble = findViewById(R.id.focused_hotseat_app_bubble);
    166             bubble.setVisibility(View.VISIBLE);
    167         }
    168     }
    169 
    170     void show(boolean animate, int duration) {
    171         setVisibility(View.VISIBLE);
    172         setLayerType(View.LAYER_TYPE_HARDWARE, null);
    173         if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
    174                 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
    175                 mDrawIdentifier.equals(WORKSPACE_LARGE) ||
    176                 mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
    177             View content = getContent();
    178             content.setAlpha(0f);
    179             content.animate()
    180                     .alpha(1f)
    181                     .setDuration(duration)
    182                     .setListener(null)
    183                     .start();
    184             setAlpha(1f);
    185         } else {
    186             if (animate) {
    187                 buildLayer();
    188                 setAlpha(0f);
    189                 animate()
    190                     .alpha(1f)
    191                     .setInterpolator(new AccelerateInterpolator())
    192                     .setDuration(duration)
    193                     .setListener(null)
    194                     .start();
    195             } else {
    196                 setAlpha(1f);
    197             }
    198         }
    199 
    200         // Show the scrim if necessary
    201         if (mScrimView != null) {
    202             mScrimView.setVisibility(View.VISIBLE);
    203             mScrimView.setAlpha(0f);
    204             mScrimView.animate()
    205                     .alpha(1f)
    206                     .setDuration(duration)
    207                     .setListener(null)
    208                     .start();
    209         }
    210 
    211         setFocusableInTouchMode(true);
    212         post(new Runnable() {
    213             public void run() {
    214                 setFocusable(true);
    215                 requestFocus();
    216             }
    217         });
    218     }
    219 
    220     void hide(final int duration, final Runnable postCb) {
    221         if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
    222                 mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
    223             View content = getContent();
    224             content.animate()
    225                 .alpha(0f)
    226                 .setDuration(duration)
    227                 .setListener(new AnimatorListenerAdapter() {
    228                     public void onAnimationEnd(Animator animation) {
    229                         // We are about to trigger the workspace cling, so don't do anything else
    230                         setVisibility(View.GONE);
    231                         postCb.run();
    232                     };
    233                 })
    234                 .start();
    235         } else {
    236             animate()
    237                 .alpha(0f)
    238                 .setDuration(duration)
    239                 .setListener(new AnimatorListenerAdapter() {
    240                     public void onAnimationEnd(Animator animation) {
    241                         // We are about to trigger the workspace cling, so don't do anything else
    242                         setVisibility(View.GONE);
    243                         postCb.run();
    244                     };
    245                 })
    246                 .start();
    247         }
    248 
    249         // Show the scrim if necessary
    250         if (mScrimView != null) {
    251             mScrimView.animate()
    252                 .alpha(0f)
    253                 .setDuration(duration)
    254                 .setListener(new AnimatorListenerAdapter() {
    255                     public void onAnimationEnd(Animator animation) {
    256                         mScrimView.setVisibility(View.GONE);
    257                     };
    258                 })
    259                 .start();
    260         }
    261     }
    262 
    263     void cleanup() {
    264         mBackground = null;
    265         mIsInitialized = false;
    266     }
    267 
    268     void bringScrimToFront() {
    269         if (mScrimView != null) {
    270             mScrimView.bringToFront();
    271         }
    272     }
    273 
    274     @Override
    275     public void setInsets(Rect insets) {
    276         mInsets.set(insets);
    277         setPadding(insets.left, insets.top, insets.right, insets.bottom);
    278     }
    279 
    280     View getContent() {
    281         return findViewById(R.id.content);
    282     }
    283 
    284     String getDrawIdentifier() {
    285         return mDrawIdentifier;
    286     }
    287 
    288     @Override
    289     public View focusSearch(int direction) {
    290         return this.focusSearch(this, direction);
    291     }
    292 
    293     @Override
    294     public View focusSearch(View focused, int direction) {
    295         return FocusFinder.getInstance().findNextFocus(this, focused, direction);
    296     }
    297 
    298     @Override
    299     public boolean onHoverEvent(MotionEvent event) {
    300         return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT)
    301                 || mDrawIdentifier.equals(WORKSPACE_LANDSCAPE)
    302                 || mDrawIdentifier.equals(WORKSPACE_LARGE)
    303                 || mDrawIdentifier.equals(WORKSPACE_CUSTOM));
    304     }
    305 
    306     @Override
    307     public boolean onTouchEvent(android.view.MotionEvent event) {
    308         if (mDrawIdentifier.equals(FOLDER_PORTRAIT) ||
    309                    mDrawIdentifier.equals(FOLDER_LANDSCAPE) ||
    310                    mDrawIdentifier.equals(FOLDER_LARGE)) {
    311             Folder f = mLauncher.getWorkspace().getOpenFolder();
    312             if (f != null) {
    313                 Rect r = new Rect();
    314                 f.getHitRect(r);
    315                 if (r.contains((int) event.getX(), (int) event.getY())) {
    316                     return false;
    317                 }
    318             }
    319         }
    320         return super.onTouchEvent(event);
    321     };
    322 
    323     @Override
    324     public boolean onTouch(View v, MotionEvent ev) {
    325         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    326             mTouchDownPt[0] = (int) ev.getX();
    327             mTouchDownPt[1] = (int) ev.getY();
    328         }
    329         return false;
    330     }
    331 
    332     @Override
    333     public void onClick(View v) {
    334         if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
    335                 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
    336                 mDrawIdentifier.equals(WORKSPACE_LARGE)) {
    337             if (mFocusedHotseatAppBounds != null &&
    338                 mFocusedHotseatAppBounds.contains(mTouchDownPt[0], mTouchDownPt[1])) {
    339                 // Launch the activity that is being highlighted
    340                 Intent intent = new Intent(Intent.ACTION_MAIN);
    341                 intent.setComponent(mFocusedHotseatAppComponent);
    342                 intent.addCategory(Intent.CATEGORY_LAUNCHER);
    343                 mLauncher.startActivity(intent, null);
    344                 mLauncher.dismissWorkspaceCling(this);
    345             }
    346         }
    347     }
    348 
    349     @Override
    350     public boolean onLongClick(View v) {
    351         if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
    352                 mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
    353                 mDrawIdentifier.equals(WORKSPACE_LARGE)) {
    354             mLauncher.dismissWorkspaceCling(null);
    355             return true;
    356         }
    357         return false;
    358     }
    359 
    360     @Override
    361     protected void dispatchDraw(Canvas canvas) {
    362         if (mIsInitialized) {
    363             canvas.save();
    364 
    365             // Get the background override if there is one
    366             if (mBackground == null) {
    367                 if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
    368                     mBackground = getResources().getDrawable(R.drawable.bg_cling5);
    369                 }
    370             }
    371             // Draw the background
    372             Bitmap eraseBg = null;
    373             Canvas eraseCanvas = null;
    374             if (mScrimView != null) {
    375                 // Skip drawing the background
    376                 mScrimView.setBackgroundColor(mBackgroundColor);
    377             } else if (mBackground != null) {
    378                 mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
    379                 mBackground.draw(canvas);
    380             } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
    381                     mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
    382                     mDrawIdentifier.equals(WORKSPACE_LARGE)) {
    383                 // Initialize the draw buffer (to allow punching through)
    384                 eraseBg = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
    385                         Bitmap.Config.ARGB_8888);
    386                 eraseCanvas = new Canvas(eraseBg);
    387                 eraseCanvas.drawColor(mBackgroundColor);
    388             } else {
    389                 canvas.drawColor(mBackgroundColor);
    390             }
    391 
    392             // Draw everything else
    393             DisplayMetrics metrics = new DisplayMetrics();
    394             mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics);
    395             float alpha = getAlpha();
    396             View content = getContent();
    397             if (content != null) {
    398                 alpha *= content.getAlpha();
    399             }
    400             if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
    401                     mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
    402                 // Draw the circle
    403                 View bubbleContent = findViewById(R.id.bubble_content);
    404                 Rect bubbleRect = new Rect();
    405                 bubbleContent.getGlobalVisibleRect(bubbleRect);
    406                 mBubblePaint.setAlpha((int) (255 * alpha));
    407                 float buffer = DynamicGrid.pxFromDp(FIRST_RUN_CIRCLE_BUFFER_DPS, metrics);
    408                 canvas.drawCircle(metrics.widthPixels / 2,
    409                         bubbleRect.centerY(),
    410                         (bubbleContent.getMeasuredWidth() + buffer) / 2,
    411                         mBubblePaint);
    412             } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
    413                     mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
    414                     mDrawIdentifier.equals(WORKSPACE_LARGE)) {
    415                 int offset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics);
    416                 mErasePaint.setAlpha((int) (128));
    417                 eraseCanvas.drawCircle(metrics.widthPixels / 2,
    418                         metrics.heightPixels / 2 - offset,
    419                         DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics),
    420                         mErasePaint);
    421                 mErasePaint.setAlpha(0);
    422                 eraseCanvas.drawCircle(metrics.widthPixels / 2,
    423                         metrics.heightPixels / 2 - offset,
    424                         DynamicGrid.pxFromDp(WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics),
    425                         mErasePaint);
    426                 canvas.drawBitmap(eraseBg, 0, 0, null);
    427                 eraseCanvas.setBitmap(null);
    428                 eraseBg = null;
    429 
    430                 // Draw the focused hotseat app icon
    431                 if (mFocusedHotseatAppBounds != null && mFocusedHotseatApp != null) {
    432                     mFocusedHotseatApp.setBounds(mFocusedHotseatAppBounds.left,
    433                             mFocusedHotseatAppBounds.top, mFocusedHotseatAppBounds.right,
    434                             mFocusedHotseatAppBounds.bottom);
    435                     mFocusedHotseatApp.setAlpha((int) (255 * alpha));
    436                     mFocusedHotseatApp.draw(canvas);
    437                 }
    438             }
    439 
    440             canvas.restore();
    441         }
    442 
    443         // Draw the rest of the cling
    444         super.dispatchDraw(canvas);
    445     };
    446 }
    447