Home | History | Annotate | Download | only in allapps
      1 /*
      2  * Copyright (C) 2015 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 package com.android.launcher3.allapps;
     17 
     18 import android.content.Context;
     19 import android.graphics.Color;
     20 import android.graphics.Rect;
     21 import android.graphics.drawable.ColorDrawable;
     22 import android.graphics.drawable.InsetDrawable;
     23 import android.support.v7.widget.LinearLayoutManager;
     24 import android.text.Selection;
     25 import android.text.SpannableStringBuilder;
     26 import android.util.AttributeSet;
     27 import android.view.KeyEvent;
     28 import android.view.MotionEvent;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 
     32 import com.android.launcher3.AppInfo;
     33 import com.android.launcher3.BaseContainerView;
     34 import com.android.launcher3.BubbleTextView;
     35 import com.android.launcher3.DeleteDropTarget;
     36 import com.android.launcher3.DeviceProfile;
     37 import com.android.launcher3.DragSource;
     38 import com.android.launcher3.DropTarget;
     39 import com.android.launcher3.Insettable;
     40 import com.android.launcher3.ItemInfo;
     41 import com.android.launcher3.Launcher;
     42 import com.android.launcher3.PromiseAppInfo;
     43 import com.android.launcher3.R;
     44 import com.android.launcher3.Utilities;
     45 import com.android.launcher3.anim.SpringAnimationHandler;
     46 import com.android.launcher3.config.FeatureFlags;
     47 import com.android.launcher3.dragndrop.DragController;
     48 import com.android.launcher3.dragndrop.DragOptions;
     49 import com.android.launcher3.folder.Folder;
     50 import com.android.launcher3.keyboard.FocusedItemDecorator;
     51 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
     52 import com.android.launcher3.util.ComponentKey;
     53 import com.android.launcher3.util.ComponentKeyMapper;
     54 import com.android.launcher3.util.PackageUserKey;
     55 
     56 import java.util.List;
     57 import java.util.Set;
     58 
     59 /**
     60  * The all apps view container.
     61  */
     62 public class AllAppsContainerView extends BaseContainerView implements DragSource,
     63         View.OnLongClickListener, Insettable {
     64 
     65     private final Launcher mLauncher;
     66     private final AlphabeticalAppsList mApps;
     67     private final AllAppsGridAdapter mAdapter;
     68     private final LinearLayoutManager mLayoutManager;
     69 
     70     private AllAppsRecyclerView mAppsRecyclerView;
     71     private SearchUiManager mSearchUiManager;
     72     private View mSearchContainer;
     73 
     74     private SpannableStringBuilder mSearchQueryBuilder = null;
     75 
     76     private int mNumAppsPerRow;
     77     private int mNumPredictedAppsPerRow;
     78 
     79     private SpringAnimationHandler mSpringAnimationHandler;
     80 
     81     public AllAppsContainerView(Context context) {
     82         this(context, null);
     83     }
     84 
     85     public AllAppsContainerView(Context context, AttributeSet attrs) {
     86         this(context, attrs, 0);
     87     }
     88 
     89     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
     90         super(context, attrs, defStyleAttr);
     91 
     92         mLauncher = Launcher.getLauncher(context);
     93         mApps = new AlphabeticalAppsList(context);
     94         mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
     95         mSpringAnimationHandler = mAdapter.getSpringAnimationHandler();
     96         mApps.setAdapter(mAdapter);
     97         mLayoutManager = mAdapter.getLayoutManager();
     98         mSearchQueryBuilder = new SpannableStringBuilder();
     99 
    100         Selection.setSelection(mSearchQueryBuilder, 0);
    101     }
    102 
    103     @Override
    104     protected void updateBackground(
    105             int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
    106         if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
    107             getRevealView().setBackground(new InsetDrawable(mBaseDrawable,
    108                     paddingLeft, paddingTop, paddingRight, paddingBottom));
    109             getContentView().setBackground(
    110                     new InsetDrawable(new ColorDrawable(Color.TRANSPARENT),
    111                             paddingLeft, paddingTop, paddingRight, paddingBottom));
    112         } else {
    113             getRevealView().setBackground(mBaseDrawable);
    114         }
    115     }
    116 
    117     /**
    118      * Sets the current set of predicted apps.
    119      */
    120     public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
    121         mApps.setPredictedApps(apps);
    122     }
    123 
    124     /**
    125      * Sets the current set of apps.
    126      */
    127     public void setApps(List<AppInfo> apps) {
    128         mApps.setApps(apps);
    129     }
    130 
    131     /**
    132      * Adds or updates existing apps in the list
    133      */
    134     public void addOrUpdateApps(List<AppInfo> apps) {
    135         mApps.addOrUpdateApps(apps);
    136         mSearchUiManager.refreshSearchResult();
    137     }
    138 
    139     public void updatePromiseAppProgress(PromiseAppInfo app) {
    140         int childCount = mAppsRecyclerView.getChildCount();
    141         for (int i = 0; i < childCount; i++) {
    142             View child = mAppsRecyclerView.getChildAt(i);
    143             if (child instanceof BubbleTextView && child.getTag() == app) {
    144                 BubbleTextView bubbleTextView = (BubbleTextView) child;
    145                 bubbleTextView.applyProgressLevel(app.level);
    146             }
    147         }
    148     }
    149 
    150     /**
    151      * Removes some apps from the list.
    152      */
    153     public void removeApps(List<AppInfo> apps) {
    154         mApps.removeApps(apps);
    155         mSearchUiManager.refreshSearchResult();
    156     }
    157 
    158     /**
    159      * Returns whether the view itself will handle the touch event or not.
    160      */
    161     public boolean shouldContainerScroll(MotionEvent ev) {
    162         // IF the MotionEvent is inside the search box, and the container keeps on receiving
    163         // touch input, container should move down.
    164         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
    165             return true;
    166         }
    167 
    168         int[] point = new int[2];
    169         point[0] = (int) ev.getX();
    170         point[1] = (int) ev.getY();
    171         Utilities.mapCoordInSelfToDescendant(
    172                 mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point);
    173         // IF the MotionEvent is inside the thumb, container should not be pulled down.
    174         if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) {
    175             return false;
    176         }
    177 
    178         // IF scroller is at the very top OR there is no scroll bar because there is probably not
    179         // enough items to scroll, THEN it's okay for the container to be pulled down.
    180         if (mAppsRecyclerView.getCurrentScrollY() == 0) {
    181             return true;
    182         }
    183         return false;
    184     }
    185 
    186     /**
    187      * Resets the state of AllApps.
    188      */
    189     public void reset() {
    190         // Reset the search bar and base recycler view after transitioning home
    191         mAppsRecyclerView.scrollToTop();
    192         mSearchUiManager.reset();
    193     }
    194 
    195     @Override
    196     protected void onFinishInflate() {
    197         super.onFinishInflate();
    198 
    199         // This is a focus listener that proxies focus from a view into the list view.  This is to
    200         // work around the search box from getting first focus and showing the cursor.
    201         getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
    202             @Override
    203             public void onFocusChange(View v, boolean hasFocus) {
    204                 if (hasFocus) {
    205                     mAppsRecyclerView.requestFocus();
    206                 }
    207             }
    208         });
    209 
    210         // Load the all apps recycler view
    211         mAppsRecyclerView = findViewById(R.id.apps_list_view);
    212         mAppsRecyclerView.setApps(mApps);
    213         mAppsRecyclerView.setLayoutManager(mLayoutManager);
    214         mAppsRecyclerView.setAdapter(mAdapter);
    215         mAppsRecyclerView.setHasFixedSize(true);
    216         // No animations will occur when changes occur to the items in this RecyclerView.
    217         mAppsRecyclerView.setItemAnimator(null);
    218         if (FeatureFlags.LAUNCHER3_PHYSICS) {
    219             mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler);
    220         }
    221 
    222         mSearchContainer = findViewById(R.id.search_container_all_apps);
    223         mSearchUiManager = (SearchUiManager) mSearchContainer;
    224         mSearchUiManager.initialize(mApps, mAppsRecyclerView);
    225 
    226 
    227         FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
    228         mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
    229         mAppsRecyclerView.preMeasureViews(mAdapter);
    230         mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
    231 
    232         getRevealView().setVisibility(View.VISIBLE);
    233         getContentView().setVisibility(View.VISIBLE);
    234         getContentView().setBackground(null);
    235     }
    236 
    237     public SearchUiManager getSearchUiManager() {
    238         return mSearchUiManager;
    239     }
    240 
    241     @Override
    242     public View getTouchDelegateTargetView() {
    243         return mAppsRecyclerView;
    244     }
    245 
    246     @Override
    247     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    248         DeviceProfile grid = mLauncher.getDeviceProfile();
    249         // Update the number of items in the grid before we measure the view
    250         grid.updateAppsViewNumCols();
    251 
    252         if (mNumAppsPerRow != grid.inv.numColumns ||
    253                 mNumPredictedAppsPerRow != grid.inv.numColumns) {
    254             mNumAppsPerRow = grid.inv.numColumns;
    255             mNumPredictedAppsPerRow = grid.inv.numColumns;
    256 
    257             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
    258             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
    259             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
    260         }
    261         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    262     }
    263 
    264     @Override
    265     public boolean dispatchKeyEvent(KeyEvent event) {
    266         mSearchUiManager.preDispatchKeyEvent(event);
    267         return super.dispatchKeyEvent(event);
    268     }
    269 
    270     @Override
    271     public boolean onLongClick(final View v) {
    272         // When we have exited all apps or are in transition, disregard long clicks
    273         if (!mLauncher.isAppsViewVisible() ||
    274                 mLauncher.getWorkspace().isSwitchingState()) return false;
    275         // Return if global dragging is not enabled or we are already dragging
    276         if (!mLauncher.isDraggingEnabled()) return false;
    277         if (mLauncher.getDragController().isDragging()) return false;
    278 
    279         // Start the drag
    280         final DragController dragController = mLauncher.getDragController();
    281         dragController.addDragListener(new DragController.DragListener() {
    282             @Override
    283             public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
    284                 v.setVisibility(INVISIBLE);
    285             }
    286 
    287             @Override
    288             public void onDragEnd() {
    289                 v.setVisibility(VISIBLE);
    290                 dragController.removeDragListener(this);
    291             }
    292         });
    293         mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
    294         return false;
    295     }
    296 
    297     @Override
    298     public boolean supportsAppInfoDropTarget() {
    299         return true;
    300     }
    301 
    302     @Override
    303     public boolean supportsDeleteDropTarget() {
    304         return false;
    305     }
    306 
    307     @Override
    308     public float getIntrinsicIconScaleFactor() {
    309         DeviceProfile grid = mLauncher.getDeviceProfile();
    310         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
    311     }
    312 
    313     @Override
    314     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
    315             boolean success) {
    316         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
    317                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
    318             // Exit spring loaded mode if we have not successfully dropped or have not handled the
    319             // drop in Workspace
    320             mLauncher.exitSpringLoadedDragModeDelayed(true,
    321                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
    322         }
    323         mLauncher.unlockScreenOrientation(false);
    324 
    325         if (!success) {
    326             d.deferDragViewCleanupPostAnimation = false;
    327         }
    328     }
    329 
    330     @Override
    331     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
    332         // This is filled in {@link AllAppsRecyclerView}
    333     }
    334 
    335     @Override
    336     public void setInsets(Rect insets) {
    337         DeviceProfile grid = mLauncher.getDeviceProfile();
    338         mAppsRecyclerView.setPadding(
    339                 mAppsRecyclerView.getPaddingLeft(), mAppsRecyclerView.getPaddingTop(),
    340                 mAppsRecyclerView.getPaddingRight(), insets.bottom);
    341 
    342         if (grid.isVerticalBarLayout()) {
    343             ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
    344             mlp.leftMargin = insets.left;
    345             mlp.topMargin = insets.top;
    346             mlp.rightMargin = insets.right;
    347             setLayoutParams(mlp);
    348         } else {
    349             View navBarBg = findViewById(R.id.nav_bar_bg);
    350             ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
    351             navBarBgLp.height = insets.bottom;
    352             navBarBg.setLayoutParams(navBarBgLp);
    353         }
    354     }
    355 
    356     public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
    357         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
    358         final int n = mAppsRecyclerView.getChildCount();
    359         for (int i = 0; i < n; i++) {
    360             View child = mAppsRecyclerView.getChildAt(i);
    361             if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
    362                 continue;
    363             }
    364             ItemInfo info = (ItemInfo) child.getTag();
    365             if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
    366                 ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
    367             }
    368         }
    369     }
    370 
    371     public SpringAnimationHandler getSpringAnimationHandler() {
    372         return mSpringAnimationHandler;
    373     }
    374 }
    375