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.annotation.NonNull;
     24 import android.support.annotation.Nullable;
     25 import android.support.v7.widget.RecyclerView;
     26 import android.text.Selection;
     27 import android.text.Spannable;
     28 import android.text.SpannableString;
     29 import android.text.SpannableStringBuilder;
     30 import android.text.TextUtils;
     31 import android.text.method.TextKeyListener;
     32 import android.util.AttributeSet;
     33 import android.view.KeyEvent;
     34 import android.view.MotionEvent;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 
     38 import com.android.launcher3.AppInfo;
     39 import com.android.launcher3.BaseContainerView;
     40 import com.android.launcher3.BubbleTextView;
     41 import com.android.launcher3.DeleteDropTarget;
     42 import com.android.launcher3.DeviceProfile;
     43 import com.android.launcher3.DragSource;
     44 import com.android.launcher3.DropTarget;
     45 import com.android.launcher3.ExtendedEditText;
     46 import com.android.launcher3.Insettable;
     47 import com.android.launcher3.ItemInfo;
     48 import com.android.launcher3.Launcher;
     49 import com.android.launcher3.R;
     50 import com.android.launcher3.Utilities;
     51 import com.android.launcher3.config.FeatureFlags;
     52 import com.android.launcher3.discovery.AppDiscoveryItem;
     53 import com.android.launcher3.discovery.AppDiscoveryUpdateState;
     54 import com.android.launcher3.dragndrop.DragController;
     55 import com.android.launcher3.dragndrop.DragOptions;
     56 import com.android.launcher3.folder.Folder;
     57 import com.android.launcher3.graphics.TintedDrawableSpan;
     58 import com.android.launcher3.keyboard.FocusedItemDecorator;
     59 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
     60 import com.android.launcher3.util.ComponentKey;
     61 import com.android.launcher3.util.PackageUserKey;
     62 
     63 import java.util.ArrayList;
     64 import java.util.List;
     65 import java.util.Set;
     66 
     67 /**
     68  * The all apps view container.
     69  */
     70 public class AllAppsContainerView extends BaseContainerView implements DragSource,
     71         View.OnLongClickListener, AllAppsSearchBarController.Callbacks, Insettable {
     72 
     73     private final Launcher mLauncher;
     74     private final AlphabeticalAppsList mApps;
     75     private final AllAppsGridAdapter mAdapter;
     76     private final RecyclerView.LayoutManager mLayoutManager;
     77 
     78     private AllAppsRecyclerView mAppsRecyclerView;
     79     private AllAppsSearchBarController mSearchBarController;
     80 
     81     private View mSearchContainer;
     82     private int mSearchContainerMinHeight;
     83     private ExtendedEditText mSearchInput;
     84     private HeaderElevationController mElevationController;
     85 
     86     private SpannableStringBuilder mSearchQueryBuilder = null;
     87 
     88     private int mNumAppsPerRow;
     89     private int mNumPredictedAppsPerRow;
     90 
     91     public AllAppsContainerView(Context context) {
     92         this(context, null);
     93     }
     94 
     95     public AllAppsContainerView(Context context, AttributeSet attrs) {
     96         this(context, attrs, 0);
     97     }
     98 
     99     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
    100         super(context, attrs, defStyleAttr);
    101 
    102         mLauncher = Launcher.getLauncher(context);
    103         mApps = new AlphabeticalAppsList(context);
    104         mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
    105         mApps.setAdapter(mAdapter);
    106         mLayoutManager = mAdapter.getLayoutManager();
    107         mSearchQueryBuilder = new SpannableStringBuilder();
    108         mSearchContainerMinHeight
    109                 = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
    110 
    111         Selection.setSelection(mSearchQueryBuilder, 0);
    112     }
    113 
    114     @Override
    115     protected void updateBackground(
    116             int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
    117         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
    118             if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
    119                 getRevealView().setBackground(new InsetDrawable(mBaseDrawable,
    120                         paddingLeft, paddingTop, paddingRight, paddingBottom));
    121                 getContentView().setBackground(
    122                         new InsetDrawable(new ColorDrawable(Color.TRANSPARENT),
    123                                 paddingLeft, paddingTop, paddingRight, paddingBottom));
    124             } else {
    125                 getRevealView().setBackground(mBaseDrawable);
    126             }
    127         } else {
    128             super.updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
    129         }
    130     }
    131 
    132     /**
    133      * Sets the current set of predicted apps.
    134      */
    135     public void setPredictedApps(List<ComponentKey> apps) {
    136         mApps.setPredictedApps(apps);
    137     }
    138 
    139     /**
    140      * Sets the current set of apps.
    141      */
    142     public void setApps(List<AppInfo> apps) {
    143         mApps.setApps(apps);
    144     }
    145 
    146     /**
    147      * Adds new apps to the list.
    148      */
    149     public void addApps(List<AppInfo> apps) {
    150         mApps.addApps(apps);
    151         mSearchBarController.refreshSearchResult();
    152     }
    153 
    154     /**
    155      * Updates existing apps in the list
    156      */
    157     public void updateApps(List<AppInfo> apps) {
    158         mApps.updateApps(apps);
    159         mSearchBarController.refreshSearchResult();
    160     }
    161 
    162     /**
    163      * Removes some apps from the list.
    164      */
    165     public void removeApps(List<AppInfo> apps) {
    166         mApps.removeApps(apps);
    167         mSearchBarController.refreshSearchResult();
    168     }
    169 
    170     public void setSearchBarVisible(boolean visible) {
    171         if (visible) {
    172             mSearchBarController.setVisibility(View.VISIBLE);
    173         } else {
    174             mSearchBarController.setVisibility(View.INVISIBLE);
    175         }
    176     }
    177 
    178     /**
    179      * Sets the search bar that shows above the a-z list.
    180      */
    181     public void setSearchBarController(AllAppsSearchBarController searchController) {
    182         if (mSearchBarController != null) {
    183             throw new RuntimeException("Expected search bar controller to only be set once");
    184         }
    185         mSearchBarController = searchController;
    186         mSearchBarController.initialize(mApps, mSearchInput, mLauncher, this);
    187         mAdapter.setSearchController(mSearchBarController);
    188     }
    189 
    190     /**
    191      * Scrolls this list view to the top.
    192      */
    193     public void scrollToTop() {
    194         mAppsRecyclerView.scrollToTop();
    195     }
    196 
    197     /**
    198      * Returns whether the view itself will handle the touch event or not.
    199      */
    200     public boolean shouldContainerScroll(MotionEvent ev) {
    201         int[] point = new int[2];
    202         point[0] = (int) ev.getX();
    203         point[1] = (int) ev.getY();
    204         Utilities.mapCoordInSelfToDescendant(mAppsRecyclerView, this, point);
    205 
    206         // IF the MotionEvent is inside the search box, and the container keeps on receiving
    207         // touch input, container should move down.
    208         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
    209             return true;
    210         }
    211 
    212         // IF the MotionEvent is inside the thumb, container should not be pulled down.
    213         if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) {
    214             return false;
    215         }
    216 
    217         // IF scroller is at the very top OR there is no scroll bar because there is probably not
    218         // enough items to scroll, THEN it's okay for the container to be pulled down.
    219         if (mAppsRecyclerView.getCurrentScrollY() == 0) {
    220             return true;
    221         }
    222         return false;
    223     }
    224 
    225     /**
    226      * Focuses the search field and begins an app search.
    227      */
    228     public void startAppsSearch() {
    229         if (mSearchBarController != null) {
    230             mSearchBarController.focusSearchField();
    231         }
    232     }
    233 
    234     /**
    235      * Resets the state of AllApps.
    236      */
    237     public void reset() {
    238         // Reset the search bar and base recycler view after transitioning home
    239         scrollToTop();
    240         mSearchBarController.reset();
    241         mAppsRecyclerView.reset();
    242     }
    243 
    244     @Override
    245     protected void onFinishInflate() {
    246         super.onFinishInflate();
    247 
    248         // This is a focus listener that proxies focus from a view into the list view.  This is to
    249         // work around the search box from getting first focus and showing the cursor.
    250         getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
    251             @Override
    252             public void onFocusChange(View v, boolean hasFocus) {
    253                 if (hasFocus) {
    254                     mAppsRecyclerView.requestFocus();
    255                 }
    256             }
    257         });
    258 
    259         mSearchContainer = findViewById(R.id.search_container);
    260         mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
    261 
    262         // Update the hint to contain the icon.
    263         // Prefix the original hint with two spaces. The first space gets replaced by the icon
    264         // using span. The second space is used for a singe space character between the hint
    265         // and the icon.
    266         SpannableString spanned = new SpannableString("  " + mSearchInput.getHint());
    267         spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
    268                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
    269         mSearchInput.setHint(spanned);
    270 
    271         mElevationController = new HeaderElevationController(mSearchContainer);
    272 
    273         // Load the all apps recycler view
    274         mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
    275         mAppsRecyclerView.setApps(mApps);
    276         mAppsRecyclerView.setLayoutManager(mLayoutManager);
    277         mAppsRecyclerView.setAdapter(mAdapter);
    278         mAppsRecyclerView.setHasFixedSize(true);
    279         mAppsRecyclerView.addOnScrollListener(mElevationController);
    280         mAppsRecyclerView.setElevationController(mElevationController);
    281 
    282         FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
    283         mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
    284         mAppsRecyclerView.preMeasureViews(mAdapter);
    285         mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
    286 
    287         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
    288             getRevealView().setVisibility(View.VISIBLE);
    289             getContentView().setVisibility(View.VISIBLE);
    290             getContentView().setBackground(null);
    291         }
    292     }
    293 
    294     @Override
    295     public View getTouchDelegateTargetView() {
    296         return mAppsRecyclerView;
    297     }
    298 
    299     @Override
    300     public void onBoundsChanged(Rect newBounds) { }
    301 
    302     @Override
    303     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    304         DeviceProfile grid = mLauncher.getDeviceProfile();
    305         grid.updateAppsViewNumCols();
    306         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
    307             if (mNumAppsPerRow != grid.inv.numColumns ||
    308                     mNumPredictedAppsPerRow != grid.inv.numColumns) {
    309                 mNumAppsPerRow = grid.inv.numColumns;
    310                 mNumPredictedAppsPerRow = grid.inv.numColumns;
    311 
    312                 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
    313                 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
    314                 mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
    315             }
    316             if (!grid.isVerticalBarLayout()) {
    317                 MarginLayoutParams searchContainerLp =
    318                         (MarginLayoutParams) mSearchContainer.getLayoutParams();
    319 
    320                 searchContainerLp.height = mLauncher.getDragLayer().getInsets().top
    321                         + mSearchContainerMinHeight;
    322                 mSearchContainer.setLayoutParams(searchContainerLp);
    323             }
    324             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    325             return;
    326         }
    327 
    328         // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
    329 
    330         // Update the number of items in the grid before we measure the view
    331         grid.updateAppsViewNumCols();
    332         if (mNumAppsPerRow != grid.allAppsNumCols ||
    333                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
    334             mNumAppsPerRow = grid.allAppsNumCols;
    335             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
    336 
    337             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
    338             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
    339             mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
    340         }
    341 
    342         // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
    343         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    344     }
    345 
    346     @Override
    347     public boolean dispatchKeyEvent(KeyEvent event) {
    348         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
    349         // the key normally so that it can process this key event
    350         if (!mSearchBarController.isSearchFieldFocused() &&
    351                 event.getAction() == KeyEvent.ACTION_DOWN) {
    352             final int unicodeChar = event.getUnicodeChar();
    353             final boolean isKeyNotWhitespace = unicodeChar > 0 &&
    354                     !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar);
    355             if (isKeyNotWhitespace) {
    356                 boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
    357                         event.getKeyCode(), event);
    358                 if (gotKey && mSearchQueryBuilder.length() > 0) {
    359                     mSearchBarController.focusSearchField();
    360                 }
    361             }
    362         }
    363 
    364         return super.dispatchKeyEvent(event);
    365     }
    366 
    367     @Override
    368     public boolean onLongClick(final View v) {
    369         // Return early if this is not initiated from a touch
    370         if (!v.isInTouchMode()) return false;
    371         // When we have exited all apps or are in transition, disregard long clicks
    372 
    373         if (!mLauncher.isAppsViewVisible() ||
    374                 mLauncher.getWorkspace().isSwitchingState()) return false;
    375         // Return if global dragging is not enabled or we are already dragging
    376         if (!mLauncher.isDraggingEnabled()) return false;
    377         if (mLauncher.getDragController().isDragging()) return false;
    378 
    379         // Start the drag
    380         final DragController dragController = mLauncher.getDragController();
    381         dragController.addDragListener(new DragController.DragListener() {
    382             @Override
    383             public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
    384                 v.setVisibility(INVISIBLE);
    385             }
    386 
    387             @Override
    388             public void onDragEnd() {
    389                 v.setVisibility(VISIBLE);
    390                 dragController.removeDragListener(this);
    391             }
    392         });
    393         mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
    394         return false;
    395     }
    396 
    397     @Override
    398     public boolean supportsAppInfoDropTarget() {
    399         return true;
    400     }
    401 
    402     @Override
    403     public boolean supportsDeleteDropTarget() {
    404         return false;
    405     }
    406 
    407     @Override
    408     public float getIntrinsicIconScaleFactor() {
    409         DeviceProfile grid = mLauncher.getDeviceProfile();
    410         return (float) grid.allAppsIconSizePx / grid.iconSizePx;
    411     }
    412 
    413     @Override
    414     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
    415             boolean success) {
    416         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
    417                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
    418             // Exit spring loaded mode if we have not successfully dropped or have not handled the
    419             // drop in Workspace
    420             mLauncher.exitSpringLoadedDragModeDelayed(true,
    421                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
    422         }
    423         mLauncher.unlockScreenOrientation(false);
    424 
    425         if (!success) {
    426             d.deferDragViewCleanupPostAnimation = false;
    427         }
    428     }
    429 
    430     @Override
    431     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
    432         if (apps != null) {
    433             mApps.setOrderedFilter(apps);
    434             mAppsRecyclerView.onSearchResultsChanged();
    435             mAdapter.setLastSearchQuery(query);
    436         }
    437     }
    438 
    439     @Override
    440     public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
    441             @NonNull AppDiscoveryUpdateState state) {
    442         if (!mLauncher.isDestroyed()) {
    443             mApps.onAppDiscoverySearchUpdate(app, state);
    444             mAppsRecyclerView.onSearchResultsChanged();
    445         }
    446     }
    447 
    448     @Override
    449     public void clearSearchResult() {
    450         if (mApps.setOrderedFilter(null)) {
    451             mAppsRecyclerView.onSearchResultsChanged();
    452         }
    453 
    454         // Clear the search query
    455         mSearchQueryBuilder.clear();
    456         mSearchQueryBuilder.clearSpans();
    457         Selection.setSelection(mSearchQueryBuilder, 0);
    458     }
    459 
    460     @Override
    461     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
    462         targetParent.containerType = mAppsRecyclerView.getContainerType(v);
    463     }
    464 
    465     public boolean shouldRestoreImeState() {
    466         return !TextUtils.isEmpty(mSearchInput.getText());
    467     }
    468 
    469     @Override
    470     public void setInsets(Rect insets) {
    471         DeviceProfile grid = mLauncher.getDeviceProfile();
    472         if (grid.isVerticalBarLayout()) {
    473             ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
    474             mlp.leftMargin = insets.left;
    475             mlp.topMargin = insets.top;
    476             mlp.rightMargin = insets.right;
    477             setLayoutParams(mlp);
    478         } else {
    479             View navBarBg = findViewById(R.id.nav_bar_bg);
    480             ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
    481             navBarBgLp.height = insets.bottom;
    482             navBarBg.setLayoutParams(navBarBgLp);
    483             navBarBg.setVisibility(View.VISIBLE);
    484         }
    485     }
    486 
    487     public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
    488         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
    489         final int n = mAppsRecyclerView.getChildCount();
    490         for (int i = 0; i < n; i++) {
    491             View child = mAppsRecyclerView.getChildAt(i);
    492             if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
    493                 continue;
    494             }
    495             ItemInfo info = (ItemInfo) child.getTag();
    496             if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
    497                 ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
    498             }
    499         }
    500     }
    501 }
    502