diff options
Diffstat (limited to 'src/com/android/launcher3/AppDrawerListAdapter.java')
-rw-r--r-- | src/com/android/launcher3/AppDrawerListAdapter.java | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/src/com/android/launcher3/AppDrawerListAdapter.java b/src/com/android/launcher3/AppDrawerListAdapter.java new file mode 100644 index 000000000..a16937308 --- /dev/null +++ b/src/com/android/launcher3/AppDrawerListAdapter.java @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.support.v7.widget.RecyclerView; +import android.widget.LinearLayout; +import android.widget.SectionIndexer; +import com.android.launcher3.locale.LocaleSetManager; +import com.android.launcher3.locale.LocaleUtils; +import com.android.launcher3.settings.SettingsProvider; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * AppDrawerListAdapter - list adapter for the vertical app drawer + */ +public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdapter.ViewHolder> + implements View.OnLongClickListener, DragSource, SectionIndexer { + + private static final String NUMERIC_OR_SPECIAL_HEADER = "#"; + + private ArrayList<AppItemIndexedInfo> mHeaderList; + private LayoutInflater mLayoutInflater; + + private Launcher mLauncher; + private DeviceProfile mDeviceProfile; + private LinkedHashMap<String, Integer> mSectionHeaders; + private LinearLayout.LayoutParams mIconParams; + private Rect mIconRect; + private LocaleSetManager mLocaleSetManager; + + private ArrayList<ComponentName> mProtectedApps; + + private boolean mHideIconLabels; + + public enum DrawerType { + Drawer(0), + Pager(1); + + private final int mValue; + private DrawerType(int value) { + mValue = value; + } + + public int getValue() { + return mValue; + } + + public static DrawerType getModeForValue(int value) { + switch (value) { + case 1: + return Pager; + default : + return Drawer; + } + } + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + public AutoFitTextView mTextView; + public ViewGroup mLayout; + public ViewHolder(View itemView) { + super(itemView); + mTextView = (AutoFitTextView) itemView.findViewById(R.id.drawer_item_title); + mLayout = (ViewGroup) itemView.findViewById(R.id.drawer_item_flow); + } + } + + public AppDrawerListAdapter(Launcher launcher) { + mLauncher = launcher; + mHeaderList = new ArrayList<AppItemIndexedInfo>(); + mLayoutInflater = LayoutInflater.from(launcher); + + mLocaleSetManager = new LocaleSetManager(mLauncher); + mLocaleSetManager.updateLocaleSet(mLocaleSetManager.getSystemLocaleSet()); + initParams(); + + updateProtectedAppsList(mLauncher); + } + + private void initParams() { + mDeviceProfile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + + int width = mDeviceProfile.cellWidthPx + 2 * mDeviceProfile.edgeMarginPx; + mIconParams = new + LinearLayout.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT); + mIconRect = new Rect(0, 0, mDeviceProfile.allAppsIconSizePx, + mDeviceProfile.allAppsIconSizePx); + + mHideIconLabels = SettingsProvider.getBoolean(mLauncher, + SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS, + R.bool.preferences_interface_drawer_hide_icon_labels_default); + } + + /** + * Create and populate mHeaderList (buckets for app sorting) + * @param info + */ + public void populateByCharacter(ArrayList<AppInfo> info) { + if (info == null || info.size() <= 0) { + Collections.sort(mHeaderList); + return; + } + + // Create a clone of AppInfo ArrayList to preserve data + ArrayList<AppInfo> tempInfo = (ArrayList<AppInfo>) info.clone(); + + ArrayList<AppInfo> appInfos = new ArrayList<AppInfo>(); + + // get next app + AppInfo app = tempInfo.get(0); + + // get starting character + LocaleUtils localeUtils = LocaleUtils.getInstance(); + int bucketIndex = localeUtils.getBucketIndex(app.title.toString()); + String startString + = localeUtils.getBucketLabel(bucketIndex); + if (TextUtils.isEmpty(startString)) { + startString = NUMERIC_OR_SPECIAL_HEADER; + bucketIndex = localeUtils.getBucketIndex(startString); + } + + // now iterate through + for (AppInfo info1 : tempInfo) { + int newBucketIndex = localeUtils.getBucketIndex(info1.title.toString()); + + String newChar + = localeUtils.getBucketLabel(newBucketIndex); + if (TextUtils.isEmpty(newChar)) { + newChar = NUMERIC_OR_SPECIAL_HEADER; + } + // if same character + if (newChar.equals(startString)) { + // add it + appInfos.add(info1); + } + } + + Collections.sort(appInfos, LauncherModel.getAppNameComparator()); + + for (int i = 0; i < appInfos.size(); i += mDeviceProfile.numColumnsBase) { + int endIndex = (int) Math.min(i + mDeviceProfile.numColumnsBase, appInfos.size()); + ArrayList<AppInfo> subList = new ArrayList<AppInfo>(appInfos.subList(i, endIndex)); + AppItemIndexedInfo indexInfo; + indexInfo = new AppItemIndexedInfo(startString, bucketIndex, subList, i != 0); + mHeaderList.add(indexInfo); + } + + for (AppInfo remove : appInfos) { + // remove from mApps + tempInfo.remove(remove); + } + populateByCharacter(tempInfo); + } + + public void setApps(ArrayList<AppInfo> list) { + if (!LauncherAppState.isDisableAllApps()) { + initParams(); + + filterProtectedApps(list); + + mHeaderList.clear(); + populateByCharacter(list); + populateSectionHeaders(); + mLauncher.updateScrubber(); + this.notifyDataSetChanged(); + } + } + + private void populateSectionHeaders() { + if (mSectionHeaders == null || mSectionHeaders.size() != mHeaderList.size()) { + mSectionHeaders = new LinkedHashMap<String, Integer>(); + } + int count = 0; + for (int i = 0; i < mHeaderList.size(); i++) { + AppItemIndexedInfo info = mHeaderList.get(i); + if (!mHeaderList.get(i).isChild) { + mSectionHeaders.put(String.valueOf(mHeaderList.get(i).mStartString), count); + } + if (info.mInfo.size() < mDeviceProfile.numColumnsBase) { + count++; + } else { + count += info.mInfo.size() / mDeviceProfile.numColumnsBase; + } + } + } + + public void reset() { + ArrayList<AppInfo> infos = getAllApps(); + + mLauncher.mAppDrawer.getLayoutManager().removeAllViews(); + setApps(infos); + } + + private ArrayList<AppInfo> getAllApps() { + ArrayList<AppInfo> indexedInfos = new ArrayList<AppInfo>(); + + for (int j = 0; j < mHeaderList.size(); ++j) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(j); + for (AppInfo info : indexedInfo.mInfo) { + indexedInfos.add(info); + } + } + return indexedInfos; + } + + public void updateApps(ArrayList<AppInfo> list) { + // We remove and re-add the updated applications list because it's properties may have + // changed (ie. the title), and this will ensure that the items will be in their proper + // place in the list. + if (!LauncherAppState.isDisableAllApps()) { + removeAppsWithoutInvalidate(list); + addAppsWithoutInvalidate(list); + reset(); + } + } + + + public void addApps(ArrayList<AppInfo> list) { + if (!LauncherAppState.isDisableAllApps()) { + addAppsWithoutInvalidate(list); + reset(); + } + } + + private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) { + // We add it in place, in alphabetical order + LocaleUtils localeUtils = LocaleUtils.getInstance(); + + int count = list.size(); + for (int i = 0; i < count; ++i) { + AppInfo info = list.get(i); + boolean found = false; + AppItemIndexedInfo lastInfoForSection = null; + int bucketIndex = localeUtils.getBucketIndex(info.title.toString()); + String start = localeUtils.getBucketLabel(bucketIndex); + if (TextUtils.isEmpty(start)) { + start = NUMERIC_OR_SPECIAL_HEADER; + bucketIndex = localeUtils.getBucketIndex(start); + } + for (int j = 0; j < mHeaderList.size(); ++j) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(j); + if (start.equals(indexedInfo.mStartString)) { + Collections.sort(indexedInfo.mInfo, LauncherModel.getAppNameComparator()); + int index = + Collections.binarySearch(indexedInfo.mInfo, + info, LauncherModel.getAppNameComparator()); + if (index >= 0) { + found = true; + break; + } else { + lastInfoForSection = indexedInfo; + } + } + } + if (!found) { + if (lastInfoForSection != null) { + lastInfoForSection.mInfo.add(info); + } else { + // we need to create a new section + ArrayList<AppInfo> newInfos = new ArrayList<AppInfo>(); + newInfos.add(info); + AppItemIndexedInfo newInfo = + new AppItemIndexedInfo(start, bucketIndex, newInfos, false); + mHeaderList.add(newInfo); + Collections.sort(mHeaderList); + } + } + } + } + + public void removeApps(ArrayList<AppInfo> appInfos) { + if (!LauncherAppState.isDisableAllApps()) { + removeAppsWithoutInvalidate(appInfos); + //recreate everything + reset(); + } + } + + private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) { + // loop through all the apps and remove apps that have the same component + int length = list.size(); + for (int i = 0; i < length; ++i) { + AppInfo info = list.get(i); + for (int j = 0; j < mHeaderList.size(); ++j) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(j); + ArrayList<AppInfo> clonedIndexedInfoApps = + (ArrayList<AppInfo>) indexedInfo.mInfo.clone(); + int index = + findAppByComponent(clonedIndexedInfoApps, info); + if (index > -1) { + indexedInfo.mInfo.remove(info); + } + } + } + } + + private int findAppByComponent(List<AppInfo> list, AppInfo item) { + ComponentName removeComponent = item.intent.getComponent(); + int length = list.size(); + for (int i = 0; i < length; ++i) { + AppInfo info = list.get(i); + if (info.intent.getComponent().equals(removeComponent)) { + return i; + } + } + return -1; + } + + /* + * AllAppsView implementation + */ + public void setup(Launcher launcher) { + mLauncher = launcher; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()). + inflate(R.layout.app_drawer_item, parent, false); + ViewHolder holder = new ViewHolder(v); + holder.mTextView.setPadding(0, 0, 0, mDeviceProfile.iconTextSizePx + 10); + for (int i = 0; i < mDeviceProfile.numColumnsBase; i++) { + AppDrawerIconView icon = (AppDrawerIconView) mLayoutInflater.inflate( + R.layout.drawer_icon, holder.mLayout, false); + icon.setOnClickListener(mLauncher); + icon.setOnLongClickListener(this); + holder.mLayout.addView(icon); + } + return holder; + } + + @Override + public int getItemCount() { + return mHeaderList.size(); + } + + public AppItemIndexedInfo getItemAt(int position) { + if (position < mHeaderList.size()) + return mHeaderList.get(position); + return null; + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + AppItemIndexedInfo indexedInfo = mHeaderList.get(position); + holder.mTextView.setVisibility(indexedInfo.isChild ? View.INVISIBLE : View.VISIBLE); + if (!indexedInfo.isChild) { + if (indexedInfo.mStartString.equals(NUMERIC_OR_SPECIAL_HEADER)) { + holder.mTextView.setText(NUMERIC_OR_SPECIAL_HEADER); + } else { + holder.mTextView.setText(String.valueOf(indexedInfo.mStartString)); + } + } + final int size = indexedInfo.mInfo.size(); + for (int i = 0; i < holder.mLayout.getChildCount(); i++) { + AppDrawerIconView icon = (AppDrawerIconView) holder.mLayout.getChildAt(i); + icon.setLayoutParams(mIconParams); + if (i >= size) { + icon.setVisibility(View.INVISIBLE); + } else { + icon.setVisibility(View.VISIBLE); + AppInfo info = indexedInfo.mInfo.get(i); + icon.setTag(info); + Drawable d = Utilities.createIconDrawable(info.iconBitmap); + d.setBounds(mIconRect); + icon.mIcon.setImageDrawable(d); + icon.mLabel.setText(info.title); + icon.mLabel.setVisibility(mHideIconLabels ? View.INVISIBLE : View.VISIBLE); + } + } + holder.itemView.setTag(indexedInfo); + } + + @Override + public boolean onLongClick(View v) { + if (v instanceof AppDrawerIconView) { + beginDraggingApplication(v); + mLauncher.enterSpringLoadedDragMode(); + } + return false; + } + + @Override + public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, + boolean success) { + // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling + if (isFlingToDelete) return; + + endDragging(target, false, success); + + // Display an error message if the drag failed due to there not being enough space on the + // target layout we were dropping on. + if (!success) { + boolean showOutOfSpaceMessage = false; + if (target instanceof Workspace) { + int currentScreen = mLauncher.getCurrentWorkspaceScreen(); + Workspace workspace = (Workspace) target; + CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); + ItemInfo itemInfo = (ItemInfo) d.dragInfo; + if (layout != null) { + layout.calculateSpans(itemInfo); + showOutOfSpaceMessage = + !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); + } + } + if (showOutOfSpaceMessage) { + mLauncher.showOutOfSpaceMessage(false); + } + + d.deferDragViewCleanupPostAnimation = false; + } + } + + /** + * Clean up after dragging. + * + * @param target where the item was dragged to (can be null if the item was flung) + */ + private void endDragging(View target, boolean isFlingToDelete, boolean success) { + if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && + !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { + // Exit spring loaded mode if we have not successfully dropped or have not handled the + // drop in Workspace + mLauncher.getWorkspace().removeExtraEmptyScreenDelayed(true, new Runnable() { + @Override + public void run() { + mLauncher.exitSpringLoadedDragMode(); + mLauncher.unlockScreenOrientation(false); + } + }, 0, true); + } else { + mLauncher.unlockScreenOrientation(false); + } + } + + @Override + public boolean supportsFlingToDelete() { + return false; + } + + @Override + public boolean supportsAppInfoDropTarget() { + return true; + } + + @Override + public boolean supportsDeleteDropTarget() { + return false; + } + + @Override + public float getIntrinsicIconScaleFactor() { + return (float) mDeviceProfile.allAppsIconSizePx / mDeviceProfile.iconSizePx; + } + + private void beginDraggingApplication(View v) { + mLauncher.getWorkspace().beginDragShared(v, this); + } + + @Override + public void onFlingToDeleteCompleted() { + // We just dismiss the drag when we fling, so cleanup here + } + + public class AppItemIndexedInfo implements Comparable { + private boolean isChild; + private String mStartString; + private int mStringIndex; + private ArrayList<AppInfo> mInfo; + + private AppItemIndexedInfo(String startString, int bucketIndex, ArrayList<AppInfo> info, + boolean isChild) { + this.mStartString = startString; + this.mStringIndex = bucketIndex; + this.mInfo = info; + this.isChild = isChild; + + if (mStartString.equals(NUMERIC_OR_SPECIAL_HEADER)) { + this.mStringIndex = 0; + } + } + + public String getString() { + return mStartString; + } + + @Override + public int compareTo(Object o) { + if (o instanceof AppItemIndexedInfo) { + int otherBucketIndex = ((AppItemIndexedInfo) o).mStringIndex; + return Integer.compare(mStringIndex, otherBucketIndex); + } + return 0; + } + } + + @Override + public Object[] getSections() { + return mSectionHeaders.keySet().toArray(new String[mSectionHeaders.size()]); + } + + @Override + public int getPositionForSection(int sectionIndex) { + return mSectionHeaders.get(getSections()[sectionIndex]); + } + + @Override + public int getSectionForPosition(int position) { + return mSectionHeaders.get(mHeaderList.get(position).mStartString); + } + + private void filterProtectedApps(ArrayList<AppInfo> list) { + updateProtectedAppsList(mLauncher); + + Iterator<AppInfo> iterator = list.iterator(); + while (iterator.hasNext()) { + AppInfo appInfo = iterator.next(); + if (mProtectedApps.contains(appInfo.componentName)) { + iterator.remove(); + } + } + } + + private void updateProtectedAppsList(Context context) { + String protectedComponents = Settings.Secure.getString(context.getContentResolver(), + LauncherModel.SETTINGS_PROTECTED_COMPONENTS); + protectedComponents = protectedComponents == null ? "" : protectedComponents; + String [] flattened = protectedComponents.split("\\|"); + mProtectedApps = new ArrayList<ComponentName>(flattened.length); + for (String flat : flattened) { + ComponentName cmp = ComponentName.unflattenFromString(flat); + if (cmp != null) { + mProtectedApps.add(cmp); + } + } + } +} |