aboutsummaryrefslogtreecommitdiffstats
path: root/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock
diff options
context:
space:
mode:
authorDominik Schürmann <dominik@dominikschuermann.de>2012-12-12 13:58:22 +0100
committerDominik Schürmann <dominik@dominikschuermann.de>2012-12-12 13:58:22 +0100
commite3fea30abe9f9f16dbfa5e989e7058842b085b7e (patch)
tree06c5bdcb4e8d18d315115a8a0fc414a1d0d64ccc /APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock
parenta4ea3e65a7feb9175e206b4aa93662bf51457072 (diff)
downloadopen-keychain-e3fea30abe9f9f16dbfa5e989e7058842b085b7e.tar.gz
open-keychain-e3fea30abe9f9f16dbfa5e989e7058842b085b7e.tar.bz2
open-keychain-e3fea30abe9f9f16dbfa5e989e7058842b085b7e.zip
Update ActionBarSherlock
Diffstat (limited to 'APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock')
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/ActionBarSherlock.java5
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/ActionBar.java9
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockActivity.java11
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java17
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockListActivity.java11
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java11
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java44
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java10
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java6
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java14
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java8
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java8
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java11
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java3
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java22
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java13
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java13
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java10
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CollapsibleActionViewWrapper.java30
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsColorDrawable.java41
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java154
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java1
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/view/MenuInflater.java41
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java31
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java13
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java1811
-rw-r--r--APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java733
27 files changed, 2961 insertions, 120 deletions
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/ActionBarSherlock.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/ActionBarSherlock.java
index 8340fb591..ab160f836 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/ActionBarSherlock.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/ActionBarSherlock.java
@@ -537,6 +537,9 @@ public abstract class ActionBarSherlock {
*/
public void dispatchDestroy() {}
+ public void dispatchSaveInstanceState(Bundle outState) {}
+
+ public void dispatchRestoreInstanceState(Bundle savedInstanceState) {}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
@@ -769,7 +772,7 @@ public abstract class ActionBarSherlock {
// Make sure that action views can get an appropriate theme.
if (mMenuInflater == null) {
if (getActionBar() != null) {
- mMenuInflater = new MenuInflater(getThemedContext());
+ mMenuInflater = new MenuInflater(getThemedContext(), mActivity);
} else {
mMenuInflater = new MenuInflater(mActivity);
}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/ActionBar.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/ActionBar.java
index 2497d24ff..03755be2b 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/ActionBar.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/ActionBar.java
@@ -17,6 +17,7 @@
package com.actionbarsherlock.app;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v4.app.FragmentTransaction;
import android.util.AttributeSet;
@@ -895,6 +896,10 @@ public abstract class ActionBar {
* @attr ref android.R.styleable#ActionBar_LayoutParams_layout_gravity
*/
public static class LayoutParams extends MarginLayoutParams {
+ private static final int[] ATTRS = new int[] {
+ android.R.attr.layout_gravity
+ };
+
/**
* Gravity for the view associated with these LayoutParams.
*
@@ -918,6 +923,10 @@ public abstract class ActionBar {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
+
+ TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
+ gravity = a.getInt(0, -1);
+ a.recycle();
}
public LayoutParams(int width, int height) {
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockActivity.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockActivity.java
index 9cb57e95a..7b4543640 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockActivity.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockActivity.java
@@ -116,6 +116,17 @@ public abstract class SherlockActivity extends Activity implements OnCreatePanel
return super.dispatchKeyEvent(event);
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ getSherlock().dispatchSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ getSherlock().dispatchRestoreInstanceState(savedInstanceState);
+ }
///////////////////////////////////////////////////////////////////////////
// Native menu handling
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java
index 5cd13ba7c..3d092f033 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockFragmentActivity.java
@@ -2,7 +2,7 @@ package com.actionbarsherlock.app;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.support.v4.app._ActionBarSherlockTrojanHorse;
+import android.support.v4.app.Watson;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -17,8 +17,8 @@ import com.actionbarsherlock.view.MenuItem;
import static com.actionbarsherlock.ActionBarSherlock.OnActionModeFinishedListener;
import static com.actionbarsherlock.ActionBarSherlock.OnActionModeStartedListener;
-/** @see {@link _ActionBarSherlockTrojanHorse} */
-public class SherlockFragmentActivity extends _ActionBarSherlockTrojanHorse implements OnActionModeStartedListener, OnActionModeFinishedListener {
+/** @see {@link android.support.v4.app.Watson} */
+public class SherlockFragmentActivity extends Watson implements OnActionModeStartedListener, OnActionModeFinishedListener {
private static final boolean DEBUG = false;
private static final String TAG = "SherlockFragmentActivity";
@@ -122,6 +122,17 @@ public class SherlockFragmentActivity extends _ActionBarSherlockTrojanHorse impl
return super.dispatchKeyEvent(event);
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ getSherlock().dispatchSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ getSherlock().dispatchRestoreInstanceState(savedInstanceState);
+ }
///////////////////////////////////////////////////////////////////////////
// Native menu handling
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockListActivity.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockListActivity.java
index 00c00fee5..aba6d85e8 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockListActivity.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockListActivity.java
@@ -116,6 +116,17 @@ public abstract class SherlockListActivity extends ListActivity implements OnCre
return super.dispatchKeyEvent(event);
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ getSherlock().dispatchSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ getSherlock().dispatchRestoreInstanceState(savedInstanceState);
+ }
///////////////////////////////////////////////////////////////////////////
// Native menu handling
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java
index 4f80be515..bee72cb25 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/app/SherlockPreferenceActivity.java
@@ -116,6 +116,17 @@ public abstract class SherlockPreferenceActivity extends PreferenceActivity impl
return super.dispatchKeyEvent(event);
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ getSherlock().dispatchSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ getSherlock().dispatchRestoreInstanceState(savedInstanceState);
+ }
///////////////////////////////////////////////////////////////////////////
// Native menu handling
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java
index 05353d28c..5e69275c7 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockCompat.java
@@ -52,6 +52,7 @@ public class ActionBarSherlockCompat extends ActionBarSherlock implements MenuBu
/** Window features which are enabled by default. */
protected static final int DEFAULT_FEATURES = 0;
+ static private final String PANELS_TAG = "sherlock:Panels";
public ActionBarSherlockCompat(Activity activity, int flags) {
super(activity, flags);
@@ -71,8 +72,6 @@ public class ActionBarSherlockCompat extends ActionBarSherlock implements MenuBu
private MenuBuilder mMenu;
/** Map between native options items and sherlock items. */
protected HashMap<android.view.MenuItem, MenuItemImpl> mNativeItemMap;
- /** Indication of a long-press on the hardware menu key. */
- private boolean mMenuKeyIsLongPress = false;
/** Parent view of the window decoration (action bar, mode, etc.). */
private ViewGroup mDecor;
@@ -293,7 +292,10 @@ public class ActionBarSherlockCompat extends ActionBarSherlock implements MenuBu
return false;
}
- return wActionBar.hideOverflowMenu();
+ if (wActionBar != null) {
+ return wActionBar.hideOverflowMenu();
+ }
+ return false;
}
@Override
@@ -424,27 +426,8 @@ public class ActionBarSherlockCompat extends ActionBarSherlock implements MenuBu
}
}
- boolean result = false;
- if (keyCode == KeyEvent.KEYCODE_MENU && isReservingOverflow()) {
- if (event.getAction() == KeyEvent.ACTION_DOWN && event.isLongPress()) {
- mMenuKeyIsLongPress = true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- if (!mMenuKeyIsLongPress) {
- if (mActionMode == null && wActionBar != null) {
- if (wActionBar.isOverflowMenuShowing()) {
- wActionBar.hideOverflowMenu();
- } else {
- wActionBar.showOverflowMenu();
- }
- }
- result = true;
- }
- mMenuKeyIsLongPress = false;
- }
- }
-
- if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning " + result);
- return result;
+ if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning false");
+ return false;
}
@Override
@@ -452,6 +435,19 @@ public class ActionBarSherlockCompat extends ActionBarSherlock implements MenuBu
mIsDestroyed = true;
}
+ @Override
+ public void dispatchSaveInstanceState(Bundle outState) {
+ if (mMenu != null) {
+ mMenuFrozenActionViewState = new Bundle();
+ mMenu.saveActionViewStates(mMenuFrozenActionViewState);
+ }
+ outState.putParcelable(PANELS_TAG, mMenuFrozenActionViewState);
+ }
+
+ @Override
+ public void dispatchRestoreInstanceState(Bundle savedInstanceState) {
+ mMenuFrozenActionViewState = savedInstanceState.getParcelable(PANELS_TAG);
+ }
///////////////////////////////////////////////////////////////////////////
// Menu callback lifecycle and creation
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java
index 9afca185a..0824d3848 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/ActionBarSherlockNative.java
@@ -208,7 +208,12 @@ public class ActionBarSherlockNative extends ActionBarSherlock {
//is where we will set the new instance to mActionMode since we need
//to pass it through to the sherlock callbacks and the call below
//will not have returned yet to store its value.
- mActivity.startActionMode(wrapped);
+ if (mActivity.startActionMode(wrapped) == null) {
+ mActionMode = null;
+ }
+ if (mActivity instanceof OnActionModeStartedListener && mActionMode != null) {
+ ((OnActionModeStartedListener)mActivity).onActionModeStarted(mActionMode);
+ }
return mActionMode;
}
@@ -241,6 +246,9 @@ public class ActionBarSherlockNative extends ActionBarSherlock {
@Override
public void onDestroyActionMode(android.view.ActionMode mode) {
mCallback.onDestroyActionMode(mActionMode);
+ if (mActivity instanceof OnActionModeFinishedListener) {
+ ((OnActionModeFinishedListener)mActivity).onActionModeFinished(mActionMode);
+ }
}
}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java
index 6ae0402c0..d022a2465 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarImpl.java
@@ -26,6 +26,7 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
+import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -36,7 +37,6 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.SpinnerAdapter;
import com.actionbarsherlock.R;
import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.internal.nineoldandroids.animation.Animator;
import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorListenerAdapter;
import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet;
@@ -506,8 +506,8 @@ public class ActionBarImpl extends ActionBar {
}
FragmentTransaction trans = null;
- if (mActivity instanceof SherlockFragmentActivity) {
- trans = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
+ if (mActivity instanceof FragmentActivity) {
+ trans = ((FragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
.disallowAddToBackStack();
}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java
index e390ea428..840cb3d27 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/app/ActionBarWrapper.java
@@ -6,12 +6,12 @@ import java.util.Set;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.widget.SpinnerAdapter;
import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
public class ActionBarWrapper extends ActionBar implements android.app.ActionBar.OnNavigationListener, android.app.ActionBar.OnMenuVisibilityListener {
private final Activity mActivity;
@@ -319,8 +319,8 @@ public class ActionBarWrapper extends ActionBar implements android.app.ActionBar
public void onTabReselected(android.app.ActionBar.Tab tab, android.app.FragmentTransaction ft) {
if (mListener != null) {
FragmentTransaction trans = null;
- if (mActivity instanceof SherlockFragmentActivity) {
- trans = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
+ if (mActivity instanceof FragmentActivity) {
+ trans = ((FragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
.disallowAddToBackStack();
}
@@ -336,8 +336,8 @@ public class ActionBarWrapper extends ActionBar implements android.app.ActionBar
public void onTabSelected(android.app.ActionBar.Tab tab, android.app.FragmentTransaction ft) {
if (mListener != null) {
- if (mFragmentTransaction == null && mActivity instanceof SherlockFragmentActivity) {
- mFragmentTransaction = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
+ if (mFragmentTransaction == null && mActivity instanceof FragmentActivity) {
+ mFragmentTransaction = ((FragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
.disallowAddToBackStack();
}
@@ -356,8 +356,8 @@ public class ActionBarWrapper extends ActionBar implements android.app.ActionBar
public void onTabUnselected(android.app.ActionBar.Tab tab, android.app.FragmentTransaction ft) {
if (mListener != null) {
FragmentTransaction trans = null;
- if (mActivity instanceof SherlockFragmentActivity) {
- trans = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
+ if (mActivity instanceof FragmentActivity) {
+ trans = ((FragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
.disallowAddToBackStack();
mFragmentTransaction = trans;
}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java
index 2c428e907..953e3e844 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineFrameLayout.java
@@ -9,18 +9,10 @@ import com.actionbarsherlock.internal.nineoldandroids.view.animation.AnimatorPro
public class NineFrameLayout extends FrameLayout {
private final AnimatorProxy mProxy;
- public NineFrameLayout(Context context) {
- super(context);
- mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null;
- }
public NineFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null;
}
- public NineFrameLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null;
- }
@Override
public void setVisibility(int visibility) {
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java
index a670b1f64..1f381013a 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/nineoldandroids/widget/NineLinearLayout.java
@@ -9,18 +9,10 @@ import com.actionbarsherlock.internal.nineoldandroids.view.animation.AnimatorPro
public class NineLinearLayout extends LinearLayout {
private final AnimatorProxy mProxy;
- public NineLinearLayout(Context context) {
- super(context);
- mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null;
- }
public NineLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null;
}
- public NineLinearLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mProxy = AnimatorProxy.NEEDS_PROXY ? AnimatorProxy.wrap(this) : null;
- }
@Override
public void setVisibility(int visibility) {
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
index 6f568c698..876a22c58 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
@@ -23,7 +23,6 @@ import java.util.Set;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -119,14 +118,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
public static boolean reserveOverflow(Context context) {
- //Check for theme-forced overflow action item
- TypedArray a = context.getTheme().obtainStyledAttributes(R.styleable.SherlockTheme);
- boolean result = a.getBoolean(R.styleable.SherlockTheme_absForceOverflow, false);
- a.recycle();
- if (result) {
- return true;
- }
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB);
} else {
@@ -621,6 +612,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter
for (View_OnAttachStateChangeListener listener : mListeners) {
listener.onViewDetachedFromWindow(this);
}
+
+ if (mOverflowPopup != null) mOverflowPopup.dismiss();
}
@Override
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
index e090677a1..0e3b1ae0d 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
@@ -520,6 +520,9 @@ public class ActionMenuView extends IcsLinearLayout implements MenuBuilder.ItemI
//@Override
protected boolean hasDividerBeforeChildAt(int childIndex) {
+ if (childIndex == 0) {
+ return false;
+ }
final View childBefore = getChildAt(childIndex - 1);
final View child = getChildAt(childIndex);
boolean result = false;
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java
index 907a7aa04..aaf2997b7 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuItemWrapper.java
@@ -2,10 +2,12 @@ package com.actionbarsherlock.internal.view.menu;
import android.content.Intent;
import android.graphics.drawable.Drawable;
-import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View;
import com.actionbarsherlock.internal.view.ActionProviderWrapper;
+import com.actionbarsherlock.internal.widget.CollapsibleActionViewWrapper;
import com.actionbarsherlock.view.ActionProvider;
+import com.actionbarsherlock.view.CollapsibleActionView;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.SubMenu;
@@ -215,19 +217,35 @@ public class MenuItemWrapper implements MenuItem, android.view.MenuItem.OnMenuIt
@Override
public MenuItem setActionView(View view) {
+ if (view != null && view instanceof CollapsibleActionView) {
+ view = new CollapsibleActionViewWrapper(view);
+ }
mNativeItem.setActionView(view);
return this;
}
@Override
public MenuItem setActionView(int resId) {
+ //Allow the native menu to inflate the resource
mNativeItem.setActionView(resId);
+ if (resId != 0) {
+ //Get newly created view
+ View view = mNativeItem.getActionView();
+ if (view instanceof CollapsibleActionView) {
+ //Wrap it and re-set it
+ mNativeItem.setActionView(new CollapsibleActionViewWrapper(view));
+ }
+ }
return this;
}
@Override
public View getActionView() {
- return mNativeItem.getActionView();
+ View actionView = mNativeItem.getActionView();
+ if (actionView instanceof CollapsibleActionViewWrapper) {
+ return ((CollapsibleActionViewWrapper)actionView).unwrap();
+ }
+ return actionView;
}
@Override
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java
index 64fc4aeaa..3d4dd42fd 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/view/menu/MenuWrapper.java
@@ -79,10 +79,15 @@ public class MenuWrapper implements Menu {
@Override
public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
- android.view.MenuItem[] nativeOutItems = new android.view.MenuItem[outSpecificItems.length];
- int result = mNativeMenu.addIntentOptions(groupId, itemId, order, caller, specifics, intent, flags, nativeOutItems);
- for (int i = 0, length = outSpecificItems.length; i < length; i++) {
- outSpecificItems[i] = new MenuItemWrapper(nativeOutItems[i]);
+ int result;
+ if (outSpecificItems != null) {
+ android.view.MenuItem[] nativeOutItems = new android.view.MenuItem[outSpecificItems.length];
+ result = mNativeMenu.addIntentOptions(groupId, itemId, order, caller, specifics, intent, flags, nativeOutItems);
+ for (int i = 0, length = outSpecificItems.length; i < length; i++) {
+ outSpecificItems[i] = new MenuItemWrapper(nativeOutItems[i]);
+ }
+ } else {
+ result = mNativeMenu.addIntentOptions(groupId, itemId, order, caller, specifics, intent, flags, null);
}
return result;
}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java
index 5e5aa2867..1d9c68b37 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ActionBarContainer.java
@@ -18,8 +18,11 @@ package com.actionbarsherlock.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -60,6 +63,16 @@ public class ActionBarContainer extends NineFrameLayout {
mStackedBackground = a.getDrawable(
R.styleable.SherlockActionBar_backgroundStacked);
+ //Fix for issue #379
+ if (mStackedBackground instanceof ColorDrawable && Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(bitmap);
+ mStackedBackground.draw(c);
+ int color = bitmap.getPixel(0, 0);
+ bitmap.recycle();
+ mStackedBackground = new IcsColorDrawable(color);
+ }
+
if (getId() == R.id.abs__split_action_bar) {
mIsSplit = true;
mSplitBackground = a.getDrawable(
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java
index 673ec554f..cae8b8aed 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CapitalizingTextView.java
@@ -1,12 +1,13 @@
package com.actionbarsherlock.internal.widget;
-import java.util.Locale;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.TextView;
+import java.util.Locale;
+
public class CapitalizingTextView extends TextView {
private static final boolean SANS_ICE_CREAM = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
private static final boolean IS_GINGERBREAD = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
@@ -33,7 +34,12 @@ public class CapitalizingTextView extends TextView {
public void setTextCompat(CharSequence text) {
if (SANS_ICE_CREAM && mAllCaps && text != null) {
if (IS_GINGERBREAD) {
- setText(text.toString().toUpperCase(Locale.ROOT));
+ try {
+ setText(text.toString().toUpperCase(Locale.ROOT));
+ } catch (NoSuchFieldError e) {
+ //Some manufacturer broke Locale.ROOT. See #572.
+ setText(text.toString().toUpperCase());
+ }
} else {
setText(text.toString().toUpperCase());
}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CollapsibleActionViewWrapper.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CollapsibleActionViewWrapper.java
new file mode 100644
index 000000000..14f092c81
--- /dev/null
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/CollapsibleActionViewWrapper.java
@@ -0,0 +1,30 @@
+package com.actionbarsherlock.internal.widget;
+
+import android.view.View;
+import android.widget.FrameLayout;
+import com.actionbarsherlock.view.CollapsibleActionView;
+
+/**
+ * Wraps an ABS collapsible action view in a native container that delegates the calls.
+ */
+public class CollapsibleActionViewWrapper extends FrameLayout implements android.view.CollapsibleActionView {
+ private final CollapsibleActionView child;
+
+ public CollapsibleActionViewWrapper(View child) {
+ super(child.getContext());
+ this.child = (CollapsibleActionView) child;
+ addView(child);
+ }
+
+ @Override public void onActionViewExpanded() {
+ child.onActionViewExpanded();
+ }
+
+ @Override public void onActionViewCollapsed() {
+ child.onActionViewCollapsed();
+ }
+
+ public View unwrap() {
+ return getChildAt(0);
+ }
+}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsColorDrawable.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsColorDrawable.java
new file mode 100644
index 000000000..a78b3f71b
--- /dev/null
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsColorDrawable.java
@@ -0,0 +1,41 @@
+package com.actionbarsherlock.internal.widget;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A version of {@link android.graphics.drawable.ColorDrawable} that respects bounds.
+ */
+public class IcsColorDrawable extends Drawable {
+ private int color;
+ private final Paint paint = new Paint();
+
+ public IcsColorDrawable(int color) {
+ this.color = color;
+ }
+
+ @Override public void draw(Canvas canvas) {
+ if ((color >>> 24) != 0) {
+ paint.setColor(color);
+ canvas.drawRect(getBounds(), paint);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ if (alpha != (color >>> 24)) {
+ color = (color & 0x00FFFFFF) & (alpha << 24);
+ invalidateSelf();
+ }
+ }
+
+ @Override public void setColorFilter(ColorFilter colorFilter) {
+ //Ignored
+ }
+
+ @Override public int getOpacity() {
+ return color >>> 24;
+ }
+}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java
index 1b4463a59..4947c41df 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsLinearLayout.java
@@ -6,6 +6,8 @@ import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.LinearLayout;
+
import com.actionbarsherlock.internal.nineoldandroids.widget.NineLinearLayout;
/**
@@ -16,14 +18,16 @@ import com.actionbarsherlock.internal.nineoldandroids.widget.NineLinearLayout;
* {@link android.widget.FrameLayout} so it can receive the margin.
*/
public class IcsLinearLayout extends NineLinearLayout {
- private static final int[] LinearLayout = new int[] {
+ private static final int[] R_styleable_LinearLayout = new int[] {
/* 0 */ android.R.attr.divider,
- /* 1 */ android.R.attr.showDividers,
- /* 2 */ android.R.attr.dividerPadding,
+ /* 1 */ android.R.attr.measureWithLargestChild,
+ /* 2 */ android.R.attr.showDividers,
+ /* 3 */ android.R.attr.dividerPadding,
};
private static final int LinearLayout_divider = 0;
- private static final int LinearLayout_showDividers = 1;
- private static final int LinearLayout_dividerPadding = 2;
+ private static final int LinearLayout_measureWithLargestChild = 1;
+ private static final int LinearLayout_showDividers = 2;
+ private static final int LinearLayout_dividerPadding = 3;
/**
* Don't show any dividers.
@@ -49,15 +53,17 @@ public class IcsLinearLayout extends NineLinearLayout {
private int mShowDividers;
private int mDividerPadding;
+ private boolean mUseLargestChild;
public IcsLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- TypedArray a = context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/LinearLayout);
+ TypedArray a = context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/R_styleable_LinearLayout);
setDividerDrawable(a.getDrawable(/*com.android.internal.R.styleable.*/LinearLayout_divider));
mShowDividers = a.getInt(/*com.android.internal.R.styleable.*/LinearLayout_showDividers, SHOW_DIVIDER_NONE);
mDividerPadding = a.getDimensionPixelSize(/*com.android.internal.R.styleable.*/LinearLayout_dividerPadding, 0);
+ mUseLargestChild = a.getBoolean(/*com.android.internal.R.styleable.*/LinearLayout_measureWithLargestChild, false);
a.recycle();
}
@@ -199,7 +205,7 @@ public class IcsLinearLayout extends NineLinearLayout {
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ //final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom()/* + lp.bottomMargin*/;
}
drawHorizontalDivider(canvas, bottom);
@@ -226,7 +232,7 @@ public class IcsLinearLayout extends NineLinearLayout {
if (child == null) {
right = getWidth() - getPaddingRight() - mDividerWidth;
} else {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ //final LayoutParams lp = (LayoutParams) child.getLayoutParams();
right = child.getRight()/* + lp.rightMargin*/;
}
drawVerticalDivider(canvas, right);
@@ -269,4 +275,136 @@ public class IcsLinearLayout extends NineLinearLayout {
}
return false;
}
+
+ /**
+ * When true, all children with a weight will be considered having
+ * the minimum size of the largest child. If false, all children are
+ * measured normally.
+ *
+ * @return True to measure children with a weight using the minimum
+ * size of the largest child, false otherwise.
+ *
+ * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
+ */
+ public boolean isMeasureWithLargestChildEnabled() {
+ return mUseLargestChild;
+ }
+
+ /**
+ * When set to true, all children with a weight will be considered having
+ * the minimum size of the largest child. If false, all children are
+ * measured normally.
+ *
+ * Disabled by default.
+ *
+ * @param enabled True to measure children with a weight using the
+ * minimum size of the largest child, false otherwise.
+ *
+ * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
+ */
+ public void setMeasureWithLargestChildEnabled(boolean enabled) {
+ mUseLargestChild = enabled;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (mUseLargestChild) {
+ final int orientation = getOrientation();
+ switch (orientation) {
+ case HORIZONTAL:
+ useLargestChildHorizontal();
+ break;
+
+ case VERTICAL:
+ useLargestChildVertical();
+ break;
+ }
+ }
+ }
+
+ private void useLargestChildHorizontal() {
+ final int childCount = getChildCount();
+
+ // Find largest child width
+ int largestChildWidth = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ largestChildWidth = Math.max(child.getMeasuredWidth(), largestChildWidth);
+ }
+
+ int totalWidth = 0;
+ // Re-measure childs
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(largestChildWidth,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
+ MeasureSpec.EXACTLY));
+ totalWidth += largestChildWidth;
+
+ } else {
+ totalWidth += child.getMeasuredWidth();
+ }
+
+ totalWidth += lp.leftMargin + lp.rightMargin;
+ }
+
+ totalWidth += getPaddingLeft() + getPaddingRight();
+ setMeasuredDimension(totalWidth, getMeasuredHeight());
+ }
+
+ private void useLargestChildVertical() {
+ final int childCount = getChildCount();
+
+ // Find largest child width
+ int largestChildHeight = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ largestChildHeight = Math.max(child.getMeasuredHeight(), largestChildHeight);
+ }
+
+ int totalHeight = 0;
+ // Re-measure childs
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(largestChildHeight,
+ MeasureSpec.EXACTLY));
+ totalHeight += largestChildHeight;
+
+ } else {
+ totalHeight += child.getMeasuredHeight();
+ }
+
+ totalHeight += lp.leftMargin + lp.rightMargin;
+ }
+
+ totalHeight += getPaddingLeft() + getPaddingRight();
+ setMeasuredDimension(getMeasuredWidth(), totalHeight);
+ }
}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java
index 1a532e06c..48fb5d8b4 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/ScrollingTabContainerView.java
@@ -188,6 +188,7 @@ public class ScrollingTabContainerView extends NineHorizontalScrollView
private IcsLinearLayout createTabLayout() {
final IcsLinearLayout tabLayout = (IcsLinearLayout) LayoutInflater.from(getContext())
.inflate(R.layout.abs__action_bar_tab_bar_view, null);
+ tabLayout.setMeasureWithLargestChildEnabled(true);
tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT));
return tabLayout;
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/view/MenuInflater.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/view/MenuInflater.java
index 969459749..5a0f40859 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/view/MenuInflater.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/view/MenuInflater.java
@@ -67,6 +67,7 @@ public class MenuInflater {
private final Object[] mActionProviderConstructorArguments;
private Context mContext;
+ private Object mRealOwner;
/**
* Constructs a menu inflater.
@@ -75,6 +76,20 @@ public class MenuInflater {
*/
public MenuInflater(Context context) {
mContext = context;
+ mRealOwner = context;
+ mActionViewConstructorArguments = new Object[] {context};
+ mActionProviderConstructorArguments = mActionViewConstructorArguments;
+ }
+
+ /**
+ * Constructs a menu inflater.
+ *
+ * @see Activity#getMenuInflater()
+ * @hide
+ */
+ public MenuInflater(Context context, Object realOwner) {
+ mContext = context;
+ mRealOwner = realOwner;
mActionViewConstructorArguments = new Object[] {context};
mActionProviderConstructorArguments = mActionViewConstructorArguments;
}
@@ -192,12 +207,12 @@ public class MenuInflater {
implements MenuItem.OnMenuItemClickListener {
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
- private Context mContext;
+ private Object mRealOwner;
private Method mMethod;
- public InflatedOnMenuItemClickListener(Context context, String methodName) {
- mContext = context;
- Class<?> c = context.getClass();
+ public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
+ mRealOwner = realOwner;
+ Class<?> c = realOwner.getClass();
try {
mMethod = c.getMethod(methodName, PARAM_TYPES);
} catch (Exception e) {
@@ -212,9 +227,9 @@ public class MenuInflater {
public boolean onMenuItemClick(MenuItem item) {
try {
if (mMethod.getReturnType() == Boolean.TYPE) {
- return (Boolean) mMethod.invoke(mContext, item);
+ return (Boolean) mMethod.invoke(mRealOwner, item);
} else {
- mMethod.invoke(mContext, item);
+ mMethod.invoke(mRealOwner, item);
return true;
}
} catch (Exception e) {
@@ -358,8 +373,16 @@ public class MenuInflater {
itemListenerMethodName = a.getString(R.styleable.SherlockMenuItem_android_onClick);
itemActionViewLayout = a.getResourceId(R.styleable.SherlockMenuItem_android_actionLayout, 0);
- itemActionViewClassName = a.getString(R.styleable.SherlockMenuItem_android_actionViewClass);
- itemActionProviderClassName = a.getString(R.styleable.SherlockMenuItem_android_actionProviderClass);
+
+ // itemActionViewClassName = a.getString(R.styleable.SherlockMenuItem_android_actionViewClass);
+ value = new TypedValue();
+ a.getValue(R.styleable.SherlockMenuItem_android_actionViewClass, value);
+ itemActionViewClassName = value.type == TypedValue.TYPE_STRING ? value.string.toString() : null;
+
+ // itemActionProviderClassName = a.getString(R.styleable.SherlockMenuItem_android_actionProviderClass);
+ value = new TypedValue();
+ a.getValue(R.styleable.SherlockMenuItem_android_actionProviderClass, value);
+ itemActionProviderClassName = value.type == TypedValue.TYPE_STRING ? value.string.toString() : null;
final boolean hasActionProvider = itemActionProviderClassName != null;
if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) {
@@ -407,7 +430,7 @@ public class MenuInflater {
+ "be used within a restricted context");
}
item.setOnMenuItemClickListener(
- new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName));
+ new InflatedOnMenuItemClickListener(mRealOwner, itemListenerMethodName));
}
if (itemCheckable >= 2) {
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java
index 379207471..d7f110fc6 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserModel.java
@@ -25,7 +25,6 @@ import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -39,11 +38,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
/**
* <p>
@@ -562,33 +561,7 @@ class ActivityChooserModel extends DataSetObservable {
}
}
- private static final SerialExecutor SERIAL_EXECUTOR = new SerialExecutor();
-
- private static class SerialExecutor implements Executor {
- final LinkedList<Runnable> mTasks = new LinkedList<Runnable>();
- Runnable mActive;
-
- public synchronized void execute(final Runnable r) {
- mTasks.offer(new Runnable() {
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
-
- protected synchronized void scheduleNext() {
- if ((mActive = mTasks.poll()) != null) {
- mActive.run();
- }
- }
- }
+ private static final Executor SERIAL_EXECUTOR = Executors.newSingleThreadExecutor();
/**
* Persists the history data to the backing file if the latter
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java
index da13bc99f..e19ea9e9e 100644
--- a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/ActivityChooserView.java
@@ -405,7 +405,11 @@ class ActivityChooserView extends ViewGroup implements ActivityChooserModelClien
super.onDetachedFromWindow();
ActivityChooserModel dataModel = mAdapter.getDataModel();
if (dataModel != null) {
- dataModel.unregisterObserver(mModelDataSetOberver);
+ try {
+ dataModel.unregisterObserver(mModelDataSetOberver);
+ } catch (IllegalStateException e) {
+ //Oh, well... fixes issue #557
+ }
}
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
@@ -526,6 +530,7 @@ class ActivityChooserView extends ViewGroup implements ActivityChooserModelClien
mActivityChooserContent.setBackgroundDrawable(mActivityChooserContentBackground);
} else {
mActivityChooserContent.setBackgroundDrawable(null);
+ mActivityChooserContent.setPadding(0, 0, 0, 0);
}
}
@@ -648,7 +653,11 @@ class ActivityChooserView extends ViewGroup implements ActivityChooserModelClien
public void setDataModel(ActivityChooserModel dataModel) {
ActivityChooserModel oldDataModel = mAdapter.getDataModel();
if (oldDataModel != null && isShown()) {
- oldDataModel.unregisterObserver(mModelDataSetOberver);
+ try {
+ oldDataModel.unregisterObserver(mModelDataSetOberver);
+ } catch (IllegalStateException e) {
+ //Oh, well... fixes issue #557
+ }
}
mDataModel = dataModel;
if (dataModel != null && isShown()) {
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java
new file mode 100644
index 000000000..c9e7897d4
--- /dev/null
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java
@@ -0,0 +1,1811 @@
+/*
+ * 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.actionbarsherlock.widget;
+
+import android.app.PendingIntent;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.speech.RecognizerIntent;
+import android.support.v4.view.KeyEventCompat;
+import android.support.v4.widget.CursorAdapter;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.style.ImageSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.AutoCompleteTextView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.view.CollapsibleActionView;
+
+import java.lang.reflect.Method;
+import java.util.WeakHashMap;
+
+import static com.actionbarsherlock.widget.SuggestionsAdapter.getColumnString;
+
+/**
+ * A widget that provides a user interface for the user to enter a search query and submit a request
+ * to a search provider. Shows a list of query suggestions or results, if available, and allows the
+ * user to pick a suggestion or result to launch into.
+ *
+ * <p>
+ * When the SearchView is used in an ActionBar as an action view for a collapsible menu item, it
+ * needs to be set to iconified by default using {@link #setIconifiedByDefault(boolean)
+ * setIconifiedByDefault(true)}. This is the default, so nothing needs to be done.
+ * </p>
+ * <p>
+ * If you want the search field to always be visible, then call setIconifiedByDefault(false).
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about using {@code SearchView}, read the
+ * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p>
+ * </div>
+ *
+ * @see android.view.MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
+ * @attr ref android.R.styleable#SearchView_iconifiedByDefault
+ * @attr ref android.R.styleable#SearchView_imeOptions
+ * @attr ref android.R.styleable#SearchView_inputType
+ * @attr ref android.R.styleable#SearchView_maxWidth
+ * @attr ref android.R.styleable#SearchView_queryHint
+ */
+public class SearchView extends LinearLayout implements CollapsibleActionView {
+
+ private static final boolean DBG = false;
+ private static final String LOG_TAG = "SearchView";
+
+ /**
+ * Private constant for removing the microphone in the keyboard.
+ */
+ private static final String IME_OPTION_NO_MICROPHONE = "nm";
+
+ private OnQueryTextListener mOnQueryChangeListener;
+ private OnCloseListener mOnCloseListener;
+ private OnFocusChangeListener mOnQueryTextFocusChangeListener;
+ private OnSuggestionListener mOnSuggestionListener;
+ private OnClickListener mOnSearchClickListener;
+
+ private boolean mIconifiedByDefault;
+ private boolean mIconified;
+ private CursorAdapter mSuggestionsAdapter;
+ private View mSearchButton;
+ private View mSubmitButton;
+ private View mSearchPlate;
+ private View mSubmitArea;
+ private ImageView mCloseButton;
+ private View mSearchEditFrame;
+ private View mVoiceButton;
+ private SearchAutoComplete mQueryTextView;
+ private View mDropDownAnchor;
+ private ImageView mSearchHintIcon;
+ private boolean mSubmitButtonEnabled;
+ private CharSequence mQueryHint;
+ private boolean mQueryRefinement;
+ private boolean mClearingFocus;
+ private int mMaxWidth;
+ private boolean mVoiceButtonEnabled;
+ private CharSequence mOldQueryText;
+ private CharSequence mUserQuery;
+ private boolean mExpandedInActionView;
+ private int mCollapsedImeOptions;
+
+ private SearchableInfo mSearchable;
+ private Bundle mAppSearchData;
+
+ /*
+ * SearchView can be set expanded before the IME is ready to be shown during
+ * initial UI setup. The show operation is asynchronous to account for this.
+ */
+ private Runnable mShowImeRunnable = new Runnable() {
+ public void run() {
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ if (imm != null) {
+ showSoftInputUnchecked(SearchView.this, imm, 0);
+ }
+ }
+ };
+
+ private Runnable mUpdateDrawableStateRunnable = new Runnable() {
+ public void run() {
+ updateFocusedState();
+ }
+ };
+
+ private Runnable mReleaseCursorRunnable = new Runnable() {
+ public void run() {
+ if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) {
+ mSuggestionsAdapter.changeCursor(null);
+ }
+ }
+ };
+
+ // For voice searching
+ private final Intent mVoiceWebSearchIntent;
+ private final Intent mVoiceAppSearchIntent;
+
+ // A weak map of drawables we've gotten from other packages, so we don't load them
+ // more than once.
+ private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
+ new WeakHashMap<String, Drawable.ConstantState>();
+
+ /**
+ * Callbacks for changes to the query text.
+ */
+ public interface OnQueryTextListener {
+
+ /**
+ * Called when the user submits the query. This could be due to a key press on the
+ * keyboard or due to pressing a submit button.
+ * The listener can override the standard behavior by returning true
+ * to indicate that it has handled the submit request. Otherwise return false to
+ * let the SearchView handle the submission by launching any associated intent.
+ *
+ * @param query the query text that is to be submitted
+ *
+ * @return true if the query has been handled by the listener, false to let the
+ * SearchView perform the default action.
+ */
+ boolean onQueryTextSubmit(String query);
+
+ /**
+ * Called when the query text is changed by the user.
+ *
+ * @param newText the new content of the query text field.
+ *
+ * @return false if the SearchView should perform the default action of showing any
+ * suggestions if available, true if the action was handled by the listener.
+ */
+ boolean onQueryTextChange(String newText);
+ }
+
+ public interface OnCloseListener {
+
+ /**
+ * The user is attempting to close the SearchView.
+ *
+ * @return true if the listener wants to override the default behavior of clearing the
+ * text field and dismissing it, false otherwise.
+ */
+ boolean onClose();
+ }
+
+ /**
+ * Callback interface for selection events on suggestions. These callbacks
+ * are only relevant when a SearchableInfo has been specified by {@link #setSearchableInfo}.
+ */
+ public interface OnSuggestionListener {
+
+ /**
+ * Called when a suggestion was selected by navigating to it.
+ * @param position the absolute position in the list of suggestions.
+ *
+ * @return true if the listener handles the event and wants to override the default
+ * behavior of possibly rewriting the query based on the selected item, false otherwise.
+ */
+ boolean onSuggestionSelect(int position);
+
+ /**
+ * Called when a suggestion was clicked.
+ * @param position the absolute position of the clicked item in the list of suggestions.
+ *
+ * @return true if the listener handles the event and wants to override the default
+ * behavior of launching any intent or submitting a search query specified on that item.
+ * Return false otherwise.
+ */
+ boolean onSuggestionClick(int position);
+ }
+
+ public SearchView(Context context) {
+ this(context, null);
+ }
+
+ public SearchView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
+ throw new IllegalStateException("SearchView is API 8+ only.");
+ }
+
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.abs__search_view, this, true);
+
+ mSearchButton = findViewById(R.id.abs__search_button);
+ mQueryTextView = (SearchAutoComplete) findViewById(R.id.abs__search_src_text);
+ mQueryTextView.setSearchView(this);
+
+ mSearchEditFrame = findViewById(R.id.abs__search_edit_frame);
+ mSearchPlate = findViewById(R.id.abs__search_plate);
+ mSubmitArea = findViewById(R.id.abs__submit_area);
+ mSubmitButton = findViewById(R.id.abs__search_go_btn);
+ mCloseButton = (ImageView) findViewById(R.id.abs__search_close_btn);
+ mVoiceButton = findViewById(R.id.abs__search_voice_btn);
+ mSearchHintIcon = (ImageView) findViewById(R.id.abs__search_mag_icon);
+
+ mSearchButton.setOnClickListener(mOnClickListener);
+ mCloseButton.setOnClickListener(mOnClickListener);
+ mSubmitButton.setOnClickListener(mOnClickListener);
+ mVoiceButton.setOnClickListener(mOnClickListener);
+ mQueryTextView.setOnClickListener(mOnClickListener);
+
+ mQueryTextView.addTextChangedListener(mTextWatcher);
+ mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
+ mQueryTextView.setOnItemClickListener(mOnItemClickListener);
+ mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
+ mQueryTextView.setOnKeyListener(mTextKeyListener);
+ // Inform any listener of focus changes
+ mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
+
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (mOnQueryTextFocusChangeListener != null) {
+ mOnQueryTextFocusChangeListener.onFocusChange(SearchView.this, hasFocus);
+ }
+ }
+ });
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockSearchView, 0, 0);
+ setIconifiedByDefault(a.getBoolean(R.styleable.SherlockSearchView_iconifiedByDefault, true));
+ int maxWidth = a.getDimensionPixelSize(R.styleable.SherlockSearchView_android_maxWidth, -1);
+ if (maxWidth != -1) {
+ setMaxWidth(maxWidth);
+ }
+ CharSequence queryHint = a.getText(R.styleable.SherlockSearchView_queryHint);
+ if (!TextUtils.isEmpty(queryHint)) {
+ setQueryHint(queryHint);
+ }
+ int imeOptions = a.getInt(R.styleable.SherlockSearchView_android_imeOptions, -1);
+ if (imeOptions != -1) {
+ setImeOptions(imeOptions);
+ }
+ int inputType = a.getInt(R.styleable.SherlockSearchView_android_inputType, -1);
+ if (inputType != -1) {
+ setInputType(inputType);
+ }
+
+ a.recycle();
+
+ boolean focusable = true;
+
+ a = context.obtainStyledAttributes(attrs, R.styleable.SherlockView, 0, 0);
+ focusable = a.getBoolean(R.styleable.SherlockView_android_focusable, focusable);
+ a.recycle();
+ setFocusable(focusable);
+
+ // Save voice intent for later queries/launching
+ mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+ RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
+
+ mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor());
+ if (mDropDownAnchor != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ adjustDropDownSizeAndPosition();
+ }
+ });
+ } else {
+ mDropDownAnchor.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override public void onGlobalLayout() {
+ adjustDropDownSizeAndPosition();
+ }
+ });
+ }
+ }
+
+ updateViewsVisibility(mIconifiedByDefault);
+ updateQueryHint();
+ }
+
+ /**
+ * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used
+ * to display labels, hints, suggestions, create intents for launching search results screens
+ * and controlling other affordances such as a voice button.
+ *
+ * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific
+ * activity or a global search provider.
+ */
+ public void setSearchableInfo(SearchableInfo searchable) {
+ mSearchable = searchable;
+ if (mSearchable != null) {
+ updateSearchAutoComplete();
+ updateQueryHint();
+ }
+ // Cache the voice search capability
+ mVoiceButtonEnabled = hasVoiceSearch();
+
+ if (mVoiceButtonEnabled) {
+ // Disable the microphone on the keyboard, as a mic is displayed near the text box
+ // TODO: use imeOptions to disable voice input when the new API will be available
+ mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
+ }
+ updateViewsVisibility(isIconified());
+ }
+
+ /**
+ * Sets the APP_DATA for legacy SearchDialog use.
+ * @param appSearchData bundle provided by the app when launching the search dialog
+ * @hide
+ */
+ public void setAppSearchData(Bundle appSearchData) {
+ mAppSearchData = appSearchData;
+ }
+
+ /**
+ * Sets the IME options on the query text field.
+ *
+ * @see TextView#setImeOptions(int)
+ * @param imeOptions the options to set on the query text field
+ *
+ * @attr ref android.R.styleable#SearchView_imeOptions
+ */
+ public void setImeOptions(int imeOptions) {
+ mQueryTextView.setImeOptions(imeOptions);
+ }
+
+ /**
+ * Returns the IME options set on the query text field.
+ * @return the ime options
+ * @see TextView#setImeOptions(int)
+ *
+ * @attr ref android.R.styleable#SearchView_imeOptions
+ */
+ public int getImeOptions() {
+ return mQueryTextView.getImeOptions();
+ }
+
+ /**
+ * Sets the input type on the query text field.
+ *
+ * @see TextView#setInputType(int)
+ * @param inputType the input type to set on the query text field
+ *
+ * @attr ref android.R.styleable#SearchView_inputType
+ */
+ public void setInputType(int inputType) {
+ mQueryTextView.setInputType(inputType);
+ }
+
+ /**
+ * Returns the input type set on the query text field.
+ * @return the input type
+ *
+ * @attr ref android.R.styleable#SearchView_inputType
+ */
+ public int getInputType() {
+ return mQueryTextView.getInputType();
+ }
+
+ /** @hide */
+ @Override
+ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ // Don't accept focus if in the middle of clearing focus
+ if (mClearingFocus) return false;
+ // Check if SearchView is focusable.
+ if (!isFocusable()) return false;
+ // If it is not iconified, then give the focus to the text field
+ if (!isIconified()) {
+ boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect);
+ if (result) {
+ updateViewsVisibility(false);
+ }
+ return result;
+ } else {
+ return super.requestFocus(direction, previouslyFocusedRect);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void clearFocus() {
+ mClearingFocus = true;
+ setImeVisibility(false);
+ super.clearFocus();
+ mQueryTextView.clearFocus();
+ mClearingFocus = false;
+ }
+
+ /**
+ * Sets a listener for user actions within the SearchView.
+ *
+ * @param listener the listener object that receives callbacks when the user performs
+ * actions in the SearchView such as clicking on buttons or typing a query.
+ */
+ public void setOnQueryTextListener(OnQueryTextListener listener) {
+ mOnQueryChangeListener = listener;
+ }
+
+ /**
+ * Sets a listener to inform when the user closes the SearchView.
+ *
+ * @param listener the listener to call when the user closes the SearchView.
+ */
+ public void setOnCloseListener(OnCloseListener listener) {
+ mOnCloseListener = listener;
+ }
+
+ /**
+ * Sets a listener to inform when the focus of the query text field changes.
+ *
+ * @param listener the listener to inform of focus changes.
+ */
+ public void setOnQueryTextFocusChangeListener(OnFocusChangeListener listener) {
+ mOnQueryTextFocusChangeListener = listener;
+ }
+
+ /**
+ * Sets a listener to inform when a suggestion is focused or clicked.
+ *
+ * @param listener the listener to inform of suggestion selection events.
+ */
+ public void setOnSuggestionListener(OnSuggestionListener listener) {
+ mOnSuggestionListener = listener;
+ }
+
+ /**
+ * Sets a listener to inform when the search button is pressed. This is only
+ * relevant when the text field is not visible by default. Calling {@link #setIconified
+ * setIconified(false)} can also cause this listener to be informed.
+ *
+ * @param listener the listener to inform when the search button is clicked or
+ * the text field is programmatically de-iconified.
+ */
+ public void setOnSearchClickListener(OnClickListener listener) {
+ mOnSearchClickListener = listener;
+ }
+
+ /**
+ * Returns the query string currently in the text field.
+ *
+ * @return the query string
+ */
+ public CharSequence getQuery() {
+ return mQueryTextView.getText();
+ }
+
+ /**
+ * Sets a query string in the text field and optionally submits the query as well.
+ *
+ * @param query the query string. This replaces any query text already present in the
+ * text field.
+ * @param submit whether to submit the query right now or only update the contents of
+ * text field.
+ */
+ public void setQuery(CharSequence query, boolean submit) {
+ mQueryTextView.setText(query);
+ if (query != null) {
+ mQueryTextView.setSelection(mQueryTextView.length());
+ mUserQuery = query;
+ }
+
+ // If the query is not empty and submit is requested, submit the query
+ if (submit && !TextUtils.isEmpty(query)) {
+ onSubmitQuery();
+ }
+ }
+
+ /**
+ * Sets the hint text to display in the query text field. This overrides any hint specified
+ * in the SearchableInfo.
+ *
+ * @param hint the hint text to display
+ *
+ * @attr ref android.R.styleable#SearchView_queryHint
+ */
+ public void setQueryHint(CharSequence hint) {
+ mQueryHint = hint;
+ updateQueryHint();
+ }
+
+ /**
+ * Gets the hint text to display in the query text field.
+ * @return the query hint text, if specified, null otherwise.
+ *
+ * @attr ref android.R.styleable#SearchView_queryHint
+ */
+ public CharSequence getQueryHint() {
+ if (mQueryHint != null) {
+ return mQueryHint;
+ } else if (mSearchable != null) {
+ CharSequence hint = null;
+ int hintId = mSearchable.getHintId();
+ if (hintId != 0) {
+ hint = getContext().getString(hintId);
+ }
+ return hint;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the default or resting state of the search field. If true, a single search icon is
+ * shown by default and expands to show the text field and other buttons when pressed. Also,
+ * if the default state is iconified, then it collapses to that state when the close button
+ * is pressed. Changes to this property will take effect immediately.
+ *
+ * <p>The default value is true.</p>
+ *
+ * @param iconified whether the search field should be iconified by default
+ *
+ * @attr ref android.R.styleable#SearchView_iconifiedByDefault
+ */
+ public void setIconifiedByDefault(boolean iconified) {
+ if (mIconifiedByDefault == iconified) return;
+ mIconifiedByDefault = iconified;
+ updateViewsVisibility(iconified);
+ updateQueryHint();
+ }
+
+ /**
+ * Returns the default iconified state of the search field.
+ * @return
+ *
+ * @attr ref android.R.styleable#SearchView_iconifiedByDefault
+ */
+ public boolean isIconfiedByDefault() {
+ return mIconifiedByDefault;
+ }
+
+ /**
+ * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is
+ * a temporary state and does not override the default iconified state set by
+ * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then
+ * a false here will only be valid until the user closes the field. And if the default
+ * state is expanded, then a true here will only clear the text field and not close it.
+ *
+ * @param iconify a true value will collapse the SearchView to an icon, while a false will
+ * expand it.
+ */
+ public void setIconified(boolean iconify) {
+ if (iconify) {
+ onCloseClicked();
+ } else {
+ onSearchClicked();
+ }
+ }
+
+ /**
+ * Returns the current iconified state of the SearchView.
+ *
+ * @return true if the SearchView is currently iconified, false if the search field is
+ * fully visible.
+ */
+ public boolean isIconified() {
+ return mIconified;
+ }
+
+ /**
+ * Enables showing a submit button when the query is non-empty. In cases where the SearchView
+ * is being used to filter the contents of the current activity and doesn't launch a separate
+ * results activity, then the submit button should be disabled.
+ *
+ * @param enabled true to show a submit button for submitting queries, false if a submit
+ * button is not required.
+ */
+ public void setSubmitButtonEnabled(boolean enabled) {
+ mSubmitButtonEnabled = enabled;
+ updateViewsVisibility(isIconified());
+ }
+
+ /**
+ * Returns whether the submit button is enabled when necessary or never displayed.
+ *
+ * @return whether the submit button is enabled automatically when necessary
+ */
+ public boolean isSubmitButtonEnabled() {
+ return mSubmitButtonEnabled;
+ }
+
+ /**
+ * Specifies if a query refinement button should be displayed alongside each suggestion
+ * or if it should depend on the flags set in the individual items retrieved from the
+ * suggestions provider. Clicking on the query refinement button will replace the text
+ * in the query text field with the text from the suggestion. This flag only takes effect
+ * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)}
+ * and not when using a custom adapter.
+ *
+ * @param enable true if all items should have a query refinement button, false if only
+ * those items that have a query refinement flag set should have the button.
+ *
+ * @see SearchManager#SUGGEST_COLUMN_FLAGS
+ * @see SearchManager#FLAG_QUERY_REFINEMENT
+ */
+ public void setQueryRefinementEnabled(boolean enable) {
+ mQueryRefinement = enable;
+ if (mSuggestionsAdapter instanceof SuggestionsAdapter) {
+ ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
+ enable ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY);
+ }
+ }
+
+ /**
+ * Returns whether query refinement is enabled for all items or only specific ones.
+ * @return true if enabled for all items, false otherwise.
+ */
+ public boolean isQueryRefinementEnabled() {
+ return mQueryRefinement;
+ }
+
+ /**
+ * You can set a custom adapter if you wish. Otherwise the default adapter is used to
+ * display the suggestions from the suggestions provider associated with the SearchableInfo.
+ *
+ * @see #setSearchableInfo(SearchableInfo)
+ */
+ public void setSuggestionsAdapter(CursorAdapter adapter) {
+ mSuggestionsAdapter = adapter;
+
+ mQueryTextView.setAdapter(mSuggestionsAdapter);
+ }
+
+ /**
+ * Returns the adapter used for suggestions, if any.
+ * @return the suggestions adapter
+ */
+ public CursorAdapter getSuggestionsAdapter() {
+ return mSuggestionsAdapter;
+ }
+
+ /**
+ * Makes the view at most this many pixels wide
+ *
+ * @attr ref android.R.styleable#SearchView_maxWidth
+ */
+ public void setMaxWidth(int maxpixels) {
+ mMaxWidth = maxpixels;
+
+ requestLayout();
+ }
+
+ /**
+ * Gets the specified maximum width in pixels, if set. Returns zero if
+ * no maximum width was specified.
+ * @return the maximum width of the view
+ *
+ * @attr ref android.R.styleable#SearchView_maxWidth
+ */
+ public int getMaxWidth() {
+ return mMaxWidth;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Let the standard measurements take effect in iconified state.
+ if (isIconified()) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+
+ switch (widthMode) {
+ case MeasureSpec.AT_MOST:
+ // If there is an upper limit, don't exceed maximum width (explicit or implicit)
+ if (mMaxWidth > 0) {
+ width = Math.min(mMaxWidth, width);
+ } else {
+ width = Math.min(getPreferredWidth(), width);
+ }
+ break;
+ case MeasureSpec.EXACTLY:
+ // If an exact width is specified, still don't exceed any specified maximum width
+ if (mMaxWidth > 0) {
+ width = Math.min(mMaxWidth, width);
+ }
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ // Use maximum width, if specified, else preferred width
+ width = mMaxWidth > 0 ? mMaxWidth : getPreferredWidth();
+ break;
+ }
+ widthMode = MeasureSpec.EXACTLY;
+ super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec);
+ }
+
+ private int getPreferredWidth() {
+ return getContext().getResources()
+ .getDimensionPixelSize(R.dimen.abs__search_view_preferred_width);
+ }
+
+ private void updateViewsVisibility(final boolean collapsed) {
+ mIconified = collapsed;
+ // Visibility of views that are visible when collapsed
+ final int visCollapsed = collapsed ? VISIBLE : GONE;
+ // Is there text in the query
+ final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
+
+ mSearchButton.setVisibility(visCollapsed);
+ updateSubmitButton(hasText);
+ mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
+ mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
+ updateCloseButton();
+ updateVoiceButton(!hasText);
+ updateSubmitArea();
+ }
+
+ private boolean hasVoiceSearch() {
+ if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) {
+ Intent testIntent = null;
+ if (mSearchable.getVoiceSearchLaunchWebSearch()) {
+ testIntent = mVoiceWebSearchIntent;
+ } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
+ testIntent = mVoiceAppSearchIntent;
+ }
+ if (testIntent != null) {
+ ResolveInfo ri = getContext().getPackageManager().resolveActivity(testIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return ri != null;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSubmitAreaEnabled() {
+ return (mSubmitButtonEnabled || mVoiceButtonEnabled) && !isIconified();
+ }
+
+ private void updateSubmitButton(boolean hasText) {
+ int visibility = GONE;
+ if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus()
+ && (hasText || !mVoiceButtonEnabled)) {
+ visibility = VISIBLE;
+ }
+ mSubmitButton.setVisibility(visibility);
+ }
+
+ private void updateSubmitArea() {
+ int visibility = GONE;
+ if (isSubmitAreaEnabled()
+ && (mSubmitButton.getVisibility() == VISIBLE
+ || mVoiceButton.getVisibility() == VISIBLE)) {
+ visibility = VISIBLE;
+ }
+ mSubmitArea.setVisibility(visibility);
+ }
+
+ private void updateCloseButton() {
+ final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
+ // Should we show the close button? It is not shown if there's no focus,
+ // field is not iconified by default and there is no text in it.
+ final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView);
+ mCloseButton.setVisibility(showClose ? VISIBLE : GONE);
+ mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
+ }
+
+ private void postUpdateFocusedState() {
+ post(mUpdateDrawableStateRunnable);
+ }
+
+ private void updateFocusedState() {
+ boolean focused = mQueryTextView.hasFocus();
+ mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
+ mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
+ invalidate();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeCallbacks(mUpdateDrawableStateRunnable);
+ post(mReleaseCursorRunnable);
+ super.onDetachedFromWindow();
+ }
+
+ private void setImeVisibility(final boolean visible) {
+ if (visible) {
+ post(mShowImeRunnable);
+ } else {
+ removeCallbacks(mShowImeRunnable);
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+ }
+ }
+
+ /**
+ * Called by the SuggestionsAdapter
+ * @hide
+ */
+ /* package */void onQueryRefine(CharSequence queryText) {
+ setQuery(queryText);
+ }
+
+ private final OnClickListener mOnClickListener = new OnClickListener() {
+
+ public void onClick(View v) {
+ if (v == mSearchButton) {
+ onSearchClicked();
+ } else if (v == mCloseButton) {
+ onCloseClicked();
+ } else if (v == mSubmitButton) {
+ onSubmitQuery();
+ } else if (v == mVoiceButton) {
+ onVoiceClicked();
+ } else if (v == mQueryTextView) {
+ forceSuggestionQuery();
+ }
+ }
+ };
+
+ /**
+ * Handles the key down event for dealing with action keys.
+ *
+ * @param keyCode This is the keycode of the typed key, and is the same value as
+ * found in the KeyEvent parameter.
+ * @param event The complete event record for the typed key
+ *
+ * @return true if the event was handled here, or false if not.
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mSearchable == null) {
+ return false;
+ }
+
+ // if it's an action specified by the searchable activity, launch the
+ // entered query with the action key
+ // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+ // TODO if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
+ // TODO launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText()
+ // TODO .toString());
+ // TODO return true;
+ // TODO }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ /**
+ * React to the user typing "enter" or other hardwired keys while typing in
+ * the search box. This handles these special keys while the edit box has
+ * focus.
+ */
+ View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // guard against possible race conditions
+ if (mSearchable == null) {
+ return false;
+ }
+
+ if (DBG) {
+ Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: "
+ + mQueryTextView.getListSelection());
+ }
+
+ // If a suggestion is selected, handle enter, search key, and action keys
+ // as presses on the selected suggestion
+ if (mQueryTextView.isPopupShowing()
+ && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) {
+ return onSuggestionsKey(v, keyCode, event);
+ }
+
+ // If there is text in the query box, handle enter, and action keys
+ // The search key is handled by the dialog's onKeyDown().
+ if (!mQueryTextView.isEmpty() && KeyEventCompat.hasNoModifiers(event)) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER) {
+ v.cancelLongPress();
+
+ // Launch as a regular search.
+ launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText()
+ .toString());
+ return true;
+ }
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+ // TODO if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
+ // TODO launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView
+ // TODO .getText().toString());
+ // TODO return true;
+ // TODO }
+ }
+ }
+ return false;
+ }
+ };
+
+ /**
+ * React to the user typing while in the suggestions list. First, check for
+ * action keys. If not handled, try refocusing regular characters into the
+ * EditText.
+ */
+ private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
+ // guard against possible race conditions (late arrival after dismiss)
+ if (mSearchable == null) {
+ return false;
+ }
+ if (mSuggestionsAdapter == null) {
+ return false;
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN && KeyEventCompat.hasNoModifiers(event)) {
+ // First, check for enter or search (both of which we'll treat as a
+ // "click")
+ if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH
+ || keyCode == KeyEvent.KEYCODE_TAB) {
+ int position = mQueryTextView.getListSelection();
+ return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
+ }
+
+ // Next, check for left/right moves, which we use to "return" the
+ // user to the edit view
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // give "focus" to text editor, with cursor at the beginning if
+ // left key, at end if right key
+ // TODO: Reverse left/right for right-to-left languages, e.g.
+ // Arabic
+ int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView
+ .length();
+ mQueryTextView.setSelection(selPoint);
+ mQueryTextView.setListSelection(0);
+ mQueryTextView.clearListSelection();
+ ensureImeVisible(mQueryTextView, true);
+
+ return true;
+ }
+
+ // Next, check for an "up and out" move
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) {
+ // TODO: restoreUserQuery();
+ // let ACTV complete the move
+ return false;
+ }
+
+ // Next, check for an "action key"
+ // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+ // TODO if ((actionKey != null)
+ // TODO && ((actionKey.getSuggestActionMsg() != null) || (actionKey
+ // TODO .getSuggestActionMsgColumn() != null))) {
+ // TODO // launch suggestion using action key column
+ // TODO int position = mQueryTextView.getListSelection();
+ // TODO if (position != ListView.INVALID_POSITION) {
+ // TODO Cursor c = mSuggestionsAdapter.getCursor();
+ // TODO if (c.moveToPosition(position)) {
+ // TODO final String actionMsg = getActionKeyMessage(c, actionKey);
+ // TODO if (actionMsg != null && (actionMsg.length() > 0)) {
+ // TODO return onItemClicked(position, keyCode, actionMsg);
+ // TODO }
+ // TODO }
+ // TODO }
+ // TODO }
+ }
+ return false;
+ }
+
+ /**
+ * For a given suggestion and a given cursor row, get the action message. If
+ * not provided by the specific row/column, also check for a single
+ * definition (for the action key).
+ *
+ * @param c The cursor providing suggestions
+ * @param actionKey The actionkey record being examined
+ *
+ * @return Returns a string, or null if no action key message for this
+ * suggestion
+ */
+ // TODO private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
+ // TODO String result = null;
+ // TODO // check first in the cursor data, for a suggestion-specific message
+ // TODO final String column = actionKey.getSuggestActionMsgColumn();
+ // TODO if (column != null) {
+ // TODO result = SuggestionsAdapter.getColumnString(c, column);
+ // TODO }
+ // TODO // If the cursor didn't give us a message, see if there's a single
+ // TODO // message defined
+ // TODO // for the actionkey (for all suggestions)
+ // TODO if (result == null) {
+ // TODO result = actionKey.getSuggestActionMsg();
+ // TODO }
+ // TODO return result;
+ // TODO }
+
+ private int getSearchIconId() {
+ TypedValue outValue = new TypedValue();
+ getContext().getTheme().resolveAttribute(R.attr.searchViewSearchIcon,
+ outValue, true);
+ return outValue.resourceId;
+ }
+
+ private CharSequence getDecoratedHint(CharSequence hintText) {
+ // If the field is always expanded, then don't add the search icon to the hint
+ if (!mIconifiedByDefault) return hintText;
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon
+ ssb.append(hintText);
+ Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId());
+ int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
+ searchIcon.setBounds(0, 0, textSize, textSize);
+ ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return ssb;
+ }
+
+ private void updateQueryHint() {
+ if (mQueryHint != null) {
+ mQueryTextView.setHint(getDecoratedHint(mQueryHint));
+ } else if (mSearchable != null) {
+ CharSequence hint = null;
+ int hintId = mSearchable.getHintId();
+ if (hintId != 0) {
+ hint = getContext().getString(hintId);
+ }
+ if (hint != null) {
+ mQueryTextView.setHint(getDecoratedHint(hint));
+ }
+ } else {
+ mQueryTextView.setHint(getDecoratedHint(""));
+ }
+ }
+
+ /**
+ * Updates the auto-complete text view.
+ */
+ private void updateSearchAutoComplete() {
+ // TODO mQueryTextView.setDropDownAnimationStyle(0); // no animation
+ mQueryTextView.setThreshold(mSearchable.getSuggestThreshold());
+ mQueryTextView.setImeOptions(mSearchable.getImeOptions());
+ int inputType = mSearchable.getInputType();
+ // We only touch this if the input type is set up for text (which it almost certainly
+ // should be, in the case of search!)
+ if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
+ // The existence of a suggestions authority is the proxy for "suggestions
+ // are available here"
+ inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
+ if (mSearchable.getSuggestAuthority() != null) {
+ inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
+ // TYPE_TEXT_FLAG_AUTO_COMPLETE means that the text editor is performing
+ // auto-completion based on its own semantics, which it will present to the user
+ // as they type. This generally means that the input method should not show its
+ // own candidates, and the spell checker should not be in action. The text editor
+ // supplies its candidates by calling InputMethodManager.displayCompletions(),
+ // which in turn will call InputMethodSession.displayCompletions().
+ inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
+ }
+ }
+ mQueryTextView.setInputType(inputType);
+ if (mSuggestionsAdapter != null) {
+ mSuggestionsAdapter.changeCursor(null);
+ }
+ // attach the suggestions adapter, if suggestions are available
+ // The existence of a suggestions authority is the proxy for "suggestions available here"
+ if (mSearchable.getSuggestAuthority() != null) {
+ mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
+ this, mSearchable, mOutsideDrawablesCache);
+ mQueryTextView.setAdapter(mSuggestionsAdapter);
+ ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
+ mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
+ : SuggestionsAdapter.REFINE_BY_ENTRY);
+ }
+ }
+
+ /**
+ * Update the visibility of the voice button. There are actually two voice search modes,
+ * either of which will activate the button.
+ * @param empty whether the search query text field is empty. If it is, then the other
+ * criteria apply to make the voice button visible.
+ */
+ private void updateVoiceButton(boolean empty) {
+ int visibility = GONE;
+ if (mVoiceButtonEnabled && !isIconified() && empty) {
+ visibility = VISIBLE;
+ mSubmitButton.setVisibility(GONE);
+ }
+ mVoiceButton.setVisibility(visibility);
+ }
+
+ private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() {
+
+ /**
+ * Called when the input method default action key is pressed.
+ */
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ onSubmitQuery();
+ return true;
+ }
+ };
+
+ private void onTextChanged(CharSequence newText) {
+ CharSequence text = mQueryTextView.getText();
+ mUserQuery = text;
+ boolean hasText = !TextUtils.isEmpty(text);
+ updateSubmitButton(hasText);
+ updateVoiceButton(!hasText);
+ updateCloseButton();
+ updateSubmitArea();
+ if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
+ mOnQueryChangeListener.onQueryTextChange(newText.toString());
+ }
+ mOldQueryText = newText.toString();
+ }
+
+ private void onSubmitQuery() {
+ CharSequence query = mQueryTextView.getText();
+ if (query != null && TextUtils.getTrimmedLength(query) > 0) {
+ if (mOnQueryChangeListener == null
+ || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
+ if (mSearchable != null) {
+ launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
+ setImeVisibility(false);
+ }
+ dismissSuggestions();
+ }
+ }
+ }
+
+ private void dismissSuggestions() {
+ mQueryTextView.dismissDropDown();
+ }
+
+ private void onCloseClicked() {
+ CharSequence text = mQueryTextView.getText();
+ if (TextUtils.isEmpty(text)) {
+ if (mIconifiedByDefault) {
+ // If the app doesn't override the close behavior
+ if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
+ // hide the keyboard and remove focus
+ clearFocus();
+ // collapse the search field
+ updateViewsVisibility(true);
+ }
+ }
+ } else {
+ mQueryTextView.setText("");
+ mQueryTextView.requestFocus();
+ setImeVisibility(true);
+ }
+
+ }
+
+ private void onSearchClicked() {
+ updateViewsVisibility(false);
+ mQueryTextView.requestFocus();
+ setImeVisibility(true);
+ if (mOnSearchClickListener != null) {
+ mOnSearchClickListener.onClick(this);
+ }
+ }
+
+ private void onVoiceClicked() {
+ // guard against possible race conditions
+ if (mSearchable == null) {
+ return;
+ }
+ SearchableInfo searchable = mSearchable;
+ try {
+ if (searchable.getVoiceSearchLaunchWebSearch()) {
+ Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent,
+ searchable);
+ getContext().startActivity(webSearchIntent);
+ } else if (searchable.getVoiceSearchLaunchRecognizer()) {
+ Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent,
+ searchable);
+ getContext().startActivity(appSearchIntent);
+ }
+ } catch (ActivityNotFoundException e) {
+ // Should not happen, since we check the availability of
+ // voice search before showing the button. But just in case...
+ Log.w(LOG_TAG, "Could not find voice search activity");
+ }
+ }
+
+ void onTextFocusChanged() {
+ updateViewsVisibility(isIconified());
+ // Delayed update to make sure that the focus has settled down and window focus changes
+ // don't affect it. A synchronous update was not working.
+ postUpdateFocusedState();
+ if (mQueryTextView.hasFocus()) {
+ forceSuggestionQuery();
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ postUpdateFocusedState();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActionViewCollapsed() {
+ clearFocus();
+ updateViewsVisibility(true);
+ mQueryTextView.setImeOptions(mCollapsedImeOptions);
+ mExpandedInActionView = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActionViewExpanded() {
+ if (mExpandedInActionView) return;
+
+ mExpandedInActionView = true;
+ mCollapsedImeOptions = mQueryTextView.getImeOptions();
+ mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
+ mQueryTextView.setText("");
+ setIconified(false);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(SearchView.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(SearchView.class.getName());
+ }
+
+ private void adjustDropDownSizeAndPosition() {
+ if (mDropDownAnchor.getWidth() > 1) {
+ Resources res = getContext().getResources();
+ int anchorPadding = mSearchPlate.getPaddingLeft();
+ Rect dropDownPadding = new Rect();
+ int iconOffset = mIconifiedByDefault
+ ? res.getDimensionPixelSize(R.dimen.abs__dropdownitem_icon_width)
+ + res.getDimensionPixelSize(R.dimen.abs__dropdownitem_text_padding_left)
+ : 0;
+ mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
+ mQueryTextView.setDropDownHorizontalOffset(-(dropDownPadding.left + iconOffset)
+ + anchorPadding);
+ mQueryTextView.setDropDownWidth(mDropDownAnchor.getWidth() + dropDownPadding.left
+ + dropDownPadding.right + iconOffset - (anchorPadding));
+ }
+ }
+
+ private boolean onItemClicked(int position, int actionKey, String actionMsg) {
+ if (mOnSuggestionListener == null
+ || !mOnSuggestionListener.onSuggestionClick(position)) {
+ launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
+ setImeVisibility(false);
+ dismissSuggestions();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean onItemSelected(int position) {
+ if (mOnSuggestionListener == null
+ || !mOnSuggestionListener.onSuggestionSelect(position)) {
+ rewriteQueryFromSuggestion(position);
+ return true;
+ }
+ return false;
+ }
+
+ private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
+
+ /**
+ * Implements OnItemClickListener
+ */
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
+ onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
+ }
+ };
+
+ private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
+
+ /**
+ * Implements OnItemSelectedListener
+ */
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
+ SearchView.this.onItemSelected(position);
+ }
+
+ /**
+ * Implements OnItemSelectedListener
+ */
+ public void onNothingSelected(AdapterView<?> parent) {
+ if (DBG)
+ Log.d(LOG_TAG, "onNothingSelected()");
+ }
+ };
+
+ /**
+ * Query rewriting.
+ */
+ private void rewriteQueryFromSuggestion(int position) {
+ CharSequence oldQuery = mQueryTextView.getText();
+ Cursor c = mSuggestionsAdapter.getCursor();
+ if (c == null) {
+ return;
+ }
+ if (c.moveToPosition(position)) {
+ // Get the new query from the suggestion.
+ CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
+ if (newQuery != null) {
+ // The suggestion rewrites the query.
+ // Update the text field, without getting new suggestions.
+ setQuery(newQuery);
+ } else {
+ // The suggestion does not rewrite the query, restore the user's query.
+ setQuery(oldQuery);
+ }
+ } else {
+ // We got a bad position, restore the user's query.
+ setQuery(oldQuery);
+ }
+ }
+
+ /**
+ * Launches an intent based on a suggestion.
+ *
+ * @param position The index of the suggestion to create the intent from.
+ * @param actionKey The key code of the action key that was pressed,
+ * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+ * @param actionMsg The message for the action key that was pressed,
+ * or <code>null</code> if none.
+ * @return true if a successful launch, false if could not (e.g. bad position).
+ */
+ private boolean launchSuggestion(int position, int actionKey, String actionMsg) {
+ Cursor c = mSuggestionsAdapter.getCursor();
+ if ((c != null) && c.moveToPosition(position)) {
+
+ Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
+
+ // launch the intent
+ launchIntent(intent);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Launches an intent, including any special intent handling.
+ */
+ private void launchIntent(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ try {
+ // If the intent was created from a suggestion, it will always have an explicit
+ // component here.
+ getContext().startActivity(intent);
+ } catch (RuntimeException ex) {
+ Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
+ }
+ }
+
+ /**
+ * Sets the text in the query box, without updating the suggestions.
+ */
+ private void setQuery(CharSequence query) {
+ setText(mQueryTextView, query, true);
+ // Move the cursor to the end
+ mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
+ }
+
+ private void launchQuerySearch(int actionKey, String actionMsg, String query) {
+ String action = Intent.ACTION_SEARCH;
+ Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
+ getContext().startActivity(intent);
+ }
+
+ /**
+ * Constructs an intent from the given information and the search dialog state.
+ *
+ * @param action Intent action.
+ * @param data Intent data, or <code>null</code>.
+ * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
+ * @param query Intent query, or <code>null</code>.
+ * @param actionKey The key code of the action key that was pressed,
+ * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+ * @param actionMsg The message for the action key that was pressed,
+ * or <code>null</code> if none.
+ * @return The intent.
+ */
+ private Intent createIntent(String action, Uri data, String extraData, String query,
+ int actionKey, String actionMsg) {
+ // Now build the Intent
+ Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // We need CLEAR_TOP to avoid reusing an old task that has other activities
+ // on top of the one we want. We don't want to do this in in-app search though,
+ // as it can be destructive to the activity stack.
+ if (data != null) {
+ intent.setData(data);
+ }
+ intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
+ if (query != null) {
+ intent.putExtra(SearchManager.QUERY, query);
+ }
+ if (extraData != null) {
+ intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
+ }
+ if (mAppSearchData != null) {
+ intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
+ }
+ if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
+ intent.putExtra(SearchManager.ACTION_KEY, actionKey);
+ intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
+ }
+ intent.setComponent(mSearchable.getSearchActivity());
+ return intent;
+ }
+
+ /**
+ * Create and return an Intent that can launch the voice search activity for web search.
+ */
+ private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) {
+ Intent voiceIntent = new Intent(baseIntent);
+ ComponentName searchActivity = searchable.getSearchActivity();
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
+ : searchActivity.flattenToShortString());
+ return voiceIntent;
+ }
+
+ /**
+ * Create and return an Intent that can launch the voice search activity, perform a specific
+ * voice transcription, and forward the results to the searchable activity.
+ *
+ * @param baseIntent The voice app search intent to start from
+ * @return A completely-configured intent ready to send to the voice search activity
+ */
+ private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) {
+ ComponentName searchActivity = searchable.getSearchActivity();
+
+ // create the necessary intent to set up a search-and-forward operation
+ // in the voice search system. We have to keep the bundle separate,
+ // because it becomes immutable once it enters the PendingIntent
+ Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
+ queryIntent.setComponent(searchActivity);
+ PendingIntent pending = PendingIntent.getActivity(getContext(), 0, queryIntent,
+ PendingIntent.FLAG_ONE_SHOT);
+
+ // Now set up the bundle that will be inserted into the pending intent
+ // when it's time to do the search. We always build it here (even if empty)
+ // because the voice search activity will always need to insert "QUERY" into
+ // it anyway.
+ Bundle queryExtras = new Bundle();
+
+ // Now build the intent to launch the voice search. Add all necessary
+ // extras to launch the voice recognizer, and then all the necessary extras
+ // to forward the results to the searchable activity
+ Intent voiceIntent = new Intent(baseIntent);
+
+ // Add all of the configuration options supplied by the searchable's metadata
+ String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
+ String prompt = null;
+ String language = null;
+ int maxResults = 1;
+
+ Resources resources = getResources();
+ if (searchable.getVoiceLanguageModeId() != 0) {
+ languageModel = resources.getString(searchable.getVoiceLanguageModeId());
+ }
+ if (searchable.getVoicePromptTextId() != 0) {
+ prompt = resources.getString(searchable.getVoicePromptTextId());
+ }
+ if (searchable.getVoiceLanguageId() != 0) {
+ language = resources.getString(searchable.getVoiceLanguageId());
+ }
+ if (searchable.getVoiceMaxResults() != 0) {
+ maxResults = searchable.getVoiceMaxResults();
+ }
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
+ : searchActivity.flattenToShortString());
+
+ // Add the values that configure forwarding the results
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
+
+ return voiceIntent;
+ }
+
+ /**
+ * When a particular suggestion has been selected, perform the various lookups required
+ * to use the suggestion. This includes checking the cursor for suggestion-specific data,
+ * and/or falling back to the XML for defaults; It also creates REST style Uri data when
+ * the suggestion includes a data id.
+ *
+ * @param c The suggestions cursor, moved to the row of the user's selection
+ * @param actionKey The key code of the action key that was pressed,
+ * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
+ * @param actionMsg The message for the action key that was pressed,
+ * or <code>null</code> if none.
+ * @return An intent for the suggestion at the cursor's position.
+ */
+ private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
+ try {
+ // use specific action if supplied, or default action if supplied, or fixed default
+ String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+
+ if (action == null) {
+ action = mSearchable.getSuggestIntentAction();
+ }
+ if (action == null) {
+ action = Intent.ACTION_SEARCH;
+ }
+
+ // use specific data if supplied, or default data if supplied
+ String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
+ if (data == null) {
+ data = mSearchable.getSuggestIntentData();
+ }
+ // then, if an ID was provided, append it.
+ if (data != null) {
+ String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
+ if (id != null) {
+ data = data + "/" + Uri.encode(id);
+ }
+ }
+ Uri dataUri = (data == null) ? null : Uri.parse(data);
+
+ String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
+ String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
+
+ return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
+ } catch (RuntimeException e ) {
+ int rowNum;
+ try { // be really paranoid now
+ rowNum = c.getPosition();
+ } catch (RuntimeException e2 ) {
+ rowNum = -1;
+ }
+ Log.w(LOG_TAG, "Search suggestions cursor at row " + rowNum +
+ " returned exception.", e);
+ return null;
+ }
+ }
+
+ private void forceSuggestionQuery() {
+ try {
+ Method before = SearchAutoComplete.class.getMethod("doBeforeTextChanged");
+ Method after = SearchAutoComplete.class.getMethod("doAfterTextChanged");
+ before.setAccessible(true);
+ after.setAccessible(true);
+ before.invoke(mQueryTextView);
+ after.invoke(mQueryTextView);
+ } catch (Exception e) {
+ // Oh well...
+ }
+ }
+
+ static boolean isLandscapeMode(Context context) {
+ return context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ /**
+ * Callback to watch the text field for empty/non-empty
+ */
+ private TextWatcher mTextWatcher = new TextWatcher() {
+
+ public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
+
+ public void onTextChanged(CharSequence s, int start,
+ int before, int after) {
+ SearchView.this.onTextChanged(s);
+ }
+
+ public void afterTextChanged(Editable s) {
+ }
+ };
+
+ /**
+ * Local subclass for AutoCompleteTextView.
+ * @hide
+ */
+ public static class SearchAutoComplete extends AutoCompleteTextView {
+
+ private int mThreshold;
+ private SearchView mSearchView;
+
+ public SearchAutoComplete(Context context) {
+ super(context);
+ mThreshold = getThreshold();
+ }
+
+ public SearchAutoComplete(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mThreshold = getThreshold();
+ }
+
+ public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mThreshold = getThreshold();
+ }
+
+ void setSearchView(SearchView searchView) {
+ mSearchView = searchView;
+ }
+
+ @Override
+ public void setThreshold(int threshold) {
+ super.setThreshold(threshold);
+ mThreshold = threshold;
+ }
+
+ /**
+ * Returns true if the text field is empty, or contains only whitespace.
+ */
+ private boolean isEmpty() {
+ return TextUtils.getTrimmedLength(getText()) == 0;
+ }
+
+ /**
+ * We override this method to avoid replacing the query box text when a
+ * suggestion is clicked.
+ */
+ @Override
+ protected void replaceText(CharSequence text) {
+ }
+
+ /**
+ * We override this method to avoid an extra onItemClick being called on
+ * the drop-down's OnItemClickListener by
+ * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is
+ * clicked with the trackball.
+ */
+ @Override
+ public void performCompletion() {
+ }
+
+ /**
+ * We override this method to be sure and show the soft keyboard if
+ * appropriate when the TextView has focus.
+ */
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
+ InputMethodManager inputManager = (InputMethodManager) getContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputManager.showSoftInput(this, 0);
+ // If in landscape mode, then make sure that
+ // the ime is in front of the dropdown.
+ if (isLandscapeMode(getContext())) {
+ ensureImeVisible(this, true);
+ }
+ }
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ mSearchView.onTextFocusChanged();
+ }
+
+ /**
+ * We override this method so that we can allow a threshold of zero,
+ * which ACTV does not.
+ */
+ @Override
+ public boolean enoughToFilter() {
+ return mThreshold <= 0 || super.enoughToFilter();
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // special case for the back key, we do not even try to send it
+ // to the drop down list but instead, consume it immediately
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ KeyEvent.DispatcherState state = getKeyDispatcherState();
+ if (state != null) {
+ state.startTracking(event, this);
+ }
+ return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ KeyEvent.DispatcherState state = getKeyDispatcherState();
+ if (state != null) {
+ state.handleUpEvent(event);
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ mSearchView.clearFocus();
+ mSearchView.setImeVisibility(false);
+ return true;
+ }
+ }
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+ }
+
+ private static void ensureImeVisible(AutoCompleteTextView view, boolean visible) {
+ try {
+ Method method = AutoCompleteTextView.class.getMethod("ensureImeVisible", boolean.class);
+ method.setAccessible(true);
+ method.invoke(view, visible);
+ } catch (Exception e) {
+ //Oh well...
+ }
+ }
+
+ private static void showSoftInputUnchecked(View view, InputMethodManager imm, int flags) {
+ try {
+ Method method = imm.getClass().getMethod("showSoftInputUnchecked", int.class, ResultReceiver.class);
+ method.setAccessible(true);
+ method.invoke(imm, flags, null);
+ } catch (Exception e) {
+ //Fallback to public API which hopefully does mostly the same thing
+ imm.showSoftInput(view, flags);
+ }
+ }
+
+ private static void setText(AutoCompleteTextView view, CharSequence text, boolean filter) {
+ try {
+ Method method = AutoCompleteTextView.class.getMethod("setText", CharSequence.class, boolean.class);
+ method.setAccessible(true);
+ method.invoke(view, text, filter);
+ } catch (Exception e) {
+ //Fallback to public API which hopefully does mostly the same thing
+ view.setText(text);
+ }
+ }
+}
diff --git a/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java
new file mode 100644
index 000000000..bd5cbd718
--- /dev/null
+++ b/APG/android-libs/ActionBarSherlock/src/com/actionbarsherlock/widget/SuggestionsAdapter.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2009 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.actionbarsherlock.widget;
+
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.widget.ResourceCursorAdapter;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.actionbarsherlock.R;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.WeakHashMap;
+
+/**
+ * Provides the contents for the suggestion drop-down list.
+ *
+ * @hide
+ */
+class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListener {
+
+ private static final boolean DBG = false;
+ private static final String LOG_TAG = "SuggestionsAdapter";
+ private static final int QUERY_LIMIT = 50;
+
+ static final int REFINE_NONE = 0;
+ static final int REFINE_BY_ENTRY = 1;
+ static final int REFINE_ALL = 2;
+
+ private SearchManager mSearchManager;
+ private SearchView mSearchView;
+ private Context mProviderContext;
+ private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
+ private boolean mClosed = false;
+ private int mQueryRefinement = REFINE_BY_ENTRY;
+
+ // URL color
+ private ColorStateList mUrlColor;
+
+ static final int INVALID_INDEX = -1;
+
+ // Cached column indexes, updated when the cursor changes.
+ private int mText1Col = INVALID_INDEX;
+ private int mText2Col = INVALID_INDEX;
+ private int mText2UrlCol = INVALID_INDEX;
+ private int mIconName1Col = INVALID_INDEX;
+ private int mIconName2Col = INVALID_INDEX;
+ private int mFlagsCol = INVALID_INDEX;
+
+ // private final Runnable mStartSpinnerRunnable;
+ // private final Runnable mStopSpinnerRunnable;
+
+ /**
+ * The amount of time we delay in the filter when the user presses the delete key.
+ */
+ //private static final long DELETE_KEY_POST_DELAY = 500L;
+
+ public SuggestionsAdapter(Context context, SearchView searchView,
+ SearchableInfo mSearchable, WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
+ super(context,
+ R.layout.abs__search_dropdown_item_icons_2line,
+ null, // no initial cursor
+ true); // auto-requery
+ mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+ mProviderContext = mContext;
+ mSearchView = searchView;
+
+ mOutsideDrawablesCache = outsideDrawablesCache;
+
+ // mStartSpinnerRunnable = new Runnable() {
+ // public void run() {
+ // // mSearchView.setWorking(true); // TODO:
+ // }
+ // };
+ //
+ // mStopSpinnerRunnable = new Runnable() {
+ // public void run() {
+ // // mSearchView.setWorking(false); // TODO:
+ // }
+ // };
+
+ // delay 500ms when deleting
+// TODO getFilter().setDelayer(new Filter.Delayer() {
+//
+// private int mPreviousLength = 0;
+//
+// public long getPostingDelay(CharSequence constraint) {
+// if (constraint == null) return 0;
+//
+// long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0;
+// mPreviousLength = constraint.length();
+// return delay;
+// }
+// });
+ }
+
+ /**
+ * Enables query refinement for all suggestions. This means that an additional icon
+ * will be shown for each entry. When clicked, the suggested text on that line will be
+ * copied to the query text field.
+ * <p>
+ *
+ * @param refineWhat which queries to refine. Possible values are {@link #REFINE_NONE},
+ * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
+ */
+ public void setQueryRefinement(int refineWhat) {
+ mQueryRefinement = refineWhat;
+ }
+
+ /**
+ * Returns the current query refinement preference.
+ * @return value of query refinement preference
+ */
+ public int getQueryRefinement() {
+ return mQueryRefinement;
+ }
+
+ /**
+ * Overridden to always return <code>false</code>, since we cannot be sure that
+ * suggestion sources return stable IDs.
+ */
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ /**
+ * Use the search suggestions provider to obtain a live cursor. This will be called
+ * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
+ * The results will be processed in the UI thread and changeCursor() will be called.
+ */
+ @Override
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
+ String query = (constraint == null) ? "" : constraint.toString();
+ /**
+ * for in app search we show the progress spinner until the cursor is returned with
+ * the results.
+ */
+ Cursor cursor = null;
+ if (mSearchView.getVisibility() != View.VISIBLE
+ || mSearchView.getWindowVisibility() != View.VISIBLE) {
+ return null;
+ }
+ //mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
+ try {
+ cursor = getSuggestions(query, QUERY_LIMIT);
+ // trigger fill window so the spinner stays up until the results are copied over and
+ // closer to being ready
+ if (cursor != null) {
+ cursor.getCount();
+ return cursor;
+ }
+ } catch (RuntimeException e) {
+ Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
+ }
+ // If cursor is null or an exception was thrown, stop the spinner and return null.
+ // changeCursor doesn't get called if cursor is null
+ // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO:
+ return null;
+ }
+
+ public Cursor getSuggestions(String query, int limit) {
+ Uri.Builder uriBuilder = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+ .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+
+ // append standard suggestion query path
+ uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
+
+ // inject query, either as selection args or inline
+ uriBuilder.appendPath(query);
+
+ if (limit > 0) {
+ uriBuilder.appendQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT, String.valueOf(limit));
+ }
+
+ Uri uri = uriBuilder.build();
+
+ // finally, make the query
+ return mContext.getContentResolver().query(uri, null, null, null, null);
+ }
+
+ public void close() {
+ if (DBG) Log.d(LOG_TAG, "close()");
+ changeCursor(null);
+ mClosed = true;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
+ super.notifyDataSetChanged();
+
+ // mSearchView.onDataSetChanged(); // TODO:
+
+ updateSpinnerState(getCursor());
+ }
+
+ @Override
+ public void notifyDataSetInvalidated() {
+ if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated");
+ super.notifyDataSetInvalidated();
+
+ updateSpinnerState(getCursor());
+ }
+
+ private void updateSpinnerState(Cursor cursor) {
+ Bundle extras = cursor != null ? cursor.getExtras() : null;
+ if (DBG) {
+ Log.d(LOG_TAG, "updateSpinnerState - extra = "
+ + (extras != null
+ ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
+ : null));
+ }
+ // Check if the Cursor indicates that the query is not complete and show the spinner
+ if (extras != null
+ && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) {
+ // mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
+ return;
+ }
+ // If cursor is null or is done, stop the spinner
+ // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO:
+ }
+
+ /**
+ * Cache columns.
+ */
+ @Override
+ public void changeCursor(Cursor c) {
+ if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
+
+ if (mClosed) {
+ Log.w(LOG_TAG, "Tried to change cursor after adapter was closed.");
+ if (c != null) c.close();
+ return;
+ }
+
+ try {
+ super.changeCursor(c);
+
+ if (c != null) {
+ mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
+ mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
+ mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
+ mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
+ mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
+ mFlagsCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FLAGS);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "error changing cursor and caching columns", e);
+ }
+ }
+
+ /**
+ * Tags the view with cached child view look-ups.
+ */
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View v = super.newView(context, cursor, parent);
+ v.setTag(new ChildViewCache(v));
+ return v;
+ }
+
+ /**
+ * Cache of the child views of drop-drown list items, to avoid looking up the children
+ * each time the contents of a list item are changed.
+ */
+ private final static class ChildViewCache {
+ public final TextView mText1;
+ public final TextView mText2;
+ public final ImageView mIcon1;
+ public final ImageView mIcon2;
+ public final ImageView mIconRefine;
+
+ public ChildViewCache(View v) {
+ mText1 = (TextView) v.findViewById(android.R.id.text1);
+ mText2 = (TextView) v.findViewById(android.R.id.text2);
+ mIcon1 = (ImageView) v.findViewById(android.R.id.icon1);
+ mIcon2 = (ImageView) v.findViewById(android.R.id.icon2);
+ mIconRefine = (ImageView) v.findViewById(R.id.edit_query);
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ChildViewCache views = (ChildViewCache) view.getTag();
+
+ int flags = 0;
+ if (mFlagsCol != INVALID_INDEX) {
+ flags = cursor.getInt(mFlagsCol);
+ }
+ if (views.mText1 != null) {
+ String text1 = getStringOrNull(cursor, mText1Col);
+ setViewText(views.mText1, text1);
+ }
+ if (views.mText2 != null) {
+ // First check TEXT_2_URL
+ CharSequence text2 = getStringOrNull(cursor, mText2UrlCol);
+ if (text2 != null) {
+ text2 = formatUrl(text2);
+ } else {
+ text2 = getStringOrNull(cursor, mText2Col);
+ }
+
+ // If no second line of text is indicated, allow the first line of text
+ // to be up to two lines if it wants to be.
+ if (TextUtils.isEmpty(text2)) {
+ if (views.mText1 != null) {
+ views.mText1.setSingleLine(false);
+ views.mText1.setMaxLines(2);
+ }
+ } else {
+ if (views.mText1 != null) {
+ views.mText1.setSingleLine(true);
+ views.mText1.setMaxLines(1);
+ }
+ }
+ setViewText(views.mText2, text2);
+ }
+
+ if (views.mIcon1 != null) {
+ setViewDrawable(views.mIcon1, getIcon1(cursor), View.INVISIBLE);
+ }
+ if (views.mIcon2 != null) {
+ setViewDrawable(views.mIcon2, getIcon2(cursor), View.GONE);
+ }
+ if (mQueryRefinement == REFINE_ALL
+ || (mQueryRefinement == REFINE_BY_ENTRY
+ && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) {
+ views.mIconRefine.setVisibility(View.VISIBLE);
+ views.mIconRefine.setTag(views.mText1.getText());
+ views.mIconRefine.setOnClickListener(this);
+ } else {
+ views.mIconRefine.setVisibility(View.GONE);
+ }
+ }
+
+ public void onClick(View v) {
+ Object tag = v.getTag();
+ if (tag instanceof CharSequence) {
+ mSearchView.onQueryRefine((CharSequence) tag);
+ }
+ }
+
+ private CharSequence formatUrl(CharSequence url) {
+ if (mUrlColor == null) {
+ // Lazily get the URL color from the current theme.
+ TypedValue colorValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
+ mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
+ }
+
+ SpannableString text = new SpannableString(url);
+ text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null),
+ 0, url.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return text;
+ }
+
+ private void setViewText(TextView v, CharSequence text) {
+ // Set the text even if it's null, since we need to clear any previous text.
+ v.setText(text);
+
+ if (TextUtils.isEmpty(text)) {
+ v.setVisibility(View.GONE);
+ } else {
+ v.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private Drawable getIcon1(Cursor cursor) {
+ if (mIconName1Col == INVALID_INDEX) {
+ return null;
+ }
+ String value = cursor.getString(mIconName1Col);
+ Drawable drawable = getDrawableFromResourceValue(value);
+ if (drawable != null) {
+ return drawable;
+ }
+ return getDefaultIcon1(cursor);
+ }
+
+ private Drawable getIcon2(Cursor cursor) {
+ if (mIconName2Col == INVALID_INDEX) {
+ return null;
+ }
+ String value = cursor.getString(mIconName2Col);
+ return getDrawableFromResourceValue(value);
+ }
+
+ /**
+ * Sets the drawable in an image view, makes sure the view is only visible if there
+ * is a drawable.
+ */
+ private void setViewDrawable(ImageView v, Drawable drawable, int nullVisibility) {
+ // Set the icon even if the drawable is null, since we need to clear any
+ // previous icon.
+ v.setImageDrawable(drawable);
+
+ if (drawable == null) {
+ v.setVisibility(nullVisibility);
+ } else {
+ v.setVisibility(View.VISIBLE);
+
+ // This is a hack to get any animated drawables (like a 'working' spinner)
+ // to animate. You have to setVisible true on an AnimationDrawable to get
+ // it to start animating, but it must first have been false or else the
+ // call to setVisible will be ineffective. We need to clear up the story
+ // about animated drawables in the future, see http://b/1878430.
+ drawable.setVisible(false, false);
+ drawable.setVisible(true, false);
+ }
+ }
+
+ /**
+ * Gets the text to show in the query field when a suggestion is selected.
+ *
+ * @param cursor The Cursor to read the suggestion data from. The Cursor should already
+ * be moved to the suggestion that is to be read from.
+ * @return The text to show, or <code>null</code> if the query should not be
+ * changed when selecting this suggestion.
+ */
+ @Override
+ public CharSequence convertToString(Cursor cursor) {
+ if (cursor == null) {
+ return null;
+ }
+
+ String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
+ if (query != null) {
+ return query;
+ }
+
+ return null;
+ }
+
+ /**
+ * This method is overridden purely to provide a bit of protection against
+ * flaky content providers.
+ *
+ * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ try {
+ return super.getView(position, convertView, parent);
+ } catch (RuntimeException e) {
+ Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
+ // Put exception string in item title
+ View v = newView(mContext, mCursor, parent);
+ if (v != null) {
+ ChildViewCache views = (ChildViewCache) v.getTag();
+ TextView tv = views.mText1;
+ tv.setText(e.toString());
+ }
+ return v;
+ }
+ }
+
+ /**
+ * Gets a drawable given a value provided by a suggestion provider.
+ *
+ * This value could be just the string value of a resource id
+ * (e.g., "2130837524"), in which case we will try to retrieve a drawable from
+ * the provider's resources. If the value is not an integer, it is
+ * treated as a Uri and opened with
+ * {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
+ *
+ * All resources and URIs are read using the suggestion provider's context.
+ *
+ * If the string is not formatted as expected, or no drawable can be found for
+ * the provided value, this method returns null.
+ *
+ * @param drawableId a string like "2130837524",
+ * "android.resource://com.android.alarmclock/2130837524",
+ * or "content://contacts/photos/253".
+ * @return a Drawable, or null if none found
+ */
+ private Drawable getDrawableFromResourceValue(String drawableId) {
+ if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
+ return null;
+ }
+ try {
+ // First, see if it's just an integer
+ int resourceId = Integer.parseInt(drawableId);
+ // It's an int, look for it in the cache
+ String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE
+ + "://" + mProviderContext.getPackageName() + "/" + resourceId;
+ // Must use URI as cache key, since ints are app-specific
+ Drawable drawable = checkIconCache(drawableUri);
+ if (drawable != null) {
+ return drawable;
+ }
+ // Not cached, find it by resource ID
+ drawable = mProviderContext.getResources().getDrawable(resourceId);
+ // Stick it in the cache, using the URI as key
+ storeInIconCache(drawableUri, drawable);
+ return drawable;
+ } catch (NumberFormatException nfe) {
+ // It's not an integer, use it as a URI
+ Drawable drawable = checkIconCache(drawableId);
+ if (drawable != null) {
+ return drawable;
+ }
+ Uri uri = Uri.parse(drawableId);
+ drawable = getDrawable(uri);
+ storeInIconCache(drawableId, drawable);
+ return drawable;
+ } catch (Resources.NotFoundException nfe) {
+ // It was an integer, but it couldn't be found, bail out
+ Log.w(LOG_TAG, "Icon resource not found: " + drawableId);
+ return null;
+ }
+ }
+
+ /**
+ * Gets a drawable by URI, without using the cache.
+ *
+ * @return A drawable, or {@code null} if the drawable could not be loaded.
+ */
+ private Drawable getDrawable(Uri uri) {
+ try {
+ String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ // Load drawables through Resources, to get the source density information
+ try {
+ return getTheDrawable(uri);
+ } catch (Resources.NotFoundException ex) {
+ throw new FileNotFoundException("Resource does not exist: " + uri);
+ }
+ } else {
+ // Let the ContentResolver handle content and file URIs.
+ InputStream stream = mProviderContext.getContentResolver().openInputStream(uri);
+ if (stream == null) {
+ throw new FileNotFoundException("Failed to open " + uri);
+ }
+ try {
+ return Drawable.createFromStream(stream, null);
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex);
+ }
+ }
+ }
+ } catch (FileNotFoundException fnfe) {
+ Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage());
+ return null;
+ }
+ }
+
+ public Drawable getTheDrawable(Uri uri) throws FileNotFoundException {
+ String authority = uri.getAuthority();
+ Resources r;
+ if (TextUtils.isEmpty(authority)) {
+ throw new FileNotFoundException("No authority: " + uri);
+ } else {
+ try {
+ r = mContext.getPackageManager().getResourcesForApplication(authority);
+ } catch (NameNotFoundException ex) {
+ throw new FileNotFoundException("No package found for authority: " + uri);
+ }
+ }
+ List<String> path = uri.getPathSegments();
+ if (path == null) {
+ throw new FileNotFoundException("No path: " + uri);
+ }
+ int len = path.size();
+ int id;
+ if (len == 1) {
+ try {
+ id = Integer.parseInt(path.get(0));
+ } catch (NumberFormatException e) {
+ throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
+ }
+ } else if (len == 2) {
+ id = r.getIdentifier(path.get(1), path.get(0), authority);
+ } else {
+ throw new FileNotFoundException("More than two path segments: " + uri);
+ }
+ if (id == 0) {
+ throw new FileNotFoundException("No resource found for: " + uri);
+ }
+ return r.getDrawable(id);
+ }
+
+ private Drawable checkIconCache(String resourceUri) {
+ Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri);
+ if (cached == null) {
+ return null;
+ }
+ if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri);
+ return cached.newDrawable();
+ }
+
+ private void storeInIconCache(String resourceUri, Drawable drawable) {
+ if (drawable != null) {
+ mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState());
+ }
+ }
+
+ /**
+ * Gets the left-hand side icon that will be used for the current suggestion
+ * if the suggestion contains an icon column but no icon or a broken icon.
+ *
+ * @param cursor A cursor positioned at the current suggestion.
+ * @return A non-null drawable.
+ */
+ private Drawable getDefaultIcon1(Cursor cursor) {
+ // Fall back to a default icon
+ return mContext.getPackageManager().getDefaultActivityIcon();
+ }
+
+ /**
+ * Gets the activity or application icon for an activity.
+ * Uses the local icon cache for fast repeated lookups.
+ *
+ * @param component Name of an activity.
+ * @return A drawable, or {@code null} if neither the activity nor the application
+ * has an icon set.
+ */
+ private Drawable getActivityIconWithCache(ComponentName component) {
+ // First check the icon cache
+ String componentIconKey = component.flattenToShortString();
+ // Using containsKey() since we also store null values.
+ if (mOutsideDrawablesCache.containsKey(componentIconKey)) {
+ Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey);
+ return cached == null ? null : cached.newDrawable(mProviderContext.getResources());
+ }
+ // Then try the activity or application icon
+ Drawable drawable = getActivityIcon(component);
+ // Stick it in the cache so we don't do this lookup again.
+ Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState();
+ mOutsideDrawablesCache.put(componentIconKey, toCache);
+ return drawable;
+ }
+
+ /**
+ * Gets the activity or application icon for an activity.
+ *
+ * @param component Name of an activity.
+ * @return A drawable, or {@code null} if neither the acitivy or the application
+ * have an icon set.
+ */
+ private Drawable getActivityIcon(ComponentName component) {
+ PackageManager pm = mContext.getPackageManager();
+ final ActivityInfo activityInfo;
+ try {
+ activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException ex) {
+ Log.w(LOG_TAG, ex.toString());
+ return null;
+ }
+ int iconId = activityInfo.getIconResource();
+ if (iconId == 0) return null;
+ String pkg = component.getPackageName();
+ Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo);
+ if (drawable == null) {
+ Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for "
+ + component.flattenToShortString());
+ return null;
+ }
+ return drawable;
+ }
+
+ /**
+ * Gets the value of a string column by name.
+ *
+ * @param cursor Cursor to read the value from.
+ * @param columnName The name of the column to read.
+ * @return The value of the given column, or <code>null</null>
+ * if the cursor does not contain the given column.
+ */
+ public static String getColumnString(Cursor cursor, String columnName) {
+ int col = cursor.getColumnIndex(columnName);
+ return getStringOrNull(cursor, col);
+ }
+
+ private static String getStringOrNull(Cursor cursor, int col) {
+ if (col == INVALID_INDEX) {
+ return null;
+ }
+ try {
+ return cursor.getString(col);
+ } catch (Exception e) {
+ Log.e(LOG_TAG,
+ "unexpected error retrieving valid column from cursor, "
+ + "did the remote process die?", e);
+ return null;
+ }
+ }
+}