From 4e4db4563a7ddf262993a9312781c66d34485ea4 Mon Sep 17 00:00:00 2001 From: Raj Yengisetty Date: Mon, 10 Nov 2014 10:35:00 -0800 Subject: Protected App [3/3] Trebuchet - Protected Apps - Build fixed to work with LOCAL_SDK_VERSION - Fixed adding components to protected folders and adding protected folders to other folders - Fixed issues with EditText for FolderName - Adding support for Settings hooks - Uses Setting's LockPattern for Protected Apps - Add Read from Settings Secure DB (DO NOT WRITE!) - Protecting a folder updates Launcher without restart - Batch send component visibility Conflicts: AndroidManifest.xml res/values/preferences_defaults.xml src/com/android/launcher3/AppsCustomizePagedView.java src/com/android/launcher3/Folder.java src/com/android/launcher3/FolderIcon.java src/com/android/launcher3/Launcher.java src/com/android/launcher3/LauncherModel.java src/com/android/launcher3/LauncherProvider.java src/com/android/launcher3/OverviewSettingsPanel.java Change-Id: I41c295e7f2c9abc9b2e77e6e3d39b7ca60d47139 --- .../android/launcher3/AppsCustomizePagedView.java | 96 ++++++ src/com/android/launcher3/Folder.java | 112 ++++++- src/com/android/launcher3/FolderIcon.java | 28 +- src/com/android/launcher3/FolderInfo.java | 4 +- .../android/launcher3/HiddenFolderFragment.java | 325 +++++++++++++++++++++ src/com/android/launcher3/Launcher.java | 81 ++++- src/com/android/launcher3/LauncherModel.java | 107 +++++++ src/com/android/launcher3/LauncherProvider.java | 8 +- src/com/android/launcher3/LauncherSettings.java | 5 + src/com/android/launcher3/Lists.java | 64 ++++ .../android/launcher3/OverviewSettingsPanel.java | 8 +- .../list/SettingsPinnedHeaderAdapter.java | 7 + 12 files changed, 832 insertions(+), 13 deletions(-) create mode 100644 src/com/android/launcher3/HiddenFolderFragment.java create mode 100644 src/com/android/launcher3/Lists.java (limited to 'src/com/android') diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java index eac84dde5..abccbb0da 100644 --- a/src/com/android/launcher3/AppsCustomizePagedView.java +++ b/src/com/android/launcher3/AppsCustomizePagedView.java @@ -35,6 +35,8 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Process; +import android.provider.Settings; +import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -202,6 +204,16 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen private ArrayList mApps; private ArrayList mWidgets; + private ArrayList mFilteredApps; + private ArrayList mFilteredWidgets; + private ArrayList mProtectedApps; + private ArrayList mProtectedPackages; + + // Cling + private boolean mHasShownAllAppsCling; + private int mClingFocusedX; + private int mClingFocusedY; + // Caching private IconCache mIconCache; @@ -292,6 +304,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } setSinglePageInViewport(); + + updateProtectedAppsList(context); } @Override @@ -1567,6 +1581,88 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen } } + 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(flattened.length); + mProtectedPackages = new ArrayList(flattened.length); + for (String flat : flattened) { + ComponentName cmp = ComponentName.unflattenFromString(flat); + if (cmp != null) { + mProtectedApps.add(cmp); + mProtectedPackages.add(cmp.getPackageName()); + } + } + } + + public void filterAppsWithoutInvalidate() { + updateProtectedAppsList(mLauncher); + + mFilteredApps = new ArrayList(mApps); + Iterator iterator = mFilteredApps.iterator(); + while (iterator.hasNext()) { + AppInfo appInfo = iterator.next(); + boolean system = (appInfo.flags & AppInfo.DOWNLOADED_FLAG) == 0; + if (mProtectedApps.contains(appInfo.componentName) || + (system && !getShowSystemApps()) || + (!system && !getShowDownloadedApps())) { + iterator.remove(); + } + } + Collections.sort(mFilteredApps, getComparatorForSortMode()); + } + + public void filterApps() { + filterAppsWithoutInvalidate(); + updatePageCountsAndInvalidateData(); + } + + public void filterWidgetsWithoutInvalidate() { + updateProtectedAppsList(mLauncher); + + mFilteredWidgets = new ArrayList(mWidgets); + + Iterator iterator = mFilteredWidgets.iterator(); + while (iterator.hasNext()) { + Object o = iterator.next(); + + String packageName; + if (o instanceof AppWidgetProviderInfo) { + AppWidgetProviderInfo widgetInfo = (AppWidgetProviderInfo) o; + if (widgetInfo.provider == null) { + continue; + } + packageName = widgetInfo.provider.getPackageName(); + } else if (o instanceof ResolveInfo) { + ResolveInfo shortcut = (ResolveInfo) o; + packageName = shortcut.activityInfo.applicationInfo.packageName; + } else { + Log.w(TAG, "Unknown class in widgets list: " + o.getClass()); + continue; + } + + int flags; + try { + flags = AppInfo.initFlags(mPackageManager.getPackageInfo(packageName, 0)); + } catch (NameNotFoundException e) { + flags = 0; + } + boolean system = (flags & AppInfo.DOWNLOADED_FLAG) == 0; + if (mProtectedPackages.contains(packageName) || + (system && !getShowSystemApps()) || + (!system && !getShowDownloadedApps())) { + iterator.remove(); + } + } + } + + public void filterWidgets() { + filterWidgetsWithoutInvalidate(); + updatePageCountsAndInvalidateData(); + } + public void reset() { // If we have reset, then we should not continue to restore the previous state mSaveInstanceStateItemIndex = -1; diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index 1890af47d..fa34e6327 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -21,10 +21,13 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Bundle; import android.os.SystemClock; import android.support.v4.widget.AutoScrollHelper; import android.text.InputType; @@ -44,7 +47,9 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; +import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -62,6 +67,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList View.OnFocusChangeListener { private static final String TAG = "Launcher.Folder"; + private static final String PROTECTED_ACTION = "cyanogenmod.intent.action.PACKAGE_PROTECTED"; + private static final String PROTECTED_STATE = + "cyanogenmod.intent.action.PACKAGE_PROTECTED_STATE"; + private static final String PROTECTED_COMPONENT = + "cyanogenmod.intent.action.PACKAGE_PROTECTED_COMPONENT"; + protected DragController mDragController; protected Launcher mLauncher; protected FolderInfo mInfo; @@ -107,6 +118,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private boolean mSuppressFolderDeletion = false; private boolean mItemAddedBackToSelfViaIcon = false; FolderEditText mFolderName; + ImageView mFolderLock; + RelativeLayout mFolderTitleSection; private float mFolderIconPivotX; private float mFolderIconPivotY; @@ -131,6 +144,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private boolean mDeferDropAfterUninstall; private boolean mUninstallSuccessful; + private boolean mHiddenFolder = false; + /** * Used to inflate the Workspace from XML. * @@ -181,6 +196,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList super.onFinishInflate(); mScrollView = (ScrollView) findViewById(R.id.scroll_view); mContent = (CellLayout) findViewById(R.id.folder_content); + int measureSpec = MeasureSpec.UNSPECIFIED; mFocusIndicatorHandler = new FocusIndicatorView(getContext()); mContent.addView(mFocusIndicatorHandler, 0); @@ -200,7 +216,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // We find out how tall the text view 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.measure(measureSpec, measureSpec); mFolderNameHeight = mFolderName.getMeasuredHeight(); @@ -211,6 +226,19 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mFolderName.setInputType(mFolderName.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView); + + if (SettingsProvider.getBoolean(mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default)) { + mFolderName.setVisibility(View.GONE); + mFolderNameHeight = getPaddingBottom(); + } + + mFolderLock = (ImageView) findViewById(R.id.folder_lock); + mFolderTitleSection = (RelativeLayout) findViewById(R.id.folder_title_section); + mFolderLock.measure(measureSpec, measureSpec); + mFolderLock.setOnClickListener(this); + mFolderTitleSection.measure(measureSpec, measureSpec); } private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { @@ -235,6 +263,60 @@ 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 String[] getComponentTitles() { + int size = mItemsInReadingOrder.size(); + String[] componentsTitles = new String[size]; + for (int i = 0; i < size; i++) { + View v = mItemsInReadingOrder.get(i); + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + componentsTitles[i] = ((ShortcutInfo) tag).title.toString(); + } + } + return componentsTitles; + } + + public String[] getComponents() { + String components = getComponentString(); + return components.split("\\|"); + } + + public void modifyProtectedApps(boolean protect) { + String components = getComponentString(); + + Intent intent = new Intent(); + intent.setAction(PROTECTED_ACTION); + intent.putExtra(PROTECTED_STATE, protect); + intent.putExtra(PROTECTED_COMPONENT, components); + + mLauncher.sendBroadcast(intent); + } + + private String getComponentString() { + int size = mItemsInReadingOrder.size(); + String components = ""; + for (int i = 0; i < size; i++) { + View v = mItemsInReadingOrder.get(i); + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + ComponentName componentName = ((ShortcutInfo) tag).getIntent().getComponent(); + components += componentName.flattenToString() + "|"; + } + } + + return components; } public boolean onLongClick(View v) { @@ -270,6 +352,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void startEditingFolderName() { mFolderName.setHint(""); mIsEditingName = true; + + mInputMethodManager.showSoftInput(mFolderName, 0); } public void dismissEditingName() { @@ -409,6 +493,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList updateTextViewFocus(); mInfo.addListener(this); + setFolderName(); + updateItemLocationsInDatabase(); + } + + public void setFolderName() { if (!sDefaultFolderName.contentEquals(mInfo.title)) { mFolderName.setText(mInfo.title); } else { @@ -1085,13 +1174,16 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } private int getFolderHeight() { - int height = getPaddingTop() + getPaddingBottom() - + getContentAreaHeight() + mFolderNameHeight; + int height = getPaddingTop() + getPaddingBottom() + mFolderNameHeight + + getContentAreaHeight(); return height; } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); + int width = getPaddingLeft() + + getPaddingRight() + + Math.max(mContent.getDesiredWidth(), + mFolderTitleSection.getMeasuredWidth()); int height = getFolderHeight(); int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(), MeasureSpec.EXACTLY); @@ -1106,8 +1198,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec); - mFolderName.measure(contentAreaWidthSpec, - MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY)); + mFolderName.measure(contentAreaWidthSpec, MeasureSpec.makeMeasureSpec( + mFolderNameHeight, MeasureSpec.EXACTLY)); + mFolderLock.measure(contentAreaWidthSpec, MeasureSpec.makeMeasureSpec( + mFolderNameHeight, MeasureSpec.EXACTLY)); + mFolderTitleSection.measure(contentAreaWidthSpec, MeasureSpec + .makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY)); setMeasuredDimension(width, height); } @@ -1392,4 +1488,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void getHitRectRelativeToDragLayer(Rect outRect) { getHitRect(outRect); } + + public View getViewFromPosition(int position) { + return mItemsInReadingOrder.get(position); + } } diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index a359f1180..ef31bcf99 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -322,13 +322,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); } @@ -619,6 +629,20 @@ public class FolderIcon extends FrameLayout implements FolderListener { } int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); + + // Hidden folder - don't display Preview + if (mInfo.hidden) { + mParams = computePreviewItemDrawingParams(NUM_ITEMS_IN_PREVIEW/2, mParams); + canvas.save(); + canvas.translate(mParams.transX + mPreviewOffsetX, mParams.transY + mPreviewOffsetY); + canvas.scale(mParams.scale, mParams.scale); + Drawable lock = getResources().getDrawable(R.drawable.folder_lock); + lock.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); + lock.draw(canvas); + canvas.restore(); + return; + } + if (!mAnimating) { for (int i = nItemsInPreview - 1; i >= 0; i--) { v = (TextView) items.get(i); diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index 85a792f4b..104bd6cc1 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -35,9 +35,10 @@ public class FolderInfo extends ItemInfo { boolean opened; /** - * The apps and shortcuts + * The apps and shortcuts and hidden status */ ArrayList contents = new ArrayList(); + Boolean hidden = false; ArrayList listeners = new ArrayList(); @@ -83,6 +84,7 @@ public class FolderInfo extends ItemInfo { void onAddToDatabase(Context context, ContentValues values) { super.onAddToDatabase(context, values); values.put(LauncherSettings.Favorites.TITLE, title.toString()); + 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..d43f61d23 --- /dev/null +++ b/src/com/android/launcher3/HiddenFolderFragment.java @@ -0,0 +1,325 @@ +package com.android.launcher3; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.media.Image; +import android.text.InputType; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.*; +import com.android.launcher3.settings.SettingsProvider; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; + +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 String[] mComponentInfo; + private String[] mComponentTitles; + 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_lock_light) + : getResources().getDrawable(R.drawable.folder_unlock); + 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(); + mComponentInfo = folder.getComponents(); + mComponentTitles = folder.getComponentTitles(); + 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_lock_light) + : getResources().getDrawable(R.drawable.folder_unlock); + mLock.setImageDrawable(mLockIcon); + mLock.setOnClickListener(mClicklistener); + + mAppsAdapter = new AppsAdapter(mLauncher, R.layout.hidden_apps_list_item); + mAppsAdapter.setNotifyOnChange(true); + mAppEntries = loadApps(); + mAppsAdapter.clear(); + mAppsAdapter.addAll(mAppEntries); + + mListView = (ListView) v.findViewById(R.id.hidden_apps_list); + mListView.setAdapter(mAppsAdapter); + + 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() { + ArrayList apps = new ArrayList(); + int size = mComponentInfo.length; + for (int i = 0; i < size; i++) { + apps.add(new AppEntry(mComponentInfo[i], mComponentTitles[i])); + } + return apps; + } + + 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) { + mLauncher.startActivitySafely(v, + ((ShortcutInfo) tag).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(); + } + + AppEntry app = getItem(position); + + viewHolder.title.setText(app.title); + + Drawable icon = mIcons.get(app.componentName.getPackageName()); + viewHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg); + + convertView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + saveHiddenFolderStatus(viewHolder.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 { + if (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 String title; + + public AppEntry(String component, String title) { + componentName = ComponentName.unflattenFromString(component); + this.title = title; + } + } + + private static class AppViewHolder { + public final TextView title; + public final ImageView icon; + public final int position; + + public AppViewHolder(View parentView, int position) { + icon = (ImageView) parentView.findViewById(R.id.icon); + 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 dcc87b83a..1c07194f7 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -168,6 +168,8 @@ public class Launcher extends Activity static final int REQUEST_PICK_ICON = 13; + private static final int REQUEST_LOCK_PATTERN = 14; + /** * IntentStarter uses request codes starting with this. This must be greater than all activity * request codes used internally. @@ -272,6 +274,8 @@ public class Launcher extends Activity private DragController mDragController; private View mWeightWatcher; private TransitionEffectsFragment mTransitionEffectsFragment; + private LauncherClings mLauncherClings; + protected HiddenFolderFragment mHiddenFolderFragment; private AppWidgetManagerCompat mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; @@ -284,6 +288,9 @@ public class Launcher extends Activity private FolderInfo mFolderInfo; + protected FolderIcon mHiddenFolderIcon; + private boolean mHiddenFolderAuth = false; + private Hotseat mHotseat; private ViewGroup mOverviewPanel; private View mDarkPanel; @@ -319,7 +326,7 @@ public class Launcher extends Activity private Dialog mTransitionEffectDialog; - private LauncherModel mModel; + protected LauncherModel mModel; private IconCache mIconCache; private boolean mUserPresent = true; private boolean mVisible = false; @@ -422,6 +429,16 @@ public class Launcher extends Activity return Log.isLoggable(propertyName, Log.VERBOSE); } + private BroadcastReceiver protectedAppsChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Update the workspace + updateDynamicGrid(); + mWorkspace.hideOutlines(); + mSearchDropTargetBar.showSearchBar(false); + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { if (DEBUG_STRICT_MODE) { @@ -514,6 +531,10 @@ public class Launcher extends Activity showFirstRunActivity(); showFirstRunClings(); } + IntentFilter protectedAppsFilter = new IntentFilter( + "cyanogenmod.intent.action.PROTECTED_COMPONENT_UPDATE"); + registerReceiver(protectedAppsChangedReceiver, protectedAppsFilter, + "cyanogenmod.permission.PROTECTED_APP", null); } @Override @@ -822,6 +843,23 @@ public class Launcher extends Activity mWorkspace.exitOverviewMode(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 || @@ -1100,12 +1138,22 @@ public class Launcher extends Activity PackageInstallerCompat.getInstance(this).onResume(); - //Close out TransitionEffects Fragment + //Close out Fragments Fragment f = getFragmentManager().findFragmentByTag( TransitionEffectsFragment.TRANSITION_EFFECTS_FRAGMENT); if (f != null) { mTransitionEffectsFragment.setEffect(); } + 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 @@ -2190,12 +2238,28 @@ public class Launcher extends Activity PackageInstallerCompat.getInstance(this).onStop(); LauncherAnimUtils.onDestroyActivity(); + + unregisterReceiver(protectedAppsChangedReceiver); } public DragController getDragController() { 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) { if (requestCode >= 0) { @@ -2541,6 +2605,14 @@ public class Launcher extends Activity @Override public void onBackPressed() { + Fragment f1 = getFragmentManager().findFragmentByTag( + HiddenFolderFragment.HIDDEN_FOLDER_FRAGMENT); + if (f1 != null) { + mHiddenFolderFragment.saveHiddenFolderStatus(-1); + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction + .remove(mHiddenFolderFragment).commit(); + } if (isAllAppsVisible()) { if (mAppsCustomizeContent.getContentType() == AppsCustomizePagedView.ContentType.Applications) { @@ -3145,6 +3217,11 @@ public class Launcher extends Activity Folder folder = folderIcon.getFolder(); FolderInfo info = folder.mInfo; + if (info.hidden) { + folder.startHiddenFolderManager(); + return; + } + info.opened = true; // Just verify that the folder hasn't already been added to the DragLayer. diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 39035cdd8..80a488b8e 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -48,6 +48,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.provider.BaseColumns; import android.text.TextUtils; +import android.provider.Settings; import android.util.Log; import android.util.Pair; @@ -89,6 +90,7 @@ public class LauncherModel extends BroadcastReceiver private static final boolean REMOVE_UNRESTORED_ICONS = true; static final String TAG = "Launcher.Model"; + public static final String SETTINGS_PROTECTED_COMPONENTS = "protected_components"; // true = use a "More Apps" folder for non-workspace apps on upgrade // false = strew non-workspace apps across the workspace on upgrade @@ -979,6 +981,7 @@ public class LauncherModel extends BroadcastReceiver final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); + final int hiddenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.HIDDEN); FolderInfo folderInfo = null; switch (c.getInt(itemTypeIndex)) { @@ -993,6 +996,7 @@ public class LauncherModel extends BroadcastReceiver folderInfo.screenId = c.getInt(screenIndex); folderInfo.cellX = c.getInt(cellXIndex); folderInfo.cellY = c.getInt(cellYIndex); + folderInfo.hidden = c.getInt(hiddenIndex) > 0; return folderInfo; } @@ -1956,6 +1960,9 @@ public class LauncherModel extends BroadcastReceiver LauncherSettings.Favorites.PROFILE_ID); //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); //final int displayModeIndex = c.getColumnIndexOrThrow( + final int hiddenIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.HIDDEN); + //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); //final int displayModeIndex = c.getColumnIndexOrThrow( // LauncherSettings.Favorites.DISPLAY_MODE); ShortcutInfo info; @@ -2178,6 +2185,7 @@ public class LauncherModel extends BroadcastReceiver folderInfo.cellY = c.getInt(cellYIndex); folderInfo.spanX = 1; folderInfo.spanY = 1; + folderInfo.hidden = c.getInt(hiddenIndex) > 0; // check & update map of what's occupied deleteOnInvalidPlacement.set(false); @@ -2586,6 +2594,105 @@ public class LauncherModel extends BroadcastReceiver runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); } + private void removeHiddenAppsWorkspaceItems( + final ArrayList workspaceItems, + final ArrayList appWidgets, + final HashMap folders) { + + // Get hidden apps + ArrayList mHiddenApps = new ArrayList(); + ArrayList mHiddenAppsPackages = new ArrayList(); + Context context = mApp.getContext(); + // Since Trebuchet is compiled using the SDK we have to hardcode this string + String protectedComponents = Settings.Secure.getString(context.getContentResolver(), + SETTINGS_PROTECTED_COMPONENTS); + protectedComponents = protectedComponents == null ? "" : protectedComponents; + String[] flattened = protectedComponents.split("\\|"); + boolean hideShortcuts = SettingsProvider.getBoolean(context, + SettingsProvider.SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_SHORTCUTS, + R.bool.preferences_interface_drawer_remove_hidden_apps_shortcuts_default); + boolean hideWidgets = SettingsProvider.getBoolean(context, + SettingsProvider.SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_WIDGETS, + R.bool.preferences_interface_drawer_remove_hidden_apps_widgets_default); + + for (String flat : flattened) { + ComponentName cmp = ComponentName.unflattenFromString(flat); + if (cmp != null) { + mHiddenApps.add(cmp); + mHiddenAppsPackages.add(cmp.getPackageName()); + } + } + + // Shortcuts + if (hideShortcuts) { + int N = workspaceItems.size() - 1; + for (int i = N; i >= 0; i--) { + final ItemInfo item = workspaceItems.get(i); + if (item instanceof ShortcutInfo) { + ShortcutInfo shortcut = (ShortcutInfo)item; + if (shortcut.intent != null && shortcut.intent.getComponent() != null) { + if (mHiddenApps.contains(shortcut.intent.getComponent())) { + LauncherModel.deleteItemFromDatabase(mContext, shortcut); + workspaceItems.remove(i); + } + } + } else { + // Only remove items from folders that aren't hidden + final FolderInfo folder = (FolderInfo)item; + List shortcuts = folder.contents; + + int NN = shortcuts.size() - 1; + for (int j = NN; j >= 0; j--) { + ShortcutInfo sci = shortcuts.get(j); + if (sci.intent != null && sci.intent.getComponent() != null) { + if (!folder.hidden){ + if (mHiddenApps.contains(sci.intent.getComponent())) { + LauncherModel.deleteItemFromDatabase(mContext, sci); + folder.remove(sci); + } + } else { + if (!mHiddenApps.contains(sci.intent.getComponent())) { + LauncherModel.deleteItemFromDatabase(mContext, sci); + folder.remove(sci); + } + } + + } + } + + if (folder.contents.size() == 1 && !folder.hidden) { + ShortcutInfo finalItem = folder.contents.get(0); + finalItem.container = folder.container; + LauncherModel.deleteItemFromDatabase(mContext, folder); + LauncherModel.addOrMoveItemInDatabase(mContext, finalItem, folder.container, + folder.screenId, folder.cellX, folder.cellY); + workspaceItems.remove(i); + workspaceItems.add(finalItem); + folders.remove(Long.valueOf(item.id)); + } else if (folder.contents.size() == 0 /*&& !(folder instanceof LiveFolderInfo)*/) { + LauncherModel.deleteFolderContentsFromDatabase(mContext, folder); + workspaceItems.remove(i); + folders.remove(Long.valueOf(item.id)); + } + } + } + } + + // AppWidgets + if (hideWidgets) { + int N = appWidgets.size() - 1; + for (int i = N; i >= 0; i--) { + final LauncherAppWidgetInfo item = appWidgets.get(i); + if (item.providerName != null) { + if (mHiddenAppsPackages.contains(item.providerName.getPackageName())) { + LauncherModel.deleteItemFromDatabase(mContext, item); + appWidgets.remove(i); + } + } + } + } + } + private void bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList workspaceItems, final ArrayList appWidgets, diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 6cc1688de..8da3a2482 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -75,7 +75,7 @@ public class LauncherProvider extends ContentProvider { private static final String DATABASE_NAME = "launcher.db"; - private static final int DATABASE_VERSION = 20; + private static final int DATABASE_VERSION = 21; static final String OLD_AUTHORITY = "com.android.launcher2.settings"; static final String AUTHORITY = ProviderConfig.AUTHORITY; @@ -496,6 +496,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" + ");"); addWorkspacesTable(db); @@ -901,6 +902,11 @@ public class LauncherProvider extends ContentProvider { // else old version remains, which means we wipe old data } + if (oldVersion < 21) { + db.execSQL("ALTER TABLE favorites ADD hidden INTEGER DEFAULT 0"); + version = 21; + } + if (version != DATABASE_VERSION) { Log.w(TAG, "Destroying all old data."); db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 355370283..357bac52d 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -39,6 +39,11 @@ class LauncherSettings { */ static final String TITLE = "title"; + /** + * Folder Hidden status + */ + 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/src/com/android/launcher3/Lists.java b/src/com/android/launcher3/Lists.java new file mode 100644 index 000000000..51f5dc272 --- /dev/null +++ b/src/com/android/launcher3/Lists.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 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 java.util.ArrayList; +import java.util.Collections; + +/** + * Provides static methods for creating {@code List} instances easily, and other + * utility methods for working with lists. + */ +public class Lists { + + /** + * Creates an empty {@code ArrayList} instance. + * + *

Note: if you only need an immutable empty List, use + * {@link Collections#emptyList} instead. + * + * @return a newly-created, initially-empty {@code ArrayList} + */ + public static ArrayList newArrayList() { + return new ArrayList(); + } + + /** + * Creates a resizable {@code ArrayList} instance containing the given + * elements. + * + *

Note: due to a bug in javac 1.5.0_06, we cannot support the + * following: + * + *

{@code List list = Lists.newArrayList(sub1, sub2);} + * + *

where {@code sub1} and {@code sub2} are references to subtypes of + * {@code Base}, not of {@code Base} itself. To get around this, you must + * use: + * + *

{@code List list = Lists.newArrayList(sub1, sub2);} + * + * @param elements the elements that the list should contain, in order + * @return a newly-created {@code ArrayList} containing those elements + */ + public static ArrayList newArrayList(E... elements) { + int capacity = (elements.length * 110) / 100 + 5; + ArrayList list = new ArrayList(capacity); + Collections.addAll(list, elements); + return list; + } +} diff --git a/src/com/android/launcher3/OverviewSettingsPanel.java b/src/com/android/launcher3/OverviewSettingsPanel.java index dad2a503c..e104d202a 100644 --- a/src/com/android/launcher3/OverviewSettingsPanel.java +++ b/src/com/android/launcher3/OverviewSettingsPanel.java @@ -12,6 +12,11 @@ import com.android.launcher3.list.PinnedHeaderListView; import com.android.launcher3.list.SettingsPinnedHeaderAdapter; public class OverviewSettingsPanel { + public static final String ANDROID_SETTINGS = "com.android.settings"; + public static final String ANDROID_PROTECTED_APPS = + "com.android.settings.applications.ProtectedAppsActivity"; + public static final String THEME_SETTINGS = + "com.android.settings.Settings$ThemeSettingsActivity"; public static final int HOME_SETTINGS_POSITION = 0; public static final int DRAWER_SETTINGS_POSITION = 1; @@ -43,7 +48,8 @@ public class OverviewSettingsPanel { String[] valuesDrawer = new String[] { res.getString(R.string.scroll_effect_text), res.getString(R.string.drawer_sorting_text), - res.getString(R.string.hide_icon_labels)}; + res.getString(R.string.hide_icon_labels), + res.getString(R.string.protected_app_settings)}; mSettingsAdapter = new SettingsPinnedHeaderAdapter(mLauncher); mSettingsAdapter.setHeaders(headers); diff --git a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java index 43d98c33e..499995375 100644 --- a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java +++ b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java @@ -1,6 +1,7 @@ package com.android.launcher3.list; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Typeface; @@ -11,6 +12,7 @@ import android.view.ViewGroup; import android.widget.TextView; import com.android.launcher3.Launcher; import com.android.launcher3.OverviewSettingsPanel; +import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.settings.SettingsProvider; import android.view.View.OnClickListener; @@ -221,6 +223,11 @@ public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter { SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS, R.bool.preferences_interface_drawer_hide_icon_labels_default); mLauncher.updateDynamicGrid(); + } else if (value.equals(res.getString(R.string.protected_app_settings))) { + Intent intent = new Intent(); + intent.setClassName(OverviewSettingsPanel.ANDROID_SETTINGS, + OverviewSettingsPanel.ANDROID_PROTECTED_APPS); + mLauncher.startActivity(intent); } View defaultHome = mLauncher.findViewById(R.id.default_home_screen_panel); -- cgit v1.2.3