From 95dfdf845c3accc749609d8b955d55fc8df5adf1 Mon Sep 17 00:00:00 2001 From: Vineet Patil Date: Tue, 24 Nov 2015 15:43:37 -0800 Subject: Re-Implementation of hidden folders Change-Id: I2cdb881eb6a2608279d30a1cdfc1327a89ae7693 --- Android.mk | 3 +- AndroidManifest.xml | 2 + res/color/listitem_text.xml | 21 ++ res/drawable/ic_remove.xml | 29 ++ res/drawable/listitem_bg.xml | 21 +- res/drawable/listitem_selector.xml | 21 ++ res/layout/hidden_apps_list.xml | 22 ++ res/layout/hidden_apps_list_item.xml | 48 +++ res/layout/hidden_folder.xml | 69 ++++ res/layout/hidden_folder_apps_list_item.xml | 48 +++ res/layout/user_folder.xml | 8 + res/values/colors.xml | 6 + res/values/dimens.xml | 9 + src/com/android/launcher3/Folder.java | 103 +++++- src/com/android/launcher3/FolderIcon.java | 33 +- src/com/android/launcher3/FolderInfo.java | 4 +- .../android/launcher3/HiddenFolderFragment.java | 376 +++++++++++++++++++++ src/com/android/launcher3/Launcher.java | 69 +++- src/com/android/launcher3/LauncherModel.java | 31 ++ src/com/android/launcher3/LauncherProvider.java | 8 +- src/com/android/launcher3/LauncherSettings.java | 7 +- tests/Android.mk | 25 +- 22 files changed, 926 insertions(+), 37 deletions(-) create mode 100644 res/color/listitem_text.xml create mode 100644 res/drawable/ic_remove.xml create mode 100644 res/drawable/listitem_selector.xml create mode 100644 res/layout/hidden_apps_list.xml create mode 100644 res/layout/hidden_apps_list_item.xml create mode 100644 res/layout/hidden_folder.xml create mode 100644 res/layout/hidden_folder_apps_list_item.xml create mode 100644 src/com/android/launcher3/HiddenFolderFragment.java diff --git a/Android.mk b/Android.mk index f9ed664e8..0d030dfb8 100644 --- a/Android.mk +++ b/Android.mk @@ -25,7 +25,8 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-v4 \ - android-support-v7-recyclerview + android-support-v7-recyclerview \ + org.cyanogenmod.platform.internal LOCAL_SRC_FILES := $(call all-java-files-under, src) \ $(call all-java-files-under, WallpaperPicker/src) \ diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 80b059751..c8c0ec4b6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -56,6 +56,7 @@ + @@ -65,6 +66,7 @@ + + + + + + diff --git a/res/drawable/ic_remove.xml b/res/drawable/ic_remove.xml new file mode 100644 index 000000000..a37a64b82 --- /dev/null +++ b/res/drawable/ic_remove.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/res/drawable/listitem_bg.xml b/res/drawable/listitem_bg.xml index be1fedbe5..1a1e93d53 100644 --- a/res/drawable/listitem_bg.xml +++ b/res/drawable/listitem_bg.xml @@ -1,6 +1,21 @@ + - - \ No newline at end of file + android:state_pressed="true" android:drawable="@android:color/white" /> + + diff --git a/res/drawable/listitem_selector.xml b/res/drawable/listitem_selector.xml new file mode 100644 index 000000000..1a1e93d53 --- /dev/null +++ b/res/drawable/listitem_selector.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/res/layout/hidden_apps_list.xml b/res/layout/hidden_apps_list.xml new file mode 100644 index 000000000..a639ae094 --- /dev/null +++ b/res/layout/hidden_apps_list.xml @@ -0,0 +1,22 @@ + + + diff --git a/res/layout/hidden_apps_list_item.xml b/res/layout/hidden_apps_list_item.xml new file mode 100644 index 000000000..7061b35c3 --- /dev/null +++ b/res/layout/hidden_apps_list_item.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + diff --git a/res/layout/hidden_folder.xml b/res/layout/hidden_folder.xml new file mode 100644 index 000000000..529384737 --- /dev/null +++ b/res/layout/hidden_folder.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + diff --git a/res/layout/hidden_folder_apps_list_item.xml b/res/layout/hidden_folder_apps_list_item.xml new file mode 100644 index 000000000..33f9fa572 --- /dev/null +++ b/res/layout/hidden_folder_apps_list_item.xml @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml index 2152a9986..1a7e2d332 100644 --- a/res/layout/user_folder.xml +++ b/res/layout/user_folder.xml @@ -79,6 +79,14 @@ android:layout_gravity="center_vertical" layout="@layout/page_indicator" /> + + diff --git a/res/values/colors.xml b/res/values/colors.xml index 0cad1cefc..80c0d6f6d 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -44,6 +44,12 @@ #009688 #009688 + #FF6cd2ea + #FF485459 + #FFb2b0ab + #26000000 + #50000000 + #009688 #FFF diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 8c64ef0c5..95557d9c5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -44,6 +44,15 @@ 16dp + 0dp + 15dp + 20dp + 50dp + 30dp + 16dp + 60dp + 175dp + 50dp 4dip diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index 1e0827e54..ccb12b62a 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -23,7 +23,9 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; @@ -39,6 +41,7 @@ import android.text.Selection; import android.text.Spannable; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.view.ActionMode; import android.view.KeyEvent; import android.view.Menu; @@ -54,6 +57,7 @@ import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -67,9 +71,15 @@ import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.UiThreadCircularReveal; +import static cyanogenmod.content.Intent.ACTION_PROTECTED; +import static cyanogenmod.content.Intent.ACTION_PROTECTED_CHANGED; +import static cyanogenmod.content.Intent.EXTRA_PROTECTED_COMPONENTS; +import static cyanogenmod.content.Intent.EXTRA_PROTECTED_STATE; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; /** * Represents a set of icons chosen by the user or generated by the system. @@ -161,6 +171,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Thunk float mFolderIconPivotY; private boolean mIsEditingName = false; + ImageView mFolderLock; + private int mFolderLockHeight; + private boolean mDestroyed; @Thunk Runnable mDeferredAction; @@ -170,6 +183,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // Folder scrolling private int mScrollAreaOffset; + private boolean mHiddenFolder = false; + @Thunk int mScrollHintDir = DragController.SCROLL_NONE; @Thunk int mCurrentScrollDir = DragController.SCROLL_NONE; @@ -212,6 +227,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mContent = (FolderPagedView) findViewById(R.id.folder_content); mContent.setFolder(this); + // We find out how tall footer wants to be (it is set to wrap_content), so that + // we can allocate the appropriate amount of space for it. + int measureSpec = MeasureSpec.UNSPECIFIED; + mFolderName = (ExtendedEditText) findViewById(R.id.folder_name); mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() { @Override @@ -236,12 +255,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (hideLabels) { mFolderName.setVisibility(View.GONE); } + mFolderLock = (ImageView) findViewById(R.id.folder_lock); + mFolderLock.measure(measureSpec, measureSpec); + mFolderLock.setOnClickListener(this); + mFolderLockHeight = mFolderLock.getMeasuredHeight(); mFooter = findViewById(R.id.folder_footer); - // We find out how tall footer wants to be (it is set to wrap_content), so that - // we can allocate the appropriate amount of space for it. - int measureSpec = MeasureSpec.UNSPECIFIED; mFooter.measure(measureSpec, measureSpec); mFooterHeight = mFooter.getMeasuredHeight(); } @@ -268,6 +288,49 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (tag instanceof ShortcutInfo) { mLauncher.onClick(v); } + + if (v.getId() == R.id.folder_lock) { + startHiddenFolderManager(); + } + } + + public void startHiddenFolderManager() { + Bundle bundle = new Bundle(); + bundle.putBoolean(HiddenFolderFragment.HIDDEN_FOLDER_STATUS, mInfo.hidden); + mLauncher.validateLockForHiddenFolders(bundle, mFolderIcon); + } + + public List> getComponents() { + int size = mItemsInReadingOrder.size(); + List> components = + new ArrayList>(); + + for (int i = 0; i < size; i++) { + View v = mItemsInReadingOrder.get(i); + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + ShortcutInfo shortcut = (ShortcutInfo) tag; + components.add(Pair.create(shortcut.getIntent().getComponent(), shortcut.title)); + } + } + + return components; + } + + public void modifyProtectedApps(boolean protect) { + ArrayList components = new ArrayList(); + for (Pair item : getComponents()) { + if (item.first != null) { + components.add(item.first); + } + } + + Intent intent = new Intent(); + intent.setAction(ACTION_PROTECTED); + intent.putExtra(EXTRA_PROTECTED_STATE, protect); + intent.putExtra(EXTRA_PROTECTED_COMPONENTS, components); + + mLauncher.sendBroadcast(intent); } public boolean onLongClick(View v) { @@ -422,6 +485,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList updateTextViewFocus(); mInfo.addListener(this); + setFolderName(); + } + + public void setFolderName() { if (!sDefaultFolderName.contentEquals(mInfo.title)) { mFolderName.setText(mInfo.title); } else { @@ -1496,6 +1563,32 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return mItemsInReadingOrder; } + public ShortcutInfo getShortcutForComponent(ComponentName componentName) { + for (View v : mItemsInReadingOrder) { + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + ComponentName cName = ((ShortcutInfo) tag).getIntent().getComponent(); + if (cName.equals(componentName)) { + return (ShortcutInfo) tag; + } + } + } + + return null; + } + + public ShortcutInfo getShortcutForPosition(int position) { + if (position < 0 || position >= mItemsInReadingOrder.size()) { + return null; + } + View v = mItemsInReadingOrder.get(position); + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + return (ShortcutInfo) tag; + } + return null; + } + public void getLocationInDragLayer(int[] loc) { mLauncher.getDragLayer().getLocationInDragLayer(this, loc); } @@ -1513,6 +1606,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList outRect.right += mScrollAreaOffset; } + public View getViewFromPosition(int position) { + return mItemsInReadingOrder.get(position); + } + @Override public void fillInLaunchSourceData(Bundle sourceData) { // Fill in from the folder icon's launch source provider first diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index 85e3c3333..9b6e3026b 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -378,13 +378,23 @@ public class FolderIcon extends FrameLayout implements FolderListener { private boolean willAcceptItem(ItemInfo item) { final int itemType = item.itemType; + + boolean hidden = false; + if (item instanceof FolderInfo){ + hidden = ((FolderInfo) item).hidden; + } return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || - itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && - !mFolder.isFull() && item != mInfo && !mInfo.opened); + itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT || + itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) && + !mFolder.isFull() && item != mInfo && !mInfo.opened && + !hidden); } public boolean acceptDrop(Object dragInfo) { final ItemInfo item = (ItemInfo) dragInfo; + if (mInfo.hidden) { + return false; + } return !mFolder.isDestroyed() && willAcceptItem(item); } @@ -423,6 +433,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { item = ((AppInfo) mDragInfo).makeShortcut(); item.spanX = 1; item.spanY = 1; + } else if (mDragInfo instanceof FolderInfo) { + return; } else { // ShortcutInfo item = (ShortcutInfo) mDragInfo; @@ -690,6 +702,23 @@ public class FolderIcon extends FrameLayout implements FolderListener { } int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); + + // Hidden folder - don't display Preview + View folderLock = findViewById(R.id.folder_lock_image); + folderLock.setVisibility(mInfo.hidden ? VISIBLE : INVISIBLE); + View appView = findViewById(R.id.app_0); + appView.setVisibility(mInfo.hidden ? INVISIBLE : VISIBLE); + appView = findViewById(R.id.app_1); + appView.setVisibility(mInfo.hidden ? INVISIBLE : VISIBLE); + appView = findViewById(R.id.app_2); + appView.setVisibility(mInfo.hidden ? INVISIBLE : VISIBLE); + appView = findViewById(R.id.app_3); + appView.setVisibility(mInfo.hidden ? INVISIBLE : VISIBLE); + + if (mInfo.hidden) { + return; + } + if (!mAnimating) { for (int i = NUM_ITEMS_IN_PREVIEW; i >= 0; i--) { d = null; diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index 32d752ac0..b8da8cb6a 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -54,9 +54,10 @@ public class FolderInfo extends ItemInfo { public int options; /** - * The apps and shortcuts + * The apps and shortcuts and hidden status */ public ArrayList contents = new ArrayList(); + public Boolean hidden = false; ArrayList listeners = new ArrayList(); @@ -103,6 +104,7 @@ public class FolderInfo extends ItemInfo { super.onAddToDatabase(context, values); values.put(LauncherSettings.Favorites.TITLE, title.toString()); values.put(LauncherSettings.Favorites.OPTIONS, options); + values.put(LauncherSettings.Favorites.HIDDEN, hidden ? 1 : 0); } void addListener(FolderListener listener) { diff --git a/src/com/android/launcher3/HiddenFolderFragment.java b/src/com/android/launcher3/HiddenFolderFragment.java new file mode 100644 index 000000000..97c4dfd44 --- /dev/null +++ b/src/com/android/launcher3/HiddenFolderFragment.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2015 The Android Open Source 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.app.Fragment; +import android.app.FragmentTransaction; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.media.Image; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.InputType; +import android.util.Pair; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public class HiddenFolderFragment extends Fragment { + public static final String HIDDEN_FOLDER_FRAGMENT = "hiddenFolderFragment"; + public static final String HIDDEN_FOLDER_NAME = "hiddenFolderName"; + public static final String HIDDEN_FOLDER_STATUS = "hiddenFolderStatus"; + public static final String HIDDEN_FOLDER_INFO = "hiddenFolderInfo"; + public static final String HIDDEN_FOLDER_INFO_TITLES = "hiddenFolderInfoTitles"; + public static final String HIDDEN_FOLDER_LAUNCH = "hiddenFolderLaunchPosition"; + + private static final int REQ_LOCK_PATTERN = 1; + + private boolean mHidden; + private PackageManager mPackageManager; + private AppsAdapter mAppsAdapter; + private ArrayList mAppEntries; + + private EditText mFolderName; + private ListView mListView; + + private Launcher mLauncher; + + private boolean mAuth = false; + private boolean mSent = false; + + private OnClickListener mClicklistener = new OnClickListener() { + @Override + public void onClick(View v) { + mHidden = !mHidden; + + ImageView mLock = (ImageView) v; + Drawable mLockIcon = mHidden ? getResources().getDrawable(R.drawable.folder_locked) + : getResources().getDrawable(R.drawable.folder_unlocked); + mLock.setImageDrawable(mLockIcon); + } + }; + + @Override + public View onCreateView (LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.hidden_folder, container, false); + + mLauncher = (Launcher) getActivity(); + mPackageManager = mLauncher.getPackageManager(); + + mHidden = getArguments().getBoolean(HIDDEN_FOLDER_STATUS); + Folder folder = mLauncher.mHiddenFolderIcon.getFolder(); + String title = mLauncher.mHiddenFolderIcon.getFolderInfo().title.toString(); + + mFolderName = (EditText) v.findViewById(R.id.folder_name); + mFolderName.setText(title); + mFolderName.setSelectAllOnFocus(true); + mFolderName.setInputType(mFolderName.getInputType() | + InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); + mFolderName.setImeOptions(EditorInfo.IME_ACTION_DONE); + mFolderName.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + doneEditingText(v); + return true; + } + return false; + } + }); + mFolderName.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + doneEditingText(v); + } + } + }); + + ImageView mLock = (ImageView) v.findViewById(R.id.folder_lock_icon); + Drawable mLockIcon = mHidden ? getResources().getDrawable(R.drawable.folder_locked) + : getResources().getDrawable(R.drawable.folder_unlocked); + mLock.setImageDrawable(mLockIcon); + mLock.setOnClickListener(mClicklistener); + + mAppsAdapter = new AppsAdapter(mLauncher, R.layout.hidden_apps_list_item); + mAppsAdapter.setNotifyOnChange(true); + mAppEntries = loadApps(folder.getComponents()); + mAppsAdapter.clear(); + mAppsAdapter.addAll(mAppEntries); + + mListView = (ListView) v.findViewById(R.id.hidden_apps_list); + mListView.setAdapter(mAppsAdapter); + + // Apply insets + Launcher launcher = (Launcher) getActivity(); + LinearLayout.LayoutParams llp = + (LinearLayout.LayoutParams) mListView.getLayoutParams(); + // TODO: Uncomment this once the Settings for Trebuchet are merged + /*llp.bottomMargin += ((FrameLayout.LayoutParams) launcher.getOverviewPanel() + .findViewById(R.id.settings_container).getLayoutParams()).bottomMargin;*/ + mListView.setLayoutParams(llp); + + return v; + } + + private void doneEditingText(View v) { + InputMethodManager mInputMethodManager = (InputMethodManager) + mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); + mInputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + + mListView.requestFocus(); + } + + private ArrayList loadApps(List> items) { + ArrayList apps = new ArrayList(); + int pos = 0; + for (Pair item : items) { + apps.add(new AppEntry(item.first, item.second, pos)); + pos++; + } + return apps; + } + + private void removeComponentFromFolder(AppEntry app) { + ShortcutInfo info; + if (app.componentName != null) { + info = mLauncher.mHiddenFolderIcon.getFolder() + .getShortcutForComponent(app.componentName); + } else { + // Shortcut does not have componentName, use position since it maps to + // reading order position in the folder + info = mLauncher.mHiddenFolderIcon.getFolder().getShortcutForPosition(app.position); + } + mLauncher.mHiddenFolderIcon.getFolderInfo().remove(info); + + mAppEntries.remove(app); + mAppsAdapter.remove(app); + mAppsAdapter.notifyDataSetInvalidated(); + } + + public void saveHiddenFolderStatus(int position) { + String newTitle = mFolderName.getText().toString(); + if (mLauncher.mHiddenFolderIcon != null) { + if (position != -1) { + Folder folder = mLauncher.mHiddenFolderIcon.getFolder(); + View v = folder.getViewFromPosition(position); + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + ShortcutInfo shortcut = (ShortcutInfo) tag; + mLauncher.startActivitySafely(v, shortcut.getIntent(), v.getTag()); + return; + } + } + + // Folder name + FolderInfo info = mLauncher.mHiddenFolderIcon.getFolderInfo(); + if (!info.title.equals(newTitle)) { + info.setTitle(newTitle); + mLauncher.mHiddenFolderIcon.getFolder().setFolderName(); + LauncherModel.updateItemInDatabase(mLauncher, info); + } + + // Folder hidden status + if (info.hidden == mHidden) { + return; + } else { + info.hidden = mHidden; + // flip the boolean value to accomodate framework + // in framework "false" is "protected" and "true" is "visible" + mLauncher.mHiddenFolderIcon.getFolder().modifyProtectedApps(!info.hidden); + + LauncherModel.updateItemInDatabase(mLauncher, info); + // We need to make sure this change gets written to the DB before + // OnResume restarts the process + mLauncher.mModel.flushWorkerThread(); + } + } + + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction + .remove(mLauncher.mHiddenFolderFragment).commit(); + } + + public class AppsAdapter extends ArrayAdapter { + + private final LayoutInflater mInflator; + + private ConcurrentHashMap mIcons; + private Drawable mDefaultImg; + private List mApps; + + public AppsAdapter(Context context, int textViewResourceId) { + super(context, textViewResourceId); + + mApps = new ArrayList(); + + mInflator = LayoutInflater.from(context); + + // set the default icon till the actual app icon is loaded in async + // task + mDefaultImg = context.getResources().getDrawable( + android.R.mipmap.sym_def_app_icon); + mIcons = new ConcurrentHashMap(); + } + + @Override + public View getView(final int position, View convertView, + ViewGroup parent) { + final AppViewHolder viewHolder; + + if (convertView == null) { + convertView = mInflator.inflate( + R.layout.hidden_folder_apps_list_item, parent, false); + viewHolder = new AppViewHolder(convertView, position); + convertView.setTag(viewHolder); + } else { + viewHolder = (AppViewHolder) convertView.getTag(); + } + + final AppEntry app = getItem(position); + + viewHolder.title.setText(app.title); + + Drawable icon = null; + if (app.componentName != null) { + icon = mIcons.get(app.componentName.getPackageName()); + } + viewHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg); + viewHolder.remove.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + removeComponentFromFolder(app); + } + }); + + convertView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + saveHiddenFolderStatus(position); + } + }); + + return convertView; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + // If we have new items, we have to load their icons + // If items were deleted, remove them from our mApps + List newApps = new ArrayList(getCount()); + List oldApps = new ArrayList(getCount()); + for (int i = 0; i < getCount(); i++) { + AppEntry app = getItem(i); + if (mApps.contains(app)) { + oldApps.add(app); + } else { + newApps.add(app); + } + } + + if (newApps.size() > 0) { + new LoadIconsTask().execute(newApps.toArray(new AppEntry[] {})); + newApps.addAll(oldApps); + mApps = newApps; + } else { + mApps = oldApps; + } + } + + /** + * An asynchronous task to load the icons of the installed applications. + */ + private class LoadIconsTask extends AsyncTask { + @Override + protected Void doInBackground(AppEntry... apps) { + for (AppEntry app : apps) { + try { + // Widget icons do not have a + if (app.componentName == null || + mIcons.containsKey(app.componentName.getPackageName())) { + continue; + } + Drawable icon = mPackageManager + .getApplicationIcon(app.componentName + .getPackageName()); + mIcons.put(app.componentName.getPackageName(), icon); + publishProgress(); + } catch (PackageManager.NameNotFoundException e) { + // ignored; app will show up with default image + } + } + + return null; + } + + @Override + protected void onProgressUpdate(Void... progress) { + notifyDataSetChanged(); + } + } + } + + private final class AppEntry { + public final ComponentName componentName; + public final CharSequence title; + public final int position; + + public AppEntry(ComponentName component, CharSequence title, int position) { + this.componentName = component; + this.title = title; + this.position = position; + } + } + + private static class AppViewHolder { + public final TextView title; + public final ImageView icon; + public final ImageView remove; + public final int position; + + public AppViewHolder(View parentView, int position) { + icon = (ImageView) parentView.findViewById(R.id.icon); + remove = (ImageView) parentView.findViewById(R.id.remove); + title = (TextView) parentView.findViewById(R.id.title); + this.position = position; + } + } +} diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 454d77614..15373c20b 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -29,6 +29,10 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AlertDialog; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.app.Dialog; import android.app.SearchManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; @@ -155,6 +159,8 @@ public class Launcher extends Activity private static final int REQUEST_PERMISSION_CALL_PHONE = 13; + private static final int REQUEST_LOCK_PATTERN = 14; + private static final int WORKSPACE_BACKGROUND_GRADIENT = 0; private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1; private static final int WORKSPACE_BACKGROUND_BLACK = 2; @@ -251,6 +257,7 @@ public class Launcher extends Activity @Thunk DragLayer mDragLayer; private DragController mDragController; private View mWeightWatcher; + protected HiddenFolderFragment mHiddenFolderFragment; private AppWidgetManagerCompat mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; @@ -261,6 +268,9 @@ public class Launcher extends Activity private int[] mTmpAddItemCellCoordinates = new int[2]; + protected FolderIcon mHiddenFolderIcon; + private boolean mHiddenFolderAuth = false; + @Thunk Hotseat mHotseat; private ViewGroup mOverviewPanel; OverviewSettingsPanel mOverviewSettingsPanel; @@ -300,7 +310,7 @@ public class Launcher extends Activity private Bundle mSavedInstanceState; - private LauncherModel mModel; + protected LauncherModel mModel; private IconCache mIconCache; @Thunk boolean mUserPresent = true; private boolean mVisible = false; @@ -758,6 +768,23 @@ public class Launcher extends Activity showWorkspace(false); } return; + } else if (requestCode == REQUEST_LOCK_PATTERN) { + mHiddenFolderAuth = true; + switch (resultCode) { + case RESULT_OK: + FragmentManager fragmentManager = getFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + + fragmentTransaction.setCustomAnimations(0, 0); + fragmentTransaction.replace(R.id.launcher, mHiddenFolderFragment, + HiddenFolderFragment.HIDDEN_FOLDER_FRAGMENT); + fragmentTransaction.commit(); + break; + case RESULT_CANCELED: + // User failed to enter/confirm a lock pattern, back out + break; + } + return; } boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || @@ -1094,6 +1121,18 @@ public class Launcher extends Activity } reloadLauncherIfNeeded(); + + //Close out Fragments + Fragment f1 = getFragmentManager().findFragmentByTag( + HiddenFolderFragment.HIDDEN_FOLDER_FRAGMENT); + if (f1 != null && !mHiddenFolderAuth) { + mHiddenFolderFragment.saveHiddenFolderStatus(-1); + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction + .remove(mHiddenFolderFragment).commit(); + } else { + mHiddenFolderAuth = false; + } } @Override @@ -2165,6 +2204,20 @@ public class Launcher extends Activity return mDragController; } + public void validateLockForHiddenFolders(Bundle bundle, FolderIcon info) { + // Validate Lock Pattern + Intent lockPatternActivity = new Intent(); + lockPatternActivity.setClassName( + "com.android.settings", + "com.android.settings.applications.LockPatternActivity"); + startActivityForResult(lockPatternActivity, REQUEST_LOCK_PATTERN); + mHiddenFolderAuth = false; + + mHiddenFolderIcon = info; + mHiddenFolderFragment = new HiddenFolderFragment(); + mHiddenFolderFragment.setArguments(bundle); + } + @Override public void startActivityForResult(Intent intent, int requestCode) { onStartForResult(requestCode); @@ -2560,6 +2613,15 @@ public class Launcher extends Activity return; } + Fragment f1 = getFragmentManager().findFragmentByTag( + HiddenFolderFragment.HIDDEN_FOLDER_FRAGMENT); + if (f1 != null) { + mHiddenFolderFragment.saveHiddenFolderStatus(-1); + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction + .remove(mHiddenFolderFragment).commit(); + } + if (isAppsViewVisible()) { showWorkspace(true); } else if (isWidgetsViewVisible()) { @@ -3245,6 +3307,11 @@ public class Launcher extends Activity FolderInfo info = folder.mInfo; + if (info.hidden) { + folder.startHiddenFolderManager(); + return; + } + info.opened = true; // While the folder is open, the position of the icon cannot change. diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index e3170e93b..c3ad6a8a1 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -109,6 +109,8 @@ public class LauncherModel extends BroadcastReceiver @Thunk boolean mIsLoaderTaskRunning; @Thunk boolean mHasLoaderCompletedOnce; + private volatile boolean mFlushingWorkerThread; + private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings"; @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); @@ -775,6 +777,35 @@ public class LauncherModel extends BroadcastReceiver } } + public void flushWorkerThread() { + mFlushingWorkerThread = true; + Runnable waiter = new Runnable() { + public void run() { + synchronized (this) { + notifyAll(); + mFlushingWorkerThread = false; + } + } + }; + + synchronized(waiter) { + runOnWorkerThread(waiter); + if (mLoaderTask != null) { + synchronized(mLoaderTask) { + mLoaderTask.notify(); + } + } + boolean success = false; + while (!success) { + try { + waiter.wait(); + success = true; + } catch (InterruptedException e) { + } + } + } + } + /** * Move an item in the DB to a new */ diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 8791e9e57..fb99853f9 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -68,7 +68,7 @@ public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; private static final boolean LOGD = false; - private static final int DATABASE_VERSION = 26; + private static final int DATABASE_VERSION = 27; public static final String AUTHORITY = ProviderConfig.AUTHORITY; @@ -537,6 +537,7 @@ public class LauncherProvider extends ContentProvider { "modified INTEGER NOT NULL DEFAULT 0," + "restored INTEGER NOT NULL DEFAULT 0," + "profileId INTEGER DEFAULT " + userSerialNumber + "," + + "hidden INTEGER DEFAULT 0" + "," + "rank INTEGER NOT NULL DEFAULT 0," + "options INTEGER NOT NULL DEFAULT 0" + ");"); @@ -718,7 +719,10 @@ public class LauncherProvider extends ContentProvider { ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mContext); case 25: convertShortcutsToLauncherActivities(db); - case 26: { + case 26: + // add hidden column + addIntegerColumn(db, "hidden", 0); + case 27: { // DB Upgraded successfully return; } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 5cde2e588..2080bc643 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -40,7 +40,12 @@ public class LauncherSettings { *

Type: TEXT

*/ public static final String TITLE = "title"; - + + /** + * Folder Hidden status + */ + public static final String HIDDEN = "hidden"; + /** * The Intent URL of the gesture, describing what it points to. This * value is given to {@link android.content.Intent#parseUri(String, int)} to create diff --git a/tests/Android.mk b/tests/Android.mk index eba4ade48..e579af78b 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -12,26 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -LOCAL_PATH := $(call my-dir) - -src_dirs := src -res_dirs := res - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_STATIC_JAVA_LIBRARIES := android-support-test - -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs)) -LOCAL_AAPT_FLAGS := --auto-add-overlay - -LOCAL_SDK_VERSION := 21 - -LOCAL_PACKAGE_NAME := Launcher3Tests - -LOCAL_INSTRUMENTATION_FOR := Launcher3 - -include $(BUILD_PACKAGE) +#LOCAL_PATH := $(call my-dir) +#include $(call all-makefiles-under,$(LOCAL_PATH)) -- cgit v1.2.3