summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRaj Yengisetty <rajesh@cyngn.com>2014-11-10 10:35:00 -0800
committerAdnan <adnan@cyngn.com>2014-11-19 15:53:46 -0800
commit0daed36da7dab3172f05aa3dc29b7facac01ce6e (patch)
tree284e4d166ce0ffaa557c7fe1c99cbd35ac35aa70 /src
parentae5f7ce8ed2e385cba8d65a67d9b108c9c3590e8 (diff)
downloadandroid_packages_apps_Trebuchet-0daed36da7dab3172f05aa3dc29b7facac01ce6e.tar.gz
android_packages_apps_Trebuchet-0daed36da7dab3172f05aa3dc29b7facac01ce6e.tar.bz2
android_packages_apps_Trebuchet-0daed36da7dab3172f05aa3dc29b7facac01ce6e.zip
Trebuchet Settings UI Refresh.
- Overview Panel contains all Launcher Settings - Accessible through long press on Workspace or HW Menu button - Settings are held in a SlidingPanel in the Overview Panel - Replace the Settings shortcut in Overview Panel with Themes shortcut - Replace widgets icon with new widgets icon from Launcher3 - Trebuchet Settings changes no longer require restarting Process - Transition Effects have PNG Sequence to show animation sequence - Remove PageIndicator click event to open Overview Panel in AppTray Contributors: Adrian Foulk - UX Lead Abishek Devkota - Project Manager Hayden Schoen - UX Designer Change-Id: I2186213960cf12e840e814757894fe104551856b
Diffstat (limited to 'src')
-rw-r--r--src/com/android/launcher3/DragLayer.java13
-rw-r--r--src/com/android/launcher3/DynamicGrid.java1
-rw-r--r--src/com/android/launcher3/Launcher.java352
-rw-r--r--src/com/android/launcher3/LauncherAppState.java11
-rw-r--r--src/com/android/launcher3/OverviewSettingsPanel.java199
-rw-r--r--src/com/android/launcher3/PagedView.java5
-rw-r--r--src/com/android/launcher3/SearchDropTargetBar.java9
-rw-r--r--src/com/android/launcher3/SlidingUpPanelLayout.java1315
-rw-r--r--src/com/android/launcher3/TransitionEffectsFragment.java222
-rw-r--r--src/com/android/launcher3/Workspace.java65
-rw-r--r--src/com/android/launcher3/list/AutoScrollListView.java117
-rw-r--r--src/com/android/launcher3/list/CompositeCursorAdapter.java532
-rw-r--r--src/com/android/launcher3/list/PinnedHeaderListAdapter.java172
-rw-r--r--src/com/android/launcher3/list/PinnedHeaderListView.java565
-rw-r--r--src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java248
-rw-r--r--src/com/android/launcher3/settings/SettingsProvider.java88
16 files changed, 3834 insertions, 80 deletions
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index a8a61ea89..e751b079d 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -127,6 +127,19 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
for (int i = 0; i < n; i++) {
final View child = getChildAt(i);
setInsets(child, insets, mInsets);
+ final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
+ if (child.getId() == R.id.overview_panel) {
+ continue;
+ }
+ if (child instanceof Insettable) {
+ ((Insettable)child).setInsets(insets);
+ } else {
+ flp.topMargin += (insets.top - mInsets.top);
+ flp.leftMargin += (insets.left - mInsets.left);
+ flp.rightMargin += (insets.right - mInsets.right);
+ flp.bottomMargin += (insets.bottom - mInsets.bottom);
+ }
+ child.setLayoutParams(flp);
}
mInsets.set(insets);
return true; // I'll take it from here
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 94a07d706..dfb41528c 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -23,7 +23,6 @@ import android.util.TypedValue;
import java.util.ArrayList;
-
public class DynamicGrid {
@SuppressWarnings("unused")
private static final String TAG = "DynamicGrid";
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 42ec4fb48..d04c42348 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -29,6 +29,9 @@ 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.SearchManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
@@ -39,7 +42,6 @@ import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
@@ -83,7 +85,6 @@ import android.view.Menu;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
@@ -111,6 +112,8 @@ import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.PagedView.TransitionEffect;
+import com.android.launcher3.settings.SettingsProvider;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -140,6 +143,8 @@ public class Launcher extends Activity
static final String TAG = "Launcher";
static final boolean LOGD = false;
+ DeviceProfile mGrid;
+
static final boolean PROFILE_STARTUP = false;
static final boolean DEBUG_WIDGETS = false;
static final boolean DEBUG_STRICT_MODE = false;
@@ -156,6 +161,9 @@ public class Launcher extends Activity
private static final int REQUEST_BIND_APPWIDGET = 11;
private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
+ public static final int REQUEST_TRANSITION_EFFECTS = 14;
+
+ static final int REQUEST_PICK_ICON = 13;
/**
* IntentStarter uses request codes starting with this. This must be greater than all activity
@@ -260,6 +268,7 @@ public class Launcher extends Activity
private DragLayer mDragLayer;
private DragController mDragController;
private View mWeightWatcher;
+ private TransitionEffectsFragment mTransitionEffectsFragment;
private AppWidgetManagerCompat mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
@@ -274,6 +283,8 @@ public class Launcher extends Activity
private Hotseat mHotseat;
private ViewGroup mOverviewPanel;
+ private View mDarkPanel;
+ OverviewSettingsPanel mOverviewSettingsPanel;
private View mAllAppsButton;
@@ -390,6 +401,23 @@ public class Launcher extends Activity
return Log.isLoggable(propertyName, Log.VERBOSE);
}
+ public Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator arg0) {}
+ @Override
+ public void onAnimationRepeat(Animator arg0) {}
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ mDarkPanel.setVisibility(View.GONE);
+ }
+ @Override
+ public void onAnimationCancel(Animator arg0) {}
+ };
+
+ private static boolean isPropertyEnabled(String propertyName) {
+ return Log.isLoggable(propertyName, Log.VERBOSE);
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG_STRICT_MODE) {
@@ -409,33 +437,12 @@ public class Launcher extends Activity
super.onCreate(savedInstanceState);
- LauncherAppState.setApplicationContext(getApplicationContext());
- LauncherAppState app = LauncherAppState.getInstance();
- LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
- // Determine the dynamic grid properties
- Point smallestSize = new Point();
- Point largestSize = new Point();
- Point realSize = new Point();
- Display display = getWindowManager().getDefaultDisplay();
- display.getCurrentSizeRange(smallestSize, largestSize);
- display.getRealSize(realSize);
- DisplayMetrics dm = new DisplayMetrics();
- display.getMetrics(dm);
-
- // Lazy-initialize the dynamic grid
- DeviceProfile grid = app.initDynamicGrid(this,
- Math.min(smallestSize.x, smallestSize.y),
- Math.min(largestSize.x, largestSize.y),
- realSize.x, realSize.y,
- dm.widthPixels, dm.heightPixels);
+ initializeDynamicGrid();
// the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
Context.MODE_PRIVATE);
mIsSafeModeEnabled = getPackageManager().isSafeMode();
- mModel = app.setLauncher(this);
- mIconCache = app.getIconCache();
- mIconCache.flushInvalidIcons(grid);
mDragController = new DragController(this);
mInflater = getLayoutInflater();
@@ -460,7 +467,7 @@ public class Launcher extends Activity
setContentView(R.layout.launcher);
setupViews();
- grid.layout(this);
+ mGrid.layout(this);
registerContentObservers();
@@ -508,6 +515,40 @@ public class Launcher extends Activity
@Override
public void onLauncherProviderChange() { }
+ private void initializeDynamicGrid() {
+ LauncherAppState.setApplicationContext(getApplicationContext());
+ LauncherAppState app = LauncherAppState.getInstance();
+
+ mHideIconLabels = SettingsProvider.getBoolean(this,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+
+ // Determine the dynamic grid properties
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ Point realSize = new Point();
+ Display display = getWindowManager().getDefaultDisplay();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+ display.getRealSize(realSize);
+ DisplayMetrics dm = new DisplayMetrics();
+ display.getMetrics(dm);
+ // Lazy-initialize the dynamic grid
+ mGrid = app.initDynamicGrid(this,
+ Math.min(smallestSize.x, smallestSize.y),
+ Math.min(largestSize.x, largestSize.y),
+ realSize.x, realSize.y,
+ dm.widthPixels, dm.heightPixels);
+
+ mModel = app.setLauncher(this);
+ mIconCache = app.getIconCache();
+ mIconCache.flushInvalidIcons(mGrid);
+ }
+
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ sPausedFromUserAction = true;
+ }
+
/** To be overriden by subclasses to hint to Launcher that we have custom content */
protected boolean hasCustomContentToLeft() {
return false;
@@ -1059,6 +1100,13 @@ public class Launcher extends Activity
mWorkspace.onResume();
PackageInstallerCompat.getInstance(this).onResume();
+
+ //Close out TransitionEffects Fragment
+ Fragment f = getFragmentManager().findFragmentByTag(
+ TransitionEffectsFragment.TRANSITION_EFFECTS_FRAGMENT);
+ if (f != null) {
+ mTransitionEffectsFragment.setEffect();
+ }
}
@Override
@@ -1077,6 +1125,9 @@ public class Launcher extends Activity
if (mWorkspace.getCustomContentCallbacks() != null) {
mWorkspace.getCustomContentCallbacks().onHide();
}
+
+ //Reset the OverviewPanel position
+ ((SlidingUpPanelLayout) mOverviewPanel).collapsePane();
}
QSBScroller mQsbScroller = new QSBScroller() {
@@ -1117,6 +1168,142 @@ public class Launcher extends Activity
return false;
}
+ public void onClickSortModeButton(View v) {
+ final PopupMenu popupMenu = new PopupMenu(this, v);
+ final Menu menu = popupMenu.getMenu();
+ popupMenu.inflate(R.menu.apps_customize_sort_mode);
+ switch(mAppsCustomizeContent.getSortMode()) {
+ case Title:
+ menu.findItem(R.id.sort_mode_title).setChecked(true);
+ break;
+ case LaunchCount:
+ menu.findItem(R.id.sort_mode_launch_count).setChecked(true);
+ break;
+ case InstallTime:
+ menu.findItem(R.id.sort_mode_install_time).setChecked(true);
+ break;
+ }
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.sort_mode_title:
+ mAppsCustomizeContent.setSortMode(AppsCustomizePagedView.SortMode.Title);
+ break;
+ case R.id.sort_mode_install_time:
+ mAppsCustomizeContent.setSortMode(AppsCustomizePagedView.SortMode.InstallTime);
+ break;
+ case R.id.sort_mode_launch_count:
+ mAppsCustomizeContent.setSortMode(AppsCustomizePagedView.SortMode.LaunchCount);
+ break;
+ }
+ mOverviewSettingsPanel.notifyDataSetInvalidated();
+ return true;
+ }
+ });
+ popupMenu.show();
+ }
+
+ public void onClickTransitionEffectButton(View v, final boolean pageOrDrawer) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(TransitionEffectsFragment.PAGE_OR_DRAWER_SCROLL_SELECT,
+ pageOrDrawer);
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+
+
+ mTransitionEffectsFragment = new TransitionEffectsFragment();
+ mTransitionEffectsFragment.setArguments(bundle);
+ fragmentTransaction.setCustomAnimations(0, 0);
+ fragmentTransaction.replace(R.id.launcher, mTransitionEffectsFragment,
+ TransitionEffectsFragment.TRANSITION_EFFECTS_FRAGMENT);
+ fragmentTransaction.commit();
+ }
+
+ public void setTransitionEffect(boolean pageOrDrawer, String newTransitionEffect) {
+ String mSettingsProviderValue = pageOrDrawer ?
+ SettingsProvider.SETTINGS_UI_DRAWER_SCROLLING_TRANSITION_EFFECT
+ : SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT;
+ PagedView pagedView = pageOrDrawer ? mAppsCustomizeContent : mWorkspace;
+
+ SettingsProvider
+ .get(getApplicationContext())
+ .edit()
+ .putString(mSettingsProviderValue,
+ newTransitionEffect).commit();
+ TransitionEffect.setFromString(pagedView, newTransitionEffect);
+
+ // Reset Settings Changed
+ SharedPreferences.Editor editor = mSharedPrefs.edit();
+ editor.putBoolean(SettingsProvider.SETTINGS_CHANGED, false);
+ editor.commit();
+
+ mOverviewSettingsPanel.notifyDataSetInvalidated();
+
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction
+ .setCustomAnimations(0, R.anim.exit_out_right);
+ fragmentTransaction
+ .remove(mTransitionEffectsFragment).commit();
+
+ mDarkPanel.setVisibility(View.VISIBLE);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(
+ mDarkPanel, "alpha", 0.3f, 0.0f);
+ anim.start();
+ anim.addListener(mAnimatorListener);
+ }
+
+ public void onClickTransitionEffectOverflowMenuButton(View v) {
+ final PopupMenu popupMenu = new PopupMenu(this, v);
+
+ final Menu menu = popupMenu.getMenu();
+ popupMenu.inflate(R.menu.scrolling_settings);
+ MenuItem pageOutlines = menu.findItem(R.id.scrolling_page_outlines);
+ MenuItem fadeAdjacent = menu.findItem(R.id.scrolling_fade_adjacent);
+
+ pageOutlines.setVisible(!isAllAppsVisible());
+ pageOutlines.setChecked(SettingsProvider.getBoolean(this,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_PAGE_OUTLINES,
+ R.bool.preferences_interface_homescreen_scrolling_page_outlines_default
+ ));
+
+ fadeAdjacent.setChecked(SettingsProvider.getBoolean(this,
+ !isAllAppsVisible() ?
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_FADE_ADJACENT :
+ SettingsProvider.SETTINGS_UI_DRAWER_SCROLLING_FADE_ADJACENT,
+ !isAllAppsVisible() ?
+ R.bool.preferences_interface_homescreen_scrolling_fade_adjacent_default :
+ R.bool.preferences_interface_drawer_scrolling_fade_adjacent_default
+ ));
+
+ final PagedView pagedView = !isAllAppsVisible() ? mWorkspace : mAppsCustomizeContent;
+
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.scrolling_page_outlines:
+ SettingsProvider.get(Launcher.this).edit()
+ .putBoolean(SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_PAGE_OUTLINES, !item.isChecked()).commit();
+ mWorkspace.setShowOutlines(!item.isChecked());
+ break;
+ case R.id.scrolling_fade_adjacent:
+ SettingsProvider.get(Launcher.this).edit()
+ .putBoolean(!isAllAppsVisible() ?
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_FADE_ADJACENT :
+ SettingsProvider.SETTINGS_UI_DRAWER_SCROLLING_FADE_ADJACENT, !item.isChecked()).commit();
+ pagedView.setFadeInAdjacentScreens(!item.isChecked());
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+ });
+
+ popupMenu.show();
+ }
+
public interface QSBScroller {
public void setScrollY(int scrollY);
}
@@ -1299,48 +1486,12 @@ public class Launcher extends Activity
mHotseat.setOnLongClickListener(this);
}
- mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
- View widgetButton = findViewById(R.id.widget_button);
- widgetButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (!mWorkspace.isSwitchingState()) {
- onClickAddWidgetButton(arg0);
- }
- }
- });
- widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View wallpaperButton = findViewById(R.id.wallpaper_button);
- wallpaperButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (!mWorkspace.isSwitchingState()) {
- onClickWallpaperPicker(arg0);
- }
- }
- });
- wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View settingsButton = findViewById(R.id.settings_button);
- if (hasSettings()) {
- settingsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (!mWorkspace.isSwitchingState()) {
- onClickSettingsButton(arg0);
- }
- }
- });
- settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
- } else {
- settingsButton.setVisibility(View.GONE);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) widgetButton.getLayoutParams();
- lp.gravity = Gravity.END | Gravity.TOP;
- widgetButton.requestLayout();
- }
-
- mOverviewPanel.setAlpha(0f);
+ mOverviewPanel = findViewById(R.id.overview_panel);
+ mOverviewSettingsPanel = new OverviewSettingsPanel(
+ this, mOverviewPanel);
+ mOverviewSettingsPanel.initializeAdapter();
+ mOverviewSettingsPanel.initializeViews();
+ mDarkPanel = ((SlidingUpPanelLayout) mOverviewPanel).findViewById(R.id.dark_panel);
// Setup the workspace
mWorkspace.setHapticFeedbackEnabled(false);
@@ -2395,7 +2546,13 @@ public class Launcher extends Activity
showOverviewMode(true);
}
} else if (mWorkspace.isInOverviewMode()) {
- mWorkspace.exitOverviewMode(true);
+ Fragment f = getFragmentManager().findFragmentByTag(
+ TransitionEffectsFragment.TRANSITION_EFFECTS_FRAGMENT);
+ if (f != null) {
+ mTransitionEffectsFragment.setEffect();
+ } else {
+ mWorkspace.exitOverviewMode(true);
+ }
} else if (mWorkspace.getOpenFolder() != null) {
Folder openFolder = mWorkspace.getOpenFolder();
if (openFolder.isEditingName()) {
@@ -3090,6 +3247,18 @@ public class Launcher extends Activity
return mHotseat != null && layout != null &&
(layout instanceof CellLayout) && (layout == mHotseat.getLayout());
}
+ Hotseat getHotseat() {
+ return mHotseat;
+ }
+ View getOverviewPanel() {
+ return mOverviewPanel;
+ }
+ View getDarkPanel() {
+ return mDarkPanel;
+ }
+ SearchDropTargetBar getSearchBar() {
+ return mSearchDropTargetBar;
+ }
/**
* Returns the CellLayout of the specified container at the specified screen.
@@ -3106,6 +3275,18 @@ public class Launcher extends Activity
}
}
+ protected Workspace getWorkspace() {
+ return mWorkspace;
+ }
+
+ protected AppsCustomizePagedView getAppsCustomizeContent() {
+ return mAppsCustomizeContent;
+ }
+
+ public void updateOverviewPanel() {
+ mOverviewSettingsPanel.update();
+ }
+
public boolean isAllAppsVisible() {
return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
}
@@ -5215,6 +5396,41 @@ public class Launcher extends Activity
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
}
+
+ public AppsCustomizePagedView.SortMode getAppsCustomizeContentSortMode () {
+ return mAppsCustomizeContent.getSortMode();
+ }
+
+ public boolean shouldShowSearchBar() {
+ return mWorkspace.getShowSearchBar();
+ }
+
+ public boolean shouldHideWorkspaceIconLables() {
+ return mWorkspace.getHideIconLables();
+ }
+
+ public String getWorkspaceTransitionEffect() {
+ TransitionEffect effect = mWorkspace.getTransitionEffect();
+ return effect == null ? TransitionEffect.TRANSITION_EFFECT_NONE : effect.getName();
+ }
+
+ public String getAppsCustomizeTransitionEffect() {
+ TransitionEffect effect = mAppsCustomizeContent.getTransitionEffect();
+ return effect == null ? TransitionEffect.TRANSITION_EFFECT_NONE : effect.getName();
+ }
+
+ public void updateDynamicGrid() {
+ mSearchDropTargetBar.setupQSB(this);
+ mSearchDropTargetBar.hideSearchBar(false);
+
+ initializeDynamicGrid();
+
+ mGrid.layout(this);
+ mWorkspace.showOutlines();
+
+ // Synchronized reload
+ mModel.startLoader(true, mWorkspace.getCurrentPage());
+ }
}
interface LauncherTransitionable {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 246278fa2..71411c29e 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -192,13 +192,10 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
DeviceProfile initDynamicGrid(Context context, int minWidth, int minHeight,
int width, int height,
int availableWidth, int availableHeight) {
- if (mDynamicGrid == null) {
- mDynamicGrid = new DynamicGrid(context,
- context.getResources(),
- minWidth, minHeight, width, height,
- availableWidth, availableHeight);
- mDynamicGrid.getDeviceProfile().addCallback(this);
- }
+ mDynamicGrid = new DynamicGrid(context,
+ context.getResources(),
+ minWidth, minHeight, width, height,
+ availableWidth, availableHeight);
// Update the icon size
DeviceProfile grid = mDynamicGrid.getDeviceProfile();
diff --git a/src/com/android/launcher3/OverviewSettingsPanel.java b/src/com/android/launcher3/OverviewSettingsPanel.java
new file mode 100644
index 000000000..2c1a7da36
--- /dev/null
+++ b/src/com/android/launcher3/OverviewSettingsPanel.java
@@ -0,0 +1,199 @@
+package com.android.launcher3;
+
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.AnimationDrawable;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.ListView;
+import com.android.launcher3.list.PinnedHeaderListView;
+import com.android.launcher3.list.SettingsPinnedHeaderAdapter;
+
+public class OverviewSettingsPanel {
+ public static final int HOME_SETTINGS_POSITION = 0;
+ public static final int DRAWER_SETTINGS_POSITION = 1;
+
+ private Launcher mLauncher;
+ private View mOverviewPanel;
+ private SettingsPinnedHeaderAdapter mSettingsAdapter;
+ private PinnedHeaderListView mListView;
+
+ OverviewSettingsPanel(Launcher launcher, View overviewPanel) {
+ mLauncher = launcher;
+ mOverviewPanel = overviewPanel;
+ }
+
+ // One time initialization of the SettingsPinnedHeaderAdapter
+ public void initializeAdapter() {
+ // Settings pane Listview
+ mListView = (PinnedHeaderListView) mLauncher
+ .findViewById(R.id.settings_home_screen_listview);
+ mListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER);
+ Resources res = mLauncher.getResources();
+ String[] headers = new String[] {
+ res.getString(R.string.home_screen_settings),
+ res.getString(R.string.drawer_settings)};
+ String[] values = new String[] {
+ res.getString(R.string.home_screen_search_text),
+ res.getString(R.string.page_scroll_effect_text),
+ res.getString(R.string.larger_icons_text),
+ res.getString(R.string.hide_icon_labels)};
+ String[] valuesDrawer = new String[] {
+ res.getString(R.string.drawer_scroll_effect_text),
+ res.getString(R.string.drawer_sorting_text),
+ res.getString(R.string.hide_icon_labels)};
+
+ mSettingsAdapter = new SettingsPinnedHeaderAdapter(mLauncher);
+ mSettingsAdapter.setHeaders(headers);
+ mSettingsAdapter.addPartition(false, true);
+ mSettingsAdapter.addPartition(false, true);
+ mSettingsAdapter.mPinnedHeaderCount = headers.length;
+
+ mSettingsAdapter.changeCursor(0, createCursor(headers[0], values));
+ mSettingsAdapter.changeCursor(1, createCursor(headers[1], valuesDrawer));
+ mListView.setAdapter(mSettingsAdapter);
+ }
+
+ private Cursor createCursor(String header, String[] values) {
+ MatrixCursor cursor = new MatrixCursor(new String[]{"_id", header});
+ int count = values.length;
+ for (int i = 0; i < count; i++) {
+ cursor.addRow(new Object[]{i, values[i]});
+ }
+ return cursor;
+ }
+
+ // One time View setup
+ public void initializeViews() {
+ mOverviewPanel.setAlpha(0f);
+ mOverviewPanel
+ .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ ((SlidingUpPanelLayout) mOverviewPanel)
+ .setPanelSlideListener(new SettingsSimplePanelSlideListener());
+
+ //Quick Settings Buttons
+ View widgetButton = mLauncher.findViewById(R.id.widget_button);
+ widgetButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mLauncher.showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+ }
+ });
+ widgetButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ View wallpaperButton = mLauncher.findViewById(R.id.wallpaper_button);
+ wallpaperButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mLauncher.startWallpaper();
+ }
+ });
+ wallpaperButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ View settingsButton = mLauncher.findViewById(R.id.settings_button);
+ settingsButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ }
+ });
+ settingsButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ View defaultScreenButton = mLauncher.findViewById(R.id.default_screen_button);
+ defaultScreenButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mLauncher.getWorkspace().onClickDefaultScreenButton();
+ }
+ });
+
+ defaultScreenButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ //Handle
+ View v = mOverviewPanel.findViewById(R.id.settings_pane_header);
+ ((SlidingUpPanelLayout) mOverviewPanel).setEnableDragViewTouchEvents(true);
+ ((SlidingUpPanelLayout) mOverviewPanel).setDragView(v);
+ v.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (((SlidingUpPanelLayout) mOverviewPanel).isExpanded()) {
+ ((SlidingUpPanelLayout) mOverviewPanel).collapsePane();
+ } else {
+ ((SlidingUpPanelLayout) mOverviewPanel).expandPane();
+ }
+ }
+ });
+ }
+
+ public void update() {
+ Resources res = mLauncher.getResources();
+ View widgetButton = mOverviewPanel.findViewById(R.id.widget_button);
+ View wallpaperButton = mOverviewPanel
+ .findViewById(R.id.wallpaper_button);
+ View settingsButton = mOverviewPanel.findViewById(R.id.settings_button);
+ View defaultHomePanel = mOverviewPanel.findViewById(R.id.default_screen_button);
+
+ boolean isAllAppsVisible = mLauncher.isAllAppsVisible();
+
+ PagedView pagedView = !isAllAppsVisible ? mLauncher.getWorkspace()
+ : mLauncher.getAppsCustomizeContent();
+
+ defaultHomePanel.setVisibility((pagedView.getPageCount() > 1) ?
+ View.VISIBLE : View.GONE);
+
+ if (mLauncher.isAllAppsVisible()) {
+ mSettingsAdapter.changeCursor(0, createCursor(res
+ .getString(R.string.home_screen_settings), new String[]{}));
+ } else {
+ String[] values = new String[] {
+ res.getString(R.string.home_screen_search_text),
+ res.getString(R.string.page_scroll_effect_text),
+ res.getString(R.string.larger_icons_text),
+ res.getString(R.string.hide_icon_labels)};
+ mSettingsAdapter.changeCursor(0, createCursor(res
+ .getString(R.string.home_screen_settings), values));
+ }
+
+ // Make sure overview panel is drawn above apps customize and collapsed
+ mOverviewPanel.bringToFront();
+ mOverviewPanel.invalidate();
+
+ ((SlidingUpPanelLayout) mOverviewPanel).setPanelHeight(isAllAppsVisible ?
+ res.getDimensionPixelSize(R.dimen.settings_pane_handle)
+ : res.getDimensionPixelSize(R.dimen.sliding_panel_padding));
+
+ ((SlidingUpPanelLayout) mOverviewPanel).collapsePane();
+ }
+
+ public void notifyDataSetInvalidated() {
+ mSettingsAdapter.notifyDataSetInvalidated();
+ }
+
+
+ class SettingsSimplePanelSlideListener extends SlidingUpPanelLayout.SimplePanelSlideListener {
+ ImageView mAnimatedArrow;
+
+ public SettingsSimplePanelSlideListener() {
+ super();
+ mAnimatedArrow = (ImageView) mOverviewPanel.findViewById(R.id.settings_drag_arrow);
+ }
+
+ @Override
+ public void onPanelCollapsed(View panel) {
+ mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow_reverse);
+
+ AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground();
+ frameAnimation.start();
+ }
+
+ @Override
+ public void onPanelExpanded(View panel) {
+ mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow);
+
+ AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground();
+ frameAnimation.start();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 48fc0c98f..248f98500 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1663,6 +1663,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
setEnableFreeScroll(false);
}
+ protected void disableFreeScroll(int snapPage) {
+ setEnableFreeScroll(false, snapPage);
+ }
+
void updateFreescrollBounds() {
getFreeScrollPageRange(mTempVisiblePagesRange);
if (isLayoutRtl()) {
@@ -2557,7 +2561,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
mPostReorderingPreZoomInRunnable = new Runnable() {
public void run() {
onCompleteRunnable.run();
- enableFreeScroll();
};
};
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 435dbda1d..00f2ee2b3 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -69,6 +69,11 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
mInfoDropTarget.setLauncher(launcher);
mDeleteDropTarget.setLauncher(launcher);
+
+ setupQSB(launcher);
+ }
+
+ public void setupQSB(Launcher launcher) {
mQSBSearchBar = launcher.getQsbBar();
if (mEnableDropDownDropTargets) {
mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "translationY", 0,
@@ -188,7 +193,7 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
// Animate out the QSB search bar, and animate in the drop target bar
prepareStartAnimation(mDropTargetBar);
mDropTargetBarAnim.start();
- if (!mIsSearchBarHidden) {
+ if (!mIsSearchBarHidden || mQSBSearchBar.getAlpha() > 0f) {
prepareStartAnimation(mQSBSearchBar);
mQSBSearchBarAnim.start();
}
@@ -204,7 +209,7 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
// Restore the QSB search bar, and animate out the drop target bar
prepareStartAnimation(mDropTargetBar);
mDropTargetBarAnim.reverse();
- if (!mIsSearchBarHidden) {
+ if (!mIsSearchBarHidden || mQSBSearchBar.getAlpha() < 1f) {
prepareStartAnimation(mQSBSearchBar);
mQSBSearchBarAnim.reverse();
}
diff --git a/src/com/android/launcher3/SlidingUpPanelLayout.java b/src/com/android/launcher3/SlidingUpPanelLayout.java
new file mode 100644
index 000000000..52deeb355
--- /dev/null
+++ b/src/com/android/launcher3/SlidingUpPanelLayout.java
@@ -0,0 +1,1315 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+
+public class SlidingUpPanelLayout extends ViewGroup {
+ private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
+
+ /**
+ * Default peeking out panel height
+ */
+ private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
+
+ /**
+ * Default height of the shadow above the peeking out panel
+ */
+ private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
+
+ /**
+ * If no fade color is given by default it will fade to 80% gray.
+ */
+ private static final int DEFAULT_FADE_COLOR = 0x99000000;
+
+ /**
+ * Default Minimum velocity that will be detected as a fling
+ */
+ private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
+ /**
+ * Default is set to false because that is how it was written
+ */
+ private static final boolean DEFAULT_OVERLAY_FLAG = false;
+ /**
+ * Default attributes for layout
+ */
+ private static final int[] DEFAULT_ATTRS = new int[] {
+ android.R.attr.gravity
+ };
+
+ /**
+ * Minimum velocity that will be detected as a fling
+ */
+ private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
+
+ /**
+ * The fade color used for the panel covered by the slider. 0 = no fading.
+ */
+ private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
+
+ /**
+ * Default paralax length of the main view
+ */
+ private static final int DEFAULT_PARALAX_OFFSET = 0;
+
+ /**
+ * The paint used to dim the main layout when sliding
+ */
+ private final Paint mCoveredFadePaint = new Paint();
+
+ /**
+ * Drawable used to draw the shadow between panes.
+ */
+ private final Drawable mShadowDrawable;
+
+ /**
+ * The size of the overhang in pixels.
+ */
+ private int mPanelHeight = -1;
+
+ /**
+ * The size of the shadow in pixels.
+ */
+ private int mShadowHeight = -1;
+
+ /**
+ * Paralax offset
+ */
+ private int mParalaxOffset = -1;
+
+ /**
+ * True if the collapsed panel should be dragged up.
+ */
+ private boolean mIsSlidingUp;
+
+ /**
+ * True if a panel can slide with the current measurements
+ */
+ private boolean mCanSlide;
+
+ /**
+ * Panel overlays the windows instead of putting it underneath it.
+ */
+ private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
+
+ /**
+ * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
+ * used for dragging.
+ */
+ private View mDragView;
+
+ /**
+ * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
+ * used for dragging.
+ */
+ private int mDragViewResId = -1;
+
+ /**
+ * The child view that can slide, if any.
+ */
+ private View mSlideableView;
+
+ /**
+ * The main view
+ */
+ private View mMainView;
+
+ /**
+ * Current state of the slideable view.
+ */
+ private enum SlideState {
+ EXPANDED,
+ COLLAPSED,
+ ANCHORED
+ }
+ private SlideState mSlideState = SlideState.COLLAPSED;
+
+ /**
+ * How far the panel is offset from its expanded position.
+ * range [0, 1] where 0 = expanded, 1 = collapsed.
+ */
+ private float mSlideOffset;
+
+ /**
+ * How far in pixels the slideable panel may move.
+ */
+ private int mSlideRange;
+
+ /**
+ * A panel view is locked into internal scrolling or another condition that
+ * is preventing a drag.
+ */
+ private boolean mIsUnableToDrag;
+
+ /**
+ * Flag indicating that sliding feature is enabled\disabled
+ */
+ private boolean mIsSlidingEnabled;
+
+ /**
+ * Flag indicating if a drag view can have its own touch events. If set
+ * to true, a drag view can scroll horizontally and have its own click listener.
+ *
+ * Default is set to false.
+ */
+ private boolean mIsUsingDragViewTouchEvents;
+
+ /**
+ * Threshold to tell if there was a scroll touch event.
+ */
+ private final int mScrollTouchSlop;
+
+ private float mInitialMotionX;
+ private float mInitialMotionY;
+ private float mAnchorPoint = 0.f;
+ private TranslateAnimation mAnimation;
+
+ private PanelSlideListener mPanelSlideListener;
+
+ private final ViewDragHelper mDragHelper;
+
+ /**
+ * Stores whether or not the pane was expanded the last time it was slideable.
+ * If expand/collapse operations are invoked this state is modified. Used by
+ * instance state save/restore.
+ */
+ private boolean mFirstLayout = true;
+
+ private final Rect mTmpRect = new Rect();
+
+ /**
+ * Listener for monitoring events about sliding panes.
+ */
+ public interface PanelSlideListener {
+ /**
+ * Called when a sliding pane's position changes.
+ * @param panel The child view that was moved
+ * @param slideOffset The new offset of this sliding pane within its range, from 0-1
+ */
+ public void onPanelSlide(View panel, float slideOffset);
+ /**
+ * Called when a sliding pane becomes slid completely collapsed. The pane may or may not
+ * be interactive at this point depending on if it's shown or hidden
+ * @param panel The child view that was slid to an collapsed position, revealing other panes
+ */
+ public void onPanelCollapsed(View panel);
+
+ /**
+ * Called when a sliding pane becomes slid completely expanded. The pane is now guaranteed
+ * to be interactive. It may now obscure other views in the layout.
+ * @param panel The child view that was slid to a expanded position
+ */
+ public void onPanelExpanded(View panel);
+
+ public void onPanelAnchored(View panel);
+ }
+
+ /**
+ * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
+ * of the listener methods you can extend this instead of implement the full interface.
+ */
+ public static class SimplePanelSlideListener implements PanelSlideListener {
+ @Override
+ public void onPanelSlide(View panel, float slideOffset) {
+ }
+ @Override
+ public void onPanelCollapsed(View panel) {
+ }
+ @Override
+ public void onPanelExpanded(View panel) {
+ }
+ @Override
+ public void onPanelAnchored(View panel) {
+ }
+ }
+
+ public SlidingUpPanelLayout(Context context) {
+ this(context, null);
+ }
+
+ public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (attrs != null) {
+ TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
+
+ if (defAttrs != null) {
+ int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);
+ if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {
+ throw new IllegalArgumentException("gravity must be set to either top or bottom");
+ }
+ mIsSlidingUp = gravity == Gravity.BOTTOM;
+ }
+
+ defAttrs.recycle();
+
+ TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
+
+ if (ta != null) {
+ mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_panelHeight, -1);
+ mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_shadowHeight, -1);
+ mParalaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_paralaxOffset, -1);
+
+ mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);
+ mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_fadeColor, DEFAULT_FADE_COLOR);
+
+ mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_dragView, -1);
+
+ mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_overlay,DEFAULT_OVERLAY_FLAG);
+ }
+
+ ta.recycle();
+ }
+
+ final float density = context.getResources().getDisplayMetrics().density;
+ if (mPanelHeight == -1) {
+ mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
+ }
+ if (mShadowHeight == -1) {
+ mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
+ }
+ if (mParalaxOffset == -1) {
+ mParalaxOffset = (int) (DEFAULT_PARALAX_OFFSET * density);
+ }
+ // If the shadow height is zero, don't show the shadow
+ if (mShadowHeight > 0) {
+ if (mIsSlidingUp) {
+ mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow);
+ } else {
+ mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow);
+ }
+
+ } else {
+ mShadowDrawable = null;
+ }
+
+ setWillNotDraw(false);
+
+ mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
+ mDragHelper.setMinVelocity(mMinFlingVelocity * density);
+
+ mCanSlide = true;
+ mIsSlidingEnabled = true;
+
+ ViewConfiguration vc = ViewConfiguration.get(context);
+ mScrollTouchSlop = vc.getScaledTouchSlop();
+ }
+
+ /**
+ * Set the Drag View after the view is inflated
+ */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ if (mDragViewResId != -1) {
+ mDragView = findViewById(mDragViewResId);
+ }
+ }
+
+ /**
+ * Set the color used to fade the pane covered by the sliding pane out when the pane
+ * will become fully covered in the expanded state.
+ *
+ * @param color An ARGB-packed color value
+ */
+ public void setCoveredFadeColor(int color) {
+ mCoveredFadeColor = color;
+ invalidate();
+ }
+
+ /**
+ * @return The ARGB-packed color value used to fade the fixed pane
+ */
+ public int getCoveredFadeColor() {
+ return mCoveredFadeColor;
+ }
+
+ /**
+ * Set sliding enabled flag
+ * @param enabled flag value
+ */
+ public void setSlidingEnabled(boolean enabled) {
+ mIsSlidingEnabled = enabled;
+ }
+
+ /**
+ * Set the collapsed panel height in pixels
+ *
+ * @param val A height in pixels
+ */
+ public void setPanelHeight(int val) {
+ mPanelHeight = val;
+ requestLayout();
+ }
+
+ /**
+ * @return The current collapsed panel height
+ */
+ public int getPanelHeight() {
+ return mPanelHeight;
+ }
+
+ /**
+ * @return The current paralax offset
+ */
+ public int getCurrentParalaxOffset() {
+ int offset = (int)(mParalaxOffset * (1 - mSlideOffset));
+ return mIsSlidingUp ? -offset : offset;
+ }
+
+ /**
+ * Sets the panel slide listener
+ * @param listener
+ */
+ public void setPanelSlideListener(PanelSlideListener listener) {
+ mPanelSlideListener = listener;
+ }
+
+ /**
+ * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
+ *
+ * @param dragView A view that will be used to drag the panel.
+ */
+ public void setDragView(View dragView) {
+ mDragView = dragView;
+ }
+
+ /**
+ * Set an anchor point where the panel can stop during sliding
+ *
+ * @param anchorPoint A value between 0 and 1, determining the position of the anchor point
+ * starting from the top of the layout.
+ */
+ public void setAnchorPoint(float anchorPoint) {
+ if (anchorPoint > 0 && anchorPoint < 1)
+ mAnchorPoint = anchorPoint;
+ }
+
+ /**
+ * Sets whether or not the panel overlays the content
+ * @param overlayed
+ */
+ public void setOverlayed(boolean overlayed) {
+ mOverlayContent = overlayed;
+ }
+
+ /**
+ * Check if the panel is set as an overlay.
+ */
+ public boolean isOverlayed() {
+ return mOverlayContent;
+ }
+
+ void dispatchOnPanelSlide(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
+ }
+ }
+
+ void dispatchOnPanelExpanded(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelExpanded(panel);
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ void dispatchOnPanelCollapsed(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelCollapsed(panel);
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ void dispatchOnPanelAnchored(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelAnchored(panel);
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ void updateObscuredViewVisibility() {
+ if (getChildCount() == 0) {
+ return;
+ }
+ final int leftBound = getPaddingLeft();
+ final int rightBound = getWidth() - getPaddingRight();
+ final int topBound = getPaddingTop();
+ final int bottomBound = getHeight() - getPaddingBottom();
+ final int left;
+ final int right;
+ final int top;
+ final int bottom;
+ if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
+ left = mSlideableView.getLeft();
+ right = mSlideableView.getRight();
+ top = mSlideableView.getTop();
+ bottom = mSlideableView.getBottom();
+ } else {
+ left = right = top = bottom = 0;
+ }
+ View child = getChildAt(0);
+ final int clampedChildLeft = Math.max(leftBound, child.getLeft());
+ final int clampedChildTop = Math.max(topBound, child.getTop());
+ final int clampedChildRight = Math.min(rightBound, child.getRight());
+ final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
+ final int vis;
+ if (clampedChildLeft >= left && clampedChildTop >= top &&
+ clampedChildRight <= right && clampedChildBottom <= bottom) {
+ vis = INVISIBLE;
+ } else {
+ vis = VISIBLE;
+ }
+ child.setVisibility(vis);
+ }
+
+ void setAllChildrenVisible() {
+ for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == INVISIBLE) {
+ child.setVisibility(VISIBLE);
+ }
+ }
+ }
+
+ private static boolean hasOpaqueBackground(View v) {
+ final Drawable bg = v.getBackground();
+ if (bg != null) {
+ return bg.getOpacity() == PixelFormat.OPAQUE;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
+ } else if (heightMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
+ }
+
+ int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
+ int panelHeight = mPanelHeight;
+
+ final int childCount = getChildCount();
+
+ if (childCount > 2) {
+ Log.e(TAG, "onMeasure: More than two child views are not supported.");
+ } else if (getChildAt(1).getVisibility() == GONE) {
+ panelHeight = 0;
+ }
+
+ // We'll find the current one below.
+ mSlideableView = null;
+ mCanSlide = false;
+
+ // First pass. Measure based on child LayoutParams width/height.
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ int height = layoutHeight;
+ if (child.getVisibility() == GONE) {
+ lp.dimWhenOffset = false;
+ continue;
+ }
+
+ if (i == 1) {
+ lp.slideable = true;
+ lp.dimWhenOffset = true;
+ mSlideableView = child;
+ mCanSlide = true;
+ } else {
+ if (!mOverlayContent) {
+ height -= panelHeight;
+ }
+ mMainView = child;
+ }
+
+ int childWidthSpec;
+ if (lp.width == LayoutParams.WRAP_CONTENT) {
+ childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+ } else if (lp.width == LayoutParams.MATCH_PARENT) {
+ childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+ } else {
+ childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+ }
+
+ int childHeightSpec;
+ if (lp.height == LayoutParams.WRAP_CONTENT) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+ } else if (lp.height == LayoutParams.MATCH_PARENT) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+ }
+
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int paddingLeft = getPaddingLeft();
+ final int paddingTop = getPaddingTop();
+ final int slidingTop = getSlidingTop();
+
+ final int childCount = getChildCount();
+
+ if (mFirstLayout) {
+ switch (mSlideState) {
+ case EXPANDED:
+ mSlideOffset = mCanSlide ? 0.f : 1.f;
+ break;
+ case ANCHORED:
+ mSlideOffset = mCanSlide ? mAnchorPoint : 1.f;
+ break;
+ default:
+ mSlideOffset = 1.f;
+ break;
+ }
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int childHeight = child.getMeasuredHeight();
+
+ if (lp.slideable) {
+ mSlideRange = childHeight - mPanelHeight;
+ }
+
+ int childTop;
+ if (mIsSlidingUp) {
+ childTop = lp.slideable ? slidingTop + (int) (mSlideRange * mSlideOffset) : paddingTop;
+ } else {
+ childTop = lp.slideable ? slidingTop - (int) (mSlideRange * mSlideOffset) : paddingTop;
+ if (!lp.slideable && !mOverlayContent) {
+ childTop += mPanelHeight;
+ }
+ }
+ final int childBottom = childTop + childHeight;
+ final int childLeft = paddingLeft;
+ final int childRight = childLeft + child.getMeasuredWidth();
+
+ child.layout(childLeft, childTop, childRight, childBottom);
+ }
+
+ if (mFirstLayout) {
+ updateObscuredViewVisibility();
+ }
+
+ mFirstLayout = false;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ // Recalculate sliding panes and their details
+ if (h != oldh) {
+ mFirstLayout = true;
+ }
+ }
+
+ /**
+ * Set if the drag view can have its own touch events. If set
+ * to true, a drag view can scroll horizontally and have its own click listener.
+ *
+ * Default is set to false.
+ */
+ public void setEnableDragViewTouchEvents(boolean enabled) {
+ mIsUsingDragViewTouchEvents = enabled;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = MotionEventCompat.getActionMasked(ev);
+
+ if (mAnimation != null || !mCanSlide || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
+ mDragHelper.cancel();
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mDragHelper.cancel();
+ return false;
+ }
+
+ final float x = ev.getX();
+ final float y = ev.getY();
+ boolean interceptTap = false;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mIsUnableToDrag = false;
+ mInitialMotionX = x;
+ mInitialMotionY = y;
+ if (isDragViewUnder((int) x, (int) y) && !mIsUsingDragViewTouchEvents) {
+ interceptTap = true;
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ final float adx = Math.abs(x - mInitialMotionX);
+ final float ady = Math.abs(y - mInitialMotionY);
+ final int dragSlop = mDragHelper.getTouchSlop();
+
+ // Handle any horizontal scrolling on the drag view.
+ if (mIsUsingDragViewTouchEvents) {
+ if (adx > mScrollTouchSlop && ady < mScrollTouchSlop) {
+ return super.onInterceptTouchEvent(ev);
+ }
+ // Intercept the touch if the drag view has any vertical scroll.
+ // onTouchEvent will determine if the view should drag vertically.
+ else if (ady > mScrollTouchSlop) {
+ interceptTap = isDragViewUnder((int) x, (int) y);
+ }
+ }
+
+ if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) x, (int) y)) {
+ mDragHelper.cancel();
+ mIsUnableToDrag = true;
+ return false;
+ }
+ break;
+ }
+ }
+
+ final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
+
+ return interceptForDrag;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mCanSlide || !mIsSlidingEnabled || mAnimation != null) {
+ return super.onTouchEvent(ev);
+ }
+
+ mDragHelper.processTouchEvent(ev);
+
+ final int action = ev.getAction();
+ boolean wantTouchEvents = true;
+
+ switch (action & MotionEventCompat.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+
+ //Fix to allow both SettingPanel Drag and Workspace Drag
+ if (mSlideState == SlideState.COLLAPSED) {
+ if (y < mSlideableView.getTop()) {
+ return false;
+ }
+ }
+
+ mInitialMotionX = x;
+ mInitialMotionY = y;
+ break;
+ }
+
+ case MotionEvent.ACTION_UP: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ final float dx = x - mInitialMotionX;
+ final float dy = y - mInitialMotionY;
+ final int slop = mDragHelper.getTouchSlop();
+ View dragView = mDragView != null ? mDragView : mSlideableView;
+ if (dx * dx + dy * dy < slop * slop &&
+ isDragViewUnder((int) x, (int) y)) {
+ dragView.playSoundEffect(SoundEffectConstants.CLICK);
+ if (!isExpanded() && !isAnchored()) {
+ expandPane(mAnchorPoint);
+ } else {
+ collapsePane();
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+ return wantTouchEvents;
+ }
+
+ private boolean isDragViewUnder(int x, int y) {
+ View dragView = mDragView != null ? mDragView : mSlideableView;
+ if (dragView == null) return false;
+ int[] viewLocation = new int[2];
+ dragView.getLocationOnScreen(viewLocation);
+ int[] parentLocation = new int[2];
+ this.getLocationOnScreen(parentLocation);
+ int screenX = parentLocation[0] + x;
+ int screenY = parentLocation[1] + y;
+ return screenX >= viewLocation[0] && screenX < viewLocation[0] + dragView.getWidth() &&
+ screenY >= viewLocation[1] && screenY < viewLocation[1] + dragView.getHeight();
+ }
+
+ private boolean expandPane(View pane, int initialVelocity, float mSlideOffset) {
+ if (mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean collapsePane(View pane, int initialVelocity) {
+ if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
+ return true;
+ }
+ return false;
+ }
+
+ private int getSlidingTop() {
+ if (mSlideableView != null) {
+ return mIsSlidingUp
+ ? getMeasuredHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight()
+ : getPaddingTop();
+ }
+
+ return getMeasuredHeight() - getPaddingBottom();
+ }
+
+ /**
+ * Collapse the sliding pane if it is currently slideable. If first layout
+ * has already completed this will animate.
+ *
+ * @return true if the pane was slideable and is now collapsed/in the process of collapsing
+ */
+ public boolean collapsePane() {
+ return collapsePane(mSlideableView, 0);
+ }
+
+ /**
+ * Expand the sliding pane if it is currently slideable. If first layout
+ * has already completed this will animate.
+ *
+ * @return true if the pane was slideable and is now expanded/in the process of expading
+ */
+ public boolean expandPane() {
+ return expandPane(0);
+ }
+
+ /**
+ * Partially expand the sliding pane up to a specific offset
+ *
+ * @param mSlideOffset Value between 0 and 1, where 0 is completely expanded.
+ * @return true if the pane was slideable and is now expanded/in the process of expading
+ */
+ public boolean expandPane(float mSlideOffset) {
+ if (!isPaneVisible()) {
+ showPane();
+ }
+ return expandPane(mSlideableView, 0, mSlideOffset);
+ }
+
+ /**
+ * Check if the layout is completely expanded.
+ *
+ * @return true if sliding panels are completely expanded
+ */
+ public boolean isExpanded() {
+ return mSlideState == SlideState.EXPANDED;
+ }
+
+ /**
+ * Check if the layout is anchored in an intermediate point.
+ *
+ * @return true if sliding panels are anchored
+ */
+ public boolean isAnchored() {
+ return mSlideState == SlideState.ANCHORED;
+ }
+
+ /**
+ * Check if the content in this layout cannot fully fit side by side and therefore
+ * the content pane can be slid back and forth.
+ *
+ * @return true if content in this layout can be expanded
+ */
+ public boolean isSlideable() {
+ return mCanSlide;
+ }
+
+ public boolean isPaneVisible() {
+ if (getChildCount() < 2) {
+ return false;
+ }
+ View slidingPane = getChildAt(1);
+ return slidingPane.getVisibility() == View.VISIBLE;
+ }
+
+ public void showPane() {
+ if (getChildCount() < 2) {
+ return;
+ }
+ final View slidingPane = getChildAt(1);
+ mAnimation = new TranslateAnimation(0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight(), 0);
+ mAnimation.setDuration(400);
+ mAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ slidingPane.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ requestLayout();
+ mAnimation = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ slidingPane.startAnimation(mAnimation);
+ }
+
+ public void hidePane() {
+ if (mSlideableView == null) {
+ return;
+ }
+ mAnimation = new TranslateAnimation(0, 0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight());
+ mAnimation.setDuration(500);
+ mAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mSlideableView.setVisibility(View.GONE);
+ requestLayout();
+ mAnimation = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+ });
+ mSlideableView.startAnimation(mAnimation);
+ }
+
+ private void onPanelDragged(int newTop) {
+ final int topBound = getSlidingTop();
+ mSlideOffset = mIsSlidingUp
+ ? (float) (newTop - topBound) / mSlideRange
+ : (float) (topBound - newTop) / mSlideRange;
+ dispatchOnPanelSlide(mSlideableView);
+
+ if (mParalaxOffset > 0) {
+ int mainViewOffset = getCurrentParalaxOffset();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mMainView.setTranslationY(mainViewOffset);
+ } else {
+ mMainView.animate().translationY(mainViewOffset);
+ }
+ }
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ boolean result;
+ final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
+
+ boolean drawScrim = false;
+
+ if (mCanSlide && !lp.slideable && mSlideableView != null) {
+ // Clip against the slider; no sense drawing what will immediately be covered,
+ // Unless the panel is set to overlay content
+ if (!mOverlayContent) {
+ canvas.getClipBounds(mTmpRect);
+ if (mIsSlidingUp) {
+ mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
+ } else {
+ mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
+ }
+ canvas.clipRect(mTmpRect);
+ }
+ if (mSlideOffset < 1) {
+ drawScrim = true;
+ }
+ }
+
+ result = super.drawChild(canvas, child, drawingTime);
+ canvas.restoreToCount(save);
+
+ if (drawScrim) {
+ final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
+ final int imag = (int) (baseAlpha * (1 - mSlideOffset));
+ final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
+ mCoveredFadePaint.setColor(color);
+ canvas.drawRect(mTmpRect, mCoveredFadePaint);
+ }
+
+ return result;
+ }
+
+ /**
+ * Smoothly animate mDraggingPane to the target X position within its range.
+ *
+ * @param slideOffset position to animate to
+ * @param velocity initial velocity in case of fling, or 0.
+ */
+ boolean smoothSlideTo(float slideOffset, int velocity) {
+ if (!mCanSlide) {
+ // Nothing to do.
+ return false;
+ }
+
+ final int topBound = getSlidingTop();
+ int y = mIsSlidingUp
+ ? (int) (topBound + slideOffset * mSlideRange)
+ : (int) (topBound - slideOffset * mSlideRange);
+
+ if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
+ setAllChildrenVisible();
+ ViewCompat.postInvalidateOnAnimation(this);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mDragHelper.continueSettling(true)) {
+ if (!mCanSlide) {
+ mDragHelper.abort();
+ return;
+ }
+
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ super.draw(c);
+
+ if (mSlideableView == null) {
+ // No need to draw a shadow if we don't have one.
+ return;
+ }
+
+ final int right = mSlideableView.getRight();
+ final int top;
+ final int bottom;
+ if (mIsSlidingUp) {
+ top = mSlideableView.getTop() - mShadowHeight;
+ bottom = mSlideableView.getTop();
+ } else {
+ top = mSlideableView.getBottom();
+ bottom = mSlideableView.getBottom() + mShadowHeight;
+ }
+ final int left = mSlideableView.getLeft();
+
+ if (mShadowDrawable != null) {
+ mShadowDrawable.setBounds(left, top, right, bottom);
+ mShadowDrawable.draw(c);
+ }
+ }
+
+ /**
+ * Tests scrollability within child views of v given a delta of dx.
+ *
+ * @param v View to test for horizontal scrollability
+ * @param checkV Whether the view v passed should itself be checked for scrollability (true),
+ * or just its children (false).
+ * @param dx Delta scrolled in pixels
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if child views of v can be scrolled by delta of dx.
+ */
+ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+ if (v instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) v;
+ final int scrollX = v.getScrollX();
+ final int scrollY = v.getScrollY();
+ final int count = group.getChildCount();
+ // Count backwards - let topmost views consume scroll distance first.
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = group.getChildAt(i);
+ if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
+ y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
+ canScroll(child, true, dx, x + scrollX - child.getLeft(),
+ y + scrollY - child.getTop())) {
+ return true;
+ }
+ }
+ }
+ return checkV && ViewCompat.canScrollHorizontally(v, -dx);
+ }
+
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof MarginLayoutParams
+ ? new LayoutParams((MarginLayoutParams) p)
+ : new LayoutParams(p);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams && super.checkLayoutParams(p);
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+ ss.mSlideState = mSlideState;
+
+ return ss;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ mSlideState = ss.mSlideState;
+ }
+
+ private class DragHelperCallback extends ViewDragHelper.Callback {
+
+ @Override
+ public boolean tryCaptureView(View child, int pointerId) {
+ if (mIsUnableToDrag) {
+ return false;
+ }
+
+ return ((LayoutParams) child.getLayoutParams()).slideable;
+ }
+
+ @Override
+ public void onViewDragStateChanged(int state) {
+ int anchoredTop = (int)(mAnchorPoint*mSlideRange);
+
+ if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
+ if (mSlideOffset == 0) {
+ if (mSlideState != SlideState.EXPANDED) {
+ updateObscuredViewVisibility();
+ dispatchOnPanelExpanded(mSlideableView);
+ mSlideState = SlideState.EXPANDED;
+ }
+ } else if (mSlideOffset == (float)anchoredTop/(float)mSlideRange) {
+ if (mSlideState != SlideState.ANCHORED) {
+ updateObscuredViewVisibility();
+ dispatchOnPanelAnchored(mSlideableView);
+ mSlideState = SlideState.ANCHORED;
+ }
+ } else if (mSlideState != SlideState.COLLAPSED) {
+ dispatchOnPanelCollapsed(mSlideableView);
+ mSlideState = SlideState.COLLAPSED;
+ }
+ }
+ }
+
+ @Override
+ public void onViewCaptured(View capturedChild, int activePointerId) {
+ // Make all child views visible in preparation for sliding things around
+ setAllChildrenVisible();
+ }
+
+ @Override
+ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ onPanelDragged(top);
+ invalidate();
+ }
+
+ @Override
+ public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ int top = mIsSlidingUp
+ ? getSlidingTop()
+ : getSlidingTop() - mSlideRange;
+
+ if (mAnchorPoint != 0) {
+ int anchoredTop;
+ float anchorOffset;
+ if (mIsSlidingUp) {
+ anchoredTop = (int)(mAnchorPoint*mSlideRange);
+ anchorOffset = (float)anchoredTop/(float)mSlideRange;
+ } else {
+ anchoredTop = mPanelHeight - (int)(mAnchorPoint*mSlideRange);
+ anchorOffset = (float)(mPanelHeight - anchoredTop)/(float)mSlideRange;
+ }
+
+ if (yvel > 0 || (yvel == 0 && mSlideOffset >= (1f+anchorOffset)/2)) {
+ top += mSlideRange;
+ } else if (yvel == 0 && mSlideOffset < (1f+anchorOffset)/2
+ && mSlideOffset >= anchorOffset/2) {
+ top += mSlideRange * mAnchorPoint;
+ }
+
+ } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
+ top += mSlideRange;
+ }
+
+ mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
+ invalidate();
+ }
+
+ @Override
+ public int getViewVerticalDragRange(View child) {
+ return mSlideRange;
+ }
+
+ @Override
+ public int clampViewPositionVertical(View child, int top, int dy) {
+ final int topBound;
+ final int bottomBound;
+ if (mIsSlidingUp) {
+ topBound = getSlidingTop();
+ bottomBound = topBound + mSlideRange;
+ } else {
+ bottomBound = getPaddingTop();
+ topBound = bottomBound - mSlideRange;
+ }
+
+ return Math.min(Math.max(top, topBound), bottomBound);
+ }
+ }
+
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ private static final int[] ATTRS = new int[] {
+ android.R.attr.layout_weight
+ };
+
+ /**
+ * True if this pane is the slideable pane in the layout.
+ */
+ boolean slideable;
+
+ /**
+ * True if this view should be drawn dimmed
+ * when it's been offset from its default position.
+ */
+ boolean dimWhenOffset;
+
+ Paint dimPaint;
+
+ public LayoutParams() {
+ super(MATCH_PARENT, MATCH_PARENT);
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(android.view.ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
+ a.recycle();
+ }
+
+ }
+
+ static class SavedState extends BaseSavedState {
+ SlideState mSlideState;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ try {
+ mSlideState = Enum.valueOf(SlideState.class, in.readString());
+ } catch (IllegalArgumentException e) {
+ mSlideState = SlideState.COLLAPSED;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeString(mSlideState.toString());
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/src/com/android/launcher3/TransitionEffectsFragment.java b/src/com/android/launcher3/TransitionEffectsFragment.java
new file mode 100644
index 000000000..69231a8f0
--- /dev/null
+++ b/src/com/android/launcher3/TransitionEffectsFragment.java
@@ -0,0 +1,222 @@
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import com.android.launcher3.settings.SettingsProvider;
+
+public class TransitionEffectsFragment extends Fragment {
+ public static final String PAGE_OR_DRAWER_SCROLL_SELECT = "pageOrDrawer";
+ public static final String SELECTED_TRANSITION_EFFECT = "selectedTransitionEffect";
+ public static final String TRANSITION_EFFECTS_FRAGMENT = "transitionEffectsFragment";
+ ImageView mTransitionIcon;
+ ListView mListView;
+ View mCurrentSelection;
+ ScrollView mScrollView;
+
+ String[] mTransitionStates;
+ TypedArray mTransitionDrawables;
+ String mCurrentState;
+ int mCurrentPosition;
+ boolean mPageOrDrawer;
+ String mSettingsProviderValue;
+ int mPreferenceValue;
+
+ OnClickListener mSettingsItemListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mCurrentPosition == (Integer) v.getTag()) {
+ return;
+ }
+ mCurrentPosition = (Integer) v.getTag();
+ mCurrentState = mTransitionStates[mCurrentPosition];
+
+ setCleared(mCurrentSelection);
+ setSelected(v);
+ mCurrentSelection = v;
+
+ new Thread(new Runnable() {
+ public void run() {
+ mTransitionIcon.post(new Runnable() {
+ public void run() {
+ setImageViewToEffect();
+ }
+ });
+ }
+ }).start();
+
+ ((TransitionsArrayAdapter) mListView.getAdapter()).notifyDataSetChanged();
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.settings_transitions_screen, container, false);
+
+ mPageOrDrawer = getArguments().getBoolean(PAGE_OR_DRAWER_SCROLL_SELECT);
+
+ mSettingsProviderValue = mPageOrDrawer ?
+ SettingsProvider.SETTINGS_UI_DRAWER_SCROLLING_TRANSITION_EFFECT
+ : SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT;
+ mPreferenceValue = mPageOrDrawer ? R.string.preferences_interface_drawer_scrolling_transition_effect
+ : R.string.preferences_interface_homescreen_scrolling_transition_effect;
+
+ mTransitionIcon = (ImageView) v.findViewById(R.id.settings_transition_image);
+ mListView = (ListView) v.findViewById(R.id.settings_transitions_list);
+ mScrollView = (ScrollView) v.findViewById(R.id.scroll_view);
+ TextView title = (TextView) v.findViewById(R.id.transition_effect_title);
+ title.setText(mPageOrDrawer ? getResources().getString(
+ R.string.drawer_scroll_effect_text) : getResources().getString(
+ R.string.page_scroll_effect_text));
+ LinearLayout titleLayout = (LinearLayout) v.findViewById(R.id.transition_title);
+ titleLayout.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setEffect();
+ }
+ });
+
+ String[] titles = getResources().getStringArray(
+ R.array.transition_effect_entries);
+ mListView.setAdapter(new TransitionsArrayAdapter(getActivity(),
+ R.layout.settings_pane_list_item, titles));
+
+ mTransitionStates = getResources().getStringArray(
+ R.array.transition_effect_values);
+ mTransitionDrawables = getResources().obtainTypedArray(
+ R.array.transition_effect_drawables);
+
+ mCurrentState = SettingsProvider.getString(getActivity(),
+ mSettingsProviderValue, mPreferenceValue);
+ mCurrentPosition = mapEffectToPosition(mCurrentState);
+
+ mListView.setSelection(mCurrentPosition);
+
+ return v;
+ }
+
+ public void setEffect() {
+ ((Launcher) getActivity()).setTransitionEffect(mPageOrDrawer, mCurrentState);
+ }
+
+ private int mapEffectToPosition(String effect) {
+ int length = mTransitionStates.length;
+ for (int i = 0; i < length; i++) {
+ if (effect.equals(mTransitionStates[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void setImageViewToEffect() {
+ mTransitionIcon.setBackgroundResource(mTransitionDrawables
+ .getResourceId(mCurrentPosition, R.drawable.transition_none));
+
+ AnimationDrawable frameAnimation = (AnimationDrawable) mTransitionIcon.getBackground();
+ frameAnimation.start();
+ }
+
+ private void setSelected(View v) {
+ v.setBackgroundColor(Color.WHITE);
+ TextView t = (TextView) v.findViewById(R.id.item_name);
+ t.setTextColor(getResources().getColor(R.color.settings_bg_color));
+ }
+
+ private void setCleared(View v) {
+ v.setBackgroundColor(getResources().getColor(R.color.settings_bg_color));
+ TextView t = (TextView) v.findViewById(R.id.item_name);
+ t.setTextColor(Color.WHITE);
+ }
+
+ @Override
+ public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+ if (enter) {
+ DisplayMetrics displaymetrics = new DisplayMetrics();
+ getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+ int width = displaymetrics.widthPixels;
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(this, "translationX", width, 0);
+
+ final View darkPanel = ((Launcher) getActivity()).getDarkPanel();
+ darkPanel.setVisibility(View.VISIBLE);
+ ObjectAnimator anim2 = ObjectAnimator.ofFloat(
+ darkPanel , "alpha", 0.0f, 0.3f);
+ anim2.start();
+
+ anim.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator arg0) {}
+ @Override
+ public void onAnimationRepeat(Animator arg0) {}
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ darkPanel.setVisibility(View.GONE);
+ setImageViewToEffect();
+ }
+ @Override
+ public void onAnimationCancel(Animator arg0) {}
+ });
+
+ return anim;
+ } else {
+ return super.onCreateAnimator(transit, enter, nextAnim);
+ }
+ }
+
+ private class TransitionsArrayAdapter extends ArrayAdapter<String> {
+ Context mContext;
+ String[] titles;
+
+ public TransitionsArrayAdapter(Context context, int textViewResourceId,
+ String[] objects) {
+ super(context, textViewResourceId, objects);
+
+ mContext = context;
+ titles = objects;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = inflater.inflate(R.layout.settings_pane_list_item,
+ parent, false);
+ TextView textView = (TextView) convertView
+ .findViewById(R.id.item_name);
+ textView.setText(titles[position]);
+ // Set Selected State
+ if (position == mCurrentPosition) {
+ mCurrentSelection = convertView;
+ setSelected(mCurrentSelection);
+ }
+
+ convertView.setOnClickListener(mSettingsItemListener);
+ convertView.setTag(position);
+ return convertView;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 774996e56..a3000d7aa 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -61,6 +61,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
@@ -71,6 +73,7 @@ import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.settings.SettingsProvider;
import java.util.ArrayList;
import java.util.HashMap;
@@ -986,6 +989,8 @@ public class Workspace extends SmoothPagedView
*/
void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
boolean insert, boolean computeXYFromRank) {
+ //Reload settings
+ reloadSettings();
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (getScreenWithId(screenId) == null) {
Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
@@ -2058,6 +2063,18 @@ public class Workspace extends SmoothPagedView
getOverviewModePages(range);
}
+ public void onClickDefaultScreenButton() {
+ if (!isInOverviewMode()) return;
+
+ mDefaultScreenId = getScreenIdForPageIndex(getPageNearestToCenterOfScreen());
+
+ updateDefaultScreenButton();
+
+ SettingsProvider.get(mLauncher).edit()
+ .putLong(SettingsProvider.SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID, mDefaultScreenId)
+ .commit();
+ }
+
private void getOverviewModePages(int[] range) {
int start = numCustomPages();
int end = getChildCount() - 1;
@@ -2116,6 +2133,9 @@ public class Workspace extends SmoothPagedView
}
private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
+ //Check to see if Settings need to taken
+ reloadSettings();
+
State finalState = Workspace.State.OVERVIEW;
if (!enable) {
finalState = Workspace.State.NORMAL;
@@ -2148,6 +2168,21 @@ public class Workspace extends SmoothPagedView
return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
}
+ float getOverviewModeScaleY() {
+ float childHeight = getNormalChildHeight();
+ int viewPortHeight = getViewportHeight();
+
+ Resources res = getResources();
+ int top = res.getDimensionPixelSize(R.dimen.overview_panel_top_padding);
+ top += res.getDimensionPixelSize(R.dimen.sliding_panel_padding);
+ top += res.getDimensionPixelSize(R.dimen.overview_scaling_padding);
+
+ float scaledChildHeight = viewPortHeight - top;
+
+ float scale = scaledChildHeight / childHeight;
+ return scale;
+ }
+
boolean shouldVoiceButtonProxyBeVisible() {
if (isOnOrMovingToCustomContent()) {
return false;
@@ -2388,7 +2423,10 @@ public class Workspace extends SmoothPagedView
hotseatAlpha.setDuration(duration);
searchBarAlpha.setDuration(duration);
- anim.play(overviewPanelAlpha);
+ overviewPanel.setAlpha(finalOverviewPanelAlpha);
+ AlphaUpdateListener.updateVisibility(overviewPanel);
+ Animation animation = AnimationUtils.loadAnimation(mLauncher, R.anim.drop_down);
+ overviewPanel.startAnimation(animation);
anim.play(hotseatAlpha);
anim.play(searchBarAlpha);
anim.play(pageIndicatorAlpha);
@@ -5156,4 +5194,29 @@ public class Workspace extends SmoothPagedView
}
}
}
+
+ private void reloadSettings() {
+ mShowSearchBar = SettingsProvider.getBoolean(mLauncher, SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+ R.bool.preferences_interface_homescreen_search_default);
+ mShowOutlines = SettingsProvider.getBoolean(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_PAGE_OUTLINES,
+ R.bool.preferences_interface_homescreen_scrolling_page_outlines_default);
+ mHideIconLabels = SettingsProvider.getBoolean(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+ mWorkspaceFadeInAdjacentScreens = SettingsProvider.getBoolean(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_FADE_ADJACENT,
+ R.bool.preferences_interface_homescreen_scrolling_fade_adjacent_default);
+ TransitionEffect.setFromString(this, SettingsProvider.getString(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT,
+ R.string.preferences_interface_homescreen_scrolling_transition_effect));
+ }
+
+ public boolean getShowSearchBar() {
+ return mShowSearchBar;
+ }
+
+ public boolean getHideIconLables() {
+ return mHideIconLabels;
+ }
}
diff --git a/src/com/android/launcher3/list/AutoScrollListView.java b/src/com/android/launcher3/list/AutoScrollListView.java
new file mode 100644
index 000000000..66336bc71
--- /dev/null
+++ b/src/com/android/launcher3/list/AutoScrollListView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ListView;
+
+/**
+ * A ListView that can be asked to scroll (smoothly or otherwise) to a specific
+ * position. This class takes advantage of similar functionality that exists
+ * in {@link ListView} and enhances it.
+ */
+public class AutoScrollListView extends ListView {
+
+ /**
+ * Position the element at about 1/3 of the list height
+ */
+ private static final float PREFERRED_SELECTION_OFFSET_FROM_TOP = 0.33f;
+
+ private int mRequestedScrollPosition = -1;
+ private boolean mSmoothScrollRequested;
+
+ public AutoScrollListView(Context context) {
+ super(context);
+ }
+
+ public AutoScrollListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoScrollListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Brings the specified position to view by optionally performing a jump-scroll maneuver:
+ * first it jumps to some position near the one requested and then does a smooth
+ * scroll to the requested position. This creates an impression of full smooth
+ * scrolling without actually traversing the entire list. If smooth scrolling is
+ * not requested, instantly positions the requested item at a preferred offset.
+ */
+ public void requestPositionToScreen(int position, boolean smoothScroll) {
+ mRequestedScrollPosition = position;
+ mSmoothScrollRequested = smoothScroll;
+ requestLayout();
+ }
+
+ @Override
+ protected void layoutChildren() {
+ super.layoutChildren();
+ if (mRequestedScrollPosition == -1) {
+ return;
+ }
+
+ final int position = mRequestedScrollPosition;
+ mRequestedScrollPosition = -1;
+
+ int firstPosition = getFirstVisiblePosition() + 1;
+ int lastPosition = getLastVisiblePosition();
+ if (position >= firstPosition && position <= lastPosition) {
+ return; // Already on screen
+ }
+
+ final int offset = (int) (getHeight() * PREFERRED_SELECTION_OFFSET_FROM_TOP);
+ if (!mSmoothScrollRequested) {
+ setSelectionFromTop(position, offset);
+
+ // Since we have changed the scrolling position, we need to redo child layout
+ // Calling "requestLayout" in the middle of a layout pass has no effect,
+ // so we call layoutChildren explicitly
+ super.layoutChildren();
+
+ } else {
+ // We will first position the list a couple of screens before or after
+ // the new selection and then scroll smoothly to it.
+ int twoScreens = (lastPosition - firstPosition) * 2;
+ int preliminaryPosition;
+ if (position < firstPosition) {
+ preliminaryPosition = position + twoScreens;
+ if (preliminaryPosition >= getCount()) {
+ preliminaryPosition = getCount() - 1;
+ }
+ if (preliminaryPosition < firstPosition) {
+ setSelection(preliminaryPosition);
+ super.layoutChildren();
+ }
+ } else {
+ preliminaryPosition = position - twoScreens;
+ if (preliminaryPosition < 0) {
+ preliminaryPosition = 0;
+ }
+ if (preliminaryPosition > lastPosition) {
+ setSelection(preliminaryPosition);
+ super.layoutChildren();
+ }
+ }
+
+
+ smoothScrollToPositionFromTop(position, offset);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/list/CompositeCursorAdapter.java b/src/com/android/launcher3/list/CompositeCursorAdapter.java
new file mode 100644
index 000000000..b1ddb67fb
--- /dev/null
+++ b/src/com/android/launcher3/list/CompositeCursorAdapter.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * A general purpose adapter that is composed of multiple cursors. It just
+ * appends them in the order they are added.
+ */
+public abstract class CompositeCursorAdapter extends BaseAdapter {
+
+ private static final int INITIAL_CAPACITY = 2;
+
+ public static class Partition {
+ boolean showIfEmpty;
+ boolean hasHeader;
+
+ Cursor cursor;
+ int idColumnIndex;
+ int count;
+
+ public Partition(boolean showIfEmpty, boolean hasHeader) {
+ this.showIfEmpty = showIfEmpty;
+ this.hasHeader = hasHeader;
+ }
+
+ /**
+ * True if the directory should be shown even if no contacts are found.
+ */
+ public boolean getShowIfEmpty() {
+ return showIfEmpty;
+ }
+
+ public boolean getHasHeader() {
+ return hasHeader;
+ }
+ }
+
+ private final Context mContext;
+ private ArrayList<Partition> mPartitions;
+ private int mCount = 0;
+ private boolean mCacheValid = true;
+ private boolean mNotificationsEnabled = true;
+ private boolean mNotificationNeeded;
+
+ public CompositeCursorAdapter(Context context) {
+ this(context, INITIAL_CAPACITY);
+ }
+
+ public CompositeCursorAdapter(Context context, int initialCapacity) {
+ mContext = context;
+ mPartitions = new ArrayList<Partition>();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Registers a partition. The cursor for that partition can be set later.
+ * Partitions should be added in the order they are supposed to appear in the
+ * list.
+ */
+ public void addPartition(boolean showIfEmpty, boolean hasHeader) {
+ addPartition(new Partition(showIfEmpty, hasHeader));
+ }
+
+ public void addPartition(Partition partition) {
+ mPartitions.add(partition);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void addPartition(int location, Partition partition) {
+ mPartitions.add(location, partition);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void removePartition(int partitionIndex) {
+ Cursor cursor = mPartitions.get(partitionIndex).cursor;
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ mPartitions.remove(partitionIndex);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Removes cursors for all partitions.
+ */
+ // TODO: Is this really what this is supposed to do? Just remove the cursors? Not close them?
+ // Not remove the partitions themselves? Isn't this leaking?
+
+ public void clearPartitions() {
+ for (Partition partition : mPartitions) {
+ partition.cursor = null;
+ }
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Closes all cursors and removes all partitions.
+ */
+ public void close() {
+ for (Partition partition : mPartitions) {
+ Cursor cursor = partition.cursor;
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+ mPartitions.clear();
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void setHasHeader(int partitionIndex, boolean flag) {
+ mPartitions.get(partitionIndex).hasHeader = flag;
+ invalidate();
+ }
+
+ public void setShowIfEmpty(int partitionIndex, boolean flag) {
+ mPartitions.get(partitionIndex).showIfEmpty = flag;
+ invalidate();
+ }
+
+ public Partition getPartition(int partitionIndex) {
+ return mPartitions.get(partitionIndex);
+ }
+
+ protected void invalidate() {
+ mCacheValid = false;
+ }
+
+ public int getPartitionCount() {
+ return mPartitions.size();
+ }
+
+ protected void ensureCacheValid() {
+ if (mCacheValid) {
+ return;
+ }
+
+ mCount = 0;
+ for (Partition partition : mPartitions) {
+ Cursor cursor = partition.cursor;
+ int count = cursor != null ? cursor.getCount() : 0;
+ if (partition.hasHeader) {
+ if (count != 0 || partition.showIfEmpty) {
+ count++;
+ }
+ }
+ partition.count = count;
+ mCount += count;
+ }
+
+ mCacheValid = true;
+ }
+
+ /**
+ * Returns true if the specified partition was configured to have a header.
+ */
+ public boolean hasHeader(int partition) {
+ return mPartitions.get(partition).hasHeader;
+ }
+
+ /**
+ * Returns the total number of list items in all partitions.
+ */
+ public int getCount() {
+ ensureCacheValid();
+ return mCount;
+ }
+
+ /**
+ * Returns the cursor for the given partition
+ */
+ public Cursor getCursor(int partition) {
+ return mPartitions.get(partition).cursor;
+ }
+
+ /**
+ * Changes the cursor for an individual partition.
+ */
+ public void changeCursor(int partition, Cursor cursor) {
+ Cursor prevCursor = mPartitions.get(partition).cursor;
+ if (prevCursor != cursor) {
+ if (prevCursor != null && !prevCursor.isClosed()) {
+ prevCursor.close();
+ }
+ mPartitions.get(partition).cursor = cursor;
+ if (cursor != null) {
+ mPartitions.get(partition).idColumnIndex = cursor.getColumnIndex("_id");
+ }
+ invalidate();
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Returns true if the specified partition has no cursor or an empty cursor.
+ */
+ public boolean isPartitionEmpty(int partition) {
+ Cursor cursor = mPartitions.get(partition).cursor;
+ return cursor == null || cursor.getCount() == 0;
+ }
+
+ /**
+ * Given a list position, returns the index of the corresponding partition.
+ */
+ public int getPartitionForPosition(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ return i;
+ }
+ start = end;
+ }
+ return -1;
+ }
+
+ /**
+ * Given a list position, return the offset of the corresponding item in its
+ * partition. The header, if any, will have offset -1.
+ */
+ public int getOffsetInPartition(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition partition : mPartitions) {
+ int end = start + partition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (partition.hasHeader) {
+ offset--;
+ }
+ return offset;
+ }
+ start = end;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the first list position for the specified partition.
+ */
+ public int getPositionForPartition(int partition) {
+ ensureCacheValid();
+ int position = 0;
+ for (int i = 0; i < partition; i++) {
+ position += mPartitions.get(i).count;
+ }
+ return position;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return getItemViewTypeCount() + 1;
+ }
+
+ /**
+ * Returns the overall number of item view types across all partitions. An
+ * implementation of this method needs to ensure that the returned count is
+ * consistent with the values returned by {@link #getItemViewType(int,int)}.
+ */
+ public int getItemViewTypeCount() {
+ return 1;
+ }
+
+ /**
+ * Returns the view type for the list item at the specified position in the
+ * specified partition.
+ */
+ protected int getItemViewType(int partition, int position) {
+ return 1;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return IGNORE_ITEM_VIEW_TYPE;
+ } else {
+ return getItemViewType(i, offset);
+ }
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader) {
+ offset--;
+ }
+ View view;
+ if (offset == -1) {
+ view = getHeaderView(i, mPartitions.get(i).cursor, convertView, parent);
+ } else {
+ if (!mPartitions.get(i).cursor.moveToPosition(offset)) {
+ throw new IllegalStateException("Couldn't move cursor to position "
+ + offset);
+ }
+ view = getView(i, mPartitions.get(i).cursor, offset, convertView, parent);
+ }
+ if (view == null) {
+ throw new NullPointerException("View should not be null, partition: " + i
+ + " position: " + offset);
+ }
+ return view;
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ /**
+ * Returns the header view for the specified partition, creating one if needed.
+ */
+ protected View getHeaderView(int partition, Cursor cursor, View convertView,
+ ViewGroup parent) {
+ View view = convertView != null
+ ? convertView
+ : newHeaderView(mContext, partition, cursor, parent);
+ bindHeaderView(view, partition, cursor);
+ return view;
+ }
+
+ /**
+ * Creates the header view for the specified partition.
+ */
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ return null;
+ }
+
+ /**
+ * Binds the header view for the specified partition.
+ */
+ protected void bindHeaderView(View view, int partition, Cursor cursor) {
+ }
+
+ /**
+ * Returns an item view for the specified partition, creating one if needed.
+ */
+ protected View getView(int partition, Cursor cursor, int position, View convertView,
+ ViewGroup parent) {
+ View view;
+ if (convertView != null) {
+ view = convertView;
+ } else {
+ view = newView(mContext, partition, cursor, position, parent);
+ }
+ bindView(view, partition, cursor, position);
+ return view;
+ }
+
+ /**
+ * Creates an item view for the specified partition and position. Position
+ * corresponds directly to the current cursor position.
+ */
+ protected abstract View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent);
+
+ /**
+ * Binds an item view for the specified partition and position. Position
+ * corresponds directly to the current cursor position.
+ */
+ protected abstract void bindView(View v, int partition, Cursor cursor, int position);
+
+ /**
+ * Returns a pre-positioned cursor for the specified list position.
+ */
+ public Object getItem(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition mPartition : mPartitions) {
+ int end = start + mPartition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartition.hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return null;
+ }
+ Cursor cursor = mPartition.cursor;
+ cursor.moveToPosition(offset);
+ return cursor;
+ }
+ start = end;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the item ID for the specified list position.
+ */
+ public long getItemId(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition mPartition : mPartitions) {
+ int end = start + mPartition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartition.hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return 0;
+ }
+ if (mPartition.idColumnIndex == -1) {
+ return 0;
+ }
+
+ Cursor cursor = mPartition.cursor;
+ if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) {
+ return 0;
+ }
+ return cursor.getLong(mPartition.idColumnIndex);
+ }
+ start = end;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns false if any partition has a header.
+ */
+ @Override
+ public boolean areAllItemsEnabled() {
+ for (Partition mPartition : mPartitions) {
+ if (mPartition.hasHeader) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true for all items except headers.
+ */
+ @Override
+ public boolean isEnabled(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader && offset == 0) {
+ return false;
+ } else {
+ return isEnabled(i, offset);
+ }
+ }
+ start = end;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the item at the specified offset of the specified
+ * partition is selectable and clickable.
+ */
+ protected boolean isEnabled(int partition, int position) {
+ return true;
+ }
+
+ /**
+ * Enable or disable data change notifications. It may be a good idea to
+ * disable notifications before making changes to several partitions at once.
+ */
+ public void setNotificationsEnabled(boolean flag) {
+ mNotificationsEnabled = flag;
+ if (flag && mNotificationNeeded) {
+ notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ if (mNotificationsEnabled) {
+ mNotificationNeeded = false;
+ super.notifyDataSetChanged();
+ } else {
+ mNotificationNeeded = true;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/list/PinnedHeaderListAdapter.java b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java
new file mode 100644
index 000000000..c15e87a46
--- /dev/null
+++ b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers.
+ */
+public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
+ implements PinnedHeaderListView.PinnedHeaderAdapter {
+
+ public static final int PARTITION_HEADER_TYPE = 0;
+
+ private boolean mPinnedPartitionHeadersEnabled;
+ private boolean mHeaderVisibility[];
+
+ public PinnedHeaderListAdapter(Context context) {
+ super(context);
+ }
+
+ public PinnedHeaderListAdapter(Context context, int initialCapacity) {
+ super(context, initialCapacity);
+ }
+
+ public boolean getPinnedPartitionHeadersEnabled() {
+ return mPinnedPartitionHeadersEnabled;
+ }
+
+ public void setPinnedPartitionHeadersEnabled(boolean flag) {
+ this.mPinnedPartitionHeadersEnabled = flag;
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ if (mPinnedPartitionHeadersEnabled) {
+ return getPartitionCount();
+ } else {
+ return 0;
+ }
+ }
+
+ protected boolean isPinnedPartitionHeaderVisible(int partition) {
+ return getPinnedPartitionHeadersEnabled() && hasHeader(partition)
+ && !isPartitionEmpty(partition);
+ }
+
+ /**
+ * The default implementation creates the same type of view as a normal
+ * partition header.
+ */
+ @Override
+ public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) {
+ if (hasHeader(partition)) {
+ View view = null;
+ if (convertView != null) {
+ Integer headerType = (Integer)convertView.getTag();
+ if (headerType != null && headerType == PARTITION_HEADER_TYPE) {
+ view = convertView;
+ }
+ }
+ if (view == null) {
+ view = newHeaderView(getContext(), partition, null, parent);
+ view.setTag(PARTITION_HEADER_TYPE);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ }
+ bindHeaderView(view, partition, getCursor(partition));
+ view.setLayoutDirection(parent.getLayoutDirection());
+ return view;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void configurePinnedHeaders(PinnedHeaderListView listView) {
+ if (!getPinnedPartitionHeadersEnabled()) {
+ return;
+ }
+
+ int size = getPartitionCount();
+ boolean unCached = false;
+ // Cache visibility bits, because we will need them several times later on
+ if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
+ mHeaderVisibility = new boolean[size];
+ unCached = true;
+ }
+ for (int i = 0; i < size; i++) {
+ boolean visible = isPinnedPartitionHeaderVisible(i);
+ mHeaderVisibility[i] = visible;
+ if (!visible) {
+ listView.setHeaderInvisible(i, true);
+ }
+ }
+
+ int headerViewsCount = listView.getHeaderViewsCount();
+
+ // Starting at the top, find and pin headers for partitions preceding the visible one(s)
+ int maxTopHeader = -1;
+ int topHeaderHeight = 0;
+ for (int i = 0; i < size; i++) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount;
+ int partition = getPartitionForPosition(position);
+ if (i > partition) {
+ break;
+ }
+
+ if (!unCached){
+ listView.setHeaderPinnedAtTop(i, topHeaderHeight, false);
+ topHeaderHeight += listView.getPinnedHeaderHeight(i);
+ maxTopHeader = i;
+ }
+
+ }
+ }
+
+ // Starting at the bottom, find and pin headers for partitions following the visible one(s)
+ /*int maxBottomHeader = size;
+ int bottomHeaderHeight = 0;
+ int listHeight = listView.getHeight();
+ for (int i = size; --i > maxTopHeader;) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(listHeight - bottomHeaderHeight)
+ - headerViewsCount;
+ if (position < 0) {
+ break;
+ }
+
+ int partition = getPartitionForPosition(position - 1);
+ if (partition == -1 || i <= partition) {
+ break;
+ }
+
+ int height = listView.getPinnedHeaderHeight(i);
+ bottomHeaderHeight += height;
+
+ listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, false);
+ maxBottomHeader = i;
+ }
+ }
+
+ // Headers in between the top-pinned and bottom-pinned should be hidden
+ for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) {
+ if (mHeaderVisibility[i]) {
+ listView.setHeaderInvisible(i, isPartitionEmpty(i));
+ }
+ }*/
+ }
+
+ @Override
+ public int getScrollPositionForHeader(int viewIndex) {
+ return getPositionForPartition(viewIndex);
+ }
+}
diff --git a/src/com/android/launcher3/list/PinnedHeaderListView.java b/src/com/android/launcher3/list/PinnedHeaderListView.java
new file mode 100644
index 000000000..30688fea3
--- /dev/null
+++ b/src/com/android/launcher3/list/PinnedHeaderListView.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2010 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.list;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ListAdapter;
+
+/**
+ * A ListView that maintains a header pinned at the top of the list. The
+ * pinned header can be pushed up and dissolved as needed.
+ */
+public class PinnedHeaderListView extends AutoScrollListView
+ implements OnScrollListener, OnItemSelectedListener {
+
+ /**
+ * Adapter interface. The list adapter must implement this interface.
+ */
+ public interface PinnedHeaderAdapter {
+
+ /**
+ * Returns the overall number of pinned headers, visible or not.
+ */
+ int getPinnedHeaderCount();
+
+ /**
+ * Creates or updates the pinned header view.
+ */
+ View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent);
+
+ /**
+ * Configures the pinned headers to match the visible list items. The
+ * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop},
+ * {@link PinnedHeaderListView#setHeaderPinnedAtBottom},
+ * {@link PinnedHeaderListView#setFadingHeader} or
+ * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that
+ * needs to change its position or visibility.
+ */
+ void configurePinnedHeaders(PinnedHeaderListView listView);
+
+ /**
+ * Returns the list position to scroll to if the pinned header is touched.
+ * Return -1 if the list does not need to be scrolled.
+ */
+ int getScrollPositionForHeader(int viewIndex);
+ }
+
+ private static final int MAX_ALPHA = 255;
+ private static final int TOP = 0;
+ private static final int BOTTOM = 1;
+ private static final int FADING = 2;
+
+ private static final int DEFAULT_ANIMATION_DURATION = 20;
+
+ private static final int DEFAULT_SMOOTH_SCROLL_DURATION = 100;
+
+ private static final class PinnedHeader {
+ View view;
+ boolean visible;
+ int y;
+ int height;
+ int alpha;
+ int state;
+
+ boolean animating;
+ boolean targetVisible;
+ int sourceY;
+ int targetY;
+ long targetTime;
+ }
+
+ private PinnedHeaderAdapter mAdapter;
+ private int mSize;
+ private PinnedHeader[] mHeaders;
+ private RectF mBounds = new RectF();
+ private Rect mClipRect = new Rect();
+ private OnScrollListener mOnScrollListener;
+ private OnItemSelectedListener mOnItemSelectedListener;
+ private int mScrollState;
+
+ private boolean mScrollToSectionOnHeaderTouch = false;
+ private boolean mHeaderTouched = false;
+
+ private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
+ private boolean mAnimating;
+ private long mAnimationTargetTime;
+ private int mHeaderPaddingStart;
+ private int mHeaderWidth;
+
+ public PinnedHeaderListView(Context context) {
+ this(context, null, android.R.attr.listViewStyle);
+ }
+
+ public PinnedHeaderListView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.listViewStyle);
+ }
+
+ public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ super.setOnScrollListener(this);
+ super.setOnItemSelectedListener(this);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mHeaderPaddingStart = getPaddingStart();
+ mHeaderWidth = r - l - mHeaderPaddingStart - getPaddingEnd();
+ }
+
+ public void setPinnedHeaderAnimationDuration(int duration) {
+ mAnimationDuration = duration;
+ }
+
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ mAdapter = (PinnedHeaderAdapter)adapter;
+ super.setAdapter(adapter);
+ }
+
+ @Override
+ public void setOnScrollListener(OnScrollListener onScrollListener) {
+ mOnScrollListener = onScrollListener;
+ super.setOnScrollListener(this);
+ }
+
+ @Override
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ super.setOnItemSelectedListener(this);
+ }
+
+ public void setScrollToSectionOnHeaderTouch(boolean value) {
+ mScrollToSectionOnHeaderTouch = value;
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (mAdapter != null) {
+ int count = mAdapter.getPinnedHeaderCount();
+ if (count != mSize) {
+ mSize = count;
+ if (mHeaders == null) {
+ mHeaders = new PinnedHeader[mSize];
+ } else if (mHeaders.length < mSize) {
+ PinnedHeader[] headers = mHeaders;
+ mHeaders = new PinnedHeader[mSize];
+ System.arraycopy(headers, 0, mHeaders, 0, headers.length);
+ }
+ }
+
+ for (int i = 0; i < mSize; i++) {
+ if (mHeaders[i] == null) {
+ mHeaders[i] = new PinnedHeader();
+ }
+ mHeaders[i].view = mAdapter.getPinnedHeaderView(i, mHeaders[i].view, this);
+ }
+
+ mAnimationTargetTime = System.currentTimeMillis() + mAnimationDuration;
+ mAdapter.configurePinnedHeaders(this);
+ invalidateIfAnimating();
+
+ }
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
+ }
+ }
+
+ @Override
+ protected float getTopFadingEdgeStrength() {
+ // Disable vertical fading at the top when the pinned header is present
+ return mSize > 0 ? 0 : super.getTopFadingEdgeStrength();
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollState = scrollState;
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrollStateChanged(this, scrollState);
+ }
+ }
+
+ /**
+ * Ensures that the selected item is positioned below the top-pinned headers
+ * and above the bottom-pinned ones.
+ */
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ int height = getHeight();
+
+ int windowTop = 0;
+ int windowBottom = height;
+
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ if (header.state == TOP) {
+ windowTop = header.y + header.height;
+ } else if (header.state == BOTTOM) {
+ windowBottom = header.y;
+ break;
+ }
+ }
+ }
+
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ if (selectedView.getTop() < windowTop) {
+ setSelectionFromTop(position, windowTop);
+ } else if (selectedView.getBottom() > windowBottom) {
+ setSelectionFromTop(position, windowBottom - selectedView.getHeight());
+ }
+ }
+
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onItemSelected(parent, view, position, id);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onNothingSelected(parent);
+ }
+ }
+
+ public int getPinnedHeaderHeight(int viewIndex) {
+ ensurePinnedHeaderLayout(viewIndex);
+ return mHeaders[viewIndex].view.getHeight();
+ }
+
+ /**
+ * Set header to be pinned at the top.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderPinnedAtTop(int viewIndex, int y, boolean animate) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.y = y;
+ header.state = TOP;
+
+ // TODO perhaps we should animate at the top as well
+ header.animating = false;
+ }
+
+ /**
+ * Set header to be pinned at the bottom.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderPinnedAtBottom(int viewIndex, int y, boolean animate) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.state = BOTTOM;
+ if (header.animating) {
+ header.targetTime = mAnimationTargetTime;
+ header.sourceY = header.y;
+ header.targetY = y;
+ } else if (animate && (header.y != y || !header.visible)) {
+ if (header.visible) {
+ header.sourceY = header.y;
+ } else {
+ header.visible = true;
+ header.sourceY = y + header.height;
+ }
+ header.animating = true;
+ header.targetVisible = true;
+ header.targetTime = mAnimationTargetTime;
+ header.targetY = y;
+ } else {
+ header.visible = true;
+ header.y = y;
+ }
+ }
+
+ /**
+ * Set header to be pinned at the top of the first visible item.
+ *
+ * @param viewIndex index of the header view
+ * @param position is position of the header in pixels.
+ */
+ public void setFadingHeader(int viewIndex, int position, boolean fade) {
+ ensurePinnedHeaderLayout(viewIndex);
+
+ View child = getChildAt(position - getFirstVisiblePosition());
+ if (child == null) return;
+
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.state = FADING;
+ header.alpha = MAX_ALPHA;
+ header.animating = false;
+
+ int top = getTotalTopPinnedHeaderHeight();
+ header.y = top;
+ if (fade) {
+ int bottom = child.getBottom() - top;
+ int headerHeight = header.height;
+ if (bottom < headerHeight) {
+ int portion = bottom - headerHeight;
+ header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight;
+ header.y = top + portion;
+ }
+ }
+ }
+
+ /**
+ * Makes header invisible.
+ *
+ * @param viewIndex index of the header view
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderInvisible(int viewIndex, boolean animate) {
+ PinnedHeader header = mHeaders[viewIndex];
+ if (header.visible && (animate || header.animating) && header.state == BOTTOM) {
+ header.sourceY = header.y;
+ if (!header.animating) {
+ header.visible = true;
+ header.targetY = getBottom() + header.height;
+ }
+ header.animating = true;
+ header.targetTime = mAnimationTargetTime;
+ header.targetVisible = false;
+ } else {
+ header.visible = false;
+ }
+ }
+
+ private void ensurePinnedHeaderLayout(int viewIndex) {
+ View view = mHeaders[viewIndex].view;
+ if (view.isLayoutRequested()) {
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(mHeaderWidth, View.MeasureSpec.EXACTLY);
+ int heightSpec;
+ ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+ if (layoutParams != null && layoutParams.height > 0) {
+ heightSpec = View.MeasureSpec
+ .makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY);
+ } else {
+ heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ }
+ view.measure(widthSpec, heightSpec);
+ int height = view.getMeasuredHeight();
+ mHeaders[viewIndex].height = height;
+ view.layout(0, 0, mHeaderWidth, height);
+ }
+ }
+
+ /**
+ * Returns the sum of heights of headers pinned to the top.
+ */
+ public int getTotalTopPinnedHeaderHeight() {
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.state == TOP) {
+ return header.y + header.height;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the list item position at the specified y coordinate.
+ */
+ public int getPositionAt(int y) {
+ do {
+ int position = pointToPosition(getPaddingLeft() + 1, y);
+ if (position != -1) {
+ return position;
+ }
+ // If position == -1, we must have hit a separator. Let's examine
+ // a nearby pixel
+ y--;
+ } while (y > 0);
+ return 0;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ mHeaderTouched = false;
+ if (super.onInterceptTouchEvent(ev)) {
+ return true;
+ }
+
+ if (mScrollState == SCROLL_STATE_IDLE) {
+ final int y = (int)ev.getY();
+ final int x = (int)ev.getX();
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ // For RTL layouts, this also takes into account that the scrollbar is on the left
+ // side.
+ final int padding = getPaddingLeft();
+ if (header.visible && header.y <= y && header.y + header.height > y &&
+ x >= padding && padding + mHeaderWidth >= x) {
+ mHeaderTouched = true;
+ if (mScrollToSectionOnHeaderTouch &&
+ ev.getAction() == MotionEvent.ACTION_DOWN) {
+ return smoothScrollToPartition(i);
+ } else {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mHeaderTouched) {
+ if (ev.getAction() == MotionEvent.ACTION_UP) {
+ mHeaderTouched = false;
+ }
+ return true;
+ }
+ return super.onTouchEvent(ev);
+ };
+
+ private boolean smoothScrollToPartition(int partition) {
+ final int position = mAdapter.getScrollPositionForHeader(partition);
+ if (position == -1) {
+ return false;
+ }
+
+ int offset = 0;
+ for (int i = 0; i < partition; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ offset += header.height;
+ }
+ }
+ smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset,
+ DEFAULT_SMOOTH_SCROLL_DURATION);
+ return true;
+ }
+
+ private void invalidateIfAnimating() {
+ mAnimating = false;
+ for (int i = 0; i < mSize; i++) {
+ if (mHeaders[i].animating) {
+ mAnimating = true;
+ invalidate();
+ return;
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ long currentTime = mAnimating ? System.currentTimeMillis() : 0;
+
+ int top = 0;
+ int bottom = getBottom();
+ boolean hasVisibleHeaders = false;
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ hasVisibleHeaders = true;
+ if (header.state == BOTTOM && header.y < bottom) {
+ bottom = header.y;
+ } else if (header.state == TOP || header.state == FADING) {
+ int newTop = header.y + header.height;
+ if (newTop > top) {
+ top = newTop;
+ }
+ }
+ }
+ }
+
+ if (hasVisibleHeaders) {
+ canvas.save();
+ mClipRect.set(0, top, getWidth(), bottom);
+ canvas.clipRect(mClipRect);
+ }
+
+ super.dispatchDraw(canvas);
+
+ if (hasVisibleHeaders) {
+ canvas.restore();
+
+ // First draw top headers, then the bottom ones to handle the Z axis correctly
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && (header.state == TOP || header.state == FADING)) {
+ drawHeader(canvas, header, currentTime);
+ }
+ }
+
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.state == BOTTOM) {
+ drawHeader(canvas, header, currentTime);
+ }
+ }
+ }
+
+ invalidateIfAnimating();
+ }
+
+ private void drawHeader(Canvas canvas, PinnedHeader header, long currentTime) {
+ if (header.animating) {
+ int timeLeft = (int)(header.targetTime - currentTime);
+ if (timeLeft <= 0) {
+ header.y = header.targetY;
+ header.visible = header.targetVisible;
+ header.animating = false;
+ } else {
+ header.y = header.targetY + (header.sourceY - header.targetY) * timeLeft
+ / mAnimationDuration;
+ }
+ }
+ if (header.visible) {
+ View view = header.view;
+ int saveCount = canvas.save();
+ canvas.translate(isLayoutRtl() ?
+ getWidth() - mHeaderPaddingStart - mHeaderWidth : mHeaderPaddingStart,
+ header.y);
+ if (header.state == FADING) {
+ mBounds.set(0, 0, mHeaderWidth, view.getHeight());
+ canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG);
+ }
+ view.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+
+ /**
+ * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
+ */
+ public boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ }
+}
diff --git a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java
new file mode 100644
index 000000000..1b6316293
--- /dev/null
+++ b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java
@@ -0,0 +1,248 @@
+package com.android.launcher3.list;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.OverviewSettingsPanel;
+import com.android.launcher3.R;
+import com.android.launcher3.settings.SettingsProvider;
+import android.view.View.OnClickListener;
+import android.content.SharedPreferences;
+
+public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter {
+ private Launcher mLauncher;
+ private Context mContext;
+
+ public SettingsPinnedHeaderAdapter(Context context) {
+ super(context);
+ mLauncher = (Launcher) context;
+ mContext = context;
+ }
+
+ private String[] mHeaders;
+ public int mPinnedHeaderCount;
+
+ public void setHeaders(String[] headers) {
+ this.mHeaders = headers;
+ }
+
+ @Override
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.settings_pane_list_header, null);
+ }
+
+ @Override
+ protected void bindHeaderView(View view, int partition, Cursor cursor) {
+ TextView textView = (TextView) view.findViewById(R.id.item_name);
+ textView.setText(mHeaders[partition]);
+ textView.setTypeface(textView.getTypeface(), Typeface.BOLD);
+
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.settings_pane_list_item, null);
+ }
+
+ @Override
+ protected void bindView(View v, int partition, Cursor cursor, int position) {
+ TextView text = (TextView)v.findViewById(R.id.item_name);
+ String title = cursor.getString(1);
+ text.setText(title);
+
+ Resources res = mLauncher.getResources();
+
+ if (title.equals(res
+ .getString(R.string.home_screen_search_text))) {
+ boolean current = mLauncher.shouldShowSearchBar();
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.drawer_sorting_text))) {
+ updateDrawerSortSettingsItem(v);
+ } else if (title.equals(res
+ .getString(R.string.drawer_scroll_effect_text)) &&
+ partition == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION) {
+ String state = mLauncher.getAppsCustomizeTransitionEffect();
+ state = mapEffectToValue(state);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.page_scroll_effect_text)) &&
+ partition == OverviewSettingsPanel.HOME_SETTINGS_POSITION) {
+ String state = mLauncher.getWorkspaceTransitionEffect();
+ state = mapEffectToValue(state);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.larger_icons_text))) {
+ boolean current = SettingsProvider
+ .getBoolean(
+ mContext,
+ SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
+ R.bool.preferences_interface_general_icons_large_default);
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.hide_icon_labels)) &&
+ partition == OverviewSettingsPanel.HOME_SETTINGS_POSITION) {
+ boolean current = mLauncher.shouldHideWorkspaceIconLables();
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.hide_icon_labels)) &&
+ partition == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION) {
+ boolean current = SettingsProvider
+ .getBoolean(
+ mContext,
+ SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_drawer_hide_icon_labels_default);
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ }
+
+ v.setTag(partition);
+ v.setOnClickListener(mSettingsItemListener);
+ }
+
+ @Override
+ public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ View view = inflater.inflate(R.layout.settings_pane_list_header, parent, false);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ bindHeaderView(view, viewIndex, null);
+ return view;
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ return mPinnedHeaderCount;
+ }
+
+ public void updateDrawerSortSettingsItem(View v) {
+ String state = "";
+ switch (mLauncher.getAppsCustomizeContentSortMode()) {
+ case Title:
+ state = mLauncher.getResources().getString(R.string.sort_mode_title);
+ break;
+ case LaunchCount:
+ state = mLauncher.getResources().getString(
+ R.string.sort_mode_launch_count);
+ break;
+ case InstallTime:
+ state = mLauncher.getResources().getString(
+ R.string.sort_mode_install_time);
+ break;
+ }
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ }
+
+ private String mapEffectToValue(String effect) {
+ final String[] titles = mLauncher.getResources().getStringArray(
+ R.array.transition_effect_entries);
+ final String[] values = mLauncher.getResources().getStringArray(
+ R.array.transition_effect_values);
+
+ int length = values.length;
+ for (int i = 0; i < length; i++) {
+ if (effect.equals(values[i])) {
+ return titles[i];
+ }
+ }
+ return "";
+ }
+
+ OnClickListener mSettingsItemListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ String value = ((TextView) v.findViewById(R.id.item_name)).getText().toString();
+ Resources res = mLauncher.getResources();
+
+ // Handle toggles or launch pickers
+ if (value.equals(res
+ .getString(R.string.home_screen_search_text))) {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+ R.bool.preferences_interface_homescreen_search_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res
+ .getString(R.string.drawer_sorting_text))) {
+ mLauncher.onClickSortModeButton(v);
+ } else if (value.equals(res
+ .getString(R.string.drawer_scroll_effect_text)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION)) {
+ mLauncher.onClickTransitionEffectButton(v, true);
+ } else if (value.equals(res
+ .getString(R.string.page_scroll_effect_text)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.HOME_SETTINGS_POSITION)) {
+ mLauncher.onClickTransitionEffectButton(v, false);
+ } else if (value.equals(res
+ .getString(R.string.larger_icons_text))) {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
+ R.bool.preferences_interface_general_icons_large_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res
+ .getString(R.string.hide_icon_labels)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.HOME_SETTINGS_POSITION)) {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res
+ .getString(R.string.hide_icon_labels)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION)) {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_drawer_hide_icon_labels_default);
+ mLauncher.updateDynamicGrid();
+ }
+
+ View defaultHome = mLauncher.findViewById(R.id.default_home_screen_panel);
+ defaultHome.setVisibility(getCursor(0).getCount() > 1 ? View.VISIBLE : View.GONE);
+ }
+ };
+
+ private void onSettingsBooleanChanged(View v, String key, int res) {
+ boolean current = SettingsProvider.getBoolean(
+ mContext, key, res);
+
+ // Set new state
+ SharedPreferences sharedPref = SettingsProvider
+ .get(mContext);
+ sharedPref.edit().putBoolean(key, !current).commit();
+ sharedPref.edit()
+ .putBoolean(SettingsProvider.SETTINGS_CHANGED, true)
+ .commit();
+
+ String state = current ? mLauncher.getResources().getString(
+ R.string.setting_state_off) : mLauncher.getResources().getString(
+ R.string.setting_state_on);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ }
+}
diff --git a/src/com/android/launcher3/settings/SettingsProvider.java b/src/com/android/launcher3/settings/SettingsProvider.java
new file mode 100644
index 000000000..e6bcc9c69
--- /dev/null
+++ b/src/com/android/launcher3/settings/SettingsProvider.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2013 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.settings;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public final class SettingsProvider {
+ public static final String SETTINGS_KEY = "trebuchet_preferences";
+
+ public static final String SETTINGS_CHANGED = "settings_changed";
+
+ public static final String SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID = "ui_homescreen_default_screen_id";
+ public static final String SETTINGS_UI_HOMESCREEN_SEARCH = "ui_homescreen_search";
+ public static final String SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS = "ui_homescreen_general_hide_icon_labels";
+ public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT = "ui_homescreen_scrolling_transition_effect";
+ public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL = "ui_homescreen_scrolling_wallpaper_scroll";
+ public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_PAGE_OUTLINES = "ui_homescreen_scrolling_page_outlines";
+ public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_FADE_ADJACENT = "ui_homescreen_scrolling_fade_adjacent";
+ public static final String SETTINGS_UI_DYNAMIC_GRID_SIZE = "ui_dynamic_grid_size";
+ public static final String SETTINGS_UI_HOMESCREEN_ROWS = "ui_homescreen_rows";
+ public static final String SETTINGS_UI_HOMESCREEN_COLUMNS = "ui_homescreen_columns";
+ public static final String SETTINGS_UI_DRAWER_SCROLLING_TRANSITION_EFFECT = "ui_drawer_scrolling_transition_effect";
+ public static final String SETTINGS_UI_DRAWER_SCROLLING_FADE_ADJACENT = "ui_drawer_scrolling_fade_adjacent";
+ public static final String SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_SHORTCUTS = "ui_drawer_remove_hidden_apps_shortcuts";
+ public static final String SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_WIDGETS = "ui_drawer_remove_hidden_apps_widgets";
+ public static final String SETTINGS_UI_DRAWER_HIDE_ICON_LABELS = "ui_drawer_hide_icon_labels";
+ public static final String SETTINGS_UI_GENERAL_ICONS_LARGE = "ui_general_icons_large";
+ public static final String SETTINGS_UI_DRAWER_SORT_MODE = "ui_drawer_sort_mode";
+
+ public static SharedPreferences get(Context context) {
+ return context.getSharedPreferences(SETTINGS_KEY, Context.MODE_MULTI_PROCESS);
+ }
+
+ public static int getIntCustomDefault(Context context, String key, int def) {
+ return get(context).getInt(key, def);
+ }
+
+ public static int getInt(Context context, String key, int resource) {
+ return getIntCustomDefault(context, key, context.getResources().getInteger(resource));
+ }
+
+ public static long getLongCustomDefault(Context context, String key, long def) {
+ return get(context).getLong(key, def);
+ }
+
+ public static long getLong(Context context, String key, int resource) {
+ return getLongCustomDefault(context, key, context.getResources().getInteger(resource));
+ }
+
+ public static boolean getBooleanCustomDefault(Context context, String key, boolean def) {
+ return get(context).getBoolean(key, def);
+ }
+
+ public static boolean getBoolean(Context context, String key, int resource) {
+ return getBooleanCustomDefault(context, key, context.getResources().getBoolean(resource));
+ }
+
+ public static String getStringCustomDefault(Context context, String key, String def) {
+ return get(context).getString(key, def);
+ }
+
+ public static String getString(Context context, String key, int resource) {
+ return getStringCustomDefault(context, key, context.getResources().getString(resource));
+ }
+
+ public static void putString(Context context, String key, String value) {
+ get(context).edit().putString(key, value).commit();
+ }
+
+ public static void putInt(Context context, String key, int value) {
+ get(context).edit().putInt(key, value).commit();
+ }
+}