diff options
66 files changed, 1542 insertions, 918 deletions
diff --git a/api/current.xml b/api/current.xml index ef8991f..287b3f3 100644 --- a/api/current.xml +++ b/api/current.xml @@ -19417,7 +19417,7 @@ <parameter name="callback" type="android.app.ActionBar.Callback"> </parameter> </method> -<method name="setCustomNavigationView" +<method name="setCustomNavigationMode" return="void" abstract="true" native="false" @@ -19471,7 +19471,7 @@ <parameter name="d" type="android.graphics.drawable.Drawable"> </parameter> </method> -<method name="setNavigationMode" +<method name="setDropdownNavigationMode" return="void" abstract="true" native="false" @@ -19481,10 +19481,10 @@ deprecated="not deprecated" visibility="public" > -<parameter name="mode" type="int"> +<parameter name="adapter" type="android.widget.SpinnerAdapter"> </parameter> </method> -<method name="setSubtitle" +<method name="setStandardNavigationMode" return="void" abstract="true" native="false" @@ -19494,10 +19494,12 @@ deprecated="not deprecated" visibility="public" > +<parameter name="title" type="java.lang.CharSequence"> +</parameter> <parameter name="subtitle" type="java.lang.CharSequence"> </parameter> </method> -<method name="setTitle" +<method name="setStandardNavigationMode" return="void" abstract="true" native="false" @@ -19565,7 +19567,7 @@ visibility="public" > </field> -<field name="NAVIGATION_MODE_NORMAL" +<field name="NAVIGATION_MODE_STANDARD" type="int" transient="false" volatile="false" @@ -19595,7 +19597,7 @@ deprecated="not deprecated" visibility="public" > -<method name="onActionItemSelected" +<method name="onActionItemClicked" return="boolean" abstract="true" native="false" @@ -19608,7 +19610,7 @@ <parameter name="item" type="android.view.MenuItem"> </parameter> </method> -<method name="onContextItemSelected" +<method name="onContextItemClicked" return="boolean" abstract="true" native="false" @@ -19651,6 +19653,21 @@ <parameter name="menu" type="android.view.Menu"> </parameter> </method> +<method name="onNavigationItemSelected" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="itemPosition" type="int"> +</parameter> +<parameter name="itemId" type="long"> +</parameter> +</method> <method name="onPrepareContextMode" return="boolean" abstract="true" @@ -19698,7 +19715,7 @@ visibility="public" > </constructor> -<method name="onActionItemSelected" +<method name="onActionItemClicked" return="boolean" abstract="false" native="false" @@ -19711,7 +19728,7 @@ <parameter name="item" type="android.view.MenuItem"> </parameter> </method> -<method name="onContextItemSelected" +<method name="onContextItemClicked" return="boolean" abstract="false" native="false" @@ -19754,6 +19771,21 @@ <parameter name="menu" type="android.view.Menu"> </parameter> </method> +<method name="onNavigationItemSelected" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="itemPosition" type="int"> +</parameter> +<parameter name="itemId" type="long"> +</parameter> +</method> <method name="onPrepareContextMode" return="boolean" abstract="false" @@ -20274,6 +20306,17 @@ visibility="public" > </method> +<method name="invalidateOptionsMenu" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isChangingConfigurations" return="boolean" abstract="false" @@ -25511,7 +25554,7 @@ native="false" synchronized="false" static="false" - final="false" + final="true" deprecated="not deprecated" visibility="public" > @@ -25522,7 +25565,7 @@ native="false" synchronized="false" static="false" - final="false" + final="true" deprecated="not deprecated" visibility="public" > @@ -25533,7 +25576,7 @@ native="false" synchronized="false" static="false" - final="false" + final="true" deprecated="not deprecated" visibility="public" > @@ -25544,7 +25587,7 @@ native="false" synchronized="false" static="false" - final="false" + final="true" deprecated="not deprecated" visibility="public" > @@ -25577,7 +25620,7 @@ native="false" synchronized="false" static="false" - final="false" + final="true" deprecated="not deprecated" visibility="public" > @@ -25588,7 +25631,7 @@ native="false" synchronized="false" static="false" - final="false" + final="true" deprecated="not deprecated" visibility="public" > @@ -25599,7 +25642,7 @@ native="false" synchronized="false" static="false" - final="false" + final="true" deprecated="not deprecated" visibility="public" > @@ -25677,6 +25720,21 @@ <parameter name="nextAnim" type="int"> </parameter> </method> +<method name="onCreateOptionsMenu" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="menu" type="android.view.Menu"> +</parameter> +<parameter name="inflater" type="android.view.MenuInflater"> +</parameter> +</method> <method name="onCreateView" return="android.view.View" abstract="false" @@ -25757,6 +25815,32 @@ visibility="public" > </method> +<method name="onOptionsItemSelected" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="item" type="android.view.MenuItem"> +</parameter> +</method> +<method name="onOptionsMenuClosed" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="menu" type="android.view.Menu"> +</parameter> +</method> <method name="onPause" return="void" abstract="false" @@ -25768,6 +25852,19 @@ visibility="public" > </method> +<method name="onPrepareOptionsMenu" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="menu" type="android.view.Menu"> +</parameter> +</method> <method name="onReady" return="void" abstract="false" @@ -25827,6 +25924,19 @@ visibility="public" > </method> +<method name="setHasOptionsMenu" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="hasMenu" type="boolean"> +</parameter> +</method> <method name="setRetainInstance" return="void" abstract="false" @@ -31839,6 +31949,19 @@ <parameter name="admin" type="android.content.ComponentName"> </parameter> </method> +<method name="getPasswordMinimumNonLetter" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="admin" type="android.content.ComponentName"> +</parameter> +</method> <method name="getPasswordMinimumNumeric" return="int" abstract="false" @@ -32044,6 +32167,21 @@ <parameter name="length" type="int"> </parameter> </method> +<method name="setPasswordMinimumNonLetter" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="admin" type="android.content.ComponentName"> +</parameter> +<parameter name="length" type="int"> +</parameter> +</method> <method name="setPasswordMinimumNumeric" return="void" abstract="false" @@ -189372,6 +189510,19 @@ visibility="protected" > </method> +<method name="invalidatePanelMenu" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="featureId" type="int"> +</parameter> +</method> <method name="isActive" return="boolean" abstract="false" diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 8263e75..39b3a20 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -175,6 +175,11 @@ public final class Bmgr { private void doTransport() { try { String which = nextArg(); + if (which == null) { + showUsage(); + return; + } + String old = mBmgr.selectBackupTransport(which); if (old == null) { System.out.println("Unknown transport '" + which @@ -318,6 +323,11 @@ public final class Bmgr { private void doRestore() { String arg = nextArg(); + if (arg == null) { + showUsage(); + return; + } + if (arg.indexOf('.') >= 0) { // it's a package name doRestorePackage(arg); diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 77633c6..cbad72e 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -20,6 +20,8 @@ import android.graphics.drawable.Drawable; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.AdapterView; +import android.widget.SpinnerAdapter; /** * This is the public interface to the contextual ActionBar. @@ -29,12 +31,12 @@ import android.view.View; */ public abstract class ActionBar { /** - * Normal/standard navigation mode. Consists of either a logo or icon + * Standard navigation mode. Consists of either a logo or icon * and title text with an optional subtitle. Clicking any of these elements * will dispatch onActionItemSelected to the registered Callback with * a MenuItem with item ID android.R.id.home. */ - public static final int NAVIGATION_MODE_NORMAL = 0; + public static final int NAVIGATION_MODE_STANDARD = 0; /** * Dropdown list navigation mode. Instead of static title text this mode @@ -74,49 +76,52 @@ public abstract class ActionBar { public abstract void setCallback(Callback callback); /** - * Set a custom navigation view. + * Set the action bar into custom navigation mode, supplying a view + * for custom navigation. * * Custom navigation views appear between the application icon and * any action buttons and may use any space available there. Common - * use cases for custom navigation views might include an address bar - * for a browser or other navigation mechanisms that do not translate - * well to provided navigation modes. - * - * Setting a non-null custom navigation view will also set the - * navigation mode to NAVMODE_CUSTOM. + * use cases for custom navigation views might include an auto-suggesting + * address bar for a browser or other navigation mechanisms that do not + * translate well to provided navigation modes. * * @param view Custom navigation view to place in the ActionBar. */ - public abstract void setCustomNavigationView(View view); + public abstract void setCustomNavigationMode(View view); /** - * Set the ActionBar's title. - * - * This is set automatically to the name of your Activity, - * but may be changed here. + * Set the action bar into dropdown navigation mode and supply an adapter + * that will provide views for navigation choices. * - * @param title Title text + * @param adapter An adapter that will provide views both to display + * the current navigation selection and populate views + * within the dropdown navigation menu. */ - public abstract void setTitle(CharSequence title); - + public abstract void setDropdownNavigationMode(SpinnerAdapter adapter); + /** - * Set the ActionBar's subtitle. - * - * The subtitle is usually displayed as a second line of text - * under the title. Good for extended descriptions of activity state. + * Set the action bar into standard navigation mode, supplying a title and subtitle. * - * @param subtitle Subtitle text. + * Standard navigation mode is default. The title is automatically set to the + * name of your Activity. Subtitles are displayed underneath the title, usually + * in a smaller font or otherwise less prominently than the title. Subtitles are + * good for extended descriptions of activity state. + * + * @param title The action bar's title. null is treated as an empty string. + * @param subtitle The action bar's subtitle. null is treated as an empty string. */ - public abstract void setSubtitle(CharSequence subtitle); - + public abstract void setStandardNavigationMode(CharSequence title, CharSequence subtitle); + /** - * Set the navigation mode. + * Set the action bar into standard navigation mode, supplying a title and subtitle. + * + * Standard navigation mode is default. The title is automatically set to the + * name of your Activity. * - * @param mode One of {@link #NAVIGATION_MODE_NORMAL}, {@link #NAVIGATION_MODE_DROPDOWN_LIST}, - * {@link #NAVIGATION_MODE_TABS}, or {@link #NAVIGATION_MODE_CUSTOM}. + * @param title The action bar's title. null is treated as an empty string. */ - public abstract void setNavigationMode(int mode); - + public abstract void setStandardNavigationMode(CharSequence title); + /** * Set display options. This changes all display option bits at once. To change * a limited subset of display options, see {@link #setDisplayOptions(int, int)}. @@ -161,17 +166,38 @@ public abstract class ActionBar { public abstract View getCustomNavigationView(); /** - * @return The current ActionBar title. + * Returns the current ActionBar title in standard mode. + * Returns null if {@link #getNavigationMode()} would not return + * {@link #NAVIGATION_MODE_STANDARD}. + * + * @return The current ActionBar title or null. */ public abstract CharSequence getTitle(); /** - * @return The current ActionBar subtitle. + * Returns the current ActionBar subtitle in standard mode. + * Returns null if {@link #getNavigationMode()} would not return + * {@link #NAVIGATION_MODE_STANDARD}. + * + * @return The current ActionBar subtitle or null. */ public abstract CharSequence getSubtitle(); /** + * Returns the current navigation mode. The result will be one of: + * <ul> + * <li>{@link #NAVIGATION_MODE_STANDARD}</li> + * <li>{@link #NAVIGATION_MODE_DROPDOWN_LIST}</li> + * <li>{@link #NAVIGATION_MODE_TABS}</li> + * <li>{@link #NAVIGATION_MODE_CUSTOM}</li> + * </ul> + * * @return The current navigation mode. + * + * @see #setStandardNavigationMode(CharSequence) + * @see #setStandardNavigationMode(CharSequence, CharSequence) + * @see #setDropdownNavigationMode(SpinnerAdapter) + * @see #setCustomNavigationMode(View) */ public abstract int getNavigationMode(); @@ -201,7 +227,7 @@ public abstract class ActionBar { * @return You must return true for actions to be displayed; * if you return false they will not be shown. * - * @see #onActionItemSelected(MenuItem) + * @see #onActionItemClicked(MenuItem) */ public boolean onCreateActionMenu(Menu menu); @@ -215,7 +241,7 @@ public abstract class ActionBar { public boolean onUpdateActionMenu(Menu menu); /** - * This hook is called whenever an item in your action bar is selected. + * This hook is called whenever an action item in your action bar is clicked. * The default implementation simply returns false to have the normal * processing happen (sending a message to its handler). You can use this * method for any items for which you would like to do processing without @@ -225,14 +251,24 @@ public abstract class ActionBar { * @return boolean Return false to allow normal menu processing to proceed, * true to consume it here. */ - public boolean onActionItemSelected(MenuItem item); - + public boolean onActionItemClicked(MenuItem item); + + /** + * This method is called whenever a navigation item in your action bar + * is selected. + * + * @param itemPosition Position of the item clicked. + * @param itemId ID of the item clicked. + * @return True if the event was handled, false otherwise. + */ + public boolean onNavigationItemSelected(int itemPosition, long itemId); + /* * In progress */ public boolean onCreateContextMode(int modeId, Menu menu); public boolean onPrepareContextMode(int modeId, Menu menu); - public boolean onContextItemSelected(int modeId, MenuItem item); + public boolean onContextItemClicked(int modeId, MenuItem item); } /** @@ -248,7 +284,7 @@ public abstract class ActionBar { return false; } - public boolean onActionItemSelected(MenuItem item) { + public boolean onActionItemClicked(MenuItem item) { return false; } @@ -260,7 +296,11 @@ public abstract class ActionBar { return false; } - public boolean onContextItemSelected(int modeId, MenuItem item) { + public boolean onContextItemClicked(int modeId, MenuItem item) { + return false; + } + + public boolean onNavigationItemSelected(int itemPosition, long itemId) { return false; } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index d64f2c5..98505c5 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -77,9 +77,6 @@ import android.widget.LinearLayout; import com.android.internal.app.SplitActionBar; import com.android.internal.policy.PolicyManager; -import java.util.ArrayList; -import java.util.HashMap; - /** * An activity is a single, focused thing that the user can do. Almost all * activities interact with the user, so the Activity class takes care of @@ -2242,7 +2239,9 @@ public class Activity extends ContextThemeWrapper */ public boolean onCreatePanelMenu(int featureId, Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL) { - return onCreateOptionsMenu(menu); + boolean show = onCreateOptionsMenu(menu); + show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater()); + return show; } return false; } @@ -2259,6 +2258,7 @@ public class Activity extends ContextThemeWrapper public boolean onPreparePanel(int featureId, View view, Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { boolean goforit = onPrepareOptionsMenu(menu); + goforit |= mFragments.dispatchPrepareOptionsMenu(menu); return goforit && menu.hasVisibleItems(); } return true; @@ -2289,7 +2289,10 @@ public class Activity extends ContextThemeWrapper // doesn't call through to superclass's implmeentation of each // of these methods below EventLog.writeEvent(50000, 0, item.getTitleCondensed()); - return onOptionsItemSelected(item); + if (onOptionsItemSelected(item)) { + return true; + } + return mFragments.dispatchOptionsItemSelected(item); case Window.FEATURE_CONTEXT_MENU: EventLog.writeEvent(50000, 1, item.getTitleCondensed()); @@ -2312,6 +2315,7 @@ public class Activity extends ContextThemeWrapper public void onPanelClosed(int featureId, Menu menu) { switch (featureId) { case Window.FEATURE_OPTIONS_PANEL: + mFragments.dispatchOptionsMenuClosed(menu); onOptionsMenuClosed(menu); break; @@ -2322,6 +2326,15 @@ public class Activity extends ContextThemeWrapper } /** + * Declare that the options menu has changed, so should be recreated. + * The {@link #onCreateOptionsMenu(Menu)} method will be called the next + * time it needs to be displayed. + */ + public void invalidateOptionsMenu() { + mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + } + + /** * Initialize the contents of the Activity's standard options menu. You * should place your menu items in to <var>menu</var>. * @@ -3887,6 +3900,7 @@ public class Activity extends ContextThemeWrapper fragment.mFromLayout = true; fragment.mFragmentId = id; fragment.mTag = tag; + fragment.mImmediateActivity = this; mFragments.addFragment(fragment, true); } // If this fragment is newly instantiated (either right now, or diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java index 33e456d..5e9aea5 100644 --- a/core/java/android/app/BackStackEntry.java +++ b/core/java/android/app/BackStackEntry.java @@ -100,9 +100,10 @@ final class BackStackEntry implements FragmentTransaction, Runnable { static final int OP_NULL = 0; static final int OP_ADD = 1; - static final int OP_REMOVE = 2; - static final int OP_HIDE = 3; - static final int OP_SHOW = 4; + static final int OP_REPLACE = 2; + static final int OP_REMOVE = 3; + static final int OP_HIDE = 4; + static final int OP_SHOW = 5; static final class Op { Op next; @@ -111,6 +112,7 @@ final class BackStackEntry implements FragmentTransaction, Runnable { Fragment fragment; int enterAnim; int exitAnim; + ArrayList<Fragment> removed; } Op mHead; @@ -142,17 +144,25 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } public FragmentTransaction add(Fragment fragment, String tag) { - return add(0, fragment, tag); + doAddOp(0, fragment, tag, OP_ADD); + return this; } public FragmentTransaction add(int containerViewId, Fragment fragment) { - return add(containerViewId, fragment, null); + doAddOp(containerViewId, fragment, null, OP_ADD); + return this; } public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { - if (fragment.mActivity != null) { + doAddOp(containerViewId, fragment, tag, OP_ADD); + return this; + } + + private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { + if (fragment.mImmediateActivity != null) { throw new IllegalStateException("Fragment already added: " + fragment); } + fragment.mImmediateActivity = mManager.mActivity; if (tag != null) { if (fragment.mTag != null && !tag.equals(fragment.mTag)) { @@ -173,11 +183,9 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } Op op = new Op(); - op.cmd = OP_ADD; + op.cmd = opcmd; op.fragment = fragment; addOp(op); - - return this; } public FragmentTransaction replace(int containerViewId, Fragment fragment) { @@ -188,21 +196,16 @@ final class BackStackEntry implements FragmentTransaction, Runnable { if (containerViewId == 0) { throw new IllegalArgumentException("Must use non-zero containerViewId"); } - if (mManager.mAdded != null) { - for (int i=0; i<mManager.mAdded.size(); i++) { - Fragment old = mManager.mAdded.get(i); - if (old.mContainerId == containerViewId) { - remove(old); - } - } - } - return add(containerViewId, fragment, tag); + + doAddOp(containerViewId, fragment, tag, OP_REPLACE); + return this; } public FragmentTransaction remove(Fragment fragment) { - if (fragment.mActivity == null) { + if (fragment.mImmediateActivity == null) { throw new IllegalStateException("Fragment not added: " + fragment); } + fragment.mImmediateActivity = null; Op op = new Op(); op.cmd = OP_REMOVE; @@ -213,7 +216,7 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } public FragmentTransaction hide(Fragment fragment) { - if (fragment.mActivity == null) { + if (fragment.mImmediateActivity == null) { throw new IllegalStateException("Fragment not added: " + fragment); } @@ -226,7 +229,7 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } public FragmentTransaction show(Fragment fragment) { - if (fragment.mActivity == null) { + if (fragment.mImmediateActivity == null) { throw new IllegalStateException("Fragment not added: " + fragment); } @@ -278,6 +281,30 @@ final class BackStackEntry implements FragmentTransaction, Runnable { f.mNextAnim = op.enterAnim; mManager.addFragment(f, false); } break; + case OP_REPLACE: { + Fragment f = op.fragment; + if (mManager.mAdded != null) { + for (int i=0; i<mManager.mAdded.size(); i++) { + Fragment old = mManager.mAdded.get(i); + if (old.mContainerId == f.mContainerId) { + if (op.removed == null) { + op.removed = new ArrayList<Fragment>(); + } + op.removed.add(old); + if (mAddToBackStack) { + old.mBackStackNesting++; + } + old.mNextAnim = op.exitAnim; + mManager.removeFragment(old, mTransition, mTransitionStyle); + } + } + } + if (mAddToBackStack) { + f.mBackStackNesting++; + } + f.mNextAnim = op.enterAnim; + mManager.addFragment(f, false); + } break; case OP_REMOVE: { Fragment f = op.fragment; if (mAddToBackStack) { @@ -312,6 +339,11 @@ final class BackStackEntry implements FragmentTransaction, Runnable { mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true); + if (mManager.mNeedMenuInvalidate && mManager.mActivity != null) { + mManager.mActivity.invalidateOptionsMenu(); + mManager.mNeedMenuInvalidate = false; + } + if (mAddToBackStack) { mManager.addBackStackState(this); } @@ -330,6 +362,24 @@ final class BackStackEntry implements FragmentTransaction, Runnable { FragmentManager.reverseTransit(mTransition), mTransitionStyle); } break; + case OP_REPLACE: { + Fragment f = op.fragment; + if (mAddToBackStack) { + f.mBackStackNesting--; + } + mManager.removeFragment(f, + FragmentManager.reverseTransit(mTransition), + mTransitionStyle); + if (op.removed != null) { + for (int i=0; i<op.removed.size(); i++) { + Fragment old = op.removed.get(i); + if (mAddToBackStack) { + old.mBackStackNesting--; + } + mManager.addFragment(old, false); + } + } + } break; case OP_REMOVE: { Fragment f = op.fragment; if (mAddToBackStack) { @@ -363,6 +413,10 @@ final class BackStackEntry implements FragmentTransaction, Runnable { mManager.moveToState(mManager.mCurState, FragmentManager.reverseTransit(mTransition), mTransitionStyle, true); + if (mManager.mNeedMenuInvalidate && mManager.mActivity != null) { + mManager.mActivity.invalidateOptionsMenu(); + mManager.mNeedMenuInvalidate = false; + } } public String getName() { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 58ae2d5..c734737 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -25,6 +25,9 @@ import android.os.Parcelable; import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; @@ -157,6 +160,10 @@ public class Fragment implements ComponentCallbacks { // Number of active back stack entries this fragment is in. int mBackStackNesting; + // Set as soon as a fragment is added to a transaction (or removed), + // to be able to do validation. + Activity mImmediateActivity; + // Activity this fragment is attached to. Activity mActivity; @@ -184,6 +191,9 @@ public class Fragment implements ComponentCallbacks { // If set this fragment is being retained across the current config change. boolean mRetaining; + // If set this fragment has menu items to contribute. + boolean mHasMenu; + // Used to verify that subclasses call through to super class. boolean mCalled; @@ -270,28 +280,28 @@ public class Fragment implements ComponentCallbacks { * the android:id value supplied in a layout or the container view ID * supplied when adding the fragment. */ - public int getId() { + final public int getId() { return mFragmentId; } /** * Get the tag name of the fragment, if specified. */ - public String getTag() { + final public String getTag() { return mTag; } /** * Return the Activity this fragment is currently associated with. */ - public Activity getActivity() { + final public Activity getActivity() { return mActivity; } /** * Return true if the fragment is currently added to its activity. */ - public boolean isAdded() { + final public boolean isAdded() { return mActivity != null && mActivity.mFragments.mAdded.contains(this); } @@ -300,7 +310,7 @@ public class Fragment implements ComponentCallbacks { * it: (1) has been added, (2) has its view attached to the window, and * (3) is not hidden. */ - public boolean isVisible() { + final public boolean isVisible() { return isAdded() && !isHidden() && mView != null && mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE; } @@ -312,7 +322,7 @@ public class Fragment implements ComponentCallbacks { * to other states -- that is, to be visible to the user, a fragment * must be both started and not hidden. */ - public boolean isHidden() { + final public boolean isHidden() { return mHidden; } @@ -344,11 +354,27 @@ public class Fragment implements ComponentCallbacks { mRetainInstance = retain; } - public boolean getRetainInstance() { + final public boolean getRetainInstance() { return mRetainInstance; } /** + * Report that this fragment would like to participate in populating + * the options menu by receiving a call to {@link #onCreateOptionsMenu(Menu)} + * and related methods. + * + * @param hasMenu If true, the fragment has menu items to contribute. + */ + public void setHasOptionsMenu(boolean hasMenu) { + if (mHasMenu != hasMenu) { + mHasMenu = hasMenu; + if (isAdded() && !isHidden()) { + mActivity.invalidateOptionsMenu(); + } + } + } + + /** * Call {@link Activity#startActivity(Intent)} on the fragment's * containing Activity. */ @@ -526,4 +552,69 @@ public class Fragment implements ComponentCallbacks { public void onDetach() { mCalled = true; } + + /** + * Initialize the contents of the Activity's standard options menu. You + * should place your menu items in to <var>menu</var>. For this method + * to be called, you must have first called {@link #setHasMenu}. See + * {@link Activity#onCreateOptionsMenu(Menu) Activity.onCreateOptionsMenu} + * for more information. + * + * @param menu The options menu in which you place your items. + * + * @see #setHasMenu + * @see #onPrepareOptionsMenu + * @see #onOptionsItemSelected + */ + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + } + + /** + * Prepare the Screen's standard options menu to be displayed. This is + * called right before the menu is shown, every time it is shown. You can + * use this method to efficiently enable/disable items or otherwise + * dynamically modify the contents. See + * {@link Activity#onPrepareOptionsMenu(Menu) Activity.onPrepareOptionsMenu} + * for more information. + * + * @param menu The options menu as last shown or first initialized by + * onCreateOptionsMenu(). + * + * @see #setHasMenu + * @see #onCreateOptionsMenu + */ + public void onPrepareOptionsMenu(Menu menu) { + } + + /** + * This hook is called whenever an item in your options menu is selected. + * The default implementation simply returns false to have the normal + * processing happen (calling the item's Runnable or sending a message to + * its Handler as appropriate). You can use this method for any items + * for which you would like to do processing without those other + * facilities. + * + * <p>Derived classes should call through to the base class for it to + * perform the default menu handling. + * + * @param item The menu item that was selected. + * + * @return boolean Return false to allow normal menu processing to + * proceed, true to consume it here. + * + * @see #onCreateOptionsMenu + */ + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + /** + * This hook is called whenever the options menu is being closed (either by the user canceling + * the menu with the back/menu button, or when an item is selected). + * + * @param menu The options menu as last shown or first initialized by + * onCreateOptionsMenu(). + */ + public void onOptionsMenuClosed(Menu menu) { + } } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index a10a191..b837c32 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -23,6 +23,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import android.util.SparseArray; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; @@ -82,6 +85,8 @@ public class FragmentManager { int mCurState = Fragment.INITIALIZING; Activity mActivity; + boolean mNeedMenuInvalidate; + // Temporary vars for state save and restore. Bundle mStateBundle = null; SparseArray<Parcelable> mStateArray = null; @@ -362,6 +367,9 @@ public class FragmentManager { mAdded.add(fragment); makeActive(fragment); fragment.mAdded = true; + if (fragment.mHasMenu) { + mNeedMenuInvalidate = true; + } if (moveToStateNow) { moveToState(fragment, mCurState, 0, 0); } @@ -374,6 +382,9 @@ public class FragmentManager { if (inactive) { makeInactive(fragment); } + if (fragment.mHasMenu) { + mNeedMenuInvalidate = true; + } fragment.mAdded = false; moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED, transition, transitionStyle); @@ -391,6 +402,9 @@ public class FragmentManager { } fragment.mView.setVisibility(View.GONE); } + if (fragment.mAdded && fragment.mHasMenu) { + mNeedMenuInvalidate = true; + } fragment.onHiddenChanged(true); } } @@ -407,6 +421,9 @@ public class FragmentManager { } fragment.mView.setVisibility(View.VISIBLE); } + if (fragment.mAdded && fragment.mHasMenu) { + mNeedMenuInvalidate = true; + } fragment.onHiddenChanged(false); } } @@ -673,6 +690,7 @@ public class FragmentManager { "No instantiated fragment for index #" + fms.mAdded[i]); } f.mAdded = true; + f.mImmediateActivity = mActivity; mAdded.add(f); } } else { @@ -721,6 +739,59 @@ public class FragmentManager { mActivity = null; } + public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { + boolean show = false; + if (mActive != null) { + for (int i=0; i<mAdded.size(); i++) { + Fragment f = mAdded.get(i); + if (f != null && !f.mHidden && f.mHasMenu) { + show = true; + f.onCreateOptionsMenu(menu, inflater); + } + } + } + return show; + } + + public boolean dispatchPrepareOptionsMenu(Menu menu) { + boolean show = false; + if (mActive != null) { + for (int i=0; i<mAdded.size(); i++) { + Fragment f = mAdded.get(i); + if (f != null && !f.mHidden && f.mHasMenu) { + show = true; + f.onPrepareOptionsMenu(menu); + } + } + } + return show; + } + + public boolean dispatchOptionsItemSelected(MenuItem item) { + if (mActive != null) { + for (int i=0; i<mAdded.size(); i++) { + Fragment f = mAdded.get(i); + if (f != null && !f.mHidden && f.mHasMenu) { + if (f.onOptionsItemSelected(item)) { + return true; + } + } + } + } + return false; + } + + public void dispatchOptionsMenuClosed(Menu menu) { + if (mActive != null) { + for (int i=0; i<mAdded.size(); i++) { + Fragment f = mAdded.get(i); + if (f != null && !f.mHidden && f.mHasMenu) { + f.onOptionsMenuClosed(menu); + } + } + } + } + public static int reverseTransit(int transit) { int rev = 0; switch (transit) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 634adb0..3066f5c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -78,6 +78,15 @@ public class DevicePolicyManager { = "android.app.action.ADD_DEVICE_ADMIN"; /** + * Activity action: send when any policy admin changes a policy. + * This is generally used to find out when a new policy is in effect. + * + * @hide + */ + public static final String ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED + = "android.app.action.DEVICE_POLICY_MANAGER_STATE_CHANGED"; + + /** * The ComponentName of the administrator component. * * @see #ACTION_ADD_DEVICE_ADMIN @@ -206,9 +215,13 @@ public class DevicePolicyManager { /** * Constant for {@link #setPasswordQuality}: the user must have entered a - * password containing numeric <em>and</em> alphabetic characters, - * <em>and</em> special symbols. Note that quality constants are ordered so - * that higher values are more restrictive. + * password containing at least a letter, a numerical digit and a special + * symbol, by default. With this password quality, passwords can be + * restricted to contain various sets of characters, like at least an + * uppercase letter, etc. These are specified using various methods, + * like {@link #setPasswordMinimumLowerCase(ComponentName, int)}. Note + * that quality constants are ordered so that higher values are more + * restrictive. */ public static final int PASSWORD_QUALITY_COMPLEX = 0x60000; @@ -320,7 +333,8 @@ public class DevicePolicyManager { * not take place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 0. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -344,7 +358,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of upper case letters required in the - * password for all admins or a particular one. + * password for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumUpperCase(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -371,7 +388,8 @@ public class DevicePolicyManager { * not take place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 0. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -395,7 +413,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of lower case letters required in the - * password for all admins or a particular one. + * password for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumLowerCase(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -422,7 +443,8 @@ public class DevicePolicyManager { * place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 1. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -445,7 +467,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of letters required in the password for all - * admins or a particular one. + * admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumLetters(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -471,7 +496,8 @@ public class DevicePolicyManager { * not take place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 1. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -494,7 +520,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of numerical digits required in the password - * for all admins or a particular one. + * for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumNumeric(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -520,7 +549,8 @@ public class DevicePolicyManager { * place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 1. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -543,7 +573,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of symbols required in the password for all - * admins or a particular one. + * admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumSymbols(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -560,6 +593,59 @@ public class DevicePolicyManager { return 0; } + /** + * Called by an application that is administering the device to set the + * minimum number of non-letter characters (numerical digits or symbols) + * required in the password. After setting this, the user will not be able + * to enter a new password that is not at least as restrictive as what has + * been set. Note that the current password will remain until the user has + * set a new one, so the change does not take place immediately. To prompt + * the user for a new password, use {@link #ACTION_SET_NEW_PASSWORD} after + * setting this value. This constraint is only imposed if the administrator + * has also requested {@link #PASSWORD_QUALITY_COMPLEX} with + * {@link #setPasswordQuality}. The default value is 0. + * <p> + * The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call + * this method; if it has not, a security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated + * with. + * @param length The new desired minimum number of letters required in the + * password. A value of 0 means there is no restriction. + */ + public void setPasswordMinimumNonLetter(ComponentName admin, int length) { + if (mService != null) { + try { + mService.setPasswordMinimumNonLetter(admin, length); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Retrieve the current number of non-letter characters required in the + * password for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumNonLetter(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. + * + * @param admin The name of the admin component to check, or null to + * aggregate all admins. + * @return The minimum number of letters required in the password. + */ + public int getPasswordMinimumNonLetter(ComponentName admin) { + if (mService != null) { + try { + return mService.getPasswordMinimumNonLetter(admin); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return 0; + } + /** * Called by an application that is administering the device to set the length * of the password history. After setting this, the user will not be able to @@ -885,11 +971,11 @@ public class DevicePolicyManager { * @hide */ public void setActivePasswordState(int quality, int length, int letters, int uppercase, - int lowercase, int numbers, int symbols) { + int lowercase, int numbers, int symbols, int nonletter) { if (mService != null) { try { mService.setActivePasswordState(quality, length, letters, uppercase, lowercase, - numbers, symbols); + numbers, symbols, nonletter); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index fa31a37..3ada95c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -45,6 +45,9 @@ interface IDevicePolicyManager { void setPasswordMinimumSymbols(in ComponentName who, int length); int getPasswordMinimumSymbols(in ComponentName who); + + void setPasswordMinimumNonLetter(in ComponentName who, int length); + int getPasswordMinimumNonLetter(in ComponentName who); void setPasswordHistoryLength(in ComponentName who, int length); int getPasswordHistoryLength(in ComponentName who); @@ -71,7 +74,8 @@ interface IDevicePolicyManager { void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result); void removeActiveAdmin(in ComponentName policyReceiver); - void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, int numbers, int symbols); + void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, + int numbers, int symbols, int nonletter); void reportFailedPasswordAttempt(); void reportSuccessfulPasswordAttempt(); } diff --git a/core/java/android/view/ActionBarView.java b/core/java/android/view/ActionBarView.java index 311274c..3ed345e 100644 --- a/core/java/android/view/ActionBarView.java +++ b/core/java/android/view/ActionBarView.java @@ -31,7 +31,10 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.SparseArray; +import android.widget.AdapterView; import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; import android.widget.TextView; import com.android.internal.R; @@ -78,12 +81,12 @@ public class ActionBarView extends ViewGroup { private ImageView mLogoView; private TextView mTitleView; private TextView mSubtitleView; - private View mNavigationView; + private Spinner mSpinner; + private View mCustomNavView; private boolean mShowMenu; private ActionMenuItem mLogoNavItem; - private ActionMenu mNavMenu; private ActionMenu mActionMenu; private ActionMenu mOptionsMenu; @@ -97,12 +100,24 @@ public class ActionBarView extends ViewGroup { ActionView av = (ActionView) v; ActionMenuItem item = (ActionMenuItem) av.menuItem; - if (!mCallback.onActionItemSelected(item)) { + if (mCallback == null || !mCallback.onActionItemClicked(item)) { item.invoke(); } } }; + private final AdapterView.OnItemSelectedListener mNavItemSelectedListener = + new AdapterView.OnItemSelectedListener() { + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (mCallback != null) { + mCallback.onNavigationItemSelected(position, id); + } + } + public void onNothingSelected(AdapterView parent) { + // Do nothing + } + }; + private OnClickListener mHomeClickListener = null; public ActionBarView(Context context, AttributeSet attrs) { @@ -123,7 +138,7 @@ public class ActionBarView extends ViewGroup { ApplicationInfo info = context.getApplicationInfo(); PackageManager pm = context.getPackageManager(); - mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, ActionBar.NAVIGATION_MODE_NORMAL); + mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, ActionBar.NAVIGATION_MODE_STANDARD); mTitle = a.getText(R.styleable.ActionBar_title); mSubtitle = a.getText(R.styleable.ActionBar_subtitle); mDisplayOptions = a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT); @@ -146,7 +161,7 @@ public class ActionBarView extends ViewGroup { final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0); if (customNavId != 0) { LayoutInflater inflater = LayoutInflater.from(context); - mNavigationView = (View) inflater.inflate(customNavId, null); + mCustomNavView = (View) inflater.inflate(customNavId, null); mNavigationMode = ActionBar.NAVIGATION_MODE_CUSTOM; } @@ -164,7 +179,7 @@ public class ActionBarView extends ViewGroup { mHomeClickListener = new OnClickListener() { public void onClick(View v) { if (mCallback != null) { - mCallback.onActionItemSelected(mLogoNavItem); + mCallback.onActionItemClicked(mLogoNavItem); } } }; @@ -201,7 +216,7 @@ public class ActionBarView extends ViewGroup { } public void setCustomNavigationView(View view) { - mNavigationView = view; + mCustomNavView = view; if (view != null) { setNavigationMode(ActionBar.NAVIGATION_MODE_CUSTOM); } @@ -258,25 +273,38 @@ public class ActionBarView extends ViewGroup { final int oldMode = mNavigationMode; if (mode != oldMode) { switch (oldMode) { - case ActionBar.NAVIGATION_MODE_NORMAL: + case ActionBar.NAVIGATION_MODE_STANDARD: if (mTitleView != null) { removeView(mTitleView); mTitleView = null; } break; + case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: + if (mSpinner != null) { + removeView(mSpinner); + mSpinner = null; + } + break; case ActionBar.NAVIGATION_MODE_CUSTOM: - if (mNavigationView != null) { - removeView(mNavigationView); - mNavigationView = null; + if (mCustomNavView != null) { + removeView(mCustomNavView); + mCustomNavView = null; } + break; } switch (mode) { - case ActionBar.NAVIGATION_MODE_NORMAL: + case ActionBar.NAVIGATION_MODE_STANDARD: initTitle(); break; + case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: + mSpinner = new Spinner(mContext, null, + com.android.internal.R.attr.dropDownSpinnerStyle); + mSpinner.setOnItemSelectedListener(mNavItemSelectedListener); + addView(mSpinner); + break; case ActionBar.NAVIGATION_MODE_CUSTOM: - addView(mNavigationView); + addView(mCustomNavView); break; } mNavigationMode = mode; @@ -284,8 +312,12 @@ public class ActionBarView extends ViewGroup { } } + public void setDropdownAdapter(SpinnerAdapter adapter) { + mSpinner.setAdapter(adapter); + } + public View getCustomNavigationView() { - return mNavigationView; + return mCustomNavView; } public int getNavigationMode() { @@ -451,7 +483,7 @@ public class ActionBarView extends ViewGroup { } switch (mNavigationMode) { - case ActionBar.NAVIGATION_MODE_NORMAL: + case ActionBar.NAVIGATION_MODE_STANDARD: if (mLogoView == null) { initTitle(); } @@ -459,15 +491,15 @@ public class ActionBarView extends ViewGroup { case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: throw new UnsupportedOperationException( - "Dropdown list navigation isn't supported yet!"); + "Inflating dropdown list navigation isn't supported yet!"); case ActionBar.NAVIGATION_MODE_TABS: throw new UnsupportedOperationException( "Tab navigation isn't supported yet!"); case ActionBar.NAVIGATION_MODE_CUSTOM: - if (mNavigationView != null) { - addView(mNavigationView); + if (mCustomNavView != null) { + addView(mCustomNavView); } break; } @@ -520,14 +552,21 @@ public class ActionBarView extends ViewGroup { } switch (mNavigationMode) { - case ActionBar.NAVIGATION_MODE_NORMAL: + case ActionBar.NAVIGATION_MODE_STANDARD: if (mTitleView != null) { measureChildView(mTitleView, availableWidth, childSpecHeight, mSpacing); } break; + case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: + if (mSpinner != null) { + mSpinner.measure( + MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + break; case ActionBar.NAVIGATION_MODE_CUSTOM: - if (mNavigationView != null) { - mNavigationView.measure( + if (mCustomNavView != null) { + mCustomNavView.measure( MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } @@ -561,15 +600,19 @@ public class ActionBarView extends ViewGroup { } switch (mNavigationMode) { - case ActionBar.NAVIGATION_MODE_NORMAL: + case ActionBar.NAVIGATION_MODE_STANDARD: if (mTitleView != null) { x += positionChild(mTitleView, x, y, contentHeight) + mSpacing; } break; - + case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST: + if (mSpinner != null) { + x += positionChild(mSpinner, x, y, contentHeight) + mSpacing; + } + break; case ActionBar.NAVIGATION_MODE_CUSTOM: - if (mNavigationView != null) { - x += positionChild(mNavigationView, x, y, contentHeight) + mSpacing; + if (mCustomNavView != null) { + x += positionChild(mCustomNavView, x, y, contentHeight) + mSpacing; } break; } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index bbd9f04..5e6c538 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -817,6 +817,8 @@ public abstract class Window { public abstract void togglePanel(int featureId, KeyEvent event); + public abstract void invalidatePanelMenu(int featureId); + public abstract boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 19171f1..8a8df5a 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -520,11 +520,6 @@ public class WebView extends AbsoluteLayout private static final int MOTIONLESS_IGNORE = 3; private int mHeldMotionless; - // whether support multi-touch - private boolean mSupportMultiTouch; - // use the framework's ScaleGestureDetector to handle multi-touch - private ScaleGestureDetector mScaleDetector; - // An instance for injecting accessibility in WebViews with disabled // JavaScript or ones for which no accessibility script exists private AccessibilityInjector mAccessibilityInjector; @@ -873,16 +868,7 @@ public class WebView extends AbsoluteLayout } void updateMultiTouchSupport(Context context) { - WebSettings settings = getSettings(); - mSupportMultiTouch = context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) - && settings.supportZoom() && settings.getBuiltInZoomControls(); - if (mSupportMultiTouch && (mScaleDetector == null)) { - mScaleDetector = new ScaleGestureDetector(context, - new ScaleDetectorListener()); - } else if (!mSupportMultiTouch && (mScaleDetector != null)) { - mScaleDetector = null; - } + mZoomManager.updateMultiTouchSupport(context); } private void init() { @@ -2125,7 +2111,7 @@ public class WebView extends AbsoluteLayout private Rect mLastGlobalRect; Rect sendOurVisibleRect() { - if (mZoomManager.mPreviewZoomOnly) return mLastVisibleRectSent; + if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent; Rect rect = new Rect(); calcOurContentVisibleRect(rect); @@ -2217,7 +2203,7 @@ public class WebView extends AbsoluteLayout * @return true if new values were sent */ boolean sendViewSizeZoom(boolean force) { - if (mZoomManager.mPreviewZoomOnly) return false; + if (mZoomManager.isPreventingWebkitUpdates()) return false; int viewWidth = getViewWidth(); int newWidth = Math.round(viewWidth * mZoomManager.mInvActualScale); @@ -2240,7 +2226,8 @@ public class WebView extends AbsoluteLayout data.mHeight = newHeight; data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.mTextWrapScale); data.mScale = mZoomManager.mActualScale; - data.mIgnoreHeight = mZoomManager.isZoomAnimating() && !mHeightCanMeasure; + data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress() + && !mHeightCanMeasure; data.mAnchorX = mAnchorX; data.mAnchorY = mAnchorY; mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data); @@ -3219,15 +3206,6 @@ public class WebView extends AbsoluteLayout } } - /** - * Need to adjust the WebTextView after a change in zoom, since mActualScale - * has changed. This is especially important for password fields, which are - * drawn by the WebTextView, since it conveys more information than what - * webkit draws. Thus we need to reposition it to show in the correct - * place. - */ - private boolean mNeedToAdjustWebTextView; - private boolean didUpdateTextViewBounds(boolean allowIntersect) { Rect contentBounds = nativeFocusCandidateNodeBounds(); Rect vBox = contentToViewRect(contentBounds); @@ -3265,6 +3243,32 @@ public class WebView extends AbsoluteLayout canvas.setDrawFilter(null); } + private void onZoomAnimationStart() { + // If it is in password mode, turn it off so it does not draw misplaced. + if (inEditingMode() && nativeFocusCandidateIsPassword()) { + mWebTextView.setInPassword(false); + } + } + + private void onZoomAnimationEnd() { + // adjust the edit text view if needed + if (inEditingMode() && didUpdateTextViewBounds(false) && nativeFocusCandidateIsPassword()) { + // If it is a password field, start drawing the WebTextView once + // again. + mWebTextView.setInPassword(true); + } + } + + void onFixedLengthZoomAnimationStart() { + WebViewCore.pauseUpdatePicture(getWebViewCore()); + onZoomAnimationStart(); + } + + void onFixedLengthZoomAnimationEnd() { + WebViewCore.resumeUpdatePicture(mWebViewCore); + onZoomAnimationEnd(); + } + private void drawCoreAndCursorRing(Canvas canvas, int color, boolean drawCursorRing) { if (mDrawHistory) { @@ -3273,7 +3277,7 @@ public class WebView extends AbsoluteLayout return; } - boolean animateZoom = mZoomManager.isZoomAnimating(); + boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress(); boolean animateScroll = ((!mScroller.isFinished() || mVelocityTracker != null) && (mTouchMode != TOUCH_DRAG_MODE || @@ -3292,39 +3296,7 @@ public class WebView extends AbsoluteLayout } } if (animateZoom) { - final float[] zoomValues = mZoomManager.animateZoom(); - final boolean isStillAnimating = mZoomManager.isZoomAnimating(); - - if (isStillAnimating) { - invalidate(); - } else { - WebViewCore.resumeUpdatePicture(mWebViewCore); - // call invalidate() again to draw with the final filters - invalidate(); - if (mNeedToAdjustWebTextView) { - mNeedToAdjustWebTextView = false; - if (didUpdateTextViewBounds(false) - && nativeFocusCandidateIsPassword()) { - // If it is a password field, start drawing the - // WebTextView once again. - mWebTextView.setInPassword(true); - } - } - } - - canvas.translate(zoomValues[0], zoomValues[1]); - canvas.scale(zoomValues[2], zoomValues[2]); - - if (inEditingMode() && !mNeedToAdjustWebTextView && isStillAnimating) { - // The WebTextView is up. Keep track of this so we can adjust - // its size and placement when we finish zooming - mNeedToAdjustWebTextView = true; - // If it is in password mode, turn it off so it does not draw - // misplaced. - if (nativeFocusCandidateIsPassword()) { - mWebTextView.setInPassword(false); - } - } + mZoomManager.animateZoom(canvas); } else { canvas.scale(mZoomManager.mActualScale, mZoomManager.mActualScale); } @@ -3339,7 +3311,7 @@ public class WebView extends AbsoluteLayout invalidate(); } mWebViewCore.drawContentPicture(canvas, color, - (animateZoom || mZoomManager.mPreviewZoomOnly || UIAnimationsRunning), + (mZoomManager.isZoomAnimating() || UIAnimationsRunning), animateScroll); if (mNativeClass == 0) return; // decide which adornments to draw @@ -3350,9 +3322,8 @@ public class WebView extends AbsoluteLayout if (!animateScroll) { extras = DRAW_EXTRAS_FIND; } - } else if (mShiftIsPressed - && !nativePageShouldHandleShiftAndArrows()) { - if (!animateZoom && !mZoomManager.mPreviewZoomOnly) { + } else if (mShiftIsPressed && !nativePageShouldHandleShiftAndArrows()) { + if (!mZoomManager.isZoomAnimating()) { extras = DRAW_EXTRAS_SELECTION; nativeSetSelectionRegion(mTouchSelection || mExtendSelection); nativeSetSelectionPointer(!mTouchSelection, mZoomManager.mInvActualScale, @@ -4425,79 +4396,6 @@ public class WebView extends AbsoluteLayout private DragTracker mDragTracker; private DragTrackerHandler mDragTrackerHandler; - private class ScaleDetectorListener implements - ScaleGestureDetector.OnScaleGestureListener { - - public boolean onScaleBegin(ScaleGestureDetector detector) { - // cancel the single touch handling - cancelTouch(); - mZoomManager.dismissZoomPicker(); - // reset the zoom overview mode so that the page won't auto grow - mZoomManager.mInZoomOverview = false; - // If it is in password mode, turn it off so it does not draw - // misplaced. - if (inEditingMode() && nativeFocusCandidateIsPassword()) { - mWebTextView.setInPassword(false); - } - - mViewManager.startZoom(); - - return true; - } - - public void onScaleEnd(ScaleGestureDetector detector) { - if (mZoomManager.mPreviewZoomOnly) { - mZoomManager.mPreviewZoomOnly = false; - mAnchorX = viewToContentX((int) mZoomManager.mZoomCenterX + mScrollX); - mAnchorY = viewToContentY((int) mZoomManager.mZoomCenterY + mScrollY); - // don't reflow when zoom in; when zoom out, do reflow if the - // new scale is almost minimum scale; - boolean reflowNow = !mZoomManager.canZoomOut() - || (mZoomManager.mActualScale <= 0.8 * mZoomManager.mTextWrapScale); - // force zoom after mPreviewZoomOnly is set to false so that the - // new view size will be passed to the WebKit - mZoomManager.refreshZoomScale(reflowNow); - // call invalidate() to draw without zoom filter - invalidate(); - } - // adjust the edit text view if needed - if (inEditingMode() && didUpdateTextViewBounds(false) - && nativeFocusCandidateIsPassword()) { - // If it is a password field, start drawing the - // WebTextView once again. - mWebTextView.setInPassword(true); - } - // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it - // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it - // may trigger the unwanted fling. - mTouchMode = TOUCH_PINCH_DRAG; - mConfirmMove = true; - startTouch(detector.getFocusX(), detector.getFocusY(), - mLastTouchTime); - - mViewManager.endZoom(); - } - - public boolean onScale(ScaleGestureDetector detector) { - float scale = (float) (Math.round(detector.getScaleFactor() - * mZoomManager.mActualScale * 100) / 100.0); - if (mZoomManager.willScaleTriggerZoom(scale)) { - mZoomManager.mPreviewZoomOnly = true; - // limit the scale change per step - if (scale > mZoomManager.mActualScale) { - scale = Math.min(scale, mZoomManager.mActualScale * 1.25f); - } else { - scale = Math.max(scale, mZoomManager.mActualScale * 0.8f); - } - mZoomManager.setZoomCenter(detector.getFocusX(), detector.getFocusY()); - mZoomManager.setZoomScale(scale, false); - invalidate(); - return true; - } - return false; - } - } - private boolean hitFocusedPlugin(int contentX, int contentY) { if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin()); @@ -4519,6 +4417,22 @@ public class WebView extends AbsoluteLayout return mFullScreenHolder != null; } + void onPinchToZoomAnimationStart() { + // cancel the single touch handling + cancelTouch(); + onZoomAnimationStart(); + } + + void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) { + onZoomAnimationEnd(); + // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as + // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE + // as it may trigger the unwanted fling. + mTouchMode = TOUCH_PINCH_DRAG; + mConfirmMove = true; + startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime); + } + @Override public boolean onTouchEvent(MotionEvent ev) { if (mNativeClass == 0 || !isClickable() || !isLongClickable()) { @@ -4536,32 +4450,36 @@ public class WebView extends AbsoluteLayout // FIXME: we may consider to give WebKit an option to handle multi-touch // events later. - if (mSupportMultiTouch && ev.getPointerCount() > 1) { - if (mZoomManager.mMinZoomScale < mZoomManager.mMaxZoomScale) { - mScaleDetector.onTouchEvent(ev); - if (mScaleDetector.isInProgress()) { - mLastTouchTime = eventTime; + if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1) { + + // if the page disallows zoom, then skip multi-pointer action + if (mZoomManager.isZoomScaleFixed()) { + return true; + } + + ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector(); + detector.onTouchEvent(ev); + + if (detector.isInProgress()) { + mLastTouchTime = eventTime; + return true; + } + + x = detector.getFocusX(); + y = detector.getFocusY(); + action = ev.getAction() & MotionEvent.ACTION_MASK; + if (action == MotionEvent.ACTION_POINTER_DOWN) { + cancelTouch(); + action = MotionEvent.ACTION_DOWN; + } else if (action == MotionEvent.ACTION_POINTER_UP) { + // set mLastTouchX/Y to the remaining point + mLastTouchX = x; + mLastTouchY = y; + } else if (action == MotionEvent.ACTION_MOVE) { + // negative x or y indicate it is on the edge, skip it. + if (x < 0 || y < 0) { return true; } - x = mScaleDetector.getFocusX(); - y = mScaleDetector.getFocusY(); - action = ev.getAction() & MotionEvent.ACTION_MASK; - if (action == MotionEvent.ACTION_POINTER_DOWN) { - cancelTouch(); - action = MotionEvent.ACTION_DOWN; - } else if (action == MotionEvent.ACTION_POINTER_UP) { - // set mLastTouchX/Y to the remaining point - mLastTouchX = x; - mLastTouchY = y; - } else if (action == MotionEvent.ACTION_MOVE) { - // negative x or y indicate it is on the edge, skip it. - if (x < 0 || y < 0) { - return true; - } - } - } else { - // if the page disallow zoom, skip multi-pointer action - return true; } } else { action = ev.getAction(); @@ -4625,7 +4543,6 @@ public class WebView extends AbsoluteLayout contentX, contentY) : false; } } else { // the normal case - mZoomManager.mPreviewZoomOnly = false; mTouchMode = TOUCH_INIT_MODE; mDeferTouchProcess = (!inFullScreenMode() && mForwardTouchEvents) ? hitFocusedPlugin( @@ -5686,11 +5603,7 @@ public class WebView extends AbsoluteLayout int viewHeight = getViewHeightWithTitle(); float scale = Math.min((float) viewWidth / view.width, (float) viewHeight / view.height); - if (scale < mZoomManager.mMinZoomScale) { - scale = mZoomManager.mMinZoomScale; - } else if (scale > mZoomManager.mMaxZoomScale) { - scale = mZoomManager.mMaxZoomScale; - } + scale = mZoomManager.computeScaleWithLimits(scale); if (!mZoomManager.willScaleTriggerZoom(scale)) { if (contentToViewX(view.x) >= mScrollX && contentToViewX(view.x + view.width) <= mScrollX @@ -5715,11 +5628,7 @@ public class WebView extends AbsoluteLayout int viewHeight = getViewHeightWithTitle(); float scale = Math.min((float) viewWidth / docWidth, (float) viewHeight / docHeight); - if (scale < mZoomManager.mMinZoomScale) { - scale = mZoomManager.mMinZoomScale; - } else if (scale > mZoomManager.mMaxZoomScale) { - scale = mZoomManager.mMaxZoomScale; - } + scale = mZoomManager.computeScaleWithLimits(scale); if (!mZoomManager.willScaleTriggerZoom(scale)) { pinScrollTo(contentToViewX(docX + docWidth / 2) - viewWidth / 2, contentToViewY(docY + docHeight / 2) - viewHeight / 2, @@ -5922,7 +5831,7 @@ public class WebView extends AbsoluteLayout boolean immediate) { // don't scroll while in zoom animation. When it is done, we will adjust // the necessary components (e.g., WebTextView if it is in editing mode) - if(mZoomManager.isZoomAnimating()) { + if (mZoomManager.isFixedLengthAnimationInProgress()) { return false; } diff --git a/core/java/android/webkit/ZoomControlEmbedded.java b/core/java/android/webkit/ZoomControlEmbedded.java index 2036635..6e07ada 100644 --- a/core/java/android/webkit/ZoomControlEmbedded.java +++ b/core/java/android/webkit/ZoomControlEmbedded.java @@ -36,8 +36,7 @@ class ZoomControlEmbedded implements ZoomControlBase { } public void show() { - if (!getControls().isVisible() - && mZoomManager.mMinZoomScale < mZoomManager.mMaxZoomScale) { + if (!getControls().isVisible() && !mZoomManager.isZoomScaleFixed()) { mZoomButtonsController.setVisible(true); diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 1540865..5ae5a6a4 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -16,11 +16,33 @@ package android.webkit; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Canvas; import android.graphics.Point; import android.os.SystemClock; import android.util.Log; +import android.view.ScaleGestureDetector; import android.view.View; +/** + * The ZoomManager is responsible for maintaining the WebView's current zoom + * level state. It is also responsible for managing the on-screen zoom controls + * as well as any animation of the WebView due to zooming. + * + * Currently, there are two methods for animating the zoom of a WebView. + * + * (1) The first method is triggered by startZoomAnimation(...) and is a fixed + * length animation where the final zoom scale is known at startup. This type of + * animation notifies webkit of the final scale BEFORE it animates. The animation + * is then done by scaling the CANVAS incrementally based on a stepping function. + * + * (2) The second method is triggered by a multi-touch pinch and the new scale + * is determined dynamically based on the user's gesture. This type of animation + * only notifies webkit of new scale AFTER the gesture is complete. The animation + * effect is achieved by scaling the VIEWS (both WebView and ViewManager.ChildView) + * to the new scale in response to events related to the user's gesture. + */ class ZoomManager { static final String LOGTAG = "webviewZoom"; @@ -43,11 +65,11 @@ class ZoomManager { static float DEFAULT_MIN_ZOOM_SCALE; // actual scale limits, which can be set through a webpage viewport meta tag - float mMaxZoomScale; - float mMinZoomScale; + private float mMaxZoomScale; + private float mMinZoomScale; // locks the minimum ZoomScale to the value currently set in mMinZoomScale - boolean mMinZoomScaleFixed = true; + private boolean mMinZoomScaleFixed = true; // while in the zoom overview mode, the page's width is fully fit to the // current window. The page is alive, in another words, you can click to @@ -73,9 +95,6 @@ class ZoomManager { private static float MINIMUM_SCALE_INCREMENT = 0.01f; - // set to true temporarily during ScaleGesture triggered zoom - boolean mPreviewZoomOnly = false; - // the current computed zoom scale and its inverse. float mActualScale; float mInvActualScale; @@ -104,6 +123,13 @@ class ZoomManager { private long mZoomStart; static final int ZOOM_ANIMATION_LENGTH = 500; + // whether support multi-touch + private boolean mSupportMultiTouch; + + // use the framework's ScaleGestureDetector to handle multi-touch + private ScaleGestureDetector mScaleDetector; + private boolean mPinchToZoomAnimating = false; + public ZoomManager(WebView webView, CallbackProxy callbackProxy) { mWebView = webView; mCallbackProxy = callbackProxy; @@ -150,6 +176,19 @@ class ZoomManager { mInitialScale = scaleInPercent * 0.01f; } + public float computeScaleWithLimits(float scale) { + if (scale < mMinZoomScale) { + scale = mMinZoomScale; + } else if (scale > mMaxZoomScale) { + scale = mMaxZoomScale; + } + return scale; + } + + public boolean isZoomScaleFixed() { + return mMinZoomScale >= mMaxZoomScale; + } + public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) { return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT; } @@ -226,7 +265,7 @@ class ZoomManager { mInvInitialZoomScale = 1.0f / oldScale; mInvFinalZoomScale = 1.0f / mActualScale; mZoomScale = mActualScale; - WebViewCore.pauseUpdatePicture(mWebView.getWebViewCore()); + mWebView.onFixedLengthZoomAnimationStart(); mWebView.invalidate(); return true; } else { @@ -235,25 +274,23 @@ class ZoomManager { } /** - * Computes and returns the relevant data needed by the WebView's drawing - * model to animate a zoom. + * This method is called by the WebView's drawing code when a fixed length zoom + * animation is occurring. Its purpose is to animate the zooming of the canvas + * to the desired scale which was specified in startZoomAnimation(...). * - * This method is to be called when a zoom animation is occurring. The - * animation begins by calling startZoomAnimation(...). The caller can - * check to see if the animation has completed by calling isZoomAnimating(). + * A fixed length animation begins when startZoomAnimation(...) is called and + * continues until the ZOOM_ANIMATION_LENGTH time has elapsed. During that + * interval each time the WebView draws it calls this function which is + * responsible for generating the animation. * - * @return an array containing the values needed to animate the drawing - * surface. - * [0] = delta for the new scrollX position - * [1] = delta for the new scrollY position - * [2] = current zoom scale + * Additionally, the WebView can check to see if such an animation is currently + * in progress by calling isFixedLengthAnimationInProgress(). */ - public float[] animateZoom() { + public void animateZoom(Canvas canvas) { if (mZoomScale == 0) { - Log.w(LOGTAG, "A WebView is attempting to animate a zoom when no " + - "zoom is in progress"); - float[] result = {0, 0, mActualScale}; - return result; + Log.w(LOGTAG, "A WebView is attempting to perform a fixed length " + + "zoom animation when no zoom is in progress"); + return; } float zoomScale; @@ -262,10 +299,12 @@ class ZoomManager { float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; zoomScale = 1.0f / (mInvInitialZoomScale + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); + mWebView.invalidate(); } else { zoomScale = mZoomScale; // set mZoomScale to be 0 as we have finished animating mZoomScale = 0; + mWebView.onFixedLengthZoomAnimationEnd(); } // calculate the intermediate scroll position. Since we need to use // zoomScale, we can't use the WebView's pinLocX/Y functions directly. @@ -281,11 +320,15 @@ class ZoomManager { - titleHeight, mWebView.getViewHeight(), Math.round(mWebView.getContentHeight() * zoomScale)) + titleHeight) + mWebView.getScrollY(); - float[] result = {tx, ty, zoomScale}; - return result; + canvas.translate(tx, ty); + canvas.scale(zoomScale, zoomScale); } public boolean isZoomAnimating() { + return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating; + } + + public boolean isFixedLengthAnimationInProgress() { return mZoomScale != 0; } @@ -316,7 +359,7 @@ class ZoomManager { float oldScale = mActualScale; float oldInvScale = mInvActualScale; - if (scale != mActualScale && !mPreviewZoomOnly) { + if (scale != mActualScale && !mPinchToZoomAnimating) { mCallbackProxy.onScaleChanged(mActualScale, scale); } @@ -357,10 +400,98 @@ class ZoomManager { } } + public void updateMultiTouchSupport(Context context) { + // check the preconditions + assert mWebView.getSettings() != null; + + WebSettings settings = mWebView.getSettings(); + mSupportMultiTouch = context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) + && settings.supportZoom() && settings.getBuiltInZoomControls(); + if (mSupportMultiTouch && (mScaleDetector == null)) { + mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener()); + } else if (!mSupportMultiTouch && (mScaleDetector != null)) { + mScaleDetector = null; + } + } + + public boolean supportsMultiTouchZoom() { + return mSupportMultiTouch; + } + + /** + * Notifies the caller that the ZoomManager is requesting that scale related + * updates should not be sent to webkit. This can occur in cases where the + * ZoomManager is performing an animation and does not want webkit to update + * until the animation is complete. + * + * @return true if scale related updates should not be sent to webkit and + * false otherwise. + */ + public boolean isPreventingWebkitUpdates() { + // currently only animating a multi-touch zoom prevents updates, but + // others can add their own conditions to this method if necessary. + return mPinchToZoomAnimating; + } + + public ScaleGestureDetector getMultiTouchGestureDetector() { + return mScaleDetector; + } + + private class ScaleDetectorListener implements ScaleGestureDetector.OnScaleGestureListener { + + public boolean onScaleBegin(ScaleGestureDetector detector) { + dismissZoomPicker(); + // reset the zoom overview mode so that the page won't auto grow + mInZoomOverview = false; + mWebView.mViewManager.startZoom(); + mWebView.onPinchToZoomAnimationStart(); + return true; + } + + public boolean onScale(ScaleGestureDetector detector) { + float scale = Math.round(detector.getScaleFactor() * mActualScale * 100) * 0.01f; + if (willScaleTriggerZoom(scale)) { + mPinchToZoomAnimating = true; + // limit the scale change per step + if (scale > mActualScale) { + scale = Math.min(scale, mActualScale * 1.25f); + } else { + scale = Math.max(scale, mActualScale * 0.8f); + } + setZoomCenter(detector.getFocusX(), detector.getFocusY()); + setZoomScale(scale, false); + mWebView.invalidate(); + return true; + } + return false; + } + + public void onScaleEnd(ScaleGestureDetector detector) { + if (mPinchToZoomAnimating) { + mPinchToZoomAnimating = false; + mWebView.setViewSizeAnchor(mWebView.viewToContentX((int) mZoomCenterX + + mWebView.getScrollX()), mWebView.viewToContentY((int) mZoomCenterY + + mWebView.getScrollY())); + // don't reflow when zoom in; when zoom out, do reflow if the + // new scale is almost minimum scale; + boolean reflowNow = !canZoomOut() || (mActualScale <= 0.8 * mTextWrapScale); + // force zoom after mPreviewZoomOnly is set to false so that the + // new view size will be passed to the WebKit + refreshZoomScale(reflowNow); + // call invalidate() to draw without zoom filter + mWebView.invalidate(); + } + + mWebView.mViewManager.endZoom(); + mWebView.onPinchToZoomAnimationEnd(detector); + } + } + public void onSizeChanged(int w, int h, int ow, int oh) { // reset zoom and anchor to the top left corner of the screen // unless we are already zooming - if (!isZoomAnimating()) { + if (!isFixedLengthAnimationInProgress()) { int visibleTitleHeight = mWebView.getVisibleTitleHeight(); mZoomCenterX = 0; mZoomCenterY = visibleTitleHeight; diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index dde9a07..60e8568 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -251,8 +251,6 @@ public class Spinner extends AbsSpinner implements OnClickListener { return child; } - - /** * Helper for makeAndAddView to set the position of a view * and fill out its layout paramters. @@ -341,32 +339,6 @@ public class Spinner extends AbsSpinner implements OnClickListener { return mPopup.getHintText(); } - /* - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (mPopup.onKeyDown(keyCode, event)) { - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (mPopup.onKeyUp(keyCode, event)) { - return true; - } - return super.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyPreIme(int keyCode, KeyEvent event) { - if (mPopup.onKeyPreIme(keyCode, event)) { - return true; - } - return super.onKeyPreIme(keyCode, event); - } - */ - /** * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance * into a ListAdapter.</p> diff --git a/core/java/com/android/internal/app/SplitActionBar.java b/core/java/com/android/internal/app/SplitActionBar.java index 9204c00..8129492 100644 --- a/core/java/com/android/internal/app/SplitActionBar.java +++ b/core/java/com/android/internal/app/SplitActionBar.java @@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable; import android.view.ActionBarView; import android.view.View; import android.widget.LinearLayout; +import android.widget.SpinnerAdapter; /** * SplitActionBar is the ActionBar implementation used @@ -42,9 +43,24 @@ public class SplitActionBar extends ActionBar { mActionView.setCallback(callback); } - public void setCustomNavigationView(View view) { + public void setCustomNavigationMode(View view) { mActionView.setCustomNavigationView(view); } + + public void setDropdownNavigationMode(SpinnerAdapter adapter) { + mActionView.setNavigationMode(NAVIGATION_MODE_DROPDOWN_LIST); + mActionView.setDropdownAdapter(adapter); + } + + public void setStandardNavigationMode(CharSequence title) { + setStandardNavigationMode(title, null); + } + + public void setStandardNavigationMode(CharSequence title, CharSequence subtitle) { + mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD); + mActionView.setTitle(title); + mActionView.setSubtitle(subtitle); + } public void setTitle(CharSequence title) { mActionView.setTitle(title); @@ -54,10 +70,6 @@ public class SplitActionBar extends ActionBar { mActionView.setSubtitle(subtitle); } - public void setNavigationMode(int mode) { - mActionView.setNavigationMode(mode); - } - public void setDisplayOptions(int options) { mActionView.setDisplayOptions(options); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 1f25b37..045c24f 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -32,9 +32,11 @@ interface IStatusBarService void removeIcon(String slot); // ---- Methods below are for use by the status bar policy services ---- + // You need the STATUS_BAR_SERVICE permission void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList, out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications); - void visibilityChanged(boolean visible); + void onPanelRevealed(); void onNotificationClick(String pkg, String tag, int id); + void onNotificationError(String pkg, String tag, int id, String message); void onClearAllNotifications(); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 9983c02..0b62a67 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -164,6 +164,9 @@ public class LockPatternUtils { return getDevicePolicyManager().getPasswordMinimumSymbols(null); } + public int getRequestedPasswordMinimumNonLetter() { + return getDevicePolicyManager().getPasswordMinimumNonLetter(null); + } /** * Returns the actual password mode, as set by keyguard after updating the password. * @@ -369,10 +372,10 @@ public class LockPatternUtils { setBoolean(PATTERN_EVER_CHOSEN_KEY, true); setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern - .size(), 0, 0, 0, 0, 0); + .size(), 0, 0, 0, 0, 0, 0); } else { dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, - 0, 0, 0, 0); + 0, 0, 0, 0, 0); } } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings @@ -441,6 +444,7 @@ public class LockPatternUtils { int lowercase = 0; int numbers = 0; int symbols = 0; + int nonletter = 0; for (int i = 0; i < password.length(); i++) { char c = password.charAt(i); if (c >= 'A' && c <= 'Z') { @@ -451,16 +455,18 @@ public class LockPatternUtils { lowercase++; } else if (c >= '0' && c <= '9') { numbers++; + nonletter++; } else { symbols++; + nonletter++; } } dpm.setActivePasswordState(Math.max(quality, computedQuality), password - .length(), letters, uppercase, lowercase, numbers, symbols); + .length(), letters, uppercase, lowercase, numbers, symbols, nonletter); } else { // The password is not anything. dpm.setActivePasswordState( - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0); + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } // Add the password to the password history. We assume all // password @@ -483,7 +489,7 @@ public class LockPatternUtils { setString(PASSWORD_HISTORY_KEY, passwordHistory); } else { dpm.setActivePasswordState( - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0); + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings provider diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4aff76f..a9dbaed 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -916,6 +916,13 @@ android:description="@string/permdesc_statusBar" android:protectionLevel="signatureOrSystem" /> + <!-- Allows an application to be the status bar. Currently used only by SystemUI.apk + @hide --> + <permission android:name="android.permission.STATUS_BAR_SERVICE" + android:label="@string/permlab_statusBarService" + android:description="@string/permdesc_statusBarService" + android:protectionLevel="signature" /> + <!-- Allows an application to force a BACK operation on whatever is the top activity. --> <permission android:name="android.permission.FORCE_BACK" @@ -1277,9 +1284,6 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> - <activity android:name="com.android.server.status.UsbStorageActivity" - android:excludeFromRecents="true"> - </activity> <activity android:name="com.android.internal.app.ExternalMediaFormatActivity" android:theme="@style/Theme.Dialog.Alert" android:excludeFromRecents="true"> diff --git a/core/res/res/drawable/status_bar_item_background.xml b/core/res/res/drawable/status_bar_item_background.xml index 088389b..425a502 100644 --- a/core/res/res/drawable/status_bar_item_background.xml +++ b/core/res/res/drawable/status_bar_item_background.xml @@ -16,7 +16,7 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" - android:drawable="@drawable/status_bar_item_background_pressed" /> + android:drawable="@drawable/status_bar_item_background_pressed" /> <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/status_bar_item_background_focus" /> <item android:drawable="@drawable/status_bar_item_background_normal" /> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 0493756..aeee8af 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -117,28 +117,4 @@ <item>3</item> </integer-array> - <!-- Do not translate. Defines the slots for the right-hand side icons. That is to say, the - icons in the status bar that are not notifications. --> - <string-array name="status_bar_icon_order"> - <item><xliff:g id="id">ime</xliff:g></item> - <item><xliff:g id="id">sync_failing</xliff:g></item> - <item><xliff:g id="id">sync_active</xliff:g></item> - <item><xliff:g id="id">gps</xliff:g></item> - <item><xliff:g id="id">bluetooth</xliff:g></item> - <item><xliff:g id="id">tty</xliff:g></item> - <item><xliff:g id="id">speakerphone</xliff:g></item> - <item><xliff:g id="id">mute</xliff:g></item> - <item><xliff:g id="id">volume</xliff:g></item> - <item><xliff:g id="id">tty</xliff:g></item> - <item><xliff:g id="id">wifi</xliff:g></item> - <item><xliff:g id="id">cdma_eri</xliff:g></item> - <item><xliff:g id="id">data_connection</xliff:g></item> - <item><xliff:g id="id">phone_evdo_signal</xliff:g></item> - <item><xliff:g id="id">phone_signal</xliff:g></item> - <item><xliff:g id="id">battery</xliff:g></item> - <item><xliff:g id="id">alarm_clock</xliff:g></item> - <item><xliff:g id="id">secure</xliff:g></item> - <item><xliff:g id="id">clock</xliff:g></item> - </string-array> - </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index af8d91c..d1fc0a3 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -397,6 +397,8 @@ <attr name="horizontalScrollViewStyle" format="reference" /> <!-- Default Spinner style. --> <attr name="spinnerStyle" format="reference" /> + <!-- Dropdown Spinner style. --> + <attr name="dropDownSpinnerStyle" format="reference" /> <!-- Default Star style. --> <attr name="starStyle" format="reference" /> <!-- Default TabWidget style. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1946388..cffcd1d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -19,7 +19,35 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> -<resources> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Component to be used as the status bar service. Must implement the IStatusBar + interface. This name is in the ComponentName flattened format (package/class) --> + <string name="config_statusBarComponent">com.android.systemui/com.android.systemui.statusbar.PhoneStatusBarService</string> + + <!-- Do not translate. Defines the slots for the right-hand side icons. That is to say, the + icons in the status bar that are not notifications. --> + <string-array name="config_statusBarIcons"> + <item><xliff:g id="id">ime</xliff:g></item> + <item><xliff:g id="id">sync_failing</xliff:g></item> + <item><xliff:g id="id">sync_active</xliff:g></item> + <item><xliff:g id="id">gps</xliff:g></item> + <item><xliff:g id="id">bluetooth</xliff:g></item> + <item><xliff:g id="id">tty</xliff:g></item> + <item><xliff:g id="id">speakerphone</xliff:g></item> + <item><xliff:g id="id">mute</xliff:g></item> + <item><xliff:g id="id">volume</xliff:g></item> + <item><xliff:g id="id">tty</xliff:g></item> + <item><xliff:g id="id">wifi</xliff:g></item> + <item><xliff:g id="id">cdma_eri</xliff:g></item> + <item><xliff:g id="id">data_connection</xliff:g></item> + <item><xliff:g id="id">phone_evdo_signal</xliff:g></item> + <item><xliff:g id="id">phone_signal</xliff:g></item> + <item><xliff:g id="id">battery</xliff:g></item> + <item><xliff:g id="id">alarm_clock</xliff:g></item> + <item><xliff:g id="id">secure</xliff:g></item> + <item><xliff:g id="id">clock</xliff:g></item> + </string-array> + <!-- Flag indicating whether the surface flinger has limited alpha compositing functionality in hardware. If set, the window manager will disable alpha trasformation in animations where not diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a30b44b..5d2ca78 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -393,6 +393,11 @@ the status bar or add and remove system icons.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_statusBarService">status bar</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_statusBarService">Allows the application to be the status bar.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_expandStatusBar">expand/collapse status bar</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_expandStatusBar">Allows application to diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 32c5f47..bc5f610 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -169,6 +169,7 @@ <item name="scrollViewStyle">@android:style/Widget.ScrollView</item> <item name="horizontalScrollViewStyle">@android:style/Widget.HorizontalScrollView</item> <item name="spinnerStyle">@android:style/Widget.Spinner</item> + <item name="dropDownSpinnerStyle">@android:style/Widget.Spinner.DropDown</item> <item name="starStyle">@android:style/Widget.CompoundButton.Star</item> <item name="tabWidgetStyle">@android:style/Widget.TabWidget</item> <item name="textViewStyle">@android:style/Widget.TextView</item> diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h index c11c855..633b543 100644 --- a/include/private/surfaceflinger/SharedBufferStack.h +++ b/include/private/surfaceflinger/SharedBufferStack.h @@ -43,15 +43,6 @@ namespace android { * unless they are in use by the server, which is only the case for the last * dequeue-able buffer. When these various conditions are not met, the caller * waits until the condition is met. - * - * - * CAVEATS: - * - * In the current implementation there are several limitations: - * - buffers must be locked in the same order they've been dequeued - * - buffers must be enqueued in the same order they've been locked - * - dequeue() is not reentrant - * - no error checks are done on the condition above * */ @@ -269,7 +260,9 @@ private: // ---------------------------------------------------------------------------- -class SharedBufferServer : public SharedBufferBase +class SharedBufferServer + : public SharedBufferBase, + public LightRefBase<SharedBufferServer> { public: SharedBufferServer(SharedClient* sharedClient, int surface, int num, @@ -290,6 +283,9 @@ public: private: + friend class LightRefBase<SharedBufferServer>; + ~SharedBufferServer(); + /* * BufferList is basically a fixed-capacity sorted-vector of * unsigned 5-bits ints using a 32-bits int as storage. diff --git a/include/surfaceflinger/Surface.h b/include/surfaceflinger/Surface.h index ac01ce5..f333911 100644 --- a/include/surfaceflinger/Surface.h +++ b/include/surfaceflinger/Surface.h @@ -60,7 +60,6 @@ public: static bool isSameSurface( const sp<SurfaceControl>& lhs, const sp<SurfaceControl>& rhs); - SurfaceID ID() const { return mToken; } uint32_t getFlags() const { return mFlags; } uint32_t getIdentity() const { return mIdentity; } @@ -145,6 +144,9 @@ public: uint32_t reserved[2]; }; + static status_t writeToParcel( + const sp<Surface>& control, Parcel* parcel); + static sp<Surface> readFromParcel( const Parcel& data, const sp<Surface>& other); @@ -153,7 +155,6 @@ public: } bool isValid(); - SurfaceID ID() const { return mToken; } uint32_t getFlags() const { return mFlags; } uint32_t getIdentity() const { return mIdentity; } @@ -267,7 +268,6 @@ private: SharedBufferClient* mSharedBufferClient; status_t mInitCheck; sp<ISurface> mSurface; - SurfaceID mToken; uint32_t mIdentity; PixelFormat mFormat; uint32_t mFlags; diff --git a/libs/surfaceflinger/Layer.cpp b/libs/surfaceflinger/Layer.cpp index a94fdd4..e7247bd 100644 --- a/libs/surfaceflinger/Layer.cpp +++ b/libs/surfaceflinger/Layer.cpp @@ -76,15 +76,18 @@ Layer::~Layer() status_t Layer::setToken(const sp<UserClient>& userClient, SharedClient* sharedClient, int32_t token) { - SharedBufferServer* lcblk = new SharedBufferServer( + sp<SharedBufferServer> lcblk = new SharedBufferServer( sharedClient, token, mBufferManager.getDefaultBufferCount(), getIdentity()); status_t err = mUserClientRef.setToken(userClient, lcblk, token); - if (err != NO_ERROR) { - LOGE("ClientRef::setToken(%p, %p, %u) failed", - userClient.get(), lcblk, token); - delete lcblk; + + LOGE_IF(err != NO_ERROR, + "ClientRef::setToken(%p, %p, %u) failed", + userClient.get(), lcblk.get(), token); + + if (err == NO_ERROR) { + // we need to free the buffers associated with this surface } return err; @@ -95,6 +98,11 @@ int32_t Layer::getToken() const return mUserClientRef.getToken(); } +sp<UserClient> Layer::getClient() const +{ + return mUserClientRef.getClient(); +} + // called with SurfaceFlinger::mStateLock as soon as the layer is entered // in the purgatory list void Layer::onRemoved() @@ -626,11 +634,10 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const // --------------------------------------------------------------------------- Layer::ClientRef::ClientRef() - : mToken(-1) { + : mControlBlock(0), mToken(-1) { } Layer::ClientRef::~ClientRef() { - delete lcblk; } int32_t Layer::ClientRef::getToken() const { @@ -638,14 +645,25 @@ int32_t Layer::ClientRef::getToken() const { return mToken; } +sp<UserClient> Layer::ClientRef::getClient() const { + Mutex::Autolock _l(mLock); + return mUserClient.promote(); +} + status_t Layer::ClientRef::setToken(const sp<UserClient>& uc, - SharedBufferServer* sharedClient, int32_t token) { + const sp<SharedBufferServer>& sharedClient, int32_t token) { Mutex::Autolock _l(mLock); - if (mToken >= 0) - return INVALID_OPERATION; + + { // scope for strong mUserClient reference + sp<UserClient> userClient(mUserClient.promote()); + if (mUserClient != 0 && mControlBlock != 0) { + mControlBlock->setStatus(NO_INIT); + } + } + mUserClient = uc; mToken = token; - lcblk = sharedClient; + mControlBlock = sharedClient; return NO_ERROR; } @@ -657,12 +675,16 @@ sp<UserClient> Layer::ClientRef::getUserClientUnsafe() const { // it makes sure the UserClient (and its associated shared memory) // won't go away while we're accessing it. Layer::ClientRef::Access::Access(const ClientRef& ref) - : lcblk(0) + : mControlBlock(0) { Mutex::Autolock _l(ref.mLock); mUserClientStrongRef = ref.mUserClient.promote(); if (mUserClientStrongRef != 0) - lcblk = ref.lcblk; + mControlBlock = ref.mControlBlock; +} + +Layer::ClientRef::Access::~Access() +{ } // --------------------------------------------------------------------------- diff --git a/libs/surfaceflinger/Layer.h b/libs/surfaceflinger/Layer.h index d396ecf..dcb27a0 100644 --- a/libs/surfaceflinger/Layer.h +++ b/libs/surfaceflinger/Layer.h @@ -60,6 +60,7 @@ public: // associate a UserClient to this Layer status_t setToken(const sp<UserClient>& uc, SharedClient* sc, int32_t idx); int32_t getToken() const; + sp<UserClient> getClient() const; // Set this Layer's buffers size void setBufferSize(uint32_t w, uint32_t h); @@ -119,24 +120,26 @@ private: ClientRef& operator = (const ClientRef& rhs); mutable Mutex mLock; // binder thread, page-flip thread - SharedBufferServer* lcblk; + sp<SharedBufferServer> mControlBlock; wp<UserClient> mUserClient; int32_t mToken; public: ClientRef(); ~ClientRef(); int32_t getToken() const; + sp<UserClient> getClient() const; status_t setToken(const sp<UserClient>& uc, - SharedBufferServer* sharedClient, int32_t token); + const sp<SharedBufferServer>& sharedClient, int32_t token); sp<UserClient> getUserClientUnsafe() const; class Access { Access(const Access& rhs); Access& operator = (const Access& rhs); sp<UserClient> mUserClientStrongRef; - SharedBufferServer* lcblk; + sp<SharedBufferServer> mControlBlock; public: Access(const ClientRef& ref); - inline SharedBufferServer* get() const { return lcblk; } + ~Access(); + inline SharedBufferServer* get() const { return mControlBlock.get(); } }; friend class Access; }; diff --git a/libs/surfaceflinger/SurfaceFlinger.cpp b/libs/surfaceflinger/SurfaceFlinger.cpp index 0f73774..4dea62f 100644 --- a/libs/surfaceflinger/SurfaceFlinger.cpp +++ b/libs/surfaceflinger/SurfaceFlinger.cpp @@ -1718,7 +1718,10 @@ void UserClient::detachLayer(const Layer* layer) { int32_t name = layer->getToken(); if (name >= 0) { - android_atomic_and(~(1LU<<name), &mBitmap); + int32_t mask = 1LU<<name; + if ((android_atomic_and(~mask, &mBitmap) & mask) == 0) { + LOGW("token %d wasn't marked as used %08x", name, int(mBitmap)); + } } } @@ -1732,24 +1735,31 @@ ssize_t UserClient::getTokenForSurface(const sp<ISurface>& sur) const sp<Layer> layer(mFlinger->getLayer(sur)); if (layer == 0) return name; - // this layer already has a token, just return it - // FIXME: we should check that this token is for the same client + // if this layer already has a token, just return it name = layer->getToken(); - if (name >= 0) return name; + if ((name >= 0) && (layer->getClient() == this)) + return name; name = 0; do { int32_t mask = 1LU<<name; if ((android_atomic_or(mask, &mBitmap) & mask) == 0) { // we found and locked that name - layer->setToken(const_cast<UserClient*>(this), ctrlblk, name); + status_t err = layer->setToken( + const_cast<UserClient*>(this), ctrlblk, name); + if (err != NO_ERROR) { + // free the name + android_atomic_and(~mask, &mBitmap); + name = err; + } break; } if (++name > 31) name = NO_MEMORY; } while(name >= 0); - //LOGD("getTokenForSurface(%p) => %d", sur->asBinder().get(), name); + //LOGD("getTokenForSurface(%p) => %d (client=%p, bitmap=%08lx)", + // sur->asBinder().get(), name, this, mBitmap); return name; } diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp index 1dd8642..d67a589 100644 --- a/libs/surfaceflinger_client/SharedBufferStack.cpp +++ b/libs/surfaceflinger_client/SharedBufferStack.cpp @@ -494,6 +494,10 @@ SharedBufferServer::SharedBufferServer(SharedClient* sharedClient, } } +SharedBufferServer::~SharedBufferServer() +{ +} + ssize_t SharedBufferServer::retireAndLock() { RWLock::AutoRLock _l(mLock); diff --git a/libs/surfaceflinger_client/Surface.cpp b/libs/surfaceflinger_client/Surface.cpp index 6fe4c4a..8617d94a 100644 --- a/libs/surfaceflinger_client/Surface.cpp +++ b/libs/surfaceflinger_client/Surface.cpp @@ -236,17 +236,15 @@ status_t SurfaceControl::validate() const status_t SurfaceControl::writeSurfaceToParcel( const sp<SurfaceControl>& control, Parcel* parcel) { - uint32_t flags = 0; - uint32_t format = 0; + sp<ISurface> sur; uint32_t identity = 0; uint32_t width = 0; uint32_t height = 0; - sp<SurfaceComposerClient> client; - sp<ISurface> sur; + uint32_t format = 0; + uint32_t flags = 0; if (SurfaceControl::isValid(control)) { - identity = control->mIdentity; - client = control->mClient; sur = control->mSurface; + identity = control->mIdentity; width = control->mWidth; height = control->mHeight; format = control->mFormat; @@ -349,6 +347,33 @@ Surface::Surface(const Parcel& parcel, const sp<IBinder>& ref) init(); } +status_t Surface::writeToParcel( + const sp<Surface>& surface, Parcel* parcel) +{ + sp<ISurface> sur; + uint32_t identity = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t format = 0; + uint32_t flags = 0; + if (Surface::isValid(surface)) { + sur = surface->mSurface; + identity = surface->mIdentity; + width = surface->mWidth; + height = surface->mHeight; + format = surface->mFormat; + flags = surface->mFlags; + } + parcel->writeStrongBinder(sur!=0 ? sur->asBinder() : NULL); + parcel->writeInt32(identity); + parcel->writeInt32(width); + parcel->writeInt32(height); + parcel->writeInt32(format); + parcel->writeInt32(flags); + return NO_ERROR; + +} + sp<Surface> Surface::readFromParcel( const Parcel& data, const sp<Surface>& other) { @@ -385,11 +410,11 @@ void Surface::init() mBuffers.insertAt(0, 2); if (mSurface != 0 && mClient.initCheck() == NO_ERROR) { - mToken = mClient.getTokenForSurface(mSurface); - if (mToken >= 0) { + int32_t token = mClient.getTokenForSurface(mSurface); + if (token >= 0) { mSharedBufferClient = new SharedBufferClient( - mClient.getSharedClient(), mToken, 2, mIdentity); - mInitCheck = mClient.getSharedClient()->validate(mToken); + mClient.getSharedClient(), token, 2, mIdentity); + mInitCheck = mClient.getSharedClient()->validate(token); } } } @@ -421,7 +446,7 @@ status_t Surface::validate() const { // check that we initialized ourself properly if (mInitCheck != NO_ERROR) { - LOGE("invalid token (%d, identity=%u)", mToken, mIdentity); + LOGE("invalid token (identity=%u)", mIdentity); return mInitCheck; } @@ -437,17 +462,17 @@ status_t Surface::validate() const } if (mIdentity != identity) { - LOGE("[Surface] using an invalid surface id=%d, " + LOGE("[Surface] using an invalid surface, " "identity=%u should be %d", - mToken, mIdentity, identity); + mIdentity, identity); return NO_INIT; } // check the surface didn't become invalid status_t err = mSharedBufferClient->getStatus(); if (err != NO_ERROR) { - LOGE("surface (id=%d, identity=%u) is invalid, err=%d (%s)", - mToken, mIdentity, err, strerror(-err)); + LOGE("surface (identity=%u) is invalid, err=%d (%s)", + mIdentity, err, strerror(-err)); return err; } diff --git a/libs/surfaceflinger_client/SurfaceComposerClient.cpp b/libs/surfaceflinger_client/SurfaceComposerClient.cpp index 5ac0d5d..4096ac6 100644 --- a/libs/surfaceflinger_client/SurfaceComposerClient.cpp +++ b/libs/surfaceflinger_client/SurfaceComposerClient.cpp @@ -305,9 +305,7 @@ sp<SurfaceControl> SurfaceComposerClient::createSurface( sp<ISurface> surface = mClient->createSurface(&data, pid, name, display, w, h, format, flags); if (surface != 0) { - if (uint32_t(data.token) < SharedBufferStack::NUM_LAYERS_MAX) { - result = new SurfaceControl(this, surface, data, w, h, format, flags); - } + result = new SurfaceControl(this, surface, data, w, h, format, flags); } } return result; diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp index 3ddde38..4b5f025 100644 --- a/libs/ui/GraphicBuffer.cpp +++ b/libs/ui/GraphicBuffer.cpp @@ -111,6 +111,9 @@ status_t GraphicBuffer::reallocate(uint32_t w, uint32_t h, PixelFormat f, if (mOwner != ownData) return INVALID_OPERATION; + if (handle && w==width && h==height && f==format && reqUsage==usage) + return NO_ERROR; + if (handle) { GraphicBufferAllocator& allocator(GraphicBufferAllocator::get()); allocator.free(handle); diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 954255b..4362d14 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -317,6 +317,12 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) mStringPoolSize = (mHeader->header.size-mHeader->stringsStart)/charSize; } else { + // check invariant: styles starts before end of data + if (mHeader->stylesStart >= (mHeader->header.size-sizeof(uint16_t))) { + LOGW("Bad style block: style block starts at %d past data size of %d\n", + (int)mHeader->stylesStart, (int)mHeader->header.size); + return (mError=BAD_TYPE); + } // check invariant: styles follow the strings if (mHeader->stylesStart <= mHeader->stringsStart) { LOGW("Bad style block: style block starts at %d, before strings at %d\n", diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 8cf2e51..c113ffe 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -317,8 +317,8 @@ android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz) jobject surface = env->GetObjectField(thiz, fields.surface); if (surface != NULL) { - const sp<Surface>& native_surface = get_surface(env, surface); - LOGI("prepare: surface=%p (id=%d)", native_surface.get(), native_surface->ID()); + const sp<Surface> native_surface = get_surface(env, surface); + LOGI("prepare: surface=%p (identity=%d)", native_surface.get(), native_surface->getIdentity()); if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface), "java/lang/RuntimeException", "setPreviewSurface failed.")) { return; } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 3f5b69d..74d87ba 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -2,21 +2,21 @@ package="com.android.systemui" android:sharedUserId="android.uid.system"> + <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <application android:allowClearUserData="false" android:label="@string/app_label" android:icon="@drawable/ic_launcher_settings"> - <receiver - android:name=".statusbar.StatusBarStarter" - > - <intent-filter> - <action android:name="com.android.internal.policy.statusbar.START" /> - </intent-filter> - </receiver> <service android:name=".statusbar.PhoneStatusBarService" android:exported="false" /> + + <activity android:name=".usb.UsbStorageActivity" + android:excludeFromRecents="true"> + </activity> + </application> </manifest> diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_focus.9.png b/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_focus.9.png Binary files differdeleted file mode 100644 index 0876bc6..0000000 --- a/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_focus.9.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_normal.9.png b/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_normal.9.png Binary files differdeleted file mode 100644 index c01c018..0000000 --- a/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_normal.9.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_pressed.9.png b/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_pressed.9.png Binary files differdeleted file mode 100644 index 343e4ca..0000000 --- a/packages/SystemUI/res/drawable-hdpi/status_bar_item_background_pressed.9.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_focus.9.png b/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_focus.9.png Binary files differdeleted file mode 100644 index c3e2415..0000000 --- a/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_focus.9.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_normal.9.png b/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_normal.9.png Binary files differdeleted file mode 100644 index b8e399d..0000000 --- a/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_normal.9.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_pressed.9.png b/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_pressed.9.png Binary files differdeleted file mode 100644 index 02b4e9a..0000000 --- a/packages/SystemUI/res/drawable-mdpi/status_bar_item_background_pressed.9.png +++ /dev/null diff --git a/core/res/res/layout/status_bar_latest_event.xml b/packages/SystemUI/res/layout/status_bar_latest_event.xml index 59cc90d..88d9739 100644 --- a/core/res/res/layout/status_bar_latest_event.xml +++ b/packages/SystemUI/res/layout/status_bar_latest_event.xml @@ -4,20 +4,20 @@ android:orientation="vertical" > - <com.android.server.status.LatestItemView android:id="@+id/content" + <com.android.systemui.statusbar.LatestItemView android:id="@+id/content" android:layout_width="match_parent" android:layout_height="64sp" - android:background="@drawable/status_bar_item_background" + android:background="@android:drawable/status_bar_item_background" android:focusable="true" android:clickable="true" android:paddingRight="6sp" > - </com.android.server.status.LatestItemView> + </com.android.systemui.statusbar.LatestItemView> <View android:layout_width="match_parent" android:layout_height="1sp" - android:background="@drawable/divider_horizontal_bright" + android:background="@android:drawable/divider_horizontal_bright" /> </LinearLayout> diff --git a/packages/SystemUI/res/values/arrays.xml b/packages/SystemUI/res/values/arrays.xml deleted file mode 100644 index dbb0e0f..0000000 --- a/packages/SystemUI/res/values/arrays.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/assets/res/any/colors.xml -** -** Copyright 2006, 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. -*/ ---> -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - - <!-- Do not translate. Defines the slots for the right-hand side icons. That is to say, the - icons in the status bar that are not notifications. --> - <string-array name="status_bar_icon_order"> - <item><xliff:g id="id">TODO: Remove; not used.</xliff:g></item> - </string-array> - -</resources> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java index e9ae69a..a7e5e31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java @@ -85,13 +85,6 @@ public class PhoneStatusBarService extends StatusBarService { private static final int MSG_ANIMATE = 1000; private static final int MSG_ANIMATE_REVEAL = 1001; - public interface NotificationCallbacks { - void onSetDisabled(int status); - void onClearAll(); - void onNotificationClick(String pkg, String tag, int id); - void onPanelRevealed(); - } - private class ExpandedDialog extends Dialog { ExpandedDialog(Context context) { super(context, com.android.internal.R.style.Theme_Light_NoTitleBar); @@ -123,7 +116,6 @@ public class PhoneStatusBarService extends StatusBarService { Object mQueueLock = new Object(); // icons - String[] mRightIconSlots; LinearLayout mIcons; IconMerger mNotificationIcons; LinearLayout mStatusIcons; @@ -160,6 +152,7 @@ public class PhoneStatusBarService extends StatusBarService { TrackingView mTrackingView; WindowManager.LayoutParams mTrackingParams; int mTrackingPosition; // the position of the top of the tracking view. + private boolean mPanelSlightlyVisible; // ticker private Ticker mTicker; @@ -208,7 +201,6 @@ public class PhoneStatusBarService extends StatusBarService { // ================================================================================ private void makeStatusBarView(Context context) { Resources res = context.getResources(); - mRightIconSlots = res.getStringArray(R.array.status_bar_icon_order); mHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); mIconWidth = mHeight; @@ -349,6 +341,16 @@ public class PhoneStatusBarService extends StatusBarService { final RemoteViews contentView = notification.notification.contentView; + if (false) { + Slog.d(TAG, "old notification: when=" + oldNotification.notification.when + + " ongoing=" + oldNotification.isOngoing() + + " expanded=" + oldEntry.expanded + + " contentView=" + oldContentView); + Slog.d(TAG, "new notification: when=" + notification.notification.when + + " ongoing=" + oldNotification.isOngoing() + + " contentView=" + contentView); + } + // Can we just reapply the RemoteViews in place? If when didn't change, the order // didn't change. if (notification.notification.when == oldNotification.notification.when @@ -370,6 +372,14 @@ public class PhoneStatusBarService extends StatusBarService { oldEntry.content.setOnClickListener(new Launcher(contentIntent, notification.pkg, notification.tag, notification.id)); } + // Update the icon. + final StatusBarIcon ic = new StatusBarIcon(notification.pkg, + notification.notification.icon, notification.notification.iconLevel, + notification.notification.number); + if (!oldEntry.icon.set(ic)) { + handleNotificationError(key, notification, "Couldn't update icon: " + ic); + return; + } } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. @@ -377,9 +387,6 @@ public class PhoneStatusBarService extends StatusBarService { removeNotificationViews(key); addNotificationViews(key, notification); } - // Update the icon. - oldEntry.icon.set(new StatusBarIcon(notification.pkg, notification.notification.icon, - notification.notification.iconLevel, notification.notification.number)); } else { Slog.d(TAG, "not reusing notification"); removeNotificationViews(key); @@ -426,11 +433,10 @@ public class PhoneStatusBarService extends StatusBarService { // create the row view LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View row = inflater.inflate(com.android.internal.R.layout.status_bar_latest_event, - parent, false); + View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false); // bind the click event to the content area - ViewGroup content = (ViewGroup)row.findViewById(com.android.internal.R.id.content); + ViewGroup content = (ViewGroup)row.findViewById(R.id.content); content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); content.setOnFocusChangeListener(mFocusChangeListener); PendingIntent contentIntent = n.contentIntent; @@ -448,8 +454,9 @@ public class PhoneStatusBarService extends StatusBarService { exception = e; } if (expanded == null) { - Slog.e(TAG, "couldn't inflate view for package " + notification.pkg, exception); - row.setVisibility(View.GONE); + String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id); + Slog.e(TAG, "couldn't inflate view for notification " + ident); + return null; } else { content.addView(expanded); row.setDrawingCacheEnabled(true); @@ -471,14 +478,23 @@ public class PhoneStatusBarService extends StatusBarService { } // Construct the expanded view. final View[] views = makeNotificationView(notification, parent); + if (views == null) { + handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " + + notification); + return; + } final View row = views[0]; final View content = views[1]; final View expanded = views[2]; // Construct the icon. - StatusBarIconView iconView = new StatusBarIconView(this, + final StatusBarIconView iconView = new StatusBarIconView(this, notification.pkg + "/0x" + Integer.toHexString(notification.id)); - iconView.set(new StatusBarIcon(notification.pkg, notification.notification.icon, - notification.notification.iconLevel, notification.notification.number)); + final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon, + notification.notification.iconLevel, notification.notification.number); + if (!iconView.set(ic)) { + handleNotificationError(key, notification, "Coulding create icon: " + ic); + return; + } // Add the expanded view. final int viewIndex = list.add(key, notification, row, content, expanded, iconView); parent.addView(row, viewIndex); @@ -964,6 +980,21 @@ public class PhoneStatusBarService extends StatusBarService { } } + /** + * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService + * about the failure. + * + * WARNING: this will call back into us. Don't hold any locks. + */ + void handleNotificationError(IBinder key, StatusBarNotification n, String message) { + removeNotification(key); + try { + mBarService.onNotificationError(n.pkg, n.tag, n.id, message); + } catch (RemoteException ex) { + // The end is nigh. + } + } + private class MyTicker extends Ticker { MyTicker(Context context, StatusBarView sb) { super(context, sb); @@ -1265,8 +1296,15 @@ public class PhoneStatusBarService extends StatusBarService { // because the window itself extends below the content view. mExpandedParams.y = -disph; } - visibilityChanged(visible); mExpandedDialog.getWindow().setAttributes(mExpandedParams); + + // As long as this isn't just a repositioning that's not supposed to affect + // the user's perception of what's showing, call to say that the visibility + // has changed. (Otherwise, someone else will call to do that). + if (expandedPosition != EXPANDED_LEAVE_ALONE) { + Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")"); + visibilityChanged(visible); + } } if (SPEW) { @@ -1296,12 +1334,11 @@ public class PhoneStatusBarService extends StatusBarService { * turned off. If any other notifications happen, the lights will turn back on. Steve says * this is what he wants. (see bug 1131461) */ - private boolean mPanelSlightlyVisible; void visibilityChanged(boolean visible) { if (mPanelSlightlyVisible != visible) { mPanelSlightlyVisible = visible; try { - mBarService.visibilityChanged(visible); + mBarService.onPanelRevealed(); } catch (RemoteException ex) { // Won't fail unless the world has ended. } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 5eb0d68..bc1e798 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -32,7 +32,6 @@ public class StatusBarIconView extends AnimatedImageView { private StatusBarIcon mIcon; @ViewDebug.ExportedProperty private String mSlot; - @ViewDebug.ExportedProperty private boolean mError; public StatusBarIconView(Context context, String slot) { super(context); @@ -52,39 +51,33 @@ public class StatusBarIconView extends AnimatedImageView { return a.equals(b); } - public void set(StatusBarIcon icon) { - error: { - final boolean iconEquals = !mError - && mIcon != null - && streq(mIcon.iconPackage, icon.iconPackage) - && mIcon.iconId == icon.iconId; - final boolean levelEquals = !mError - && iconEquals - && mIcon.iconLevel == icon.iconLevel; - final boolean visibilityEquals = !mError - && mIcon != null - && mIcon.visible == icon.visible; - mError = false; - if (!iconEquals) { - Drawable drawable = getIcon(icon); - if (drawable == null) { - mError = true; - Slog.w(PhoneStatusBarService.TAG, "No icon ID for slot " + mSlot); - break error; - } - setImageDrawable(drawable); - } - if (!levelEquals) { - setImageLevel(icon.iconLevel); - } - if (!visibilityEquals) { - setVisibility(icon.visible ? VISIBLE : GONE); + /** + * Returns whether the set succeeded. + */ + public boolean set(StatusBarIcon icon) { + final boolean iconEquals = mIcon != null + && streq(mIcon.iconPackage, icon.iconPackage) + && mIcon.iconId == icon.iconId; + final boolean levelEquals = iconEquals + && mIcon.iconLevel == icon.iconLevel; + final boolean visibilityEquals = mIcon != null + && mIcon.visible == icon.visible; + if (!iconEquals) { + Drawable drawable = getIcon(icon); + if (drawable == null) { + Slog.w(PhoneStatusBarService.TAG, "No icon for slot " + mSlot); + return false; } - mIcon = icon.clone(); + setImageDrawable(drawable); + } + if (!levelEquals) { + setImageLevel(icon.iconLevel); } - if (mError) { - setVisibility(GONE); + if (!visibilityEquals) { + setVisibility(icon.visible ? VISIBLE : GONE); } + mIcon = icon.clone(); + return true; } private Drawable getIcon(StatusBarIcon icon) { @@ -106,7 +99,7 @@ public class StatusBarIconView extends AnimatedImageView { try { r = context.getPackageManager().getResourcesForApplication(icon.iconPackage); } catch (PackageManager.NameNotFoundException ex) { - Slog.e(PhoneStatusBarService.TAG, "Icon package not found: "+icon.iconPackage, ex); + Slog.e(PhoneStatusBarService.TAG, "Icon package not found: " + icon.iconPackage); return null; } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java index 9ef9d0d..4f39ee4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java @@ -365,7 +365,7 @@ public class StatusBarPolicy { // storage mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); mStorageManager.registerListener( - new com.android.server.status.StorageNotification(context)); + new com.android.systemui.usb.StorageNotification(context)); // battery mService.setIcon("battery", com.android.internal.R.drawable.stat_sys_battery_unknown, 0); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java index 4d9e695..d200886 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java @@ -48,12 +48,6 @@ public abstract class StatusBarService extends Service implements CommandQueue.C CommandQueue mCommandQueue; IStatusBarService mBarService; - /* TODO - H mHandler = new H(); - Object mQueueLock = new Object(); - NotificationCallbacks mNotificationCallbacks; - */ - @Override public void onCreate() { // Connect in to the status bar manager service diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStarter.java deleted file mode 100644 index 2b9dfb0..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStarter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar; - -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; -import android.util.Log; - -/** - * Receive a broadcast from the StatusBarManagerService at boot time, and - * kick off the StatusBarService. - */ -public class StatusBarStarter extends BroadcastReceiver { - private static final String TAG = "StatusBarStarter"; - - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "StatusBarStarter onReceive intent=" + intent); - context.startService(new Intent(context, PhoneStatusBarService.class)); - } -} - - diff --git a/services/java/com/android/server/status/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 8da8cd3..f8abc5a 100644 --- a/services/java/com/android/server/status/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.status; +package com.android.systemui.usb; import android.app.Activity; import android.app.Notification; @@ -80,9 +80,10 @@ public class StorageNotification extends StorageEventListener { mContext = context; mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - mUmsAvailable = mStorageManager.isUsbMassStorageConnected(); + final boolean connected = mStorageManager.isUsbMassStorageConnected(); Slog.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", mUmsAvailable, Environment.getExternalStorageState())); + onUsbMassStorageConnectionChanged(connected); } /* @@ -122,7 +123,7 @@ public class StorageNotification extends StorageEventListener { * for stopping UMS. */ Intent intent = new Intent(); - intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class); + intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class); PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); setUsbStorageNotification( com.android.internal.R.string.usb_storage_stop_notification_title, @@ -240,7 +241,7 @@ public class StorageNotification extends StorageEventListener { if (available) { Intent intent = new Intent(); - intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class); + intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final boolean adbOn = 1 == Settings.Secure.getInt( diff --git a/services/java/com/android/server/status/UsbStorageActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java index e8631c5..55d31ec 100644 --- a/services/java/com/android/server/status/UsbStorageActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.status; +package com.android.systemui.usb; import com.android.internal.R; import android.app.Activity; diff --git a/policy/com/android/internal/policy/impl/PhoneWindow.java b/policy/com/android/internal/policy/impl/PhoneWindow.java index 0bf0d52..1c78a01 100644 --- a/policy/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/com/android/internal/policy/impl/PhoneWindow.java @@ -321,9 +321,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (st.createdPanelView == null) { // Init the panel state's menu--return false if init failed - if (st.menu == null) { - if (!initializePanelMenu(st) || (st.menu == null)) { - return false; + if (st.menu == null || st.refreshMenuContent) { + if (st.menu == null) { + if (!initializePanelMenu(st) || (st.menu == null)) { + return false; + } } // Call callback, and return if it doesn't want to display menu if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) { @@ -332,6 +334,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return false; } + st.refreshMenuContent = false; } // Callback and return if the callback does not want to show the menu @@ -551,6 +554,16 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } + @Override + public void invalidatePanelMenu(int featureId) { + PanelFeatureState st = getPanelState(featureId, true); + if (st.menu != null) { + st.menu.clear(); + } + st.refreshMenuContent = true; + st.refreshDecorView = true; + } + /** * Called when the panel key is pushed down. * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. @@ -2640,6 +2653,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { boolean refreshDecorView; + boolean refreshMenuContent; + boolean wasLastOpen; boolean wasLastExpanded; diff --git a/policy/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/com/android/internal/policy/impl/PhoneWindowManager.java index 73a57ee..d152bc4 100755 --- a/policy/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/com/android/internal/policy/impl/PhoneWindowManager.java @@ -957,12 +957,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) { switch (attrs.type) { case TYPE_STATUS_BAR: + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, + "PhoneWindowManager"); + // TODO: Need to handle the race condition of the status bar proc + // dying and coming back before the removeWindowLw cleanup has happened. if (mStatusBar != null) { return WindowManagerImpl.ADD_MULTIPLE_SINGLETON; } mStatusBar = win; break; case TYPE_STATUS_BAR_PANEL: + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, + "PhoneWindowManager"); mStatusBarPanels.add(win); break; case TYPE_KEYGUARD: diff --git a/services/java/com/android/server/DemoDataSet.java b/services/java/com/android/server/DemoDataSet.java deleted file mode 100644 index 277985f..0000000 --- a/services/java/com/android/server/DemoDataSet.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetManager; -import android.net.Uri; -import android.os.Environment; -import android.provider.Contacts; -import android.provider.Settings; -import android.provider.MediaStore.Images; -import android.util.Config; -import android.util.Slog; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.OutputStream; - -public class DemoDataSet -{ - private final static String LOG_TAG = "DemoDataSet"; - - private ContentResolver mContentResolver; - - public final void add(Context context) - { - mContentResolver = context.getContentResolver(); - - // Remove all the old data - mContentResolver.delete(Contacts.People.CONTENT_URI, null, null); - - // Add the new data - addDefaultData(); - - // Add images from /android/images - addDefaultImages(); - } - - private final void addDefaultImages() - { - File rootDirectory = Environment.getRootDirectory(); - String [] files - = new File(rootDirectory, "images").list(); - int count = files.length; - - if (count == 0) { - Slog.i(LOG_TAG, "addDefaultImages: no images found!"); - return; - } - - for (int i = 0; i < count; i++) - { - String name = files[i]; - String path = rootDirectory + "/" + name; - - try { - Images.Media.insertImage(mContentResolver, path, name, null); - } catch (FileNotFoundException e) { - Slog.e(LOG_TAG, "Failed to import image " + path, e); - } - } - } - - private final void addDefaultData() - { - Slog.i(LOG_TAG, "Adding default data..."); - -// addImage("Violet", "images/violet.png"); -// addImage("Corky", "images/corky.png"); - - // PENDING: should this be done here?!?! - Intent intent = new Intent( - Intent.ACTION_CALL, Uri.fromParts("voicemail", "", null)); - addShortcut("1", intent); - } - - private final Uri addImage(String name, Uri file) - { - ContentValues imagev = new ContentValues(); - imagev.put("name", name); - - Uri url = null; - - AssetManager ass = AssetManager.getSystem(); - InputStream in = null; - OutputStream out = null; - - try - { - in = ass.open(file.toString()); - - url = mContentResolver.insert(Images.Media.INTERNAL_CONTENT_URI, imagev); - out = mContentResolver.openOutputStream(url); - - final int size = 8 * 1024; - byte[] buf = new byte[size]; - - int count = 0; - do - { - count = in.read(buf, 0, size); - if (count > 0) { - out.write(buf, 0, count); - } - } while (count > 0); - } - catch (Exception e) - { - Slog.e(LOG_TAG, "Failed to insert image '" + file + "'", e); - url = null; - } - - return url; - } - - private final Uri addShortcut(String shortcut, Intent intent) - { - if (Config.LOGV) Slog.v(LOG_TAG, "addShortcut: shortcut=" + shortcut + ", intent=" + intent); - return Settings.Bookmarks.add(mContentResolver, intent, null, null, - shortcut != null ? shortcut.charAt(0) : 0, 0); - } -} diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index d2add10..c0ea68d 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -80,6 +80,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int mActivePasswordLetters = 0; int mActivePasswordNumeric = 0; int mActivePasswordSymbols = 0; + int mActivePasswordNonLetter = 0; int mFailedPasswordAttempts = 0; int mPasswordOwner = -1; @@ -100,6 +101,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int minimumPasswordLetters = 1; int minimumPasswordNumeric = 1; int minimumPasswordSymbols = 1; + int minimumPasswordNonLetter = 0; long maximumTimeToUnlock = 0; int maximumFailedPasswordsForWipe = 0; @@ -153,6 +155,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, "value", Integer.toString(minimumPasswordSymbols)); out.endTag(null, "min-password-symbols"); } + if (minimumPasswordNonLetter > 0) { + out.startTag(null, "min-password-nonletter"); + out.attribute(null, "value", Integer.toString(minimumPasswordNonLetter)); + out.endTag(null, "min-password-nonletter"); + } } if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { out.startTag(null, "max-time-to-unlock"); @@ -202,6 +209,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if ("min-password-symbols".equals(tag)) { minimumPasswordSymbols = Integer.parseInt( parser.getAttributeValue(null, "value")); + } else if ("min-password-nonletter".equals(tag)) { + minimumPasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "value")); } else if ("max-time-to-unlock".equals(tag)) { maximumTimeToUnlock = Long.parseLong( parser.getAttributeValue(null, "value")); @@ -240,6 +250,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.println(minimumPasswordNumeric); pw.print(prefix); pw.print("minimumPasswordSymbols="); pw.println(minimumPasswordSymbols); + pw.print(prefix); pw.print("minimumPasswordNonLetter="); + pw.println(minimumPasswordNonLetter); pw.print(prefix); pw.print("maximumTimeToUnlock="); pw.println(maximumTimeToUnlock); pw.print(prefix); pw.print("maximumFailedPasswordsForWipe="); @@ -429,7 +441,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mActivePasswordQuality != 0 || mActivePasswordLength != 0 || mActivePasswordUpperCase != 0 || mActivePasswordLowerCase != 0 || mActivePasswordLetters != 0 || mActivePasswordNumeric != 0 - || mActivePasswordSymbols != 0) { + || mActivePasswordSymbols != 0 || mActivePasswordNonLetter != 0) { out.startTag(null, "active-password"); out.attribute(null, "quality", Integer.toString(mActivePasswordQuality)); out.attribute(null, "length", Integer.toString(mActivePasswordLength)); @@ -439,6 +451,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, "numeric", Integer .toString(mActivePasswordNumeric)); out.attribute(null, "symbols", Integer.toString(mActivePasswordSymbols)); + out.attribute(null, "nonletter", Integer.toString(mActivePasswordNonLetter)); out.endTag(null, "active-password"); } @@ -447,6 +460,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endDocument(); stream.close(); journal.commit(); + sendChangedNotification(); } catch (IOException e) { try { if (stream != null) { @@ -459,6 +473,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void sendChangedNotification() { + Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcast(intent); + } + private void loadSettingsLocked() { JournaledFile journal = makeJournaledFile(); FileInputStream stream = null; @@ -522,6 +542,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { parser.getAttributeValue(null, "numeric")); mActivePasswordSymbols = Integer.parseInt( parser.getAttributeValue(null, "symbols")); + mActivePasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "nonletter")); XmlUtils.skipCurrentTag(parser); } else { Slog.w(TAG, "Unknown tag: " + tag); @@ -564,6 +586,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mActivePasswordLetters = 0; mActivePasswordNumeric = 0; mActivePasswordSymbols = 0; + mActivePasswordNonLetter = 0; } validatePasswordOwnerLocked(); @@ -967,6 +990,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + public void setPasswordMinimumNonLetter(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordNonLetter != length) { + ap.minimumPasswordNonLetter = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumNonLetter(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordNonLetter : length; + } + + final int N = mAdminList.size(); + for (int i=0; i<N; i++) { + ActiveAdmin admin = mAdminList.get(i); + if (length < admin.minimumPasswordNonLetter) { + length = admin.minimumPasswordNonLetter; + } + } + return length; + } + } + public boolean isActivePasswordSufficient() { synchronized (this) { // This API can only be called by an active device admin, @@ -984,7 +1041,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { && mActivePasswordLowerCase >= getPasswordMinimumLowerCase(null) && mActivePasswordLetters >= getPasswordMinimumLetters(null) && mActivePasswordNumeric >= getPasswordMinimumNumeric(null) - && mActivePasswordSymbols >= getPasswordMinimumSymbols(null); + && mActivePasswordSymbols >= getPasswordMinimumSymbols(null) + && mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null); } } @@ -1068,6 +1126,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int lowercase = 0; int numbers = 0; int symbols = 0; + int nonletter = 0; for (int i = 0; i < password.length(); i++) { char c = password.charAt(i); if (c >= 'A' && c <= 'Z') { @@ -1078,8 +1137,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { lowercase++; } else if (c >= '0' && c <= '9') { numbers++; + nonletter++; } else { symbols++; + nonletter++; } } int neededLetters = getPasswordMinimumLetters(null); @@ -1116,6 +1177,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + " does not meet required number of special symbols " + neededSymbols); return false; } + int neededNonLetter = getPasswordMinimumNonLetter(null); + if (nonletter < neededNonLetter) { + Slog.w(TAG, "resetPassword: number of non-letter characters " + nonletter + + " does not meet required number of non-letter characters " + + neededNonLetter); + return false; + } } LockPatternUtils utils = new LockPatternUtils(mContext); @@ -1275,7 +1343,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setActivePasswordState(int quality, int length, int letters, int uppercase, - int lowercase, int numbers, int symbols) { + int lowercase, int numbers, int symbols, int nonletter) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); @@ -1286,7 +1354,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || mFailedPasswordAttempts != 0 || mActivePasswordLetters != letters || mActivePasswordUpperCase != uppercase || mActivePasswordLowerCase != lowercase || mActivePasswordNumeric != numbers - || mActivePasswordSymbols != symbols) { + || mActivePasswordSymbols != symbols || mActivePasswordNonLetter != nonletter) { long ident = Binder.clearCallingIdentity(); try { mActivePasswordQuality = quality; @@ -1296,6 +1364,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mActivePasswordUpperCase = uppercase; mActivePasswordNumeric = numbers; mActivePasswordSymbols = symbols; + mActivePasswordNonLetter = nonletter; mFailedPasswordAttempts = 0; saveSettingsLocked(); sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, @@ -1384,6 +1453,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.print(" mActivePasswordLetters="); pw.println(mActivePasswordLetters); pw.print(" mActivePasswordNumeric="); pw.println(mActivePasswordNumeric); pw.print(" mActivePasswordSymbols="); pw.println(mActivePasswordSymbols); + pw.print(" mActivePasswordNonLetter="); pw.println(mActivePasswordNonLetter); pw.print(" mFailedPasswordAttempts="); pw.println(mFailedPasswordAttempts); pw.print(" mPasswordOwner="); pw.println(mPasswordOwner); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 4b86135..f6e3441 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -26,7 +26,7 @@ import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; -import com.android.server.status.StatusBarManagerService; +import com.android.server.StatusBarManagerService; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index ac3b23b..6f44e8e 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -17,7 +17,7 @@ package com.android.server; import com.android.internal.statusbar.StatusBarNotification; -import com.android.server.status.StatusBarManagerService; +import com.android.server.StatusBarManagerService; import android.app.ActivityManagerNative; import android.app.IActivityManager; @@ -70,6 +70,8 @@ class NotificationManagerService extends INotificationManager.Stub private static final String TAG = "NotificationService"; private static final boolean DBG = false; + private static final int MAX_PACKAGE_NOTIFICATIONS = 50; + // message codes private static final int MESSAGE_TIMEOUT = 2; @@ -301,6 +303,12 @@ class NotificationManagerService extends INotificationManager.Stub updateLightsLocked(); } } + + public void onNotificationError(String pkg, String tag, int id, String message) { + Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id); + cancelNotification(pkg, tag, id, 0, 0); + // TODO: Tell the activity manager. + } }; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -657,6 +665,26 @@ class NotificationManagerService extends INotificationManager.Stub { checkIncomingCall(pkg); + // Limit the number of notifications that any given package except the android + // package can enqueue. Prevents DOS attacks and deals with leaks. + if (!"android".equals(pkg)) { + synchronized (mNotificationList) { + int count = 0; + final int N = mNotificationList.size(); + for (int i=0; i<N; i++) { + final NotificationRecord r = mNotificationList.get(i); + if (r.pkg.equals(pkg)) { + count++; + if (count >= MAX_PACKAGE_NOTIFICATIONS) { + Slog.e(TAG, "Package has already posted " + count + + " notifications. Not showing more. package=" + pkg); + return; + } + } + } + } + } + // This conditional is a dirty hack to limit the logging done on // behalf of the download manager without affecting other apps. if (!pkg.equals("com.android.providers.downloads") diff --git a/services/java/com/android/server/status/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index 0af1ebb..1a16387 100644 --- a/services/java/com/android/server/status/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.android.server.status; +package com.android.server; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -55,9 +56,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub static final String TAG = "StatusBarManagerService"; static final boolean SPEW = true; - public static final String ACTION_STATUSBAR_START - = "com.android.internal.policy.statusbar.START"; - final Context mContext; Handler mHandler = new Handler(); NotificationCallbacks mNotificationCallbacks; @@ -87,6 +85,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub void onClearAll(); void onNotificationClick(String pkg, String tag, int id); void onPanelRevealed(); + void onNotificationError(String pkg, String tag, int id, String message); } /** @@ -96,7 +95,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub mContext = context; final Resources res = context.getResources(); - mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.status_bar_icon_order)); + mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons)); } public void setNotificationCallbacks(NotificationCallbacks listener) { @@ -111,9 +110,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub } public void systemReady2() { - // Start the status bar app - Intent intent = new Intent(ACTION_STATUSBAR_START); - mContext.sendBroadcast(intent /** permission **/); + ComponentName cn = ComponentName.unflattenFromString( + mContext.getString(com.android.internal.R.string.config_statusBarComponent)); + Intent intent = new Intent(); + intent.setComponent(cn); + Slog.i(TAG, "Starting service: " + cn); + mContext.startService(intent); } // ================================================================================ @@ -248,12 +250,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub "StatusBarManagerService"); } + private void enforceStatusBarService() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + "StatusBarManagerService"); + } + // ================================================================================ // Callbacks from the status bar service. // ================================================================================ public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, List<IBinder> notificationKeys, List<StatusBarNotification> notifications) { + enforceStatusBarService(); + Slog.i(TAG, "registerStatusBar bar=" + bar); mBar = bar; synchronized (mIcons) { @@ -268,18 +277,32 @@ public class StatusBarManagerService extends IStatusBarService.Stub } /** - * The status bar service should call this when the user changes whether - * the status bar is visible or not. + * The status bar service should call this each time the user brings the panel from + * invisible to visible in order to clear the notification light. */ - public void visibilityChanged(boolean visible) { - //Slog.d(TAG, "visibilityChanged visible=" + visible); + public void onPanelRevealed() { + enforceStatusBarService(); + + // tell the notification manager to turn off the lights. + mNotificationCallbacks.onPanelRevealed(); } public void onNotificationClick(String pkg, String tag, int id) { + enforceStatusBarService(); + mNotificationCallbacks.onNotificationClick(pkg, tag, id); } + public void onNotificationError(String pkg, String tag, int id, String message) { + enforceStatusBarService(); + + // WARNING: this will call back into us to do the remove. Don't hold any locks. + mNotificationCallbacks.onNotificationError(pkg, tag, id, message); + } + public void onClearAllNotifications() { + enforceStatusBarService(); + mNotificationCallbacks.onClearAll(); } @@ -423,24 +446,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } - /** - * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. - * This was added last-minute and is inconsistent with the way the rest of the notifications - * are handled, because the notification isn't really cancelled. The lights are just - * turned off. If any other notifications happen, the lights will turn back on. Steve says - * this is what he wants. (see bug 1131461) - */ - private boolean mPanelSlightlyVisible; - void panelSlightlyVisible(boolean visible) { - if (mPanelSlightlyVisible != visible) { - mPanelSlightlyVisible = visible; - if (visible) { - // tell the notification manager to turn off the lights. - mNotificationCallbacks.onPanelRevealed(); - } - } - } - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4307cdc..e7b8c02 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -17,33 +17,32 @@ package com.android.server; import com.android.server.am.ActivityManagerService; -import com.android.server.status.StatusBarManagerService; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; import dalvik.system.VMRuntime; import dalvik.system.Zygote; +import android.accounts.AccountManagerService; import android.app.ActivityManagerNative; import android.bluetooth.BluetoothAdapter; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentService; import android.content.Context; -import android.content.Intent; import android.content.pm.IPackageManager; import android.database.ContentObserver; -import android.database.Cursor; import android.media.AudioService; -import android.os.*; -import android.provider.Contacts.People; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.server.search.SearchManagerService; import android.util.EventLog; import android.util.Slog; -import android.accounts.AccountManagerService; import java.io.File; import java.util.Timer; @@ -51,11 +50,8 @@ import java.util.TimerTask; class ServerThread extends Thread { private static final String TAG = "SystemServer"; - private final static boolean INCLUDE_DEMO = false; - - private static final int LOG_BOOT_PROGRESS_SYSTEM_RUN = 3010; - private ContentResolver mContentResolver; + ContentResolver mContentResolver; private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { @@ -329,11 +325,6 @@ class ServerThread extends Thread { Slog.e(TAG, "Failure starting Search Service", e); } - if (INCLUDE_DEMO) { - Slog.i(TAG, "Installing demo data..."); - (new DemoThread(context)).start(); - } - try { Slog.i(TAG, "DropBox Service"); ServiceManager.addService(Context.DROPBOX_SERVICE, @@ -504,37 +495,7 @@ class ServerThread extends Thread { } } -class DemoThread extends Thread -{ - DemoThread(Context context) - { - mContext = context; - } - - @Override - public void run() - { - try { - Cursor c = mContext.getContentResolver().query(People.CONTENT_URI, null, null, null, null); - boolean hasData = c != null && c.moveToFirst(); - if (c != null) { - c.deactivate(); - } - if (!hasData) { - DemoDataSet dataset = new DemoDataSet(); - dataset.add(mContext); - } - } catch (Throwable e) { - Slog.e("SystemServer", "Failure installing demo data", e); - } - - } - - Context mContext; -} - -public class SystemServer -{ +public class SystemServer { private static final String TAG = "SystemServer"; public static final int FACTORY_TEST_OFF = 0; diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index 87f8a6e..445dd03 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -1217,8 +1217,9 @@ public class WifiWatchdogService { private static Random sRandom = new Random(); static boolean isDnsReachable(int dns, int timeout) { + DatagramSocket socket = null; try { - DatagramSocket socket = new DatagramSocket(); + socket = new DatagramSocket(); // Set some socket properties socket.setSoTimeout(timeout); @@ -1271,6 +1272,10 @@ public class WifiWatchdogService { Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e); } return false; + } finally { + if (socket != null) { + socket.close(); + } } } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index c828a7e..97d0f9b 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -55,7 +55,10 @@ import android.Manifest; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; @@ -234,11 +237,20 @@ public class WindowManagerService extends IWindowManager.Stub */ private boolean mKeyguardDisabled = false; + private static final int ALLOW_DISABLE_YES = 1; + private static final int ALLOW_DISABLE_NO = 0; + private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager + private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher + final TokenWatcher mKeyguardTokenWatcher = new TokenWatcher( new Handler(), "WindowManagerService.mKeyguardTokenWatcher") { public void acquired() { - mPolicy.enableKeyguard(false); - mKeyguardDisabled = true; + if (shouldAllowDisableKeyguard()) { + mPolicy.enableKeyguard(false); + mKeyguardDisabled = true; + } else { + Log.v(TAG, "Not disabling keyguard since device policy is enforced"); + } } public void released() { mPolicy.enableKeyguard(true); @@ -249,6 +261,18 @@ public class WindowManagerService extends IWindowManager.Stub } }; + final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mPolicy.enableKeyguard(true); + synchronized(mKeyguardTokenWatcher) { + // lazily evaluate this next time we're asked to disable keyguard + mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; + mKeyguardDisabled = false; + } + } + }; + final Context mContext; final boolean mHaveInputMethods; @@ -609,6 +633,11 @@ public class WindowManagerService extends IWindowManager.Stub mTransitionAnimationScale = Settings.System.getFloat(context.getContentResolver(), Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale); + // Track changes to DevicePolicyManager state so we can enable/disable keyguard. + IntentFilter filter = new IntentFilter(); + filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, filter); + int max_events_per_sec = 35; try { max_events_per_sec = Integer.parseInt(SystemProperties @@ -4172,17 +4201,20 @@ public class WindowManagerService extends IWindowManager.Stub // Misc IWindowSession methods // ------------------------------------------------------------- - private boolean allowDisableKeyguard() + private boolean shouldAllowDisableKeyguard() { - // We fail safe if this gets called before the service has started. - boolean allow = false; - DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( - Context.DEVICE_POLICY_SERVICE); - if (dpm != null) { - allow = dpm.getPasswordQuality(null) - == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + // We fail safe and prevent disabling keyguard in the unlikely event this gets + // called before DevicePolicyManagerService has started. + if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) { + DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (dpm != null) { + mAllowDisableKeyguard = dpm.getPasswordQuality(null) + == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ? + ALLOW_DISABLE_YES : ALLOW_DISABLE_NO; + } } - return allow; + return mAllowDisableKeyguard == ALLOW_DISABLE_YES; } public void disableKeyguard(IBinder token, String tag) { @@ -4191,12 +4223,8 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } - if (allowDisableKeyguard()) { - synchronized (mKeyguardTokenWatcher) { - mKeyguardTokenWatcher.acquire(token, tag); - } - } else { - Log.w(TAG, tag + ": disableKeyguard() ignored while DevicePolicyAmin is enabled."); + synchronized (mKeyguardTokenWatcher) { + mKeyguardTokenWatcher.acquire(token, tag); } } @@ -4206,29 +4234,25 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } - if (allowDisableKeyguard()) { - synchronized (mKeyguardTokenWatcher) { - mKeyguardTokenWatcher.release(token); - - if (!mKeyguardTokenWatcher.isAcquired()) { - // If we are the last one to reenable the keyguard wait until - // we have actaully finished reenabling until returning. - // It is possible that reenableKeyguard() can be called before - // the previous disableKeyguard() is handled, in which case - // neither mKeyguardTokenWatcher.acquired() or released() would - // be called. In that case mKeyguardDisabled will be false here - // and we have nothing to wait for. - while (mKeyguardDisabled) { - try { - mKeyguardTokenWatcher.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + synchronized (mKeyguardTokenWatcher) { + mKeyguardTokenWatcher.release(token); + + if (!mKeyguardTokenWatcher.isAcquired()) { + // If we are the last one to reenable the keyguard wait until + // we have actually finished reenabling until returning. + // It is possible that reenableKeyguard() can be called before + // the previous disableKeyguard() is handled, in which case + // neither mKeyguardTokenWatcher.acquired() or released() would + // be called. In that case mKeyguardDisabled will be false here + // and we have nothing to wait for. + while (mKeyguardDisabled) { + try { + mKeyguardTokenWatcher.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } } - } else { - Log.w(TAG, "reenableKeyguard() ignored while DevicePolicyAmin is enabled."); } } diff --git a/services/java/com/android/server/status/LatestItemView.java b/services/java/com/android/server/status/LatestItemView.java deleted file mode 100644 index fe8d164..0000000 --- a/services/java/com/android/server/status/LatestItemView.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.util.Slog; -import android.view.MotionEvent; -import android.widget.FrameLayout; - -public class LatestItemView extends FrameLayout { - - public LatestItemView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public boolean dispatchTouchEvent(MotionEvent ev) { - return onTouchEvent(ev); - } -} diff --git a/services/java/com/android/server/status/package.html b/services/java/com/android/server/status/package.html deleted file mode 100755 index c9f96a6..0000000 --- a/services/java/com/android/server/status/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<body> - -{@hide} - -</body> diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java index 3602fec..4d071e3 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -44,6 +44,7 @@ public class NotificationTestList extends TestActivity Vibrator mVibrator = new Vibrator(); Handler mHandler = new Handler(); + long mActivityCreateTime = System.currentTimeMillis(); long mChronometerBase = 0; @Override @@ -122,29 +123,49 @@ public class NotificationTestList extends TestActivity } }, - new Test("Bad Icon") { + new Test("Bad Icon #1 (when=create)") { public void run() { - mNM.notify(1, new Notification(NotificationTestList.this, - R.layout.chrono_notification, /* not a drawable! */ - null, System.currentTimeMillis()-(1000*60*60*24), - "(453) 123-2328", - "", null)); + Notification n = new Notification(R.layout.chrono_notification /* not an icon */, + null, mActivityCreateTime); + n.setLatestEventInfo(NotificationTestList.this, "Persistent #1", + "This is the same notification!!!", makeIntent()); + mNM.notify(1, n); } }, - new Test("Bad resource #2") { - public void run() - { - Notification n = new Notification(NotificationTestList.this, - R.drawable.ic_statusbar_missedcall, - null, System.currentTimeMillis()-(1000*60*60*24), - "(453) 123-2328", - "", null); + new Test("Bad Icon #1 (when=now)") { + public void run() { + Notification n = new Notification(R.layout.chrono_notification /* not an icon */, + null, System.currentTimeMillis()); + n.setLatestEventInfo(NotificationTestList.this, "Persistent #1", + "This is the same notification!!!", makeIntent()); + mNM.notify(1, n); + } + }, + + new Test("Bad resource #1 (when=create)") { + public void run() { + Notification n = new Notification(R.drawable.icon2, + null, mActivityCreateTime); + n.setLatestEventInfo(NotificationTestList.this, "Persistent #1", + "This is the same notification!!!", makeIntent()); n.contentView.setInt(1 /*bogus*/, "bogus method", 666); - mNM.notify(2, n); + mNM.notify(1, n); } }, + new Test("Bad resource #1 (when=now)") { + public void run() { + Notification n = new Notification(R.drawable.icon2, + null, System.currentTimeMillis()); + n.setLatestEventInfo(NotificationTestList.this, "Persistent #1", + "This is the same notification!!!", makeIntent()); + n.contentView.setInt(1 /*bogus*/, "bogus method", 666); + mNM.notify(1, n); + } + }, + + new Test("Bad resource #3") { public void run() { @@ -421,7 +442,7 @@ public class NotificationTestList extends TestActivity new Test("Persistent #1") { public void run() { Notification n = new Notification(R.drawable.icon1, "tick tick tick", - System.currentTimeMillis()); + mActivityCreateTime); n.setLatestEventInfo(NotificationTestList.this, "Persistent #1", "This is a notification!!!", makeIntent()); mNM.notify(1, n); @@ -481,6 +502,16 @@ public class NotificationTestList extends TestActivity } }, + new Test("Persistent #1 - different icon") { + public void run() { + Notification n = new Notification(R.drawable.icon2, null, + mActivityCreateTime); + n.setLatestEventInfo(NotificationTestList.this, "Persistent #1", + "This is the same notification!!!", makeIntent()); + mNM.notify(1, n); + } + }, + new Test("Chronometer Start") { public void run() { Notification n = new Notification(R.drawable.icon2, "me me me me", diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java index 3532e30..cb94e52 100644 --- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java @@ -157,5 +157,22 @@ public class StatusBarTest extends TestActivity }, 3000); } }, + new Test("More icons") { + public void run() { + for (String slot: new String[] { + "sync_failing", + "gps", + "bluetooth", + "tty", + "speakerphone", + "mute", + "wifi", + "alarm_clock", + "secure", + }) { + mStatusBarManager.setIconVisibility(slot, true); + } + } + }, }; } |
