diff options
248 files changed, 11909 insertions, 4085 deletions
@@ -167549,16 +167549,6 @@ visibility="public" > </method> -<field name="mDeviceId" - type="int" - transient="false" - volatile="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="protected" -> -</field> </class> <class name="KeyCharacterMap" extends="java.lang.Object" diff --git a/api/current.xml b/api/current.xml index 819c730..310944f 100644 --- a/api/current.xml +++ b/api/current.xml @@ -2176,6 +2176,17 @@ visibility="public" > </field> +<field name="actionLayout" + type="int" + transient="false" + volatile="false" + value="16843580" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="actionModeBackground" type="int" transient="false" @@ -2187,6 +2198,17 @@ visibility="public" > </field> +<field name="actionModeCloseButtonStyle" + type="int" + transient="false" + volatile="false" + value="16843576" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="actionModeCloseDrawable" type="int" transient="false" @@ -2209,6 +2231,17 @@ visibility="public" > </field> +<field name="actionViewClass" + type="int" + transient="false" + volatile="false" + value="16843581" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="activityCloseEnterAnimation" type="int" transient="false" @@ -3100,17 +3133,6 @@ visibility="public" > </field> -<field name="closeButtonStyle" - type="int" - transient="false" - volatile="false" - value="16843576" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="codes" type="int" transient="false" @@ -4926,6 +4948,17 @@ visibility="public" > </field> +<field name="iconifiedByDefault" + type="int" + transient="false" + volatile="false" + value="16843579" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="id" type="int" transient="false" @@ -53774,6 +53807,16 @@ visibility="public" > </field> +<field name="nativeLibraryDir" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="permission" type="java.lang.String" transient="false" @@ -65875,7 +65918,7 @@ return="void" abstract="false" native="false" - synchronized="true" + synchronized="false" static="false" final="false" deprecated="not deprecated" @@ -65914,7 +65957,7 @@ return="void" abstract="false" native="false" - synchronized="true" + synchronized="false" static="false" final="false" deprecated="not deprecated" @@ -93934,6 +93977,8 @@ > <parameter name="uid" type="int"> </parameter> +<parameter name="ws" type="android.os.WorkSource"> +</parameter> </method> <method name="onDisable" return="void" @@ -94063,6 +94108,8 @@ > <parameter name="uid" type="int"> </parameter> +<parameter name="ws" type="android.os.WorkSource"> +</parameter> </method> <method name="onRequiresCell" return="boolean" @@ -94124,6 +94171,8 @@ > <parameter name="minTime" type="long"> </parameter> +<parameter name="ws" type="android.os.WorkSource"> +</parameter> </method> <method name="onSupportsAltitude" return="boolean" @@ -98303,6 +98352,78 @@ <parameter name="quality" type="int"> </parameter> </method> +<method name="hasProfile" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="quality" type="int"> +</parameter> +</method> +<method name="hasProfile" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="cameraId" type="int"> +</parameter> +<parameter name="quality" type="int"> +</parameter> +</method> +<field name="QUALITY_1080P" + type="int" + transient="false" + volatile="false" + value="6" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_480P" + type="int" + transient="false" + volatile="false" + value="4" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_720P" + type="int" + transient="false" + volatile="false" + value="5" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_CIF" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="QUALITY_HIGH" type="int" transient="false" @@ -98325,6 +98446,94 @@ visibility="public" > </field> +<field name="QUALITY_QCIF" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_TIME_LAPSE_1080P" + type="int" + transient="false" + volatile="false" + value="1006" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_TIME_LAPSE_480P" + type="int" + transient="false" + volatile="false" + value="1004" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_TIME_LAPSE_720P" + type="int" + transient="false" + volatile="false" + value="1005" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_TIME_LAPSE_CIF" + type="int" + transient="false" + volatile="false" + value="1003" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_TIME_LAPSE_HIGH" + type="int" + transient="false" + volatile="false" + value="1001" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_TIME_LAPSE_LOW" + type="int" + transient="false" + volatile="false" + value="1000" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="QUALITY_TIME_LAPSE_QCIF" + type="int" + transient="false" + volatile="false" + value="1002" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="audioBitRate" type="int" transient="false" @@ -106264,6 +106473,19 @@ <parameter name="title" type="java.lang.String"> </parameter> </method> +<method name="setVisibleInDownloadsUi" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="isVisible" type="boolean"> +</parameter> +</method> <field name="NETWORK_MOBILE" type="int" transient="false" @@ -111287,6 +111509,19 @@ <parameter name="refCounted" type="boolean"> </parameter> </method> +<method name="setWorkSource" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="ws" type="android.os.WorkSource"> +</parameter> +</method> </class> </package> <package name="android.opengl" @@ -138139,6 +138374,19 @@ <parameter name="value" type="boolean"> </parameter> </method> +<method name="setWorkSource" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="ws" type="android.os.WorkSource"> +</parameter> +</method> </class> <class name="Process" extends="java.lang.Object" @@ -139287,6 +139535,134 @@ </parameter> </method> </class> +<class name="WorkSource" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.os.Parcelable"> +</implements> +<constructor name="WorkSource" + type="android.os.WorkSource" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<constructor name="WorkSource" + type="android.os.WorkSource" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="orig" type="android.os.WorkSource"> +</parameter> +</constructor> +<method name="add" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="other" type="android.os.WorkSource"> +</parameter> +</method> +<method name="clear" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="diff" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="other" type="android.os.WorkSource"> +</parameter> +</method> +<method name="remove" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="other" type="android.os.WorkSource"> +</parameter> +</method> +<method name="set" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="other" type="android.os.WorkSource"> +</parameter> +</method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dest" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> </package> <package name="android.os.storage" > @@ -141853,6 +142229,19 @@ <parameter name="args" type="android.os.Bundle"> </parameter> </method> +<method name="switchToHeader" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="android.preference.PreferenceActivity.Header"> +</parameter> +</method> <field name="EXTRA_NO_HEADERS" type="java.lang.String" transient="false" @@ -141886,15 +142275,28 @@ visibility="public" > </field> +<field name="HEADER_ID_UNDEFINED" + type="long" + transient="false" + volatile="false" + value="-1L" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="PreferenceActivity.Header" extends="java.lang.Object" abstract="false" static="true" - final="false" + final="true" deprecated="not deprecated" visibility="public" > +<implements name="android.os.Parcelable"> +</implements> <constructor name="PreferenceActivity.Header" type="android.preference.PreferenceActivity.Header" static="false" @@ -141903,6 +142305,65 @@ visibility="public" > </constructor> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="readFromParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="in" type="android.os.Parcel"> +</parameter> +</method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dest" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="extras" + type="android.os.Bundle" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="fragment" type="java.lang.String" transient="false" @@ -141923,8 +142384,8 @@ visibility="public" > </field> -<field name="icon" - type="android.graphics.drawable.Drawable" +<field name="iconRes" + type="int" transient="false" volatile="false" static="false" @@ -141933,8 +142394,8 @@ visibility="public" > </field> -<field name="iconRes" - type="int" +<field name="id" + type="long" transient="false" volatile="false" static="false" @@ -189619,7 +190080,7 @@ native="false" synchronized="false" static="false" - final="true" + final="false" deprecated="not deprecated" visibility="public" > @@ -189667,26 +190128,6 @@ visibility="public" > </field> -<field name="mDeviceId" - type="int" - transient="false" - volatile="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="protected" -> -</field> -<field name="mSource" - type="int" - transient="false" - volatile="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="protected" -> -</field> </class> <class name="InputQueue" extends="java.lang.Object" @@ -193077,6 +193518,17 @@ deprecated="not deprecated" visibility="public" > +<method name="getActionView" + return="android.view.View" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getAlphabeticShortcut" return="char" abstract="true" @@ -193253,6 +193705,19 @@ visibility="public" > </method> +<method name="setActionView" + return="android.view.MenuItem" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="view" type="android.view.View"> +</parameter> +</method> <method name="setAlphabeticShortcut" return="android.view.MenuItem" abstract="true" @@ -198575,6 +199040,17 @@ visibility="public" > </method> +<method name="isHardwareAccelerated" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isHorizontalFadingEdgeEnabled" return="boolean" abstract="false" @@ -202620,6 +203096,19 @@ <parameter name="drawingTime" type="long"> </parameter> </method> +<method name="endViewTransition" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="view" type="android.view.View"> +</parameter> +</method> <method name="focusSearch" return="android.view.View" abstract="false" @@ -203517,6 +204006,19 @@ visibility="public" > </method> +<method name="startViewTransition" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="view" type="android.view.View"> +</parameter> +</method> <method name="updateViewLayout" return="void" abstract="false" @@ -233481,6 +233983,265 @@ > </method> </class> +<class name="SearchView" + extends="android.widget.LinearLayout" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="SearchView" + type="android.widget.SearchView" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</constructor> +<constructor name="SearchView" + type="android.widget.SearchView" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="attrs" type="android.util.AttributeSet"> +</parameter> +</constructor> +<method name="getSuggestionsAdapter" + return="android.widget.CursorAdapter" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isIconfiedByDefault" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isIconified" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isSubmitButtonEnabled" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="setIconified" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="iconify" type="boolean"> +</parameter> +</method> +<method name="setIconifiedByDefault" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="iconified" type="boolean"> +</parameter> +</method> +<method name="setOnCloseListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.widget.SearchView.OnCloseListener"> +</parameter> +</method> +<method name="setOnQueryChangeListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.widget.SearchView.OnQueryChangeListener"> +</parameter> +</method> +<method name="setQuery" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="query" type="java.lang.CharSequence"> +</parameter> +<parameter name="submit" type="boolean"> +</parameter> +</method> +<method name="setQueryHint" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="hint" type="java.lang.CharSequence"> +</parameter> +</method> +<method name="setSearchableInfo" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="searchable" type="android.app.SearchableInfo"> +</parameter> +</method> +<method name="setSubmitButtonEnabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="enabled" type="boolean"> +</parameter> +</method> +<method name="setSuggestionsAdapter" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="adapter" type="android.widget.CursorAdapter"> +</parameter> +</method> +</class> +<interface name="SearchView.FilterableListAdapter" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.widget.Filterable"> +</implements> +<implements name="android.widget.ListAdapter"> +</implements> +</interface> +<interface name="SearchView.OnCloseListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onClose" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +</interface> +<interface name="SearchView.OnQueryChangeListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onQueryTextChanged" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="newText" type="java.lang.String"> +</parameter> +</method> +<method name="onSubmitQuery" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="query" type="java.lang.String"> +</parameter> +</method> +</interface> <interface name="SectionIndexer" abstract="true" static="false" diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java index d3ec3d9..e1d6619 100644 --- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java +++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java @@ -64,7 +64,7 @@ public class PowerCommand extends Svc.Command { = IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE)); try { IBinder lock = new Binder(); - pm.acquireWakeLock(PowerManager.FULL_WAKE_LOCK, lock, "svc power"); + pm.acquireWakeLock(PowerManager.FULL_WAKE_LOCK, lock, "svc power", null); pm.setStayOnSetting(val); pm.releaseWakeLock(lock, 0); } diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 54a8e4b..1e2bbcc 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -686,6 +686,13 @@ public class ValueAnimator<T> extends Animator { private void start(boolean playBackwards) { mPlayingBackwards = playBackwards; if ((mStartDelay == 0) && (Thread.currentThread() == Looper.getMainLooper().getThread())) { + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationStart(this); + } + } // This sets the initial value of the animation, prior to actually starting it running setCurrentPlayTime(getCurrentPlayTime()); } @@ -783,7 +790,9 @@ public class ValueAnimator<T> extends Animator { private void startAnimation() { initAnimation(); sAnimations.add(this); - if (mListeners != null) { + if (mStartDelay > 0 && mListeners != null) { + // Listeners were already notified in start() if startDelay is 0; this is + // just for delayed animations ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); for (AnimatorListener listener : tmpListeners) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 3dcdc0a..2e8d682 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4058,6 +4058,7 @@ public class Activity extends ContextThemeWrapper fragment.mFragmentId = id; fragment.mTag = tag; fragment.mImmediateActivity = this; + fragment.mFragmentManager = mFragments; // If this fragment is newly instantiated (either right now, or // from last saved state), then give it the attributes to // initialize itself. @@ -4203,6 +4204,7 @@ public class Activity extends ContextThemeWrapper } final void performStart() { + mFragments.mStateSaved = false; mCalled = false; mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); @@ -4220,6 +4222,8 @@ public class Activity extends ContextThemeWrapper } final void performRestart() { + mFragments.mStateSaved = false; + synchronized (mManagedCursors) { final int N = mManagedCursors.size(); for (int i=0; i<N; i++) { @@ -4358,6 +4362,7 @@ public class Activity extends ContextThemeWrapper if (Config.LOGV) Log.v( TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode + ", resCode=" + resultCode + ", data=" + data); + mFragments.mStateSaved = false; if (who == null) { onActivityResult(requestCode, resultCode, data); } else { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e4455fb..60fcdc4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3154,6 +3154,8 @@ public final class ActivityThread { /** * For system applications on userdebug/eng builds, log stack * traces of disk and network access to dropbox for analysis. + * + * Similar logic exists in SystemServer.java. */ if ((data.appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java index 71fd5e5..296b495 100644 --- a/core/java/android/app/BackStackEntry.java +++ b/core/java/android/app/BackStackEntry.java @@ -205,6 +205,7 @@ final class BackStackEntry implements FragmentTransaction, Runnable { throw new IllegalStateException("Fragment already added: " + fragment); } fragment.mImmediateActivity = mManager.mActivity; + fragment.mFragmentManager = mManager; if (tag != null) { if (fragment.mTag != null && !tag.equals(fragment.mTag)) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 16d105a..1cbed79 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -94,6 +94,7 @@ final class FragmentState implements Parcelable { mInstance.mContainerId = mContainerId; mInstance.mTag = mTag; mInstance.mRetainInstance = mRetainInstance; + mInstance.mFragmentManager = activity.mFragments; return mInstance; } @@ -318,6 +319,11 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener // Number of active back stack entries this fragment is in. int mBackStackNesting; + // The fragment manager we are associated with. Set as soon as the + // fragment is used in a transaction; cleared after it has been removed + // from all transactions. + FragmentManager mFragmentManager; + // Set as soon as a fragment is added to a transaction (or removed), // to be able to do validation. Activity mImmediateActivity; @@ -578,10 +584,13 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener /** * Return the FragmentManager for interacting with fragments associated - * with this fragment's activity. + * with this fragment's activity. Note that this will be non-null slightly + * before {@link #getActivity()}, in the time from when the fragment is + * placed in a {@link FragmentTransaction} until it is committed and + * attached to its activity. */ final public FragmentManager getFragmentManager() { - return mActivity.mFragments; + return mFragmentManager; } /** @@ -858,7 +867,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener * {@link #onActivityCreated(Bundle)}. * * <p>This corresponds to {@link Activity#onSaveInstanceState(Bundle) - * Activity.onnSaveInstanceState(Bundle)} and most of the discussion there + * Activity.onSaveInstanceState(Bundle)} and most of the discussion there * applies here as well. Note however: <em>this method may be called * at any time before {@link #onDestroy()}</em>. There are many situations * where a fragment may be mostly torn down (such as when placed on the diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 4d4f892..a704ec8 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -18,8 +18,7 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorInflater; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; +import android.animation.AnimatorListenerAdapter; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Handler; @@ -193,6 +192,7 @@ final class FragmentManagerImpl implements FragmentManager { Activity mActivity; boolean mNeedMenuInvalidate; + boolean mStateSaved; // Temporary vars for state save and restore. Bundle mStateBundle = null; @@ -363,11 +363,7 @@ final class FragmentManagerImpl implements FragmentManager { Animator anim = loadAnimator(f, transit, true, transitionStyle); if (anim != null) { - if (anim instanceof AnimatorSet) { - ((AnimatorSet)anim).setTarget(f.mView); - } else if (anim instanceof ObjectAnimator) { - ((ObjectAnimator)anim).setTarget(f.mView); - } + anim.setTarget(f.mView); anim.start(); } container.addView(f.mView); @@ -447,17 +443,24 @@ final class FragmentManagerImpl implements FragmentManager { + " did not call through to super.onDestroyedView()"); } if (f.mView != null && f.mContainer != null) { + Animator anim = null; if (mCurState > Fragment.INITIALIZING) { - Animator anim = loadAnimator(f, transit, true, + anim = loadAnimator(f, transit, false, transitionStyle); - if (anim != null) { - if (anim instanceof AnimatorSet) { - ((AnimatorSet)anim).setTarget(f.mView); - } else if (anim instanceof ObjectAnimator) { - ((ObjectAnimator)anim).setTarget(f.mView); + } + if (anim != null) { + final ViewGroup container = f.mContainer; + final View view = f.mView; + container.startViewTransition(view); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator anim) { + container.endViewTransition(view); } - anim.start(); - } + }); + anim.setTarget(f.mView); + anim.start(); + } f.mContainer.removeView(f.mView); } @@ -482,6 +485,7 @@ final class FragmentManagerImpl implements FragmentManager { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onDetach()"); } + f.mImmediateActivity = null; f.mActivity = null; } } @@ -591,11 +595,7 @@ final class FragmentManagerImpl implements FragmentManager { Animator anim = loadAnimator(fragment, transition, true, transitionStyle); if (anim != null) { - if (anim instanceof AnimatorSet) { - ((AnimatorSet)anim).setTarget(fragment.mView); - } else if (anim instanceof ObjectAnimator) { - ((ObjectAnimator)anim).setTarget(fragment.mView); - } + anim.setTarget(fragment.mView); anim.start(); } fragment.mView.setVisibility(View.GONE); @@ -615,11 +615,7 @@ final class FragmentManagerImpl implements FragmentManager { Animator anim = loadAnimator(fragment, transition, true, transitionStyle); if (anim != null) { - if (anim instanceof AnimatorSet) { - ((AnimatorSet)anim).setTarget(fragment.mView); - } else if (anim instanceof ObjectAnimator) { - ((ObjectAnimator)anim).setTarget(fragment.mView); - } + anim.setTarget(fragment.mView); anim.start(); } fragment.mView.setVisibility(View.VISIBLE); @@ -684,6 +680,10 @@ final class FragmentManagerImpl implements FragmentManager { } public void enqueueAction(Runnable action) { + if (mStateSaved) { + throw new IllegalStateException( + "Can not perform this action after onSaveInstanceState"); + } synchronized (this) { if (mPendingActions == null) { mPendingActions = new ArrayList<Runnable>(); @@ -894,6 +894,8 @@ final class FragmentManagerImpl implements FragmentManager { } Parcelable saveAllState() { + mStateSaved = true; + if (mActive == null || mActive.size() <= 0) { return null; } @@ -1035,6 +1037,22 @@ final class FragmentManagerImpl implements FragmentManager { } } + // Update the target of all retained fragments. + if (nonConfig != null) { + for (int i=0; i<nonConfig.size(); i++) { + Fragment f = nonConfig.get(i); + if (f.mTarget != null) { + if (f.mTarget.mIndex < mActive.size()) { + f.mTarget = mActive.get(f.mTarget.mIndex); + } else { + Log.w(TAG, "Re-attaching retained fragment " + f + + " target no longer exists: " + f.mTarget); + f.mTarget = null; + } + } + } + } + // Build the list of currently added fragments. if (fms.mAdded != null) { mAdded = new ArrayList<Fragment>(fms.mAdded.length); @@ -1076,18 +1094,22 @@ final class FragmentManagerImpl implements FragmentManager { } public void dispatchCreate() { + mStateSaved = false; moveToState(Fragment.CREATED, false); } public void dispatchActivityCreated() { + mStateSaved = false; moveToState(Fragment.ACTIVITY_CREATED, false); } public void dispatchStart() { + mStateSaved = false; moveToState(Fragment.STARTED, false); } public void dispatchResume() { + mStateSaved = false; moveToState(Fragment.RESUMED, false); } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index e455a59..92b7cf5 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -235,8 +235,13 @@ public class WallpaperManager { if (width <= 0 || height <= 0) { // Degenerate case: no size requested, just load // bitmap as-is. - Bitmap bm = BitmapFactory.decodeFileDescriptor( - fd.getFileDescriptor(), null, null); + Bitmap bm = null; + try { + bm = BitmapFactory.decodeFileDescriptor( + fd.getFileDescriptor(), null, null); + } catch (OutOfMemoryError e) { + Log.w(TAG, "Can't decode file", e); + } try { fd.close(); } catch (IOException e) { @@ -277,7 +282,12 @@ public class WallpaperManager { if (width <= 0 || height <= 0) { // Degenerate case: no size requested, just load // bitmap as-is. - Bitmap bm = BitmapFactory.decodeStream(is, null, null); + Bitmap bm = null; + try { + bm = BitmapFactory.decodeStream(is, null, null); + } catch (OutOfMemoryError e) { + Log.w(TAG, "Can't decode stream", e); + } try { is.close(); } catch (IOException e) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 03bcadc..16a8c57 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -26,8 +26,10 @@ import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import android.util.Pair; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; @@ -863,6 +865,37 @@ public final class BluetoothAdapter { return socket; } + /** + * Read the local Out of Band Pairing Data + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return Pair<byte[], byte[]> of Hash and Randomizer + * + * @hide + */ + public Pair<byte[], byte[]> readOutOfBandData() { + if (getState() != STATE_ON) return null; + try { + byte[] hash = new byte[16]; + byte[] randomizer = new byte[16]; + + byte[] ret = mService.readOutOfBandData(); + + if (ret == null || ret.length != 32) return null; + + hash = Arrays.copyOfRange(ret, 0, 16); + randomizer = Arrays.copyOfRange(ret, 16, 32); + + if (DBG) { + Log.d(TAG, "readOutOfBandData:" + Arrays.toString(hash) + + ":" + Arrays.toString(randomizer)); + } + return new Pair<byte[], byte[]>(hash, randomizer); + + } catch (RemoteException e) {Log.e(TAG, "", e);} + return null; + } + private Set<BluetoothDevice> toDeviceSet(String[] addresses) { Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length); for (int i = 0; i < addresses.length; i++) { diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index e77e76f..e577ec4 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -325,7 +325,9 @@ public final class BluetoothDevice implements Parcelable { /** The user will be prompted to enter the passkey displayed on remote device * @hide */ public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; - + /** The user will be prompted to accept or deny the OOB pairing request + * @hide */ + public static final int PAIRING_VARIANT_OOB_CONSENT = 5; /** * Used as an extra field in {@link #ACTION_UUID} intents, * Contains the {@link android.os.ParcelUuid}s of the remote device which @@ -464,6 +466,52 @@ public final class BluetoothDevice implements Parcelable { } /** + * Start the bonding (pairing) process with the remote device using the + * Out Of Band mechanism. + * + * <p>This is an asynchronous call, it will return immediately. Register + * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when + * the bonding process completes, and its result. + * + * <p>Android system services will handle the necessary user interactions + * to confirm and complete the bonding process. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * + * @param hash - Simple Secure pairing hash + * @param randomizer - The random key obtained using OOB + * @return false on immediate error, true if bonding will begin + * + * @hide + */ + public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) { + try { + return sService.createBondOutOfBand(mAddress, hash, randomizer); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Set the Out Of Band data for a remote device to be used later + * in the pairing mechanism. Users can obtain this data through other + * trusted channels + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * + * @param hash Simple Secure pairing hash + * @param randomizer The random key obtained using OOB + * @return false on error; true otherwise + * + * @hide + */ + public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) { + try { + return sService.setDeviceOutOfBandData(mAddress, hash, randomizer); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** * Cancel an in-progress bonding request started with {@link #createBond}. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * @@ -617,6 +665,14 @@ public final class BluetoothDevice implements Parcelable { } /** @hide */ + public boolean setRemoteOutOfBandData() { + try { + return sService.setRemoteOutOfBandData(mAddress); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** @hide */ public boolean cancelPairingUserInput() { try { return sService.cancelPairingUserInput(mAddress); diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index c4a40cd..cc23146 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -45,12 +45,15 @@ interface IBluetooth boolean startDiscovery(); boolean cancelDiscovery(); boolean isDiscovering(); + byte[] readOutOfBandData(); boolean createBond(in String address); + boolean createBondOutOfBand(in String address, in byte[] hash, in byte[] randomizer); boolean cancelBondProcess(in String address); boolean removeBond(in String address); String[] listBonds(); int getBondState(in String address); + boolean setDeviceOutOfBandData(in String address, in byte[] hash, in byte[] randomizer); String getRemoteName(in String address); int getRemoteClass(in String address); @@ -61,6 +64,7 @@ interface IBluetooth boolean setPin(in String address, in byte[] pin); boolean setPasskey(in String address, int passkey); boolean setPairingConfirmation(in String address, boolean confirm); + boolean setRemoteOutOfBandData(in String addres); boolean cancelPairingUserInput(in String address); boolean setTrust(in String address, in boolean value); diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 7f749bb..950d339 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -47,6 +47,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; import android.text.format.Time; @@ -129,8 +130,8 @@ public class SyncManager implements OnAccountsUpdateListener { private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000; - private static final String SYNC_WAKE_LOCK_PREFIX = "SyncWakeLock"; - private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock"; + private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*"; + private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; private Context mContext; @@ -1735,6 +1736,7 @@ public class SyncManager implements OnAccountsUpdateListener { PowerManager.WakeLock oldWakeLock = mSyncWakeLock; try { mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority); + mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid)); mSyncWakeLock.acquire(); } finally { if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) { diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index ae6a311..38d897e 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -354,8 +354,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * Full path to the directory where native JNI libraries are stored. - * - * {@hide} */ public String nativeLibraryDir; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8963d0c..1a3bcc4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -192,7 +192,7 @@ public abstract class PackageManager { /** * Signature check result: this is returned by {@link #checkSignatures} - * if the two packages have a matching signature. + * if all signatures on the two packages match. */ public static final int SIGNATURE_MATCH = 0; @@ -204,25 +204,25 @@ public abstract class PackageManager { /** * Signature check result: this is returned by {@link #checkSignatures} - * if the first package is not signed, but the second is. + * if the first package is not signed but the second is. */ public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; /** * Signature check result: this is returned by {@link #checkSignatures} - * if the second package is not signed, but the first is. + * if the second package is not signed but the first is. */ public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; /** * Signature check result: this is returned by {@link #checkSignatures} - * if both packages are signed but there is no matching signature. + * if not all signatures on both packages match. */ public static final int SIGNATURE_NO_MATCH = -3; /** * Signature check result: this is returned by {@link #checkSignatures} - * if either of the given package names are not valid. + * if either of the packages are not valid. */ public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; @@ -1245,20 +1245,14 @@ public abstract class PackageManager { * * @param pkg1 First package name whose signature will be compared. * @param pkg2 Second package name whose signature will be compared. - * @return Returns an integer indicating whether there is a matching - * signature: the value is >= 0 if there is a match (or neither package - * is signed), or < 0 if there is not a match. The match result can be - * further distinguished with the success (>= 0) constants - * {@link #SIGNATURE_MATCH}, {@link #SIGNATURE_NEITHER_SIGNED}; or - * failure (< 0) constants {@link #SIGNATURE_FIRST_NOT_SIGNED}, - * {@link #SIGNATURE_SECOND_NOT_SIGNED}, {@link #SIGNATURE_NO_MATCH}, - * or {@link #SIGNATURE_UNKNOWN_PACKAGE}. + * + * @return Returns an integer indicating whether all signatures on the + * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if + * all signatures match or < 0 if there is not a match ({@link + * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}). * * @see #checkSignatures(int, int) * @see #SIGNATURE_MATCH - * @see #SIGNATURE_NEITHER_SIGNED - * @see #SIGNATURE_FIRST_NOT_SIGNED - * @see #SIGNATURE_SECOND_NOT_SIGNED * @see #SIGNATURE_NO_MATCH * @see #SIGNATURE_UNKNOWN_PACKAGE */ @@ -1273,20 +1267,14 @@ public abstract class PackageManager { * * @param uid1 First UID whose signature will be compared. * @param uid2 Second UID whose signature will be compared. - * @return Returns an integer indicating whether there is a matching - * signature: the value is >= 0 if there is a match (or neither package - * is signed), or < 0 if there is not a match. The match result can be - * further distinguished with the success (>= 0) constants - * {@link #SIGNATURE_MATCH}, {@link #SIGNATURE_NEITHER_SIGNED}; or - * failure (< 0) constants {@link #SIGNATURE_FIRST_NOT_SIGNED}, - * {@link #SIGNATURE_SECOND_NOT_SIGNED}, {@link #SIGNATURE_NO_MATCH}, - * or {@link #SIGNATURE_UNKNOWN_PACKAGE}. * - * @see #checkSignatures(int, int) + * @return Returns an integer indicating whether all signatures on the + * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if + * all signatures match or < 0 if there is not a match ({@link + * #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}). + * + * @see #checkSignatures(String, String) * @see #SIGNATURE_MATCH - * @see #SIGNATURE_NEITHER_SIGNED - * @see #SIGNATURE_FIRST_NOT_SIGNED - * @see #SIGNATURE_SECOND_NOT_SIGNED * @see #SIGNATURE_NO_MATCH * @see #SIGNATURE_UNKNOWN_PACKAGE */ diff --git a/core/java/android/database/sqlite/DatabaseConnectionPool.java b/core/java/android/database/sqlite/DatabaseConnectionPool.java index 54b0605..aede438 100644 --- a/core/java/android/database/sqlite/DatabaseConnectionPool.java +++ b/core/java/android/database/sqlite/DatabaseConnectionPool.java @@ -99,7 +99,7 @@ import java.util.Random; poolObj = mPool.get(0); } else { for (int i = 0; i < mMaxPoolSize; i++) { - if (mPool.get(i).mDb.isSqlInStatementCache(sql)) { + if (mPool.get(i).mDb.mCache.isSqlInStatementCache(sql)) { poolObj = mPool.get(i); break; } @@ -125,7 +125,8 @@ import java.util.Random; // there are free connections available. pick one // preferably a connection caching the pre-compiled statement of the given SQL for (int i = 0; i < poolSize; i++) { - if (mPool.get(i).isFree() && mPool.get(i).mDb.isSqlInStatementCache(sql)) { + if (mPool.get(i).isFree() && + mPool.get(i).mDb.mCache.isSqlInStatementCache(sql)) { poolObj = mPool.get(i); break; } diff --git a/core/java/android/database/sqlite/SQLiteCache.java b/core/java/android/database/sqlite/SQLiteCache.java new file mode 100644 index 0000000..049fb62 --- /dev/null +++ b/core/java/android/database/sqlite/SQLiteCache.java @@ -0,0 +1,214 @@ +/* + * 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 android.database.sqlite; + +import android.util.Log; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * For each instance of {@link SQLiteDatabase}, this class maintains a LRU cache to store + * the compiled query statement ids returned by sqlite database. + *<p> + *<ul> + * <li>key = SQL statement with "?" for bind args</li> + * <li>value = {@link SQLiteCompiledSql}</li> + *</ul> + * If an application opens the database and keeps it open during its entire life, then + * there will not be an overhead of compilation of SQL statements by sqlite. + *<p> + * Why is this cache NOT static? because sqlite attaches compiled-sql statements to the + * database connections. + *<p> + * This cache has an upper limit of mMaxSqlCacheSize (settable by calling the method + * (@link #setMaxSqlCacheSize(int)}). + */ +/* package */ class SQLiteCache { + private static final String TAG = "SQLiteCache"; + + /** The {@link SQLiteDatabase} instance this cache is attached to */ + private final SQLiteDatabase mDatabase; + + /** Default statement-cache size per database connection ( = instance of this class) */ + private int mMaxSqlCacheSize = 25; + + /** The LRU cache */ + private final Map<String, SQLiteCompiledSql> mCompiledQueries = + new LinkedHashMap<String, SQLiteCompiledSql>(mMaxSqlCacheSize + 1, 0.75f, true) { + @Override + public boolean removeEldestEntry(Map.Entry<String, SQLiteCompiledSql> eldest) { + // eldest = least-recently used entry + // if it needs to be removed to accommodate a new entry, + // close {@link SQLiteCompiledSql} represented by this entry, if not in use + // and then let it be removed from the Map. + mDatabase.verifyLockOwner(); + if (this.size() <= mMaxSqlCacheSize) { + // cache is not full. nothing needs to be removed + return false; + } + // cache is full. eldest will be removed. + eldest.getValue().releaseIfNotInUse(); + // return true, so that this entry is removed automatically by the caller. + return true; + } + }; + + /** Maintains whether or not cacheFullWarning has been logged */ + private boolean mCacheFullWarning; + + /** The following 2 members maintain stats about cache hits and misses */ + private int mNumCacheHits; + private int mNumCacheMisses; + + /** + * Constructor used by {@link SQLiteDatabase}. + * @param db + */ + /* package */ SQLiteCache(SQLiteDatabase db) { + mDatabase = db; + } + + /** + * Adds the given SQL and its compiled-statement to the cache, if the given SQL statement + * doesn't already exist in cache. + * + * @return true if added to cache. false otherwise. + */ + /* package */ synchronized boolean addToCompiledQueries(String sql, + SQLiteCompiledSql compiledStatement) { + if (mCompiledQueries.containsKey(sql)) { + // already exists. + return false; + } + + /* add the given SQLiteCompiledSql compiledStatement to cache. + * no need to worry about the cache size - because {@link #mCompiledQueries} + * self-limits its size to {@link #mMaxSqlCacheSize}. + */ + mCompiledQueries.put(sql, compiledStatement); + + // need to log a warning to say that the cache is full? + if (!isCacheFullWarningLogged() && mCompiledQueries.size() == mMaxSqlCacheSize) { + /* + * cache size of {@link #mMaxSqlCacheSize} is not enough for this app. + * log a warning. + * chances are it is NOT using ? for bindargs - or cachesize is too small. + */ + Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + + mDatabase.getPath() + + ". Use setMaxSqlCacheSize() in SQLiteDatabase to increase cachesize. "); + setCacheFullWarningLogged(); + } + return true; + } + + /** + * Returns the compiled-statement for the given SQL statement, if the entry exists in cache + * and is free to use. Returns null otherwise. + * <p> + * If a compiled-sql statement is returned for the caller, it is reserved for the caller. + * So, don't use this method unless the caller needs to acquire the object. + */ + /* package */ synchronized SQLiteCompiledSql getCompiledStatementForSql(String sql) { + SQLiteCompiledSql compiledStatement = mCompiledQueries.get(sql); + if (compiledStatement == null) { + mNumCacheMisses++; + return null; + } + mNumCacheHits++; + // reserve it for the caller, if it is not already in use + if (!compiledStatement.acquire()) { + // couldn't acquire it since it is already in use. bug in app? + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + Log.w(TAG, "Possible bug: Either using the same SQL in 2 threads at " + + " the same time, or previous instance of this SQL statement was " + + "never close()d. " + compiledStatement.toString()); + } + return null; + } + return compiledStatement; + } + + /** + * If the given statement is in cache, it is released back to cache and it is made available for + * others to use. + * <p> + * return true if the statement is put back in cache, false otherwise (false = the statement + * is NOT in cache) + */ + /* package */ synchronized boolean releaseBackToCache(SQLiteCompiledSql stmt) { + if (!mCompiledQueries.containsValue(stmt)) { + return false; + } + // it is in cache. release it from the caller, make it available for others to use + stmt.free(); + return true; + } + + /** + * releases all compiled-sql statements in the cache. + */ + /* package */ synchronized void dealloc() { + for (SQLiteCompiledSql stmt : mCompiledQueries.values()) { + stmt.setState(SQLiteCompiledSql.NSTATE_CACHE_DEALLOC); + stmt.releaseFromDatabase(); + } + mCompiledQueries.clear(); + } + + /** + * see documentation on {@link SQLiteDatabase#setMaxSqlCacheSize(int)}. + */ + /* package */ synchronized void setMaxSqlCacheSize(int cacheSize) { + if (cacheSize > SQLiteDatabase.MAX_SQL_CACHE_SIZE || cacheSize < 0) { + throw new IllegalStateException("expected value between 0 and " + + SQLiteDatabase.MAX_SQL_CACHE_SIZE); + } else if (cacheSize < mMaxSqlCacheSize) { + throw new IllegalStateException("cannot set cacheSize to a value less than the value " + + "set with previous setMaxSqlCacheSize() call."); + } + mMaxSqlCacheSize = cacheSize; + } + + /* package */ synchronized boolean isSqlInStatementCache(String sql) { + return mCompiledQueries.containsKey(sql); + } + + private synchronized boolean isCacheFullWarningLogged() { + return mCacheFullWarning; + } + + private synchronized void setCacheFullWarningLogged() { + mCacheFullWarning = true; + } + /* package */ synchronized int getCacheHitNum() { + return mNumCacheHits; + } + /* package */ synchronized int getCacheMissNum() { + return mNumCacheMisses; + } + /* package */ synchronized int getCachesize() { + return mCompiledQueries.size(); + } + + // only for testing + /* package */ synchronized Set<String> getKeys() { + return mCompiledQueries.keySet(); + } +} diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java index 21d2ab0..58dab2b 100644 --- a/core/java/android/database/sqlite/SQLiteClosable.java +++ b/core/java/android/database/sqlite/SQLiteClosable.java @@ -23,13 +23,12 @@ import android.database.CursorWindow; */ public abstract class SQLiteClosable { private int mReferenceCount = 1; - private Object mLock = new Object(); protected abstract void onAllReferencesReleased(); protected void onAllReferencesReleasedFromContainer() {} public void acquireReference() { - synchronized(mLock) { + synchronized(this) { checkRefCount(); if (mReferenceCount <= 0) { throw new IllegalStateException( @@ -40,7 +39,7 @@ public abstract class SQLiteClosable { } public void releaseReference() { - synchronized(mLock) { + synchronized(this) { checkRefCount(); mReferenceCount--; if (mReferenceCount == 0) { @@ -50,7 +49,7 @@ public abstract class SQLiteClosable { } public void releaseReferenceFromContainer() { - synchronized(mLock) { + synchronized(this) { checkRefCount(); mReferenceCount--; if (mReferenceCount == 0) { diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java index 0b5a4df..120d090 100644 --- a/core/java/android/database/sqlite/SQLiteCompiledSql.java +++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java @@ -16,6 +16,7 @@ package android.database.sqlite; +import android.database.DatabaseUtils; import android.util.Log; /** @@ -24,19 +25,19 @@ import android.util.Log; * and it is released in one of the 2 following ways * 1. when {@link SQLiteDatabase} object is closed. * 2. if this is not cached in {@link SQLiteDatabase}, {@link android.database.Cursor#close()} - * releaases this obj. + * releases this obj. */ /* package */ class SQLiteCompiledSql { private static final String TAG = "SQLiteCompiledSql"; /** The database this program is compiled against. */ - /* package */ SQLiteDatabase mDatabase; + /* package */ final SQLiteDatabase mDatabase; /** * Native linkage, do not modify. This comes from the database. */ - /* package */ int nHandle = 0; + /* package */ final int nHandle; /** * Native linkage, do not modify. When non-0 this holds a reference to a valid @@ -46,52 +47,53 @@ import android.util.Log; */ /* package */ int nStatement = 0; - /** the following are for debugging purposes */ - private String mSqlStmt = null; - private Throwable mStackTrace = null; - - /** when in cache and is in use, this member is set */ - private boolean mInUse = false; - - /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) { - if (!db.isOpen()) { - throw new IllegalStateException("database " + db.getPath() + " already closed"); - } + /** the following 3 members are for debugging purposes */ + private final String mSqlStmt; + private final Throwable mStackTrace; + private int nState = 0; + /** values the above member can take */ + private static final int NSTATE_CACHEABLE = 64; + private static final int NSTATE_IN_CACHE = 32; + private static final int NSTATE_INUSE = 16; + private static final int NSTATE_INUSE_RESETMASK = 0x0f; + /* package */ static final int NSTATE_CLOSE_NOOP = 1; + private static final int NSTATE_EVICTED_FROM_CACHE = 2; + /* package */ static final int NSTATE_CACHE_DEALLOC = 4; + private static final int NSTATE_IN_FINALIZER_Q = 8; + + private SQLiteCompiledSql(SQLiteDatabase db, String sql) { + db.verifyDbIsOpen(); + db.verifyLockOwner(); mDatabase = db; mSqlStmt = sql; mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); - this.nHandle = db.mNativeHandle; - compile(sql, true); + nHandle = db.mNativeHandle; + native_compile(sql); } - /** - * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If - * this method has been called previously without a call to close and forCompilation is set - * to false the previous compilation will be used. Setting forceCompilation to true will - * always re-compile the program and should be done if you pass differing SQL strings to this - * method. - * - * <P>Note: this method acquires the database lock.</P> - * - * @param sql the SQL string to compile - * @param forceCompilation forces the SQL to be recompiled in the event that there is an - * existing compiled SQL program already around - */ - private void compile(String sql, boolean forceCompilation) { - mDatabase.verifyLockOwner(); - // Only compile if we don't have a valid statement already or the caller has - // explicitly requested a recompile. - if (forceCompilation) { - // Note that the native_compile() takes care of destroying any previously - // existing programs before it compiles. - native_compile(sql); + /* package */ static SQLiteCompiledSql get(SQLiteDatabase db, String sql, int type) { + // only CRUD statements are cached. + if (type != DatabaseUtils.STATEMENT_SELECT && type != DatabaseUtils.STATEMENT_UPDATE) { + return new SQLiteCompiledSql(db, sql); } + // the given SQL statement is cacheable. + SQLiteCompiledSql stmt = db.mCache.getCompiledStatementForSql(sql); + if (stmt != null) { + return stmt; + } + // either the entry doesn't exist in cache or the one in cache is currently in use. + // try to add it to cache and let cache worry about what copy to keep + stmt = new SQLiteCompiledSql(db, sql); + stmt.nState |= NSTATE_CACHEABLE | + ((db.mCache.addToCompiledQueries(sql, stmt)) ? NSTATE_IN_CACHE : 0); + return stmt; } - /* package */ void releaseSqlStatement() { + /* package */ synchronized void releaseFromDatabase() { // Note that native_finalize() checks to make sure that nStatement is // non-null before destroying it. if (nStatement != 0) { + nState |= NSTATE_IN_FINALIZER_Q; mDatabase.finalizeStatementLater(nStatement); nStatement = 0; } @@ -101,20 +103,47 @@ import android.util.Log; * returns true if acquire() succeeds. false otherwise. */ /* package */ synchronized boolean acquire() { - if (mInUse) { - // someone already has acquired it. + if ((nState & NSTATE_INUSE) > 0 ) { + // this object is already in use return false; } - mInUse = true; + nState |= NSTATE_INUSE; return true; } - /* package */ synchronized void release() { - mInUse = false; + /* package */ synchronized void free() { + nState &= NSTATE_INUSE_RESETMASK; + } + + /* package */ void release(int type) { + if (type != DatabaseUtils.STATEMENT_SELECT && type != DatabaseUtils.STATEMENT_UPDATE) { + // it is not cached. release its memory from the database. + releaseFromDatabase(); + return; + } + // if in cache, reset its in-use flag + if (!mDatabase.mCache.releaseBackToCache(this)) { + // not in cache. release its memory from the database. + releaseFromDatabase(); + } } + /* package */ synchronized void releaseIfNotInUse() { + nState |= NSTATE_EVICTED_FROM_CACHE; + // if it is not in use, release its memory from the database + if ((nState & NSTATE_INUSE) == 0) { + releaseFromDatabase(); + } + } + + // only for testing purposes /* package */ synchronized boolean isInUse() { - return mInUse; + return (nState & NSTATE_INUSE) > 0; + } + + /* package */ synchronized SQLiteCompiledSql setState(int val) { + nState = nState & val; + return this; // for chaining } /** @@ -125,11 +154,18 @@ import android.util.Log; try { if (nStatement == 0) return; // finalizer should NEVER get called - int len = mSqlStmt.length(); - Log.w(TAG, "Releasing statement in a finalizer. Please ensure " + - "that you explicitly call close() on your cursor: " + - mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace); - releaseSqlStatement(); + // but if the database itself is not closed and is GC'ed, then + // all sub-objects attached to the database could end up getting GC'ed too. + // in that case, don't print any warning. + if ((nState & NSTATE_INUSE) == 0) { + // no need to print warning + } else { + int len = mSqlStmt.length(); + Log.w(TAG, "Releasing SQL statement in finalizer. " + + "Could be due to close() not being called on the cursor or on the database. " + + toString(), mStackTrace); + } + releaseFromDatabase(); } finally { super.finalize(); } @@ -140,6 +176,27 @@ import android.util.Log; StringBuilder buff = new StringBuilder(); buff.append(" nStatement="); buff.append(nStatement); + if ((nState & NSTATE_CACHEABLE) > 0) { + buff.append(",cacheable"); + } + if ((nState & NSTATE_IN_CACHE) > 0) { + buff.append(",cached"); + } + if ((nState & NSTATE_INUSE) > 0) { + buff.append(",in_use"); + } + if ((nState & NSTATE_CLOSE_NOOP) > 0) { + buff.append(",no_op_close"); + } + if ((nState & NSTATE_EVICTED_FROM_CACHE) > 0) { + buff.append(",evicted_from_cache"); + } + if ((nState & NSTATE_CACHE_DEALLOC) > 0) { + buff.append(",dealloc_cache"); + } + if ((nState & NSTATE_IN_FINALIZER_Q) > 0) { + buff.append(",in dbFInalizerQ"); + } buff.append(", db="); buff.append(mDatabase.getPath()); buff.append(", db_connectionNum="); diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 6937da0..70bd3d8 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -41,7 +41,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Random; @@ -260,60 +259,11 @@ public class SQLiteDatabase extends SQLiteClosable { private final WeakHashMap<SQLiteClosable, Object> mPrograms; /** - * for each instance of this class, a LRU cache is maintained to store - * the compiled query statement ids returned by sqlite database. - * key = SQL statement with "?" for bind args - * value = {@link SQLiteCompiledSql} - * If an application opens the database and keeps it open during its entire life, then - * there will not be an overhead of compilation of SQL statements by sqlite. - * - * why is this cache NOT static? because sqlite attaches compiledsql statements to the - * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is - * invoked. - * - * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method - * (@link #setMaxSqlCacheSize(int)}). - */ - // default statement-cache size per database connection ( = instance of this class) - private int mMaxSqlCacheSize = 25; - /* package */ final Map<String, SQLiteCompiledSql> mCompiledQueries = - new LinkedHashMap<String, SQLiteCompiledSql>(mMaxSqlCacheSize + 1, 0.75f, true) { - @Override - public boolean removeEldestEntry(Map.Entry<String, SQLiteCompiledSql> eldest) { - // eldest = least-recently used entry - // if it needs to be removed to accommodate a new entry, - // close {@link SQLiteCompiledSql} represented by this entry, if not in use - // and then let it be removed from the Map. - // when this is called, the caller must be trying to add a just-compiled stmt - // to cache; i.e., caller should already have acquired database lock AND - // the lock on mCompiledQueries. do as assert of these two 2 facts. - verifyLockOwner(); - if (this.size() <= mMaxSqlCacheSize) { - // cache is not full. nothing needs to be removed - return false; - } - // cache is full. eldest will be removed. - SQLiteCompiledSql entry = eldest.getValue(); - if (!entry.isInUse()) { - // this {@link SQLiteCompiledSql} is not in use. release it. - entry.releaseSqlStatement(); - } - // return true, so that this entry is removed automatically by the caller. - return true; - } - }; - /** - * absolute max value that can be set by {@link #setMaxSqlCacheSize(int)} - * size of each prepared-statement is between 1K - 6K, depending on the complexity of the + * Absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}. + * Size of each prepared-statement is between 1K - 6K, depending on the complexity of the * SQL statement & schema. */ public static final int MAX_SQL_CACHE_SIZE = 100; - private int mCacheFullWarnings; - private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1; - - /** maintain stats about number of cache hits and misses */ - private int mNumCacheHits; - private int mNumCacheMisses; /** Used to find out where this object was created in case it never got closed. */ private final Throwable mStackTrace; @@ -322,6 +272,9 @@ public class SQLiteDatabase extends SQLiteClosable { private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold"; private final int mSlowQueryThreshold; + /** the LRU cache */ + /* package */ final SQLiteCache mCache = new SQLiteCache(this); + /** stores the list of statement ids that need to be finalized by sqlite */ private final ArrayList<Integer> mClosedStatementIds = new ArrayList<Integer>(); @@ -1087,7 +1040,7 @@ public class SQLiteDatabase extends SQLiteClosable { * to be closed. sqlite doesn't let a database close if there are * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries. */ - deallocCachedSqlStatements(); + mCache.dealloc(); Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator(); while (iter.hasNext()) { @@ -2090,113 +2043,20 @@ public class SQLiteDatabase extends SQLiteClosable { } } - /* - * ============================================================================ - * - * The following methods deal with compiled-sql cache - * ============================================================================ - */ - /** - * Adds the given SQL and its compiled-statement-id-returned-by-sqlite to the - * cache of compiledQueries attached to 'this'. - * <p> - * If there is already a {@link SQLiteCompiledSql} in compiledQueries for the given SQL, - * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current - * mapping is NOT replaced with the new mapping). - */ - /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { - synchronized(mCompiledQueries) { - // don't insert the new mapping if a mapping already exists - if (mCompiledQueries.containsKey(sql)) { - return; - } - - if (mCompiledQueries.size() == mMaxSqlCacheSize) { - /* - * cache size of {@link #mMaxSqlCacheSize} is not enough for this app. - * log a warning. - * chances are it is NOT using ? for bindargs - or cachesize is too small. - */ - if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) { - Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + - getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. "); - } - } - /* add the given SQLiteCompiledSql compiledStatement to cache. - * no need to worry about the cache size - because {@link #mCompiledQueries} - * self-limits its size to {@link #mMaxSqlCacheSize}. - */ - mCompiledQueries.put(sql, compiledStatement); - if (SQLiteDebug.DEBUG_SQL_CACHE) { - Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + - mCompiledQueries.size() + "|" + sql); - } - } - } - - /** package-level access for testing purposes */ - /* package */ void deallocCachedSqlStatements() { - synchronized (mCompiledQueries) { - for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { - compiledSql.releaseSqlStatement(); - } - mCompiledQueries.clear(); - } - } - - /** - * From the compiledQueries cache, returns the compiled-statement-id for the given SQL. - * Returns null, if not found in the cache. - */ - /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) { - SQLiteCompiledSql compiledStatement = null; - boolean cacheHit; - synchronized(mCompiledQueries) { - cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null; - } - if (cacheHit) { - mNumCacheHits++; - } else { - mNumCacheMisses++; - } - - if (SQLiteDebug.DEBUG_SQL_CACHE) { - Log.v(TAG, "|cache_stats|" + - getPath() + "|" + mCompiledQueries.size() + - "|" + mNumCacheHits + "|" + mNumCacheMisses + - "|" + cacheHit + "|" + sql); - } - return compiledStatement; - } - /** * Sets the maximum size of the prepared-statement cache for this database. * (size of the cache = number of compiled-sql-statements stored in the cache). *<p> - * Maximum cache size can ONLY be increased from its current size (default = 10). + * Maximum cache size can ONLY be increased from its current size (default = 25). * If this method is called with smaller size than the current maximum value, * then IllegalStateException is thrown. - *<p> - * This method is thread-safe. * * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE}) * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or * the value set with previous setMaxSqlCacheSize() call. */ - public synchronized void setMaxSqlCacheSize(int cacheSize) { - if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { - throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE); - } else if (cacheSize < mMaxSqlCacheSize) { - throw new IllegalStateException("cannot set cacheSize to a value less than the value " + - "set with previous setMaxSqlCacheSize() call."); - } - mMaxSqlCacheSize = cacheSize; - } - - /* package */ boolean isSqlInStatementCache(String sql) { - synchronized (mCompiledQueries) { - return mCompiledQueries.containsKey(sql); - } + public void setMaxSqlCacheSize(int cacheSize) { + mCache.setMaxSqlCacheSize(cacheSize); } /* package */ void finalizeStatementLater(int id) { @@ -2352,17 +2212,19 @@ public class SQLiteDatabase extends SQLiteClosable { * * @param size the value the connection handle pool size should be set to. */ - public synchronized void setConnectionPoolSize(int size) { - if (mConnectionPool == null) { - throw new IllegalStateException("connection pool not enabled"); - } - int i = mConnectionPool.getMaxPoolSize(); - if (size < i) { - throw new IllegalArgumentException( - "cannot set max pool size to a value less than the current max value(=" + - i + ")"); + public void setConnectionPoolSize(int size) { + synchronized(this) { + if (mConnectionPool == null) { + throw new IllegalStateException("connection pool not enabled"); + } + int i = mConnectionPool.getMaxPoolSize(); + if (size < i) { + throw new IllegalArgumentException( + "cannot set max pool size to a value less than the current max value(=" + + i + ")"); + } + mConnectionPool.setMaxPoolSize(size); } - mConnectionPool.setMaxPoolSize(size); } /* package */ SQLiteDatabase createPoolConnection(short connectionNum) { @@ -2478,16 +2340,16 @@ public class SQLiteDatabase extends SQLiteClosable { } if (pageCount > 0) { dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), - lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses, - db.mCompiledQueries.size())); + lookasideUsed, db.mCache.getCacheHitNum(), + db.mCache.getCacheMissNum(), db.mCache.getCachesize())); } } // if there are pooled connections, return the cache stats for them also. if (db.mConnectionPool != null) { for (SQLiteDatabase pDb : db.mConnectionPool.getConnectionList()) { dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") " - + lastnode, 0, 0, 0, pDb.mNumCacheHits, pDb.mNumCacheMisses, - pDb.mCompiledQueries.size())); + + lastnode, 0, 0, 0, pDb.mCache.getCacheHitNum(), + pDb.mCache.getCacheMissNum(), pDb.mCache.getCachesize())); } } } catch (SQLiteException e) { diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index bcb0c48..b390369 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -110,42 +110,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { } } - private void compileSql() { - // only cache CRUD statements - if (mStatementType != DatabaseUtils.STATEMENT_SELECT && - mStatementType != DatabaseUtils.STATEMENT_UPDATE) { - mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql); - nStatement = mCompiledSql.nStatement; - // since it is not in the cache, no need to acquire() it. - return; - } - - mCompiledSql = mDatabase.getCompiledStatementForSql(mSql); - if (mCompiledSql == null) { - // create a new compiled-sql obj - mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql); - - // add it to the cache of compiled-sqls - // but before adding it and thus making it available for anyone else to use it, - // make sure it is acquired by me. - mCompiledSql.acquire(); - mDatabase.addToCompiledQueries(mSql, mCompiledSql); - } else { - // it is already in compiled-sql cache. - // try to acquire the object. - if (!mCompiledSql.acquire()) { - int last = mCompiledSql.nStatement; - // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object. - // we can't have two different SQLiteProgam objects can't share the same - // CompiledSql object. create a new one. - // finalize it when I am done with it in "this" object. - mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql); - // since it is not in the cache, no need to acquire() it. - } - } - nStatement = mCompiledSql.nStatement; - } - @Override protected void onAllReferencesReleased() { release(); @@ -163,16 +127,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { if (mCompiledSql == null) { return; } - synchronized(mDatabase.mCompiledQueries) { - if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) { - // it is NOT in compiled-sql cache. i.e., responsibility of - // releasing this statement is on me. - mCompiledSql.releaseSqlStatement(); - } else { - // it is in compiled-sql cache. reset its CompiledSql#mInUse flag - mCompiledSql.release(); - } - } + mCompiledSql.release(mStatementType); mCompiledSql = null; nStatement = 0; } @@ -215,8 +170,8 @@ public abstract class SQLiteProgram extends SQLiteClosable { } private void bind(int type, int index, Object value) { - mDatabase.verifyDbIsOpen(); synchronized (this) { + mDatabase.verifyDbIsOpen(); addToBindArgs(index, (type == Cursor.FIELD_TYPE_NULL) ? null : value); if (nStatement > 0) { // bind only if the SQL statement is compiled @@ -333,6 +288,9 @@ public abstract class SQLiteProgram extends SQLiteClosable { synchronized (this) { mBindArgs = null; if (nHandle == 0 || !mDatabase.isOpen()) { + if (mCompiledSql != null) { + mCompiledSql.setState(SQLiteCompiledSql.NSTATE_CLOSE_NOOP); + } return; } releaseReference(); @@ -349,7 +307,8 @@ public abstract class SQLiteProgram extends SQLiteClosable { /* package */ synchronized void compileAndbindAllArgs() { if (nStatement == 0) { // SQL statement is not compiled yet. compile it now. - compileSql(); + mCompiledSql = SQLiteCompiledSql.get(mDatabase, mSql, mStatementType); + nStatement = mCompiledSql.nStatement; } if (mBindArgs == null) { return; @@ -395,6 +354,10 @@ public abstract class SQLiteProgram extends SQLiteClosable { } } + /* package */ synchronized final void setNativeHandle(int nHandle) { + this.nHandle = nHandle; + } + /** * @deprecated This method is deprecated and must not be used. * Compiles SQL into a SQLite program. diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index dc07db0..658fc58 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -37,7 +37,6 @@ import dalvik.system.BlockGuard; @SuppressWarnings("deprecation") public class SQLiteStatement extends SQLiteProgram { - private static final String TAG = "SQLiteStatement"; private static final boolean READ = true; @@ -45,9 +44,9 @@ public class SQLiteStatement extends SQLiteProgram private SQLiteDatabase mOrigDb; private int mState; - /** possible value for {@link #mState}. indicates that a transaction is started.} */ + /** possible value for {@link #mState}. indicates that a transaction is started. */ private static final int TRANS_STARTED = 1; - /** possible value for {@link #mState}. indicates that a lock is acquired.} */ + /** possible value for {@link #mState}. indicates that a lock is acquired. */ private static final int LOCK_ACQUIRED = 2; /** @@ -81,8 +80,8 @@ public class SQLiteStatement extends SQLiteProgram */ public int executeUpdateDelete() { synchronized(this) { - long timeStart = acquireAndLock(WRITE); try { + long timeStart = acquireAndLock(WRITE); int numChanges = native_execute(); mDatabase.logTimeStat(mSql, timeStart); return numChanges; @@ -103,8 +102,8 @@ public class SQLiteStatement extends SQLiteProgram */ public long executeInsert() { synchronized(this) { - long timeStart = acquireAndLock(WRITE); try { + long timeStart = acquireAndLock(WRITE); long lastInsertedRowId = native_executeInsert(); mDatabase.logTimeStat(mSql, timeStart); return lastInsertedRowId; @@ -124,8 +123,8 @@ public class SQLiteStatement extends SQLiteProgram */ public long simpleQueryForLong() { synchronized(this) { - long timeStart = acquireAndLock(READ); try { + long timeStart = acquireAndLock(READ); long retValue = native_1x1_long(); mDatabase.logTimeStat(mSql, timeStart); return retValue; @@ -145,8 +144,8 @@ public class SQLiteStatement extends SQLiteProgram */ public String simpleQueryForString() { synchronized(this) { - long timeStart = acquireAndLock(READ); try { + long timeStart = acquireAndLock(READ); String retValue = native_1x1_string(); mDatabase.logTimeStat(mSql, timeStart); return retValue; @@ -166,8 +165,8 @@ public class SQLiteStatement extends SQLiteProgram */ public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() { synchronized(this) { - long timeStart = acquireAndLock(READ); try { + long timeStart = acquireAndLock(READ); ParcelFileDescriptor retValue = native_1x1_blob_ashmem(); mDatabase.logTimeStat(mSql, timeStart); return retValue; @@ -205,7 +204,7 @@ public class SQLiteStatement extends SQLiteProgram // use the database connection obtained above mOrigDb = mDatabase; mDatabase = db; - nHandle = mDatabase.mNativeHandle; + setNativeHandle(mDatabase.mNativeHandle); if (rwFlag == WRITE) { BlockGuard.getThreadPolicy().onWriteToDisk(); } else { @@ -268,7 +267,7 @@ public class SQLiteStatement extends SQLiteProgram release(); // restore the database connection handle to the original value mDatabase = mOrigDb; - nHandle = mDatabase.mNativeHandle; + setNativeHandle(mDatabase.mNativeHandle); } private final native int native_execute(); diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java index e04367a..e8237c9 100644 --- a/core/java/android/net/DownloadManager.java +++ b/core/java/android/net/DownloadManager.java @@ -276,6 +276,7 @@ public class DownloadManager { private String mMediaType; private boolean mRoamingAllowed = true; private int mAllowedNetworkTypes = ~0; // default to all network types allowed + private boolean mIsVisibleInDownloadsUi = true; /** * @param uri the HTTP URI to download. @@ -387,6 +388,17 @@ public class DownloadManager { } /** + * Set whether this download should be displayed in the system's Downloads UI. True by + * default. + * @param isVisible whether to display this download in the Downloads UI + * @return this object + */ + public Request setVisibleInDownloadsUi(boolean isVisible) { + mIsVisibleInDownloadsUi = isVisible; + return this; + } + + /** * @return ContentValues to be passed to DownloadProvider.insert() */ ContentValues toContentValues(String packageName) { @@ -418,6 +430,7 @@ public class DownloadManager { values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); + values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); return values; } @@ -458,6 +471,7 @@ public class DownloadManager { private Integer mStatusFlags = null; private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION; private int mOrderDirection = ORDER_DESCENDING; + private boolean mOnlyIncludeVisibleInDownloadsUi = false; /** * Include only the download with the given ID. @@ -479,6 +493,19 @@ public class DownloadManager { } /** + * Controls whether this query includes downloads not visible in the system's Downloads UI. + * @param value if true, this query will only include downloads that should be displayed in + * the system's Downloads UI; if false (the default), this query will include + * both visible and invisible downloads. + * @return this object + * @hide + */ + public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) { + mOnlyIncludeVisibleInDownloadsUi = value; + return this; + } + + /** * Change the sort order of the returned Cursor. * * @param column one of the COLUMN_* constants; currently, only @@ -511,7 +538,7 @@ public class DownloadManager { */ Cursor runQuery(ContentResolver resolver, String[] projection) { Uri uri = Downloads.CONTENT_URI; - String selection = null; + List<String> selectionParts = new ArrayList<String>(); if (mId != null) { uri = Uri.withAppendedPath(uri, mId.toString()); @@ -536,9 +563,14 @@ public class DownloadManager { parts.add("(" + statusClause(">=", 400) + " AND " + statusClause("<", 600) + ")"); } - selection = joinStrings(" OR ", parts); + selectionParts.add(joinStrings(" OR ", parts)); } + if (mOnlyIncludeVisibleInDownloadsUi) { + selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'"); + } + + String selection = joinStrings(" AND ", selectionParts); String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); String orderBy = mOrderByColumn + " " + orderDirection; @@ -628,6 +660,34 @@ public class DownloadManager { } /** + * Restart the given download, which must have already completed (successfully or not). This + * method will only work when called from within the download manager's process. + * @param id the ID of the download + * @hide + */ + public void restartDownload(long id) { + Cursor cursor = query(new Query().setFilterById(id)); + try { + if (!cursor.moveToFirst()) { + throw new IllegalArgumentException("No download with id " + id); + } + int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS)); + if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) { + throw new IllegalArgumentException("Cannot restart incomplete download: " + id); + } + } finally { + cursor.close(); + } + + ContentValues values = new ContentValues(); + values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); + values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); + values.putNull(Downloads.Impl._DATA); + values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); + mResolver.update(getDownloadUri(id), values, null, null); + } + + /** * Get the DownloadProvider URI for the download with the given ID. */ private Uri getDownloadUri(long id) { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index f5b1e57..f182a7a 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -404,6 +404,7 @@ public abstract class BatteryStats implements Parcelable { public static final byte CMD_UPDATE = 0; public static final byte CMD_START = 1; + public static final byte CMD_OVERFLOW = 2; public byte cmd; @@ -1703,6 +1704,8 @@ public abstract class BatteryStats implements Parcelable { pw.print(" "); if (rec.cmd == HistoryItem.CMD_START) { pw.println(" START"); + } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) { + pw.println(" *OVERFLOW*"); } else { if (rec.batteryLevel < 10) pw.print("00"); else if (rec.batteryLevel < 100) pw.print("0"); diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 3b2bf1e..165e438 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -58,7 +58,7 @@ import java.lang.reflect.Modifier; * they create. You can create your own threads, and communicate back with * the main application thread through a Handler. This is done by calling * the same <em>post</em> or <em>sendMessage</em> methods as before, but from - * your new thread. The given Runnable or Message will than be scheduled + * your new thread. The given Runnable or Message will then be scheduled * in the Handler's message queue and processed when appropriate. */ public class Handler { diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 01cc408..0067e94 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -17,10 +17,13 @@ package android.os; +import android.os.WorkSource; + /** @hide */ interface IPowerManager { - void acquireWakeLock(int flags, IBinder lock, String tag); + void acquireWakeLock(int flags, IBinder lock, String tag, in WorkSource ws); + void updateWakeLockWorkSource(IBinder lock, in WorkSource ws); void goToSleep(long time); void goToSleepWithReason(long time, int reason); void releaseWakeLock(IBinder lock, int flags); diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index 75dfdd2..e89f7c2 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -225,8 +225,8 @@ public class MessageQueue { msg.next = prev.next; prev.next = msg; } - nativeWake(); } + nativeWake(); return true; } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index f4ca8bc..3876a3e 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -209,6 +209,7 @@ public class PowerManager int mCount = 0; boolean mRefCounted = true; boolean mHeld = false; + WorkSource mWorkSource; WakeLock(int flags, String tag) { @@ -247,7 +248,7 @@ public class PowerManager synchronized (mToken) { if (!mRefCounted || mCount++ == 0) { try { - mService.acquireWakeLock(mFlags, mToken, mTag); + mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource); } catch (RemoteException e) { } mHeld = true; @@ -313,6 +314,32 @@ public class PowerManager } } + public void setWorkSource(WorkSource ws) { + synchronized (mToken) { + if (ws != null && ws.size() == 0) { + ws = null; + } + boolean changed = true; + if (ws == null) { + mWorkSource = null; + } else if (mWorkSource == null) { + changed = mWorkSource != null; + mWorkSource = new WorkSource(ws); + } else { + changed = mWorkSource.diff(ws); + if (changed) { + mWorkSource.set(ws); + } + } + if (changed && mHeld) { + try { + mService.updateWakeLockWorkSource(mToken, mWorkSource); + } catch (RemoteException e) { + } + } + } + } + public String toString() { synchronized (mToken) { return "WakeLock{" diff --git a/core/java/android/os/WorkSource.aidl b/core/java/android/os/WorkSource.aidl new file mode 100644 index 0000000..1e7fabc --- /dev/null +++ b/core/java/android/os/WorkSource.aidl @@ -0,0 +1,18 @@ +/* Copyright 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 android.os; + +parcelable WorkSource; diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java new file mode 100644 index 0000000..bba1984 --- /dev/null +++ b/core/java/android/os/WorkSource.java @@ -0,0 +1,311 @@ +package android.os; + +/** + * Describes the source of some work that may be done by someone else. + * Currently the public representation of what a work source is is not + * defined; this is an opaque container. + */ +public class WorkSource implements Parcelable { + int mNum; + int[] mUids; + + /** + * Internal statics to avoid object allocations in some operations. + * The WorkSource object itself is not thread safe, but we need to + * hold sTmpWorkSource lock while working with these statics. + */ + static final WorkSource sTmpWorkSource = new WorkSource(0); + /** + * For returning newbie work from a modification operation. + */ + static WorkSource sNewbWork; + /** + * For returning gone work form a modification operation. + */ + static WorkSource sGoneWork; + + /** + * Create an empty work source. + */ + public WorkSource() { + mNum = 0; + } + + /** + * Create a new WorkSource that is a copy of an existing one. + * If <var>orig</var> is null, an empty WorkSource is created. + */ + public WorkSource(WorkSource orig) { + if (orig == null) { + mNum = 0; + return; + } + mNum = orig.mNum; + if (orig.mUids != null) { + mUids = orig.mUids.clone(); + } else { + mUids = null; + } + } + + /** @hide */ + public WorkSource(int uid) { + mNum = 1; + mUids = new int[] { uid, 0 }; + } + + WorkSource(Parcel in) { + mNum = in.readInt(); + mUids = in.createIntArray(); + } + + /** @hide */ + public int size() { + return mNum; + } + + /** @hide */ + public int get(int index) { + return mUids[index]; + } + + /** + * Clear this WorkSource to be empty. + */ + public void clear() { + mNum = 0; + } + + /** + * Compare this WorkSource with another. + * @param other The WorkSource to compare against. + * @return If there is a difference, true is returned. + */ + public boolean diff(WorkSource other) { + int N = mNum; + if (N != other.mNum) { + return true; + } + final int[] uids1 = mUids; + final int[] uids2 = other.mUids; + for (int i=0; i<N; i++) { + if (uids1[i] != uids2[i]) { + return true; + } + } + return false; + } + + /** + * Replace the current contents of this work source with the given + * work source. If <var>other</var> is null, the current work source + * will be made empty. + */ + public void set(WorkSource other) { + if (other == null) { + mNum = 0; + return; + } + mNum = other.mNum; + if (other.mUids != null) { + if (mUids != null && mUids.length >= mNum) { + System.arraycopy(other.mUids, 0, mUids, 0, mNum); + } else { + mUids = other.mUids.clone(); + } + } else { + mUids = null; + } + } + + /** @hide */ + public void set(int uid) { + mNum = 1; + if (mUids == null) mUids = new int[2]; + mUids[0] = uid; + } + + /** @hide */ + public WorkSource[] setReturningDiffs(WorkSource other) { + synchronized (sTmpWorkSource) { + sNewbWork = null; + sGoneWork = null; + updateLocked(other, true, true); + if (sNewbWork != null || sGoneWork != null) { + WorkSource[] diffs = new WorkSource[2]; + diffs[0] = sNewbWork; + diffs[1] = sGoneWork; + return diffs; + } + return null; + } + } + + /** + * Merge the contents of <var>other</var> WorkSource in to this one. + * + * @param other The other WorkSource whose contents are to be merged. + * @return Returns true if any new sources were added. + */ + public boolean add(WorkSource other) { + synchronized (sTmpWorkSource) { + return updateLocked(other, false, false); + } + } + + /** @hide */ + public WorkSource addReturningNewbs(WorkSource other) { + synchronized (sTmpWorkSource) { + sNewbWork = null; + updateLocked(other, false, true); + return sNewbWork; + } + } + + /** @hide */ + public boolean add(int uid) { + synchronized (sTmpWorkSource) { + sTmpWorkSource.mUids[0] = uid; + return updateLocked(sTmpWorkSource, false, false); + } + } + + /** @hide */ + public WorkSource addReturningNewbs(int uid) { + synchronized (sTmpWorkSource) { + sNewbWork = null; + sTmpWorkSource.mUids[0] = uid; + updateLocked(sTmpWorkSource, false, true); + return sNewbWork; + } + } + + public boolean remove(WorkSource other) { + int N1 = mNum; + final int[] uids1 = mUids; + final int N2 = other.mNum; + final int[] uids2 = other.mUids; + boolean changed = false; + int i1 = 0; + for (int i2=0; i2<N2 && i1<N1; i2++) { + if (uids2[i2] == uids1[i1]) { + N1--; + if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1-1, N1-i1); + } + while (i1 < N1 && uids2[i2] > uids1[i1]) { + i1++; + } + } + + mNum = N1; + + return changed; + } + + private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) { + int N1 = mNum; + int[] uids1 = mUids; + final int N2 = other.mNum; + final int[] uids2 = other.mUids; + boolean changed = false; + int i1 = 0; + for (int i2=0; i2<N2; i2++) { + if (i1 >= N1 || uids2[i2] < uids1[i1]) { + // Need to insert a new uid. + changed = true; + if (uids1 == null) { + uids1 = new int[4]; + uids1[0] = uids2[i2]; + } else if (i1 >= uids1.length) { + int[] newuids = new int[(uids1.length*3)/2]; + if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1); + if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1); + uids1 = newuids; + uids1[i1] = uids2[i2]; + } else { + if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1); + uids1[i1] = uids2[i2]; + } + if (returnNewbs) { + if (sNewbWork == null) { + sNewbWork = new WorkSource(uids2[i2]); + } else { + sNewbWork.addLocked(uids2[i2]); + } + } + N1++; + i1++; + } else { + if (!set) { + // Skip uids that already exist or are not in 'other'. + do { + i1++; + } while (i1 < N1 && uids2[i2] >= uids1[i1]); + } else { + // Remove any uids that don't exist in 'other'. + int start = i1; + while (i1 < N1 && uids2[i2] > uids1[i1]) { + if (sGoneWork == null) { + sGoneWork = new WorkSource(uids1[i1]); + } else { + sGoneWork.addLocked(uids1[i1]); + } + i1++; + } + if (start < i1) { + System.arraycopy(uids1, i1, uids1, start, i1-start); + N1 -= i1-start; + i1 = start; + } + // If there is a matching uid, skip it. + if (i1 < N1 && uids2[i1] == uids1[i1]) { + i1++; + } + } + } + } + + mNum = N1; + mUids = uids1; + + return changed; + } + + private void addLocked(int uid) { + if (mUids == null) { + mUids = new int[4]; + mUids[0] = uid; + mNum = 1; + return; + } + if (mNum >= mUids.length) { + int[] newuids = new int[(mNum*3)/2]; + System.arraycopy(mUids, 0, newuids, 0, mNum); + mUids = newuids; + } + + mUids[mNum] = uid; + mNum++; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mNum); + dest.writeIntArray(mUids); + } + + public static final Parcelable.Creator<WorkSource> CREATOR + = new Parcelable.Creator<WorkSource>() { + public WorkSource createFromParcel(Parcel in) { + return new WorkSource(in); + } + public WorkSource[] newArray(int size) { + return new WorkSource[size]; + } + }; +} diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index c28ccd3..60d810e 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -29,10 +29,11 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Xml; @@ -40,6 +41,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.widget.AbsListView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.FrameLayout; @@ -112,7 +114,11 @@ public abstract class PreferenceActivity extends ListActivity implements PreferenceFragment.OnPreferenceStartFragmentCallback { private static final String TAG = "PreferenceActivity"; - private static final String PREFERENCES_TAG = "android:preferences"; + // Constants for state save/restore + private static final String HEADERS_TAG = ":android:headers"; + private static final String CUR_HEADER_TAG = ":android:cur_header"; + private static final String SINGLE_PANE_TAG = ":android:single_pane"; + private static final String PREFERENCES_TAG = ":android:preferences"; /** * When starting this activity, the invoking Intent can contain this extra @@ -165,6 +171,8 @@ public abstract class PreferenceActivity extends ListActivity implements private boolean mSinglePane; + private Header mCurHeader; + // --- State for old mode when showing a single preference list private PreferenceManager mPreferenceManager; @@ -180,23 +188,35 @@ public abstract class PreferenceActivity extends ListActivity implements */ private static final int FIRST_REQUEST_CODE = 100; - private static final int MSG_BIND_PREFERENCES = 0; - private static final int MSG_BUILD_HEADERS = 1; + private static final int MSG_BIND_PREFERENCES = 1; + private static final int MSG_BUILD_HEADERS = 2; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_BIND_PREFERENCES: + case MSG_BIND_PREFERENCES: { bindPreferences(); - break; - case MSG_BUILD_HEADERS: + } break; + case MSG_BUILD_HEADERS: { + ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders); + mHeaders.clear(); onBuildHeaders(mHeaders); mAdapter.notifyDataSetChanged(); Header header = onGetNewHeader(); if (header != null && header.fragment != null) { - switchToHeader(header.fragment, header.fragmentArguments); + Header mappedHeader = findBestMatchingHeader(header, oldHeaders); + if (mappedHeader == null || mCurHeader != mappedHeader) { + switchToHeader(header); + } + } else if (mCurHeader != null) { + Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders); + if (mappedHeader != null) { + setSelectedHeader(mappedHeader); + } else { + switchToHeader(null); + } } - break; + } break; } } }; @@ -235,13 +255,7 @@ public abstract class PreferenceActivity extends ListActivity implements // All view fields must be updated every time, because the view may be recycled Header header = getItem(position); - if (header.icon == null) { - holder.icon.setImageDrawable(null); - holder.icon.setImageResource(header.iconRes); - } else { - holder.icon.setImageResource(0); - holder.icon.setImageDrawable(header.icon); - } + holder.icon.setImageResource(header.iconRes); holder.title.setText(header.title); if (TextUtils.isEmpty(header.summary)) { holder.summary.setVisibility(View.GONE); @@ -255,9 +269,24 @@ public abstract class PreferenceActivity extends ListActivity implements } /** + * Default value for {@link Header#id Header.id} indicating that no + * identifier value is set. All other values (including those below -1) + * are valid. + */ + public static final long HEADER_ID_UNDEFINED = -1; + + /** * Description of a single Header item that the user can select. */ - public static class Header { + public static final class Header implements Parcelable { + /** + * Identifier for this header, to correlate with a new list when + * it is updated. The default value is + * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id. + * @attr ref android.R.styleable#PreferenceHeader_id + */ + public long id = HEADER_ID_UNDEFINED; + /** * Title of the header that is shown to the user. * @attr ref android.R.styleable#PreferenceHeader_title @@ -277,12 +306,6 @@ public abstract class PreferenceActivity extends ListActivity implements public int iconRes; /** - * Optional icon drawable to show for this header. (If this is non-null, - * the iconRes will be ignored.) - */ - public Drawable icon; - - /** * Full class name of the fragment to display when this header is * selected. * @attr ref android.R.styleable#PreferenceHeader_fragment @@ -299,6 +322,62 @@ public abstract class PreferenceActivity extends ListActivity implements * Intent to launch when the preference is selected. */ public Intent intent; + + /** + * Optional additional data for use by subclasses of PreferenceActivity. + */ + public Bundle extras; + + public Header() { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + TextUtils.writeToParcel(title, dest, flags); + TextUtils.writeToParcel(summary, dest, flags); + dest.writeInt(iconRes); + dest.writeString(fragment); + dest.writeBundle(fragmentArguments); + if (intent != null) { + dest.writeInt(1); + intent.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + dest.writeBundle(extras); + } + + public void readFromParcel(Parcel in) { + id = in.readLong(); + title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + iconRes = in.readInt(); + fragment = in.readString(); + fragmentArguments = in.readBundle(); + if (in.readInt() != 0) { + intent = Intent.CREATOR.createFromParcel(in); + } + extras = in.readBundle(); + } + + Header(Parcel in) { + readFromParcel(in); + } + + public static final Creator<Header> CREATOR = new Creator<Header>() { + public Header createFromParcel(Parcel source) { + return new Header(source); + } + public Header[] newArray(int size) { + return new Header[size]; + } + }; } @Override @@ -314,40 +393,66 @@ public abstract class PreferenceActivity extends ListActivity implements String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); - if (initialFragment != null && mSinglePane) { - // If we are just showing a fragment, we want to run in - // new fragment mode, but don't need to compute and show - // the headers. - getListView().setVisibility(View.GONE); - mPrefsContainer.setVisibility(View.VISIBLE); - switchToHeader(initialFragment, initialArguments); + if (savedInstanceState != null) { + // We are restarting from a previous saved state; used that to + // initialize, instead of starting fresh. + ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG); + if (headers != null) { + mHeaders.addAll(headers); + int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG, + (int)HEADER_ID_UNDEFINED); + if (curHeader >= 0 && curHeader < mHeaders.size()) { + setSelectedHeader(mHeaders.get(curHeader)); + } + } + mSinglePane = savedInstanceState.getBoolean(SINGLE_PANE_TAG); } else { - // We need to try to build the headers. - onBuildHeaders(mHeaders); - - // If there are headers, then at this point we need to show - // them and, depending on the screen, we may also show in-line - // the currently selected preference fragment. - if (mHeaders.size() > 0) { - mAdapter = new HeaderAdapter(this, mHeaders); - setListAdapter(mAdapter); - if (!mSinglePane) { - mPrefsContainer.setVisibility(View.VISIBLE); - if (initialFragment == null) { - Header h = onGetInitialHeader(); - initialFragment = h.fragment; - initialArguments = h.fragmentArguments; + if (initialFragment != null && mSinglePane) { + // If we are just showing a fragment, we want to run in + // new fragment mode, but don't need to compute and show + // the headers. + switchToHeader(initialFragment, initialArguments); + + } else { + // We need to try to build the headers. + onBuildHeaders(mHeaders); + + // If there are headers, then at this point we need to show + // them and, depending on the screen, we may also show in-line + // the currently selected preference fragment. + if (mHeaders.size() > 0) { + if (!mSinglePane) { + if (initialFragment == null) { + Header h = onGetInitialHeader(); + switchToHeader(h); + } else { + switchToHeader(initialFragment, initialArguments); + } } - switchToHeader(initialFragment, initialArguments); } + } + } + // The default configuration is to only show the list view. Adjust + // visibility for other configurations. + if (initialFragment != null && mSinglePane) { + // Single pane, showing just a prefs fragment. + getListView().setVisibility(View.GONE); + mPrefsContainer.setVisibility(View.VISIBLE); + } else if (mHeaders.size() > 0) { + mAdapter = new HeaderAdapter(this, mHeaders); + setListAdapter(mAdapter); + if (!mSinglePane) { + // Multi-pane. + getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); + mPrefsContainer.setVisibility(View.VISIBLE); + } + } else { // If there are no headers, we are in the old "just show a screen // of preferences" mode. - } else { - mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE); - mPreferenceManager.setOnPreferenceTreeClickListener(this); - } + mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE); + mPreferenceManager.setOnPreferenceTreeClickListener(this); } getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); @@ -534,6 +639,9 @@ public abstract class PreferenceActivity extends ListActivity implements TypedArray sa = getResources().obtainAttributes(attrs, com.android.internal.R.styleable.PreferenceHeader); + header.id = sa.getInt( + com.android.internal.R.styleable.PreferenceHeader_id, + (int)HEADER_ID_UNDEFINED); header.title = sa.getText( com.android.internal.R.styleable.PreferenceHeader_title); header.summary = sa.getText( @@ -621,6 +729,17 @@ public abstract class PreferenceActivity extends ListActivity implements protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); + if (mHeaders.size() > 0) { + outState.putParcelableArrayList(HEADERS_TAG, mHeaders); + if (mCurHeader != null) { + int index = mHeaders.indexOf(mCurHeader); + if (index >= 0) { + outState.putInt(CUR_HEADER_TAG, index); + } + } + } + outState.putBoolean(SINGLE_PANE_TAG, mSinglePane); + if (mPreferenceManager != null) { final PreferenceScreen preferenceScreen = getPreferenceScreen(); if (preferenceScreen != null) { @@ -680,7 +799,7 @@ public abstract class PreferenceActivity extends ListActivity implements /** * Called when the user selects an item in the header list. The default * implementation will call either {@link #startWithFragment(String, Bundle)} - * or {@link #switchToHeader(String, Bundle)} as appropriate. + * or {@link #switchToHeader(Header)} as appropriate. * * @param header The header that was selected. * @param position The header's position in the list. @@ -690,7 +809,7 @@ public abstract class PreferenceActivity extends ListActivity implements if (mSinglePane) { startWithFragment(header.fragment, header.fragmentArguments); } else { - switchToHeader(header.fragment, header.fragmentArguments); + switchToHeader(header); } } else if (header.intent != null) { startActivity(header.intent); @@ -715,6 +834,16 @@ public abstract class PreferenceActivity extends ListActivity implements startActivity(intent); } + void setSelectedHeader(Header header) { + mCurHeader = header; + int index = mHeaders.indexOf(header); + if (index >= 0) { + getListView().setItemChecked(index, true); + } else { + getListView().clearChoices(); + } + } + /** * When in two-pane mode, switch the fragment pane to show the given * preference fragment. @@ -723,6 +852,8 @@ public abstract class PreferenceActivity extends ListActivity implements * @param args Optional arguments to supply to the fragment. */ public void switchToHeader(String fragmentName, Bundle args) { + setSelectedHeader(null); + getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE); Fragment f = Fragment.instantiate(this, fragmentName, args); @@ -731,6 +862,63 @@ public abstract class PreferenceActivity extends ListActivity implements } /** + * When in two-pane mode, switch to the fragment pane to show the given + * preference fragment. + * + * @param header The new header to display. + */ + public void switchToHeader(Header header) { + switchToHeader(header.fragment, header.fragmentArguments); + mCurHeader = header; + setSelectedHeader(header); + } + + Header findBestMatchingHeader(Header cur, ArrayList<Header> from) { + ArrayList<Header> matches = new ArrayList<Header>(); + for (int j=0; j<from.size(); j++) { + Header oh = from.get(j); + if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) { + // Must be this one. + matches.clear(); + matches.add(oh); + break; + } + if (cur.fragment != null) { + if (cur.fragment.equals(oh.fragment)) { + matches.add(oh); + } + } else if (cur.intent != null) { + if (cur.intent.equals(oh.intent)) { + matches.add(oh); + } + } else if (cur.title != null) { + if (cur.title.equals(oh.title)) { + matches.add(oh); + } + } + } + final int NM = matches.size(); + if (NM == 1) { + return matches.get(0); + } else if (NM > 1) { + for (int j=0; j<NM; j++) { + Header oh = matches.get(j); + if (cur.fragmentArguments != null && + cur.fragmentArguments.equals(oh.fragmentArguments)) { + return oh; + } + if (cur.extras != null && cur.extras.equals(oh.extras)) { + return oh; + } + if (cur.title != null && cur.title.equals(oh.title)) { + return oh; + } + } + } + return null; + } + + /** * Start a new fragment. * * @param fragment The fragment to start diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index 0b5dffe..479497a 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -106,7 +106,7 @@ public abstract class PreferenceFragment extends Fragment implements */ private static final int FIRST_REQUEST_CODE = 100; - private static final int MSG_BIND_PREFERENCES = 0; + private static final int MSG_BIND_PREFERENCES = 1; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index 6bf0d5b..603e598 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -880,6 +880,14 @@ public final class Downloads { */ public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types"; + /** + * Whether or not this download should be displayed in the system's Downloads UI. Defaults + * to true. + * <P>Type: INTEGER</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui"; + /* * Lists the destinations that an application can specify for a download. */ diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index d3718f8..1417ef5 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -339,6 +339,18 @@ public final class MediaStore { */ public static final String MEDIA_ID = "media_id"; } + + /** + * The MIME type of the file + * <P>Type: TEXT</P> + */ + public static final String MIME_TYPE = "mime_type"; + + /** + * The title of the content + * <P>Type: TEXT</P> + */ + public static final String TITLE = "title"; } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 6af6a81..e1c84ef 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -34,7 +34,10 @@ import android.content.res.Resources; import android.database.Cursor; import android.database.SQLException; import android.net.Uri; -import android.os.*; +import android.os.BatteryManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemProperties; import android.text.TextUtils; import android.util.AndroidException; import android.util.Config; @@ -2527,6 +2530,60 @@ public final class Settings { "enabled_accessibility_services"; /** + * If injection of accessibility enhancing JavaScript scripts + * is enabled. + * <p> + * Note: Accessibility injecting scripts are served by the + * Google infrastructure and enable users with disabilities to + * efficiantly navigate in and explore web content. + * </p> + * <p> + * This property represents a boolean value. + * </p> + * @hide + */ + public static final String ACCESSIBILITY_SCRIPT_INJECTION = + "accessibility_script_injection"; + + /** + * Key bindings for navigation in built-in accessibility support for web content. + * <p> + * Note: These key bindings are for the built-in accessibility navigation for + * web content which is used as a fall back solution if JavaScript in a WebView + * is not enabled or the user has not opted-in script injection from Google. + * </p> + * <p> + * The bindings are separated by semi-colon. A binding is a mapping from + * a key to a sequence of actions (for more details look at + * android.webkit.AccessibilityInjector). A key is represented as the hexademical + * string representation of an integer obtained from a meta state (optional) shifted + * sixteen times left and bitwise ored with a key code. An action is represented + * as a hexademical string representation of an integer where the first two digits + * are navigation action index, the second, the third, and the fourth digit pairs + * represent the action arguments. The separate actions in a binding are colon + * separated. The key and the action sequence it maps to are separated by equals. + * </p> + * <p> + * For example, the binding below maps the DPAD right button to traverse the + * current navigation axis once without firing an accessibility event and to + * perform the same traversal again but to fire an event: + * <code> + * 0x16=0x01000100:0x01000101; + * </code> + * </p> + * <p> + * The goal of this binding is to enable dynamic rebinding of keys to + * navigation actions for web content without requiring a framework change. + * </p> + * <p> + * This property represents a string value. + * </p> + * @hide + */ + public static final String ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS = + "accessibility_web_content_key_bindings"; + + /** * Setting to always use the default text-to-speech settings regardless * of the application settings. * 1 = override application settings, @@ -3497,6 +3554,7 @@ public final class Settings { PARENTAL_CONTROL_REDIRECT_URL, USB_MASS_STORAGE_ENABLED, ACCESSIBILITY_ENABLED, + ACCESSIBILITY_SCRIPT_INJECTION, BACKUP_AUTO_RESTORE, ENABLED_ACCESSIBILITY_SERVICES, TTS_USE_DEFAULTS, @@ -3525,13 +3583,7 @@ public final class Settings { */ public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED); - if (allowedProviders != null) { - return (allowedProviders.equals(provider) || - allowedProviders.contains("," + provider + ",") || - allowedProviders.startsWith(provider + ",") || - allowedProviders.endsWith("," + provider)); - } - return false; + return TextUtils.delimitedStringContains(allowedProviders, ',', provider); } /** diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index fde3769..7ba11bb 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -614,6 +614,17 @@ class BluetoothEventLoop { mWakeLock.release(); } + private void onRequestOobData(String objectPath , int nativeData) { + String address = checkPairingRequestAndGetAddress(objectPath, nativeData); + if (address == null) return; + + Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); + intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, + BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + } + private boolean onAgentAuthorize(String objectPath, String deviceUuid) { if (!mBluetoothService.isEnabled()) return false; @@ -672,6 +683,19 @@ class BluetoothEventLoop { return false; } + private boolean onAgentOutOfBandDataAvailable(String objectPath) { + if (!mBluetoothService.isEnabled()) return false; + + String address = mBluetoothService.getAddressFromObjectPath(objectPath); + if (address == null) return false; + + if (mBluetoothService.getDeviceOutOfBandData( + mAdapter.getRemoteDevice(address)) != null) { + return true; + } + return false; + } + private boolean isOtherSinkInNonDisconnectingState(String address) { BluetoothA2dp a2dp = new BluetoothA2dp(mContext); Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks(); diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 7252736..71f2477 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -55,6 +55,7 @@ import android.os.ServiceManager; import android.os.SystemService; import android.provider.Settings; import android.util.Log; +import android.util.Pair; import com.android.internal.app.IBatteryStats; @@ -144,6 +145,7 @@ public class BluetoothService extends IBluetooth.Stub { private BluetoothA2dpService mA2dpService; private final HashMap<BluetoothDevice, Integer> mInputDevices; private final HashMap<BluetoothDevice, Integer> mPanDevices; + private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData; private static String mDockAddress; private String mDockPin; @@ -200,6 +202,7 @@ public class BluetoothService extends IBluetooth.Stub { mDeviceProperties = new HashMap<String, Map<String,String>>(); mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>(); + mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>(); mUuidIntentTracker = new ArrayList<String>(); mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>(); mServiceRecordToPid = new HashMap<Integer, Integer>(); @@ -1155,7 +1158,7 @@ public class BluetoothService extends IBluetooth.Stub { mIsDiscovering = isDiscovering; } - public synchronized boolean createBond(String address) { + private boolean isBondingFeasible(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (!isEnabledInternal()) return false; @@ -1185,17 +1188,67 @@ public class BluetoothService extends IBluetooth.Stub { return false; } } + return true; + } + + public synchronized boolean createBond(String address) { + if (!isBondingFeasible(address)) return false; + + if (!createPairedDeviceNative(address, 60000 /*1 minute*/ )) { + return false; + } + + mBondState.setPendingOutgoingBonding(address); + mBondState.setBondState(address, BluetoothDevice.BOND_BONDING); + + return true; + } + + public synchronized boolean createBondOutOfBand(String address, byte[] hash, + byte[] randomizer) { + if (!isBondingFeasible(address)) return false; - if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) { + if (!createPairedDeviceOutOfBandNative(address, 60000 /* 1 minute */)) { return false; } + setDeviceOutOfBandData(address, hash, randomizer); mBondState.setPendingOutgoingBonding(address); mBondState.setBondState(address, BluetoothDevice.BOND_BONDING); return true; } + public synchronized boolean setDeviceOutOfBandData(String address, byte[] hash, + byte[] randomizer) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + + Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer); + + if (DBG) { + log("Setting out of band data for:" + address + ":" + + Arrays.toString(hash) + ":" + Arrays.toString(randomizer)); + } + + mDeviceOobData.put(address, value); + return true; + } + + Pair<byte[], byte[]> getDeviceOutOfBandData(BluetoothDevice device) { + return mDeviceOobData.get(device.getAddress()); + } + + + public synchronized byte[] readOutOfBandData() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, + "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return null; + + return readAdapterOutOfBandDataNative(); + } + public synchronized boolean cancelBondProcess(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); @@ -1954,6 +2007,32 @@ public class BluetoothService extends IBluetooth.Stub { return setPairingConfirmationNative(address, confirm, data.intValue()); } + public synchronized boolean setRemoteOutOfBandData(String address) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + address = address.toUpperCase(); + Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); + if (data == null) { + Log.w(TAG, "setRemoteOobData(" + address + ") called but no native data available, " + + "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + + " or by bluez.\n"); + return false; + } + + Pair<byte[], byte[]> val = mDeviceOobData.get(address); + byte[] hash, randomizer; + if (val == null) { + // TODO: check what should be passed in this case. + hash = new byte[16]; + randomizer = new byte[16]; + } else { + hash = val.first; + randomizer = val.second; + } + return setRemoteOutOfBandDataNative(address, hash, randomizer, data.intValue()); + } + public synchronized boolean cancelPairingUserInput(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); @@ -2487,6 +2566,9 @@ public class BluetoothService extends IBluetooth.Stub { private native boolean stopDiscoveryNative(); private native boolean createPairedDeviceNative(String address, int timeout_ms); + private native boolean createPairedDeviceOutOfBandNative(String address, int timeout_ms); + private native byte[] readAdapterOutOfBandDataNative(); + private native boolean cancelDeviceCreationNative(String address); private native boolean removeDeviceNative(String objectPath); private native int getDeviceServiceChannelNative(String objectPath, String uuid, @@ -2497,6 +2579,9 @@ public class BluetoothService extends IBluetooth.Stub { private native boolean setPasskeyNative(String address, int passkey, int nativeData); private native boolean setPairingConfirmationNative(String address, boolean confirm, int nativeData); + private native boolean setRemoteOutOfBandDataNative(String address, byte[] hash, + byte[] randomizer, int nativeData); + private native boolean setDevicePropertyBooleanNative(String objectPath, String key, int value); private native boolean createDeviceNative(String address); diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 2d6c7b6..7748265 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1508,6 +1508,35 @@ public class TextUtils { return mode; } + /** + * Does a comma-delimited list 'delimitedString' contain a certain item? + * (without allocating memory) + * + * @hide + */ + public static boolean delimitedStringContains( + String delimitedString, char delimiter, String item) { + if (isEmpty(delimitedString) || isEmpty(item)) { + return false; + } + int pos = -1; + int length = delimitedString.length(); + while ((pos = delimitedString.indexOf(item, pos + 1)) != -1) { + if (pos > 0 && delimitedString.charAt(pos - 1) != delimiter) { + continue; + } + int expectedDelimiterPos = pos + item.length(); + if (expectedDelimiterPos == length) { + // Match at end of string. + return true; + } + if (delimitedString.charAt(expectedDelimiterPos) == delimiter) { + return true; + } + } + return false; + } + private static Object sLock = new Object(); private static char[] sTemp = null; } diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index 15fb839..8ad9a62 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -266,7 +266,7 @@ public class FocusFinder { /** - * Do the "beams" w.r.t the given direcition's axos of rect1 and rect2 overlap? + * Do the "beams" w.r.t the given direcition's axis of rect1 and rect2 overlap? * @param direction the direction (up, down, left, right) * @param rect1 The first rectangle * @param rect2 The second rectangle diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 9afd16e..184e0fc 100755 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -23,7 +23,9 @@ import android.os.Parcelable; * Common base class for input events. */ public abstract class InputEvent implements Parcelable { + /** @hide */ protected int mDeviceId; + /** @hide */ protected int mSource; /** @hide */ @@ -76,7 +78,7 @@ public abstract class InputEvent implements Parcelable { mSource = source; } - public final int describeContents() { + public int describeContents() { return 0; } diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index d0985d9..7d5dcd8 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -16,8 +16,7 @@ package android.view; -import java.io.IOException; -import java.lang.reflect.Method; +import com.android.internal.view.menu.MenuItemImpl; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -29,7 +28,9 @@ import android.content.res.XmlResourceParser; import android.util.AttributeSet; import android.util.Xml; -import com.android.internal.view.menu.MenuItemImpl; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; /** * This class is used to instantiate menu XML files into Menu objects. @@ -52,6 +53,8 @@ public class MenuInflater { private static final int NO_ID = 0; + private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[]{Context.class}; + private Context mContext; /** @@ -249,6 +252,9 @@ public class MenuInflater { * - -1: Safe sentinel for "no value". */ private int itemShowAsAction; + + private int itemActionViewLayout; + private String itemActionViewClassName; private String itemListenerMethodName; @@ -325,6 +331,8 @@ public class MenuInflater { itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled); itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, -1); itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick); + itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0); + itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass); a.recycle(); @@ -368,6 +376,19 @@ public class MenuInflater { impl.setExclusiveCheckable(true); } } + + if (itemActionViewClassName != null) { + try { + final Class<?> clazz = Class.forName(itemActionViewClassName); + Constructor<?> c = clazz.getConstructor(ACTION_VIEW_CONSTRUCTOR_SIGNATURE); + item.setActionView((View) c.newInstance(mContext)); + } catch (Exception e) { + throw new InflateException(e); + } + } else if (itemActionViewLayout > 0) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + item.setActionView(inflater.inflate(itemActionViewLayout, null)); + } } public void addItem() { diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java index 2604cf9..8b9d659 100644 --- a/core/java/android/view/MenuItem.java +++ b/core/java/android/view/MenuItem.java @@ -405,6 +405,29 @@ public interface MenuItem { * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default. * * @see android.app.ActionBar + * @see #setActionView(View) */ public void setShowAsAction(int actionEnum); + + /** + * Set an action view for this menu item. An action view will be displayed in place + * of an automatically generated menu item element in the UI when this item is shown + * as an action within a parent. + * + * @param view View to use for presenting this item to the user. + * @return This Item so additional setters can be called. + * + * @see #setShowAsAction(int) + */ + public MenuItem setActionView(View view); + + /** + * Returns the currently set action view for this menu item. + * + * @return This item's action view + * + * @see #setActionView(View) + * @see #setShowAsAction(int) + */ + public View getActionView(); }
\ No newline at end of file diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 833fa70..51c3879 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,9 +16,6 @@ package android.view; -import com.android.internal.R; -import com.android.internal.view.menu.MenuBuilder; - import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -49,8 +46,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.AttributeSet; -import android.util.Config; -import android.util.EventLog; import android.util.Log; import android.util.Pool; import android.util.Poolable; @@ -67,6 +62,8 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.ScrollBarDrawable; +import com.android.internal.R; +import com.android.internal.view.menu.MenuBuilder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -4022,12 +4019,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ public boolean dispatchKeyEvent(KeyEvent event) { // If any attached key listener a first crack at the event. - //noinspection SimplifiableIfStatement + //noinspection SimplifiableIfStatement,deprecation if (android.util.Config.LOGV) { captureViewInfo("captureViewKeyEvent", this); } + //noinspection SimplifiableIfStatement if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnKeyListener.onKey(this, event.getKeyCode(), event)) { return true; @@ -4059,6 +4057,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility return false; } + //noinspection SimplifiableIfStatement if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; @@ -4075,6 +4074,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @see #getFilterTouchesWhenObscured */ public boolean onFilterTouchEventForSecurity(MotionEvent event) { + //noinspection RedundantIfStatement if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) { // Window is obscured, drop this touch. @@ -7690,6 +7690,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * <p>Indicates whether this view is attached to an hardware accelerated + * window or not.</p> + * + * <p>Even if this method returns true, it does not mean that every call + * to {@link #draw(android.graphics.Canvas)} will be made with an hardware + * accelerated {@link android.graphics.Canvas}. For instance, if this view + * is drawn onto an offscren {@link android.graphics.Bitmap} and its + * window is hardware accelerated, + * {@link android.graphics.Canvas#isHardwareAccelerated()} will likely + * return false, and this method will return true.</p> + * + * @return True if the view is attached to a window and the window is + * hardware accelerated; false in any other case. + */ + public boolean isHardwareAccelerated() { + return mAttachInfo != null && mAttachInfo.mHardwareAccelerated; + } + + /** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called. When implementing a view, do not override this method; instead, @@ -7829,8 +7848,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility saveCount = canvas.getSaveCount(); int solidColor = getSolidColor(); - // TODO: Temporarily disable fading edges with hardware acceleration - if (solidColor == 0 && !canvas.isHardwareAccelerated()) { + if (solidColor == 0) { final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if (drawTop) { @@ -9577,6 +9595,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility if (mAttachInfo == null) { return false; } + //noinspection SimplifiableIfStatement if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0 && !isHapticFeedbackEnabled()) { return false; @@ -10057,6 +10076,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility IBinder mPanelParentWindowToken; Surface mSurface; + boolean mHardwareAccelerated; + /** * Scale factor used by the compatibility mode */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index c8b26ef..012f3cc 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -877,7 +877,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mSplitMotionTargets = new SplitMotionTargets(); } return dispatchSplitTouchEvent(ev); - } + } final int action = ev.getAction(); final float xf = ev.getX(); @@ -1222,13 +1222,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager uniqueTargetCount--; } - final boolean childHandled = target.dispatchTouchEvent(targetEvent); - handled |= childHandled; - if (!childHandled) { - // Child doesn't want these events anymore, but we're still dispatching - // other split events to children. - targets.removeView(target); - } + handled |= target.dispatchTouchEvent(targetEvent); } finally { targetEvent.recycle(); } @@ -2603,6 +2597,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges */ public void setLayoutTransition(LayoutTransition transition) { + if (mTransition != null) { + mTransition.removeTransitionListener(mLayoutTransitionListener); + } mTransition = transition; if (mTransition != null) { mTransition.addTransitionListener(mLayoutTransitionListener); @@ -3731,6 +3728,54 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** + * This method tells the ViewGroup that the given View object, which should have this + * ViewGroup as its parent, + * should be kept around (re-displayed when the ViewGroup draws its children) even if it + * is removed from its parent. This allows animations, such as those used by + * {@link android.app.Fragment} and {@link android.animation.LayoutTransition} to animate + * the removal of views. A call to this method should always be accompanied by a later call + * to {@link #endViewTransition(View)}, such as after an animation on the View has finished, + * so that the View finally gets removed. + * + * @param view The View object to be kept visible even if it gets removed from its parent. + */ + public void startViewTransition(View view) { + if (view.mParent == this) { + if (mTransitioningViews == null) { + mTransitioningViews = new ArrayList<View>(); + } + mTransitioningViews.add(view); + } + } + + /** + * This method should always be called following an earlier call to + * {@link #startViewTransition(View)}. The given View is finally removed from its parent + * and will no longer be displayed. Note that this method does not perform the functionality + * of removing a view from its parent; it just discontinues the display of a View that + * has previously been removed. + * + * @return view The View object that has been removed but is being kept around in the visible + * hierarchy by an earlier call to {@link #startViewTransition(View)}. + */ + public void endViewTransition(View view) { + if (mTransitioningViews != null) { + mTransitioningViews.remove(view); + final ArrayList<View> disappearingChildren = mDisappearingChildren; + if (disappearingChildren != null && disappearingChildren.contains(view)) { + disappearingChildren.remove(view); + if (view.mAttachInfo != null) { + view.dispatchDetachedFromWindow(); + } + if (view.mParent != null) { + view.mParent = null; + } + mGroupFlags |= FLAG_INVALIDATE_REQUIRED; + } + } + } + private LayoutTransition.TransitionListener mLayoutTransitionListener = new LayoutTransition.TransitionListener() { @Override @@ -3739,10 +3784,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // We only care about disappearing items, since we need special logic to keep // those items visible after they've been 'removed' if (transitionType == LayoutTransition.DISAPPEARING) { - if (mTransitioningViews == null) { - mTransitioningViews = new ArrayList<View>(); - } - mTransitioningViews.add(view); + startViewTransition(view); } } @@ -3750,18 +3792,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) { - mTransitioningViews.remove(view); - final ArrayList<View> disappearingChildren = mDisappearingChildren; - if (disappearingChildren != null && disappearingChildren.contains(view)) { - disappearingChildren.remove(view); - if (view.mAttachInfo != null) { - view.dispatchDetachedFromWindow(); - } - if (view.mParent != null) { - view.mParent = null; - } - mGroupFlags |= FLAG_INVALIDATE_REQUIRED; - } + endViewTransition(view); } } }; diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index e5fc859..7b20b8b 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -455,6 +455,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn mHwRenderer.destroy(true); } mHwRenderer = HardwareRenderer.createGlRenderer(2, translucent); + mAttachInfo.mHardwareAccelerated = true; } } } @@ -1255,6 +1256,11 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn dirty.setEmpty(); return; } + + if (fullRedrawNeeded) { + mAttachInfo.mIgnoreDirtyState = true; + dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); + } if (mHwRenderer != null && mHwRenderer.isEnabled()) { if (!dirty.isEmpty()) { @@ -1269,11 +1275,6 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn return; } - if (fullRedrawNeeded) { - mAttachInfo.mIgnoreDirtyState = true; - dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); - } - if (DEBUG_ORIENTATION || DEBUG_DRAW) { Log.v(TAG, "Draw " + mView + "/" + mWindowAttributes.getTitle() @@ -1586,6 +1587,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn if (mHwRenderer != null) { mHwRenderer.destroy(true); mHwRenderer = null; + mAttachInfo.mHardwareAccelerated = false; } mSurface.release(); diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java index 49ddc19..ba16c8a 100644 --- a/core/java/android/webkit/AccessibilityInjector.java +++ b/core/java/android/webkit/AccessibilityInjector.java @@ -16,27 +16,95 @@ package android.webkit; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; +import android.util.Log; +import android.util.SparseArray; import android.view.KeyEvent; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.webkit.WebViewCore.EventHub; +import java.util.ArrayList; +import java.util.Stack; + /** * This class injects accessibility into WebViews with disabled JavaScript or * WebViews with enabled JavaScript but for which we have no accessibility * script to inject. + * </p> + * Note: To avoid changes in the framework upon changing the available + * navigation axis, or reordering the navigation axis, or changing + * the key bindings, or defining sequence of actions to be bound to + * a given key this class is navigation axis agnostic. It is only + * aware of one navigation axis which is in fact the default behavior + * of webViews while using the DPAD/TrackBall. + * </p> + * In general a key binding is a mapping from meta state + key code to + * a sequence of actions. For more detail how to specify key bindings refer to + * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}. + * </p> + * The possible actions are invocations to + * {@link #setCurrentAxis(int, boolean, String)}, or + * {@link #traverseCurrentAxis(int, boolean, String)} + * {@link #traverseGivenAxis(int, int, boolean, String)} + * {@link #prefromAxisTransition(int, int, boolean, String)} + * referred via the values of: + * {@link #ACTION_SET_CURRENT_AXIS}, + * {@link #ACTION_TRAVERSE_CURRENT_AXIS}, + * {@link #ACTION_TRAVERSE_GIVEN_AXIS}, + * {@link #ACTION_PERFORM_AXIS_TRANSITION}, + * respectively. + * The arguments for the action invocation are specified as offset + * hexademical pairs. Note the last argument of the invocation + * should NOT be specified in the binding as it is provided by + * this class. For details about the key binding implementation + * refer to {@link AccessibilityWebContentKeyBinding}. */ class AccessibilityInjector { + private static final String LOG_TAG = "AccessibilityInjector"; + + private static final boolean DEBUG = true; + + private static final int ACTION_SET_CURRENT_AXIS = 0; + private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1; + private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2; + private static final int ACTION_PERFORM_AXIS_TRANSITION = 3; - // Handle to the WebView this injector is associated with. + // the default WebView behavior abstracted as a navigation axis + private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7; + + // these are the same for all instances so make them process wide + private static SparseArray<AccessibilityWebContentKeyBinding> sBindings = + new SparseArray<AccessibilityWebContentKeyBinding>(); + + // handle to the WebView this injector is associated with. private final WebView mWebView; + // events scheduled for sending as soon as we receive the selected text + private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>(); + + // the current traversal axis + private int mCurrentAxis = 2; // sentence + + // we need to consume the up if we have handled the last down + private boolean mLastDownEventHandled; + + // getting two empty selection strings in a row we let the WebView handle the event + private boolean mIsLastSelectionStringNull; + + // keep track of last direction + private int mLastDirection; + /** - * Creates a new injector associated with a given VwebView. + * Creates a new injector associated with a given {@link WebView}. * * @param webView The associated WebView. */ public AccessibilityInjector(WebView webView) { mWebView = webView; + ensureWebContentKeyBindings(); } /** @@ -45,55 +113,372 @@ class AccessibilityInjector { * @return True if the event was processed. */ public boolean onKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) { + return mLastDownEventHandled; + } + + mLastDownEventHandled = false; - // as a proof of concept let us do the simplest example + int key = event.getMetaState() << AccessibilityWebContentKeyBinding.OFFSET_META_STATE | + event.getKeyCode() << AccessibilityWebContentKeyBinding.OFFSET_KEY_CODE; - if (event.getAction() != KeyEvent.ACTION_UP) { + AccessibilityWebContentKeyBinding binding = sBindings.get(key); + if (binding == null) { return false; } - int keyCode = event.getKeyCode(); + for (int i = 0, count = binding.getActionCount(); i < count; i++) { + int actionCode = binding.getActionCode(i); + String contentDescription = Integer.toHexString(binding.getAction(i)); + switch (actionCode) { + case ACTION_SET_CURRENT_AXIS: + int axis = binding.getFirstArgument(i); + boolean sendEvent = (binding.getSecondArgument(i) == 1); + setCurrentAxis(axis, sendEvent, contentDescription); + mLastDownEventHandled = true; + break; + case ACTION_TRAVERSE_CURRENT_AXIS: + int direction = binding.getFirstArgument(i); + // on second null selection string in same direction => WebView handle the event + if (direction == mLastDirection && mIsLastSelectionStringNull) { + mLastDirection = direction; + mIsLastSelectionStringNull = false; + return false; + } + mLastDirection = direction; + sendEvent = (binding.getSecondArgument(i) == 1); + mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent, + contentDescription); + break; + case ACTION_TRAVERSE_GIVEN_AXIS: + direction = binding.getFirstArgument(i); + // on second null selection string in same direction => WebView handle the event + if (direction == mLastDirection && mIsLastSelectionStringNull) { + mLastDirection = direction; + mIsLastSelectionStringNull = false; + return false; + } + mLastDirection = direction; + axis = binding.getSecondArgument(i); + sendEvent = (binding.getThirdArgument(i) == 1); + traverseGivenAxis(direction, axis, sendEvent, contentDescription); + mLastDownEventHandled = true; + break; + case ACTION_PERFORM_AXIS_TRANSITION: + int fromAxis = binding.getFirstArgument(i); + int toAxis = binding.getSecondArgument(i); + sendEvent = (binding.getThirdArgument(i) == 1); + prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription); + mLastDownEventHandled = true; + break; + default: + Log.w(LOG_TAG, "Unknown action code: " + actionCode); + } + } + + return mLastDownEventHandled; + } - switch (keyCode) { - case KeyEvent.KEYCODE_N: - modifySelection("extend", "forward", "sentence"); - break; - case KeyEvent.KEYCODE_P: - modifySelection("extend", "backward", "sentence"); - break; + /** + * Set the current navigation axis which will be used while + * calling {@link #traverseCurrentAxis(int, boolean, String)}. + * + * @param axis The axis to set. + * @param sendEvent Whether to send an accessibility event to + * announce the change. + */ + private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) { + mCurrentAxis = axis; + if (sendEvent) { + AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(); + event.getText().add(String.valueOf(axis)); + event.setContentDescription(contentDescription); + sendAccessibilityEvent(event); } + } - return false; + /** + * Performs conditional transition one axis to another. + * + * @param fromAxis The axis which must be the current for the transition to occur. + * @param toAxis The axis to which to transition. + * @param sendEvent Flag if to send an event to announce successful transition. + * @param contentDescription A description of the performed action. + */ + private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent, + String contentDescription) { + if (mCurrentAxis == fromAxis) { + setCurrentAxis(toAxis, sendEvent, contentDescription); + } + } + + /** + * Traverse the document along the current navigation axis. + * + * @param direction The direction of traversal. + * @param sendEvent Whether to send an accessibility event to + * announce the change. + * @param contentDescription A description of the performed action. + * @see #setCurrentAxis(int, boolean, String) + */ + private boolean traverseCurrentAxis(int direction, boolean sendEvent, + String contentDescription) { + return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription); + } + + /** + * Traverse the document along the given navigation axis. + * + * @param direction The direction of traversal. + * @param axis The axis along which to traverse. + * @param sendEvent Whether to send an accessibility event to + * announce the change. + * @param contentDescription A description of the performed action. + */ + private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent, + String contentDescription) { + // if the axis is the default let WebView handle the event + if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) { + return false; + } + WebViewCore webViewCore = mWebView.getWebViewCore(); + if (webViewCore != null) { + AccessibilityEvent event = null; + if (sendEvent) { + event = getPartialyPopulatedAccessibilityEvent(); + // the text will be set upon receiving the selection string + event.setContentDescription(contentDescription); + } + mScheduledEventStack.push(event); + webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis); + } + return true; } /** * Called when the <code>selectionString</code> has changed. */ public void onSelectionStringChange(String selectionString) { - // put the selection string in an AccessibilityEvent and send it - AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); - event.getText().add(selectionString); - mWebView.sendAccessibilityEventUnchecked(event); + mIsLastSelectionStringNull = (selectionString == null); + AccessibilityEvent event = mScheduledEventStack.pop(); + if (event != null) { + event.getText().add(selectionString); + sendAccessibilityEvent(event); + } } /** - * Modifies the current selection. + * Sends an {@link AccessibilityEvent}. * - * @param alter Specifies how to alter the selection. - * @param direction The direction in which to alter the selection. - * @param granularity The granularity of the selection modification. + * @param event The event to send. */ - private void modifySelection(String alter, String direction, String granularity) { - WebViewCore webViewCore = mWebView.getWebViewCore(); + private void sendAccessibilityEvent(AccessibilityEvent event) { + if (DEBUG) { + Log.d(LOG_TAG, "Dispatching: " + event); + } + AccessibilityManager.getInstance(mWebView.getContext()).sendAccessibilityEvent(event); + } - if (webViewCore == null) { + /** + * @return An accessibility event whose members are populated except its + * text and content description. + */ + private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() { + AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED); + event.setClassName(mWebView.getClass().getName()); + event.setPackageName(mWebView.getContext().getPackageName()); + event.setEnabled(mWebView.isEnabled()); + return event; + } + + /** + * Ensures that the Web content key bindings are loaded. + */ + private void ensureWebContentKeyBindings() { + if (sBindings.size() > 0) { return; } - WebViewCore.ModifySelectionData data = new WebViewCore.ModifySelectionData(); - data.mAlter = alter; - data.mDirection = direction; - data.mGranularity = granularity; - webViewCore.sendMessage(EventHub.MODIFY_SELECTION, data); + String webContentKeyBindingsString = Settings.Secure.getString( + mWebView.getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS); + + SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';'); + semiColonSplitter.setString(webContentKeyBindingsString); + + ArrayList<AccessibilityWebContentKeyBinding> bindings = + new ArrayList<AccessibilityWebContentKeyBinding>(); + + while (semiColonSplitter.hasNext()) { + String bindingString = semiColonSplitter.next(); + if (TextUtils.isEmpty(bindingString)) { + Log.e(LOG_TAG, "Malformed Web content key binding: " + + webContentKeyBindingsString); + continue; + } + String[] keyValueArray = bindingString.split("="); + if (keyValueArray.length != 2) { + Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + + bindingString); + continue; + } + try { + SimpleStringSplitter colonSplitter = new SimpleStringSplitter(':');//remove + int key = Integer.decode(keyValueArray[0].trim()); + String[] actionStrings = keyValueArray[1].split(":"); + int[] actions = new int[actionStrings.length]; + for (int i = 0, count = actions.length; i < count; i++) { + actions[i] = Integer.decode(actionStrings[i].trim()); + } + + bindings.add(new AccessibilityWebContentKeyBinding(key, actions)); + } catch (NumberFormatException nfe) { + Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString); + } + } + + for (AccessibilityWebContentKeyBinding binding : bindings) { + sBindings.put(binding.getKey(), binding); + } + } + + /** + * Represents a web content key-binding. + */ + private class AccessibilityWebContentKeyBinding { + + private static final int OFFSET_META_STATE = 0x00000010; + + private static final int MASK_META_STATE = 0xFFFF0000; + + private static final int OFFSET_KEY_CODE = 0x00000000; + + private static final int MASK_KEY_CODE = 0x0000FFFF; + + private static final int OFFSET_ACTION = 0x00000018; + + private static final int MASK_ACTION = 0xFF000000; + + private static final int OFFSET_FIRST_ARGUMENT = 0x00000010; + + private static final int MASK_FIRST_ARGUMENT = 0x00FF0000; + + private static final int OFFSET_SECOND_ARGUMENT = 0x00000008; + + private static final int MASK_SECOND_ARGUMENT = 0x0000FF00; + + private static final int OFFSET_THIRD_ARGUMENT = 0x00000000; + + private static final int MASK_THIRD_ARGUMENT = 0x000000FF; + + private int mKey; + + private int [] mActionSequence; + + /** + * @return The binding key with key code and meta state. + * + * @see #MASK_KEY_CODE + * @see #MASK_META_STATE + * @see #OFFSET_KEY_CODE + * @see #OFFSET_META_STATE + */ + public int getKey() { + return mKey; + } + + /** + * @return The key code of the binding key. + */ + public int getKeyCode() { + return (mKey & MASK_KEY_CODE) >> OFFSET_KEY_CODE; + } + + /** + * @return The meta state of the binding key. + */ + public int getMetaState() { + return (mKey & MASK_META_STATE) >> OFFSET_META_STATE; + } + + /** + * @return The number of actions in the key binding. + */ + public int getActionCount() { + return mActionSequence.length; + } + + /** + * @param index The action for a given action <code>index</code>. + */ + public int getAction(int index) { + return mActionSequence[index]; + } + + /** + * @param index The action code for a given action <code>index</code>. + */ + public int getActionCode(int index) { + return (mActionSequence[index] & MASK_ACTION) >> OFFSET_ACTION; + } + + /** + * @param index The first argument for a given action <code>index</code>. + */ + public int getFirstArgument(int index) { + return (mActionSequence[index] & MASK_FIRST_ARGUMENT) >> OFFSET_FIRST_ARGUMENT; + } + + /** + * @param index The second argument for a given action <code>index</code>. + */ + public int getSecondArgument(int index) { + return (mActionSequence[index] & MASK_SECOND_ARGUMENT) >> OFFSET_SECOND_ARGUMENT; + } + + /** + * @param index The third argument for a given action <code>index</code>. + */ + public int getThirdArgument(int index) { + return (mActionSequence[index] & MASK_THIRD_ARGUMENT) >> OFFSET_THIRD_ARGUMENT; + } + + /** + * Creates a new instance. + * @param key The key for the binding (key and meta state) + * @param actionSequence The sequence of action for the binding. + * @see #getKey() + */ + public AccessibilityWebContentKeyBinding(int key, int[] actionSequence) { + mKey = key; + mActionSequence = actionSequence; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("key: "); + builder.append(getKey()); + builder.append(", metaState: "); + builder.append(getMetaState()); + builder.append(", keyCode: "); + builder.append(getKeyCode()); + builder.append(", actions["); + for (int i = 0, count = getActionCount(); i < count; i++) { + builder.append("{actionCode"); + builder.append(i); + builder.append(": "); + builder.append(getActionCode(i)); + builder.append(", firstArgument: "); + builder.append(getFirstArgument(i)); + builder.append(", secondArgument: "); + builder.append(getSecondArgument(i)); + builder.append(", thirdArgument: "); + builder.append(getThirdArgument(i)); + builder.append("}"); + } + builder.append("]"); + return builder.toString(); + } } } diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 1b5651b..b00f88c 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -256,17 +256,10 @@ class CallbackProxy extends Handler { // 32-bit reads and writes. switch (msg.what) { case PAGE_STARTED: - // every time we start a new page, we want to reset the - // WebView certificate: - // if the new site is secure, we will reload it and get a - // new certificate set; - // if the new site is not secure, the certificate must be - // null, and that will be the case - mWebView.setCertificate(null); + String startedUrl = msg.getData().getString("url"); + mWebView.onPageStarted(startedUrl); if (mWebViewClient != null) { - mWebViewClient.onPageStarted(mWebView, - msg.getData().getString("url"), - (Bitmap) msg.obj); + mWebViewClient.onPageStarted(mWebView, startedUrl, (Bitmap) msg.obj); } break; diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index bc85444..d4acff8 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -49,6 +49,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.provider.Settings; import android.speech.tts.TextToSpeech; import android.text.Selection; import android.text.Spannable; @@ -101,6 +102,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * <p>A View that displays web pages. This class is the basis upon which you @@ -535,6 +538,10 @@ public class WebView extends AbsoluteLayout // JavaScript or ones for which no accessibility script exists private AccessibilityInjector mAccessibilityInjector; + // flag indicating if accessibility script is injected so we + // know to handle Shift and arrows natively first + private boolean mAccessibilityScriptInjected; + // the color used to highlight the touch rectangles private static final int mHightlightColor = 0x33000000; // the round corner for the highlight path @@ -694,6 +701,11 @@ public class WebView extends AbsoluteLayout private int mHorizontalScrollBarMode = SCROLLBAR_AUTO; private int mVerticalScrollBarMode = SCROLLBAR_AUTO; + // constants for determining script injection strategy + private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1; + private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0; + private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1; + // the alias via which accessibility JavaScript interface is exposed private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility"; @@ -707,6 +719,14 @@ public class WebView extends AbsoluteLayout " document.getElementsByTagName('head')[0].appendChild(chooser);" + " })();"; + // Regular expression that matches the "axs" URL parameter. + // The value of 0 means the accessibility script is opted out + // The value of 1 means the accessibility script is already injected + private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))"; + + // variable to cache the above pattern in case accessibility is enabled. + private Pattern mMatchAxsUrlParameterPattern; + // Used to match key downs and key ups private boolean mGotKeyDown; @@ -2947,6 +2967,23 @@ public class WebView extends AbsoluteLayout } /** + * Called by CallbackProxy when the page starts loading. + * @param url The URL of the page which has started loading. + */ + /* package */ void onPageStarted(String url) { + // every time we start a new page, we want to reset the + // WebView certificate: if the new site is secure, we + // will reload it and get a new certificate set; + // if the new site is not secure, the certificate must be + // null, and that will be the case + setCertificate(null); + + // reset the flag since we set to true in if need after + // loading is see onPageFinished(Url) + mAccessibilityScriptInjected = false; + } + + /** * Called by CallbackProxy when the page finishes loading. * @param url The URL of the page which has finished loading. */ @@ -2971,23 +3008,93 @@ public class WebView extends AbsoluteLayout * is enabled. If JavaScript is enabled we try to inject a URL specific script. * If no URL specific script is found or JavaScript is disabled we fallback to * the default {@link AccessibilityInjector} implementation. + * </p> + * If the URL has the "axs" paramter set to 1 it has already done the + * script injection so we do nothing. If the parameter is set to 0 + * the URL opts out accessibility script injection so we fall back to + * the default {@link AccessibilityInjector}. + * </p> + * Note: If the user has not opted-in the accessibility script injection no scripts + * are injected rather the default {@link AccessibilityInjector} implementation + * is used. * * @param url The URL loaded by this {@link WebView}. */ private void injectAccessibilityForUrl(String url) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - if (getSettings().getJavaScriptEnabled()) { + AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); + + if (!accessibilityManager.isEnabled()) { + // it is possible that accessibility was turned off between reloads + ensureAccessibilityScriptInjectorInstance(false); + return; + } + + if (!getSettings().getJavaScriptEnabled()) { + // no JS so we fallback to the basic buil-in support + ensureAccessibilityScriptInjectorInstance(true); + return; + } + + // check the URL "axs" parameter to choose appropriate action + int axsParameterValue = getAxsUrlParameterValue(url); + if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) { + boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext + .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1); + if (onDeviceScriptInjectionEnabled) { + ensureAccessibilityScriptInjectorInstance(false); + // neither script injected nor script injection opted out => we inject loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT); - } else if (mAccessibilityInjector == null) { - mAccessibilityInjector = new AccessibilityInjector(this); + // TODO: Set this flag after successfull script injection. Maybe upon injection + // the chooser should update the meta tag and we check it to declare success + mAccessibilityScriptInjected = true; + } else { + // injection disabled so we fallback to the basic built-in support + ensureAccessibilityScriptInjectorInstance(true); } + } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) { + // injection opted out so we fallback to the basic buil-in support + ensureAccessibilityScriptInjectorInstance(true); + } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) { + ensureAccessibilityScriptInjectorInstance(false); + // the URL provides accessibility but we still need to add our generic script + loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT); + } else { + Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue); + } + } + + /** + * Ensures the instance of the {@link AccessibilityInjector} to be present ot not. + * + * @param present True to ensure an insance, false to ensure no instance. + */ + private void ensureAccessibilityScriptInjectorInstance(boolean present) { + if (present && mAccessibilityInjector == null) { + mAccessibilityInjector = new AccessibilityInjector(this); } else { - // it is possible that accessibility was turned off between reloads mAccessibilityInjector = null; } } /** + * Gets the "axs" URL parameter value. + * + * @param url A url to fetch the paramter from. + * @return The parameter value if such, -1 otherwise. + */ + private int getAxsUrlParameterValue(String url) { + if (mMatchAxsUrlParameterPattern == null) { + mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER); + } + Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url); + if (matcher.find()) { + String keyValuePair = url.substring(matcher.start(), matcher.end()); + return Integer.parseInt(keyValuePair.split("=")[1]); + } + return -1; + } + + /** * The URL of a page that sent a message to scroll the title bar off screen. * * Many mobile sites tell the page to scroll to (0,1) in order to scroll the @@ -3390,6 +3497,14 @@ public class WebView extends AbsoluteLayout // to windows overview, the WebView will be temporarily removed from the // view system. In that case, do nothing. if (getParent() == null) return false; + + // A multi-finger gesture can look like a long press; make sure we don't take + // long press actions if we're scaling. + final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector(); + if (detector != null && detector.isInProgress()) { + return false; + } + if (mNativeClass != 0 && nativeCursorIsTextInput()) { // Send the click so that the textfield is in focus centerKeyPressOnTextField(); @@ -3956,7 +4071,7 @@ public class WebView extends AbsoluteLayout if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { - if (nativePageShouldHandleShiftAndArrows()) { + if (pageShouldHandleShiftAndArrows()) { mShiftIsPressed = true; } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) { setUpSelect(); @@ -3976,7 +4091,7 @@ public class WebView extends AbsoluteLayout if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { switchOutDrawHistory(); - if (nativePageShouldHandleShiftAndArrows()) { + if (pageShouldHandleShiftAndArrows()) { letPageHandleNavKey(keyCode, event.getEventTime(), true); return true; } @@ -4110,7 +4225,7 @@ public class WebView extends AbsoluteLayout if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { - if (nativePageShouldHandleShiftAndArrows()) { + if (pageShouldHandleShiftAndArrows()) { mShiftIsPressed = false; } else if (copySelection()) { selectionDone(); @@ -4120,7 +4235,7 @@ public class WebView extends AbsoluteLayout if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { - if (nativePageShouldHandleShiftAndArrows()) { + if (pageShouldHandleShiftAndArrows()) { letPageHandleNavKey(keyCode, event.getEventTime(), false); return true; } @@ -4838,7 +4953,7 @@ public class WebView extends AbsoluteLayout mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) { // if the page disallows zoom, skip multi-pointer action - if (mZoomManager.isZoomScaleFixed()) { + if (!mZoomManager.supportsPanDuringZoom() && mZoomManager.isZoomScaleFixed()) { return true; } @@ -4849,17 +4964,29 @@ public class WebView extends AbsoluteLayout MotionEvent temp = MotionEvent.obtain(ev); // Clear the original event and set it to // ACTION_POINTER_DOWN. - temp.setAction(temp.getAction() & - ~MotionEvent.ACTION_MASK | - MotionEvent.ACTION_POINTER_DOWN); - detector.onTouchEvent(temp); + try { + temp.setAction(temp.getAction() & + ~MotionEvent.ACTION_MASK | + MotionEvent.ACTION_POINTER_DOWN); + detector.onTouchEvent(temp); + } finally { + temp.recycle(); + } } detector.onTouchEvent(ev); if (detector.isInProgress()) { mLastTouchTime = eventTime; - return true; + cancelLongPress(); + mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); + if (!mZoomManager.supportsPanDuringZoom()) { + return true; + } + mTouchMode = TOUCH_DRAG_MODE; + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } } x = detector.getFocusX(); @@ -5073,15 +5200,21 @@ public class WebView extends AbsoluteLayout mLastTouchTime = eventTime; break; } - // if it starts nearly horizontal or vertical, enforce it - int ax = Math.abs(deltaX); - int ay = Math.abs(deltaY); - if (ax > MAX_SLOPE_FOR_DIAG * ay) { - mSnapScrollMode = SNAP_X; - mSnapPositive = deltaX > 0; - } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { - mSnapScrollMode = SNAP_Y; - mSnapPositive = deltaY > 0; + + // Only lock dragging to one axis if we don't have a scale in progress. + // Scaling implies free-roaming movement. Note this is only ever a question + // if mZoomManager.supportsPanDuringZoom() is true. + if (detector != null && !detector.isInProgress()) { + // if it starts nearly horizontal or vertical, enforce it + int ax = Math.abs(deltaX); + int ay = Math.abs(deltaY); + if (ax > MAX_SLOPE_FOR_DIAG * ay) { + mSnapScrollMode = SNAP_X; + mSnapPositive = deltaX > 0; + } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { + mSnapScrollMode = SNAP_Y; + mSnapPositive = deltaY > 0; + } } mTouchMode = TOUCH_DRAG_MODE; @@ -5524,7 +5657,8 @@ public class WebView extends AbsoluteLayout } return false; // let common code in onKeyUp at it } - if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { + if ((mMapTrackballToArrowKeys && mShiftIsPressed == false) || + (mAccessibilityInjector != null || mAccessibilityScriptInjected)) { if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); return false; } @@ -7262,6 +7396,16 @@ public class WebView extends AbsoluteLayout } /** + * @return If the page should receive Shift and arrows. + */ + private boolean pageShouldHandleShiftAndArrows() { + // TODO: Maybe the injected script should announce its presence in + // the page meta-tag so the nativePageShouldHandleShiftAndArrows + // will check that as one of the conditions it looks for + return (nativePageShouldHandleShiftAndArrows() || mAccessibilityScriptInjected); + } + + /** * Set the background color. It's white by default. Pass * zero to make the view transparent. * @param color the ARGB color described by Color.java diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 60ddf08..56d6296 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -572,13 +572,12 @@ final class WebViewCore { /** * Modifies the current selection. * - * @param alter Specifies how to alter the selection. * @param direction The direction in which to alter the selection. * @param granularity The granularity of the selection modification. * * @return The selection string. */ - private native String nativeModifySelection(String alter, String direction, String granularity); + private native String nativeModifySelection(int direction, int granularity); // EventHub for processing messages private final EventHub mEventHub; @@ -724,12 +723,6 @@ final class WebViewCore { boolean mRemember; } - static class ModifySelectionData { - String mAlter; - String mDirection; - String mGranularity; - } - static final String[] HandlerDebugString = { "REVEAL_SELECTION", // 96 "REQUEST_LABEL", // 97 @@ -1270,16 +1263,9 @@ final class WebViewCore { break; case MODIFY_SELECTION: - ModifySelectionData modifySelectionData = - (ModifySelectionData) msg.obj; - String selectionString = nativeModifySelection( - modifySelectionData.mAlter, - modifySelectionData.mDirection, - modifySelectionData.mGranularity); - - mWebView.mPrivateHandler.obtainMessage( - WebView.SELECTION_STRING_CHANGED, selectionString) - .sendToTarget(); + String selectionString = nativeModifySelection(msg.arg1, msg.arg2); + mWebView.mPrivateHandler.obtainMessage(WebView.SELECTION_STRING_CHANGED, + selectionString).sendToTarget(); break; case LISTBOX_CHOICES: diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 1323217..a760e91 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -166,6 +166,11 @@ class ZoomManager { // whether support multi-touch private boolean mSupportMultiTouch; + + /** + * True if we have a touch panel capable of detecting smooth pan/scale at the same time + */ + private boolean mAllowPanAndScale; // use the framework's ScaleGestureDetector to handle multi-touch private ScaleGestureDetector mScaleDetector; @@ -599,10 +604,12 @@ class ZoomManager { // check the preconditions assert mWebView.getSettings() != null; - WebSettings settings = mWebView.getSettings(); - mSupportMultiTouch = context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) + final WebSettings settings = mWebView.getSettings(); + final PackageManager pm = context.getPackageManager(); + mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) && settings.supportZoom() && settings.getBuiltInZoomControls(); + mAllowPanAndScale = pm.hasSystemFeature( + PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); if (mSupportMultiTouch && (mScaleDetector == null)) { mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener()); } else if (!mSupportMultiTouch && (mScaleDetector != null)) { @@ -614,6 +621,10 @@ class ZoomManager { return mSupportMultiTouch; } + public boolean supportsPanDuringZoom() { + return mAllowPanAndScale; + } + /** * 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 diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index 052a38a..0310a31 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -42,7 +42,7 @@ import android.view.animation.AnimationUtils; * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView */ public abstract class AdapterViewAnimator extends AdapterView<Adapter> - implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{ + implements RemoteViewsAdapter.RemoteAdapterConnectionCallback { private static final String TAG = "RemoteViewAnimator"; /** @@ -358,6 +358,17 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> } } + /** + * This method can be overridden so that subclasses can provide a custom frame in which their + * children can live. For example, StackView adds padding to its childrens' frames so as to + * accomodate for the highlight effect. + * + * @return The FrameLayout into which children can be placed. + */ + FrameLayout getFrameForChild() { + return new FrameLayout(mContext); + } + void showOnly(int childIndex, boolean animate, boolean onLayout) { if (mAdapter == null) return; @@ -436,7 +447,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> // We wrap the new view in a FrameLayout so as to respect the contract // with the adapter, that is, that we don't modify this view directly - FrameLayout fl = new FrameLayout(mContext); + FrameLayout fl = getFrameForChild(); // If the view from the adapter is null, we still keep an empty frame in place if (newView != null) { diff --git a/core/java/android/widget/NumberPickerButton.java b/core/java/android/widget/NumberPickerButton.java index 1c8579c..292b668 100644 --- a/core/java/android/widget/NumberPickerButton.java +++ b/core/java/android/widget/NumberPickerButton.java @@ -85,4 +85,12 @@ class NumberPickerButton extends ImageButton { mNumberPicker.cancelDecrement(); } } + + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (!hasWindowFocus) { + cancelLongpress(); + } + } + } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java new file mode 100644 index 0000000..57dc725 --- /dev/null +++ b/core/java/android/widget/SearchView.java @@ -0,0 +1,689 @@ +/* + * 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 android.widget; + +import static android.widget.SuggestionsAdapter.getColumnString; + +import com.android.internal.R; + +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.TextView.OnEditorActionListener; + +import java.util.WeakHashMap; + +/** + * Provides the user interface elements for the user to enter a search query and submit a + * request to a search provider. Shows a list of query suggestions or results, if + * available and allows the user to pick a suggestion or result to launch into. + */ +public class SearchView extends LinearLayout { + + private static final boolean DBG = false; + private static final String LOG_TAG = "SearchView"; + + private OnQueryChangeListener mOnQueryChangeListener; + private OnCloseListener mOnCloseListener; + + private boolean mIconifiedByDefault; + private boolean mIconified; + private CursorAdapter mSuggestionsAdapter; + private View mSearchButton; + private View mSubmitButton; + private View mCloseButton; + private View mSearchEditFrame; + private AutoCompleteTextView mQueryTextView; + private boolean mSubmitButtonEnabled; + private CharSequence mQueryHint; + + private SearchableInfo mSearchable; + + // A weak map of drawables we've gotten from other packages, so we don't load them + // more than once. + private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache = + new WeakHashMap<String, Drawable.ConstantState>(); + + /** + * Callbacks for changes to the query text. + */ + public interface OnQueryChangeListener { + + /** + * Called when the user submits the query. This could be due to a key press on the + * keyboard or due to pressing a submit button. + * The listener can override the standard behavior by returning true + * to indicate that it has handled the submit request. Otherwise return false to + * let the SearchView handle the submission by launching any associated intent. + * + * @param query the query text that is to be submitted + * + * @return true if the query has been handled by the listener, false to let the + * SearchView perform the default action. + */ + boolean onSubmitQuery(String query); + + /** + * Called when the query text is changed by the user. + * + * @param newText the new content of the query text field. + * + * @return false if the SearchView should perform the default action of showing any + * suggestions if available, true if the action was handled by the listener. + */ + boolean onQueryTextChanged(String newText); + } + + public interface OnCloseListener { + + /** + * The user is attempting to close the SearchView. + * + * @return true if the listener wants to override the default behavior of clearing the + * text field and dismissing it, false otherwise. + */ + boolean onClose(); + } + + public SearchView(Context context) { + this(context, null); + } + + public SearchView(Context context, AttributeSet attrs) { + super(context, attrs); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.search_view, this, true); + + mSearchButton = findViewById(R.id.search_button); + mQueryTextView = (AutoCompleteTextView) findViewById(R.id.search_src_text); + mSearchEditFrame = findViewById(R.id.search_edit_frame); + mSubmitButton = findViewById(R.id.search_go_btn); + mCloseButton = findViewById(R.id.search_close_btn); + + mSearchButton.setOnClickListener(mOnClickListener); + mCloseButton.setOnClickListener(mOnClickListener); + mSubmitButton.setOnClickListener(mOnClickListener); + mQueryTextView.addTextChangedListener(mTextWatcher); + mQueryTextView.setOnEditorActionListener(mOnEditorActionListener); + mQueryTextView.setOnItemClickListener(mOnItemClickListener); + mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, 0, 0); + setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true)); + a.recycle(); + + updateViewsVisibility(mIconifiedByDefault); + } + + /** + * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used + * to display labels, hints, suggestions, create intents for launching search results screens + * and controlling other affordances such as a voice button. + * + * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific + * activity or a global search provider. + */ + public void setSearchableInfo(SearchableInfo searchable) { + mSearchable = searchable; + if (mSearchable != null) { + updateSearchAutoComplete(); + } + updateViewsVisibility(mIconifiedByDefault); + } + + /** + * Sets a listener for user actions within the SearchView. + * + * @param listener the listener object that receives callbacks when the user performs + * actions in the SearchView such as clicking on buttons or typing a query. + */ + public void setOnQueryChangeListener(OnQueryChangeListener listener) { + mOnQueryChangeListener = listener; + } + + /** + * Sets a listener to inform when the user closes the SearchView. + * + * @param listener the listener to call when the user closes the SearchView. + */ + public void setOnCloseListener(OnCloseListener listener) { + mOnCloseListener = listener; + } + + /** + * Sets a query string in the text field and optionally submits the query as well. + * + * @param query the query string. This replaces any query text already present in the + * text field. + * @param submit whether to submit the query right now or only update the contents of + * text field. + */ + public void setQuery(CharSequence query, boolean submit) { + mQueryTextView.setText(query); + // If the query is not empty and submit is requested, submit the query + if (submit && !TextUtils.isEmpty(query)) { + onSubmitQuery(); + } + } + + /** + * Sets the hint text to display in the query text field. This overrides any hint specified + * in the SearchableInfo. + * + * @param hint the hint text to display + */ + public void setQueryHint(CharSequence hint) { + mQueryHint = hint; + updateQueryHint(); + } + + /** + * Sets the default or resting state of the search field. If true, a single search icon is + * shown by default and expands to show the text field and other buttons when pressed. Also, + * if the default state is iconified, then it collapses to that state when the close button + * is pressed. Changes to this property will take effect immediately. + * + * <p>The default value is false.</p> + * + * @param iconified whether the search field should be iconified by default + */ + public void setIconifiedByDefault(boolean iconified) { + mIconifiedByDefault = iconified; + updateViewsVisibility(iconified); + } + + /** + * Returns the default iconified state of the search field. + * @return + */ + public boolean isIconfiedByDefault() { + return mIconifiedByDefault; + } + + /** + * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is + * a temporary state and does not override the default iconified state set by + * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then + * a false here will only be valid until the user closes the field. And if the default + * state is expanded, then a true here will only clear the text field and not close it. + * + * @param iconify a true value will collapse the SearchView to an icon, while a false will + * expand it. + */ + public void setIconified(boolean iconify) { + if (iconify) { + onCloseClicked(); + } else { + onSearchClicked(); + } + } + + /** + * Returns the current iconified state of the SearchView. + * + * @return true if the SearchView is currently iconified, false if the search field is + * fully visible. + */ + public boolean isIconified() { + return mIconified; + } + + /** + * Enables showing a submit button when the query is non-empty. In cases where the SearchView + * is being used to filter the contents of the current activity and doesn't launch a separate + * results activity, then the submit button should be disabled. + * + * @param enabled true to show a submit button for submitting queries, false if a submit + * button is not required. + */ + public void setSubmitButtonEnabled(boolean enabled) { + mSubmitButton.setVisibility(enabled ? VISIBLE : GONE); + mSubmitButtonEnabled = enabled; + } + + /** + * Returns whether the submit button is enabled when necessary or never displayed. + * + * @return whether the submit button is enabled automatically when necessary + */ + public boolean isSubmitButtonEnabled() { + return mSubmitButtonEnabled; + } + + public interface FilterableListAdapter extends ListAdapter, Filterable { + } + + /** + * You can set a custom adapter if you wish. Otherwise the default adapter is used to + * display the suggestions from the suggestions provider associated with the SearchableInfo. + * + * @see #setSearchableInfo(SearchableInfo) + */ + public void setSuggestionsAdapter(CursorAdapter adapter) { + mSuggestionsAdapter = adapter; + + mQueryTextView.setAdapter(mSuggestionsAdapter); + } + + /** + * Returns the adapter used for suggestions, if any. + * @return the suggestions adapter + */ + public CursorAdapter getSuggestionsAdapter() { + return mSuggestionsAdapter; + } + + private void updateViewsVisibility(final boolean collapsed) { + mIconified = collapsed; + // Visibility of views that are visible when collapsed + final int visCollapsed = collapsed ? VISIBLE : GONE; + // Visibility of views that are visible when expanded + final int visExpanded = collapsed ? GONE : VISIBLE; + + mSearchButton.setVisibility(visCollapsed); + mSubmitButton.setVisibility(mSubmitButtonEnabled ? visExpanded : GONE); + mSearchEditFrame.setVisibility(visExpanded); + + setImeVisibility(!collapsed); + } + + private void setImeVisibility(boolean visible) { + // We made sure the IME was displayed, so also make sure it is closed + // when we go away. + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + if (visible) { + imm.showSoftInputUnchecked(0, null); + } else { + imm.hideSoftInputFromWindow(getWindowToken(), 0); + } + } + } + + private final OnClickListener mOnClickListener = new OnClickListener() { + + public void onClick(View v) { + if (v == mSearchButton) { + onSearchClicked(); + } else if (v == mCloseButton) { + onCloseClicked(); + } else if (v == mSubmitButton) { + onSubmitQuery(); + } + } + }; + + /** + * Handles the key down event for dealing with action keys. + * + * @param keyCode This is the keycode of the typed key, and is the same value as + * found in the KeyEvent parameter. + * @param event The complete event record for the typed key + * + * @return true if the event was handled here, or false if not. + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mSearchable == null) { + return false; + } + + // if it's an action specified by the searchable activity, launch the + // entered query with the action key + SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); + if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) { + launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText() + .toString()); + return true; + } + + return super.onKeyDown(keyCode, event); + } + + private void updateQueryHint() { + if (mQueryHint != null) { + mQueryTextView.setHint(mQueryHint); + } else if (mSearchable != null) { + CharSequence hint = null; + int hintId = mSearchable.getHintId(); + if (hintId != 0) { + hint = getContext().getString(hintId); + } + if (hint != null) { + mQueryTextView.setHint(hint); + } + } + } + + /** + * Updates the auto-complete text view. + */ + private void updateSearchAutoComplete() { + // close any existing suggestions adapter + //closeSuggestionsAdapter(); + + mQueryTextView.setDropDownAnimationStyle(0); // no animation + + // attach the suggestions adapter, if suggestions are available + // The existence of a suggestions authority is the proxy for "suggestions available here" + if (mSearchable.getSuggestAuthority() != null) { + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), + this, mSearchable, mOutsideDrawablesCache); + mQueryTextView.setAdapter(mSuggestionsAdapter); + } + } + + private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() { + + /** + * Called when the input method default action key is pressed. + */ + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + onSubmitQuery(); + return true; + } + }; + + private void onTextChanged(CharSequence newText) { + CharSequence text = mQueryTextView.getText(); + boolean hasText = !TextUtils.isEmpty(text); + if (isSubmitButtonEnabled()) { + mSubmitButton.setVisibility(hasText ? VISIBLE : GONE); + } + if (mOnQueryChangeListener != null) + mOnQueryChangeListener.onQueryTextChanged(newText.toString()); + } + + private void onSubmitQuery() { + CharSequence query = mQueryTextView.getText(); + if (!TextUtils.isEmpty(query)) { + if (mOnQueryChangeListener == null + || !mOnQueryChangeListener.onSubmitQuery(query.toString())) { + if (mSearchable != null) { + launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString()); + } + } + } + } + + private void onCloseClicked() { + if (mOnCloseListener == null || !mOnCloseListener.onClose()) { + mQueryTextView.setText(""); + updateViewsVisibility(mIconifiedByDefault); + } + } + + private void onSearchClicked() { + mQueryTextView.requestFocus(); + updateViewsVisibility(false); + } + + private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() { + + /** + * Implements OnItemClickListener + */ + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (DBG) + Log.d(LOG_TAG, "onItemClick() position " + position); + launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null); + } + }; + + private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() { + + /** + * Implements OnItemSelectedListener + */ + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (DBG) + Log.d(LOG_TAG, "onItemSelected() position " + position); + // A suggestion has been selected, rewrite the query if possible, + // otherwise the restore the original query. + rewriteQueryFromSuggestion(position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onNothingSelected(AdapterView<?> parent) { + if (DBG) + Log.d(LOG_TAG, "onNothingSelected()"); + } + }; + + /** + * Query rewriting. + */ + private void rewriteQueryFromSuggestion(int position) { + CharSequence oldQuery = mQueryTextView.getText(); + Cursor c = mSuggestionsAdapter.getCursor(); + if (c == null) { + return; + } + if (c.moveToPosition(position)) { + // Get the new query from the suggestion. + CharSequence newQuery = mSuggestionsAdapter.convertToString(c); + if (newQuery != null) { + // The suggestion rewrites the query. + // Update the text field, without getting new suggestions. + setQuery(newQuery); + } else { + // The suggestion does not rewrite the query, restore the user's query. + setQuery(oldQuery); + } + } else { + // We got a bad position, restore the user's query. + setQuery(oldQuery); + } + } + + /** + * Launches an intent based on a suggestion. + * + * @param position The index of the suggestion to create the intent from. + * @param actionKey The key code of the action key that was pressed, + * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. + * @param actionMsg The message for the action key that was pressed, + * or <code>null</code> if none. + * @return true if a successful launch, false if could not (e.g. bad position). + */ + private boolean launchSuggestion(int position, int actionKey, String actionMsg) { + Cursor c = mSuggestionsAdapter.getCursor(); + if ((c != null) && c.moveToPosition(position)) { + + Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg); + + // launch the intent + launchIntent(intent); + + return true; + } + return false; + } + + /** + * Launches an intent, including any special intent handling. + */ + private void launchIntent(Intent intent) { + if (intent == null) { + return; + } + try { + // If the intent was created from a suggestion, it will always have an explicit + // component here. + getContext().startActivity(intent); + } catch (RuntimeException ex) { + Log.e(LOG_TAG, "Failed launch activity: " + intent, ex); + } + } + + /** + * Sets the text in the query box, without updating the suggestions. + */ + private void setQuery(CharSequence query) { + mQueryTextView.setText(query, false); + } + + private void launchQuerySearch(int actionKey, String actionMsg, String query) { + String action = Intent.ACTION_SEARCH; + Intent intent = createIntent(action, null, null, query, null, actionKey, actionMsg); + getContext().startActivity(intent); + } + + /** + * Constructs an intent from the given information and the search dialog state. + * + * @param action Intent action. + * @param data Intent data, or <code>null</code>. + * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>. + * @param query Intent query, or <code>null</code>. + * @param componentName Data for {@link SearchManager#COMPONENT_NAME_KEY} or <code>null</code>. + * @param actionKey The key code of the action key that was pressed, + * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. + * @param actionMsg The message for the action key that was pressed, + * or <code>null</code> if none. + * @param mode The search mode, one of the acceptable values for + * {@link SearchManager#SEARCH_MODE}, or {@code null}. + * @return The intent. + */ + private Intent createIntent(String action, Uri data, String extraData, String query, + String componentName, int actionKey, String actionMsg) { + // Now build the Intent + Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // We need CLEAR_TOP to avoid reusing an old task that has other activities + // on top of the one we want. We don't want to do this in in-app search though, + // as it can be destructive to the activity stack. + if (data != null) { + intent.setData(data); + } + intent.putExtra(SearchManager.USER_QUERY, query); + if (query != null) { + intent.putExtra(SearchManager.QUERY, query); + } + if (extraData != null) { + intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); + } + if (actionKey != KeyEvent.KEYCODE_UNKNOWN) { + intent.putExtra(SearchManager.ACTION_KEY, actionKey); + intent.putExtra(SearchManager.ACTION_MSG, actionMsg); + } + intent.setComponent(mSearchable.getSearchActivity()); + return intent; + } + + /** + * When a particular suggestion has been selected, perform the various lookups required + * to use the suggestion. This includes checking the cursor for suggestion-specific data, + * and/or falling back to the XML for defaults; It also creates REST style Uri data when + * the suggestion includes a data id. + * + * @param c The suggestions cursor, moved to the row of the user's selection + * @param actionKey The key code of the action key that was pressed, + * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. + * @param actionMsg The message for the action key that was pressed, + * or <code>null</code> if none. + * @return An intent for the suggestion at the cursor's position. + */ + private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) { + try { + // use specific action if supplied, or default action if supplied, or fixed default + String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION); + + // some items are display only, or have effect via the cursor respond click reporting. + if (SearchManager.INTENT_ACTION_NONE.equals(action)) { + return null; + } + + if (action == null) { + action = mSearchable.getSuggestIntentAction(); + } + if (action == null) { + action = Intent.ACTION_SEARCH; + } + + // use specific data if supplied, or default data if supplied + String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA); + if (data == null) { + data = mSearchable.getSuggestIntentData(); + } + // then, if an ID was provided, append it. + if (data != null) { + String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); + if (id != null) { + data = data + "/" + Uri.encode(id); + } + } + Uri dataUri = (data == null) ? null : Uri.parse(data); + + String componentName = getColumnString( + c, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME); + + String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY); + String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); + + return createIntent(action, dataUri, extraData, query, componentName, actionKey, + actionMsg); + } catch (RuntimeException e ) { + int rowNum; + try { // be really paranoid now + rowNum = c.getPosition(); + } catch (RuntimeException e2 ) { + rowNum = -1; + } + Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum + + " returned exception" + e.toString()); + return null; + } + } + + /** + * Callback to watch the text field for empty/non-empty + */ + private TextWatcher mTextWatcher = new TextWatcher() { + + public void beforeTextChanged(CharSequence s, int start, int before, int after) { } + + public void onTextChanged(CharSequence s, int start, + int before, int after) { + SearchView.this.onTextChanged(s); + } + + public void afterTextChanged(Editable s) { + } + }; +} diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 2aa0bc6..0f1acbe 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -19,6 +19,7 @@ package android.widget; import android.animation.PropertyValuesHolder; import android.animation.ObjectAnimator; import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; @@ -30,6 +31,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.TableMaskFilter; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -93,6 +95,7 @@ public class StackView extends AdapterViewAnimator { */ private static final int NUM_ACTIVE_VIEWS = 5; + private static final int FRAME_PADDING = 4; /** * These variables are all related to the current state of touch interaction @@ -103,7 +106,7 @@ public class StackView extends AdapterViewAnimator { private int mActivePointerId; private int mYVelocity = 0; private int mSwipeGestureType = GESTURE_NONE; - private int mViewHeight; + private int mSlideAmount; private int mSwipeThreshold; private int mTouchSlop; private int mMaximumVelocity; @@ -116,6 +119,7 @@ public class StackView extends AdapterViewAnimator { private ViewGroup mAncestorContainingAllChildren = null; private int mAncestorHeight = 0; private int mStackMode; + private int mFramePadding; public StackView(Context context) { super(context); @@ -141,7 +145,7 @@ public class StackView extends AdapterViewAnimator { mStackSlider = new StackSlider(); if (sHolographicHelper == null) { - sHolographicHelper = new HolographicHelper(); + sHolographicHelper = new HolographicHelper(mContext); } setClipChildren(false); setClipToPadding(false); @@ -153,6 +157,11 @@ public class StackView extends AdapterViewAnimator { // This is a flag to indicate the the stack is loading for the first time mWhichChild = -1; + + // Adjust the frame padding based on the density, since the highlight changes based + // on the density + final float density = mContext.getResources().getDisplayMetrics().density; + mFramePadding = (int) Math.ceil(density * FRAME_PADDING); } /** @@ -205,7 +214,7 @@ public class StackView extends AdapterViewAnimator { view.setAlpha(0.0f); view.setVisibility(INVISIBLE); LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.setVerticalOffset(-mViewHeight); + lp.setVerticalOffset(-mSlideAmount); } else if (toIndex == -1) { // Fade item out ObjectAnimator<Float> fadeOut = new ObjectAnimator<Float> @@ -220,7 +229,7 @@ public class StackView extends AdapterViewAnimator { } private void transformViewAtIndex(int index, View view) { - float maxPerpectiveShift = mViewHeight * PERSPECTIVE_SHIFT_FACTOR; + float maxPerpectiveShift = mMeasuredHeight * PERSPECTIVE_SHIFT_FACTOR; if (index == mNumActiveViews -1) index--; @@ -233,8 +242,10 @@ public class StackView extends AdapterViewAnimator { r = (float) Math.pow(r, 2); int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; - float transY = -stackDirection * r * maxPerpectiveShift + - stackDirection * (1 - scale) * (mViewHeight / 2.0f); + float perspectiveTranslation = -stackDirection * r * maxPerpectiveShift; + float scaleShiftCorrection = stackDirection * (1 - scale) * + (mMeasuredHeight * (1 - PERSPECTIVE_SHIFT_FACTOR) / 2.0f); + float transY = perspectiveTranslation + scaleShiftCorrection; PropertyValuesHolder<Float> translationY = new PropertyValuesHolder<Float>("translationY", transY); @@ -251,6 +262,13 @@ public class StackView extends AdapterViewAnimator { } } + @Override + FrameLayout getFrameForChild() { + FrameLayout fl = new FrameLayout(mContext); + fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding); + return fl; + } + /** * Apply any necessary tranforms for the child that is being added. */ @@ -282,9 +300,9 @@ public class StackView extends AdapterViewAnimator { private void onLayout() { if (!mFirstLayoutHappened) { - mViewHeight = Math.round(SLIDE_UP_RATIO * getMeasuredHeight()); + mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight()); updateChildTransforms(); - mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mViewHeight); + mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount); mFirstLayoutHappened = true; } } @@ -395,15 +413,15 @@ public class StackView extends AdapterViewAnimator { case MotionEvent.ACTION_MOVE: { beginGestureIfNeeded(deltaY); - float rx = deltaX / (mViewHeight * 1.0f); + float rx = deltaX / (mSlideAmount * 1.0f); if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { - float r = (deltaY - mTouchSlop * 1.0f) / mViewHeight * 1.0f; + float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f; if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; mStackSlider.setYProgress(1 - r); mStackSlider.setXProgress(rx); return true; } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { - float r = -(deltaY + mTouchSlop * 1.0f) / mViewHeight * 1.0f; + float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f; if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; mStackSlider.setYProgress(r); mStackSlider.setXProgress(rx); @@ -620,8 +638,8 @@ public class StackView extends AdapterViewAnimator { switch (mMode) { case NORMAL_MODE: - viewLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight)); - highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight)); + viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); + highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); mHighlight.setAlpha(highlightAlphaInterpolator(r)); float alpha = viewAlphaInterpolator(1 - r); @@ -641,14 +659,14 @@ public class StackView extends AdapterViewAnimator { break; case BEGINNING_OF_STACK_MODE: r = r * 0.2f; - viewLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight)); - highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight)); + viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); + highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); mHighlight.setAlpha(highlightAlphaInterpolator(r)); break; case END_OF_STACK_MODE: r = (1-r) * 0.2f; - viewLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight)); - highlightLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight)); + viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); + highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); mHighlight.setAlpha(highlightAlphaInterpolator(r)); break; } @@ -665,8 +683,8 @@ public class StackView extends AdapterViewAnimator { final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); r *= 0.2f; - viewLp.setHorizontalOffset(Math.round(r * mViewHeight)); - highlightLp.setHorizontalOffset(Math.round(r * mViewHeight)); + viewLp.setHorizontalOffset(Math.round(r * mSlideAmount)); + highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount)); } void setMode(int mode) { @@ -695,8 +713,8 @@ public class StackView extends AdapterViewAnimator { float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) + Math.pow(viewLp.verticalOffset, 2)); - float maxd = (float) Math.sqrt(Math.pow(mViewHeight, 2) + - Math.pow(0.4f * mViewHeight, 2)); + float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) + + Math.pow(0.4f * mSlideAmount, 2)); if (velocity == 0) { return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION; @@ -940,17 +958,19 @@ public class StackView extends AdapterViewAnimator { private final Paint mErasePaint = new Paint(); private final Paint mBlurPaint = new Paint(); - HolographicHelper() { - initializePaints(); + HolographicHelper(Context context) { + initializePaints(context); } - void initializePaints() { + void initializePaints(Context context) { + final float density = context.getResources().getDisplayMetrics().density; + mHolographicPaint.setColor(0xff6699ff); mHolographicPaint.setFilterBitmap(true); mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); mErasePaint.setFilterBitmap(true); - mBlurPaint.setMaskFilter(new BlurMaskFilter(2, BlurMaskFilter.Blur.NORMAL)); + mBlurPaint.setMaskFilter(new BlurMaskFilter(2*density, BlurMaskFilter.Blur.NORMAL)); } Bitmap createOutline(View v) { @@ -968,12 +988,10 @@ public class StackView extends AdapterViewAnimator { v.setRotationX(0); v.setRotation(0); v.setTranslationY(0); - canvas.concat(v.getMatrix()); v.draw(canvas); v.setRotationX(rotationX); v.setRotation(rotation); v.setTranslationY(translationY); - canvas.setMatrix(id); drawOutline(canvas, bitmap); return bitmap; diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java new file mode 100644 index 0000000..1b2449e --- /dev/null +++ b/core/java/android/widget/SuggestionsAdapter.java @@ -0,0 +1,705 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import com.android.internal.R; + +import android.app.SearchDialog; +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContentResolver.OpenResourceIdResult; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.TextAppearanceSpan; +import android.util.Log; +import android.util.SparseArray; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.WeakHashMap; + +/** + * Provides the contents for the suggestion drop-down list.in {@link SearchDialog}. + * + * @hide + */ +class SuggestionsAdapter extends ResourceCursorAdapter { + + private static final boolean DBG = false; + private static final String LOG_TAG = "SuggestionsAdapter"; + private static final int QUERY_LIMIT = 50; + + private SearchManager mSearchManager; + private SearchView mSearchView; + private SearchableInfo mSearchable; + private Context mProviderContext; + private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache; + private SparseArray<Drawable.ConstantState> mBackgroundsCache; + private boolean mClosed = false; + + // URL color + private ColorStateList mUrlColor; + + // Cached column indexes, updated when the cursor changes. + private int mText1Col; + private int mText2Col; + private int mText2UrlCol; + private int mIconName1Col; + private int mIconName2Col; + private int mBackgroundColorCol; + + static final int NONE = -1; + + private final Runnable mStartSpinnerRunnable; + private final Runnable mStopSpinnerRunnable; + + /** + * The amount of time we delay in the filter when the user presses the delete key. + * @see Filter#setDelayer(android.widget.Filter.Delayer). + */ + private static final long DELETE_KEY_POST_DELAY = 500L; + + public SuggestionsAdapter(Context context, SearchView searchView, + SearchableInfo searchable, + WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) { + super(context, + com.android.internal.R.layout.search_dropdown_item_icons_2line, + null, // no initial cursor + true); // auto-requery + mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + mSearchView = searchView; + mSearchable = searchable; + + // set up provider resources (gives us icons, etc.) + Context activityContext = mSearchable.getActivityContext(mContext); + mProviderContext = mSearchable.getProviderContext(mContext, activityContext); + + mOutsideDrawablesCache = outsideDrawablesCache; + mBackgroundsCache = new SparseArray<Drawable.ConstantState>(); + + mStartSpinnerRunnable = new Runnable() { + public void run() { + // mSearchView.setWorking(true); // TODO: + } + }; + + mStopSpinnerRunnable = new Runnable() { + public void run() { + // mSearchView.setWorking(false); // TODO: + } + }; + + // delay 500ms when deleting + getFilter().setDelayer(new Filter.Delayer() { + + private int mPreviousLength = 0; + + public long getPostingDelay(CharSequence constraint) { + if (constraint == null) return 0; + + long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0; + mPreviousLength = constraint.length(); + return delay; + } + }); + } + + /** + * Overridden to always return <code>false</code>, since we cannot be sure that + * suggestion sources return stable IDs. + */ + @Override + public boolean hasStableIds() { + return false; + } + + /** + * Use the search suggestions provider to obtain a live cursor. This will be called + * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions). + * The results will be processed in the UI thread and changeCursor() will be called. + */ + @Override + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")"); + String query = (constraint == null) ? "" : constraint.toString(); + /** + * for in app search we show the progress spinner until the cursor is returned with + * the results. + */ + Cursor cursor = null; + //mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO: + try { + cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT); + // trigger fill window so the spinner stays up until the results are copied over and + // closer to being ready + if (cursor != null) { + cursor.getCount(); + return cursor; + } + } catch (RuntimeException e) { + Log.w(LOG_TAG, "Search suggestions query threw an exception.", e); + } + // If cursor is null or an exception was thrown, stop the spinner and return null. + // changeCursor doesn't get called if cursor is null + // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO: + return null; + } + + public void close() { + if (DBG) Log.d(LOG_TAG, "close()"); + changeCursor(null); + mClosed = true; + } + + @Override + public void notifyDataSetChanged() { + if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged"); + super.notifyDataSetChanged(); + + // mSearchView.onDataSetChanged(); // TODO: + + updateSpinnerState(getCursor()); + } + + @Override + public void notifyDataSetInvalidated() { + if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated"); + super.notifyDataSetInvalidated(); + + updateSpinnerState(getCursor()); + } + + private void updateSpinnerState(Cursor cursor) { + Bundle extras = cursor != null ? cursor.getExtras() : null; + if (DBG) { + Log.d(LOG_TAG, "updateSpinnerState - extra = " + + (extras != null + ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS) + : null)); + } + // Check if the Cursor indicates that the query is not complete and show the spinner + if (extras != null + && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) { + // mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO: + return; + } + // If cursor is null or is done, stop the spinner + // mSearchView.getWindow().getDecorView().post(mStopSpinnerRunnable); // TODO: + } + + /** + * Cache columns. + */ + @Override + public void changeCursor(Cursor c) { + if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")"); + + if (mClosed) { + Log.w(LOG_TAG, "Tried to change cursor after adapter was closed."); + if (c != null) c.close(); + return; + } + + try { + super.changeCursor(c); + + if (c != null) { + mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); + mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); + mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL); + mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); + mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); + mBackgroundColorCol = + c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR); + } + } catch (Exception e) { + Log.e(LOG_TAG, "error changing cursor and caching columns", e); + } + } + + /** + * Tags the view with cached child view look-ups. + */ + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View v = super.newView(context, cursor, parent); + v.setTag(new ChildViewCache(v)); + return v; + } + + /** + * Cache of the child views of drop-drown list items, to avoid looking up the children + * each time the contents of a list item are changed. + */ + private final static class ChildViewCache { + public final TextView mText1; + public final TextView mText2; + public final ImageView mIcon1; + public final ImageView mIcon2; + + public ChildViewCache(View v) { + mText1 = (TextView) v.findViewById(com.android.internal.R.id.text1); + mText2 = (TextView) v.findViewById(com.android.internal.R.id.text2); + mIcon1 = (ImageView) v.findViewById(com.android.internal.R.id.icon1); + mIcon2 = (ImageView) v.findViewById(com.android.internal.R.id.icon2); + } + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ChildViewCache views = (ChildViewCache) view.getTag(); + + int backgroundColor = 0; + if (mBackgroundColorCol != -1) { + backgroundColor = cursor.getInt(mBackgroundColorCol); + } + Drawable background = getItemBackground(backgroundColor); + view.setBackgroundDrawable(background); + + if (views.mText1 != null) { + String text1 = getStringOrNull(cursor, mText1Col); + setViewText(views.mText1, text1); + } + if (views.mText2 != null) { + // First check TEXT_2_URL + CharSequence text2 = getStringOrNull(cursor, mText2UrlCol); + if (text2 != null) { + text2 = formatUrl(text2); + } else { + text2 = getStringOrNull(cursor, mText2Col); + } + + // If no second line of text is indicated, allow the first line of text + // to be up to two lines if it wants to be. + if (TextUtils.isEmpty(text2)) { + if (views.mText1 != null) { + views.mText1.setSingleLine(false); + views.mText1.setMaxLines(2); + } + } else { + if (views.mText1 != null) { + views.mText1.setSingleLine(true); + views.mText1.setMaxLines(1); + } + } + setViewText(views.mText2, text2); + } + + if (views.mIcon1 != null) { + setViewDrawable(views.mIcon1, getIcon1(cursor)); + } + if (views.mIcon2 != null) { + setViewDrawable(views.mIcon2, getIcon2(cursor)); + } + } + + private CharSequence formatUrl(CharSequence url) { + if (mUrlColor == null) { + // Lazily get the URL color from the current theme. + TypedValue colorValue = new TypedValue(); + mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true); + mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId); + } + + SpannableString text = new SpannableString(url); + text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null), + 0, url.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return text; + } + + /** + * Gets a drawable with no color when selected or pressed, and the given color when + * neither selected nor pressed. + * + * @return A drawable, or {@code null} if the given color is transparent. + */ + private Drawable getItemBackground(int backgroundColor) { + if (backgroundColor == 0) { + return null; + } else { + Drawable.ConstantState cachedBg = mBackgroundsCache.get(backgroundColor); + if (cachedBg != null) { + if (DBG) Log.d(LOG_TAG, "Background cache hit for color " + backgroundColor); + return cachedBg.newDrawable(mProviderContext.getResources()); + } + if (DBG) Log.d(LOG_TAG, "Creating new background for color " + backgroundColor); + ColorDrawable transparent = new ColorDrawable(0); + ColorDrawable background = new ColorDrawable(backgroundColor); + StateListDrawable newBg = new StateListDrawable(); + newBg.addState(new int[]{android.R.attr.state_selected}, transparent); + newBg.addState(new int[]{android.R.attr.state_pressed}, transparent); + newBg.addState(new int[]{}, background); + mBackgroundsCache.put(backgroundColor, newBg.getConstantState()); + return newBg; + } + } + + private void setViewText(TextView v, CharSequence text) { + // Set the text even if it's null, since we need to clear any previous text. + v.setText(text); + + if (TextUtils.isEmpty(text)) { + v.setVisibility(View.GONE); + } else { + v.setVisibility(View.VISIBLE); + } + } + + private Drawable getIcon1(Cursor cursor) { + if (mIconName1Col < 0) { + return null; + } + String value = cursor.getString(mIconName1Col); + Drawable drawable = getDrawableFromResourceValue(value); + if (drawable != null) { + return drawable; + } + return getDefaultIcon1(cursor); + } + + private Drawable getIcon2(Cursor cursor) { + if (mIconName2Col < 0) { + return null; + } + String value = cursor.getString(mIconName2Col); + return getDrawableFromResourceValue(value); + } + + /** + * Sets the drawable in an image view, makes sure the view is only visible if there + * is a drawable. + */ + private void setViewDrawable(ImageView v, Drawable drawable) { + // Set the icon even if the drawable is null, since we need to clear any + // previous icon. + v.setImageDrawable(drawable); + + if (drawable == null) { + v.setVisibility(View.GONE); + } else { + v.setVisibility(View.VISIBLE); + + // This is a hack to get any animated drawables (like a 'working' spinner) + // to animate. You have to setVisible true on an AnimationDrawable to get + // it to start animating, but it must first have been false or else the + // call to setVisible will be ineffective. We need to clear up the story + // about animated drawables in the future, see http://b/1878430. + drawable.setVisible(false, false); + drawable.setVisible(true, false); + } + } + + /** + * Gets the text to show in the query field when a suggestion is selected. + * + * @param cursor The Cursor to read the suggestion data from. The Cursor should already + * be moved to the suggestion that is to be read from. + * @return The text to show, or <code>null</code> if the query should not be + * changed when selecting this suggestion. + */ + @Override + public CharSequence convertToString(Cursor cursor) { + if (cursor == null) { + return null; + } + + String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY); + if (query != null) { + return query; + } + + if (mSearchable.shouldRewriteQueryFromData()) { + String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA); + if (data != null) { + return data; + } + } + + if (mSearchable.shouldRewriteQueryFromText()) { + String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1); + if (text1 != null) { + return text1; + } + } + + return null; + } + + /** + * This method is overridden purely to provide a bit of protection against + * flaky content providers. + * + * @see android.widget.ListAdapter#getView(int, View, ViewGroup) + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + try { + return super.getView(position, convertView, parent); + } catch (RuntimeException e) { + Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e); + // Put exception string in item title + View v = newView(mContext, mCursor, parent); + if (v != null) { + ChildViewCache views = (ChildViewCache) v.getTag(); + TextView tv = views.mText1; + tv.setText(e.toString()); + } + return v; + } + } + + /** + * Gets a drawable given a value provided by a suggestion provider. + * + * This value could be just the string value of a resource id + * (e.g., "2130837524"), in which case we will try to retrieve a drawable from + * the provider's resources. If the value is not an integer, it is + * treated as a Uri and opened with + * {@link ContentResolver#openOutputStream(android.net.Uri, String)}. + * + * All resources and URIs are read using the suggestion provider's context. + * + * If the string is not formatted as expected, or no drawable can be found for + * the provided value, this method returns null. + * + * @param drawableId a string like "2130837524", + * "android.resource://com.android.alarmclock/2130837524", + * or "content://contacts/photos/253". + * @return a Drawable, or null if none found + */ + private Drawable getDrawableFromResourceValue(String drawableId) { + if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) { + return null; + } + try { + // First, see if it's just an integer + int resourceId = Integer.parseInt(drawableId); + // It's an int, look for it in the cache + String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + mProviderContext.getPackageName() + "/" + resourceId; + // Must use URI as cache key, since ints are app-specific + Drawable drawable = checkIconCache(drawableUri); + if (drawable != null) { + return drawable; + } + // Not cached, find it by resource ID + drawable = mProviderContext.getResources().getDrawable(resourceId); + // Stick it in the cache, using the URI as key + storeInIconCache(drawableUri, drawable); + return drawable; + } catch (NumberFormatException nfe) { + // It's not an integer, use it as a URI + Drawable drawable = checkIconCache(drawableId); + if (drawable != null) { + return drawable; + } + Uri uri = Uri.parse(drawableId); + drawable = getDrawable(uri); + storeInIconCache(drawableId, drawable); + return drawable; + } catch (Resources.NotFoundException nfe) { + // It was an integer, but it couldn't be found, bail out + Log.w(LOG_TAG, "Icon resource not found: " + drawableId); + return null; + } + } + + /** + * Gets a drawable by URI, without using the cache. + * + * @return A drawable, or {@code null} if the drawable could not be loaded. + */ + private Drawable getDrawable(Uri uri) { + try { + String scheme = uri.getScheme(); + if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { + // Load drawables through Resources, to get the source density information + OpenResourceIdResult r = + mProviderContext.getContentResolver().getResourceId(uri); + try { + return r.r.getDrawable(r.id); + } catch (Resources.NotFoundException ex) { + throw new FileNotFoundException("Resource does not exist: " + uri); + } + } else { + // Let the ContentResolver handle content and file URIs. + InputStream stream = mProviderContext.getContentResolver().openInputStream(uri); + if (stream == null) { + throw new FileNotFoundException("Failed to open " + uri); + } + try { + return Drawable.createFromStream(stream, null); + } finally { + try { + stream.close(); + } catch (IOException ex) { + Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex); + } + } + } + } catch (FileNotFoundException fnfe) { + Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage()); + return null; + } + } + + private Drawable checkIconCache(String resourceUri) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri); + if (cached == null) { + return null; + } + if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri); + return cached.newDrawable(); + } + + private void storeInIconCache(String resourceUri, Drawable drawable) { + if (drawable != null) { + mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState()); + } + } + + /** + * Gets the left-hand side icon that will be used for the current suggestion + * if the suggestion contains an icon column but no icon or a broken icon. + * + * @param cursor A cursor positioned at the current suggestion. + * @return A non-null drawable. + */ + private Drawable getDefaultIcon1(Cursor cursor) { + // First check the component that the suggestion is originally from + String c = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME); + if (c != null) { + ComponentName component = ComponentName.unflattenFromString(c); + if (component != null) { + Drawable drawable = getActivityIconWithCache(component); + if (drawable != null) { + return drawable; + } + } else { + Log.w(LOG_TAG, "Bad component name: " + c); + } + } + + // Then check the component that gave us the suggestion + Drawable drawable = getActivityIconWithCache(mSearchable.getSearchActivity()); + if (drawable != null) { + return drawable; + } + + // Fall back to a default icon + return mContext.getPackageManager().getDefaultActivityIcon(); + } + + /** + * Gets the activity or application icon for an activity. + * Uses the local icon cache for fast repeated lookups. + * + * @param component Name of an activity. + * @return A drawable, or {@code null} if neither the activity nor the application + * has an icon set. + */ + private Drawable getActivityIconWithCache(ComponentName component) { + // First check the icon cache + String componentIconKey = component.flattenToShortString(); + // Using containsKey() since we also store null values. + if (mOutsideDrawablesCache.containsKey(componentIconKey)) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey); + return cached == null ? null : cached.newDrawable(mProviderContext.getResources()); + } + // Then try the activity or application icon + Drawable drawable = getActivityIcon(component); + // Stick it in the cache so we don't do this lookup again. + Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState(); + mOutsideDrawablesCache.put(componentIconKey, toCache); + return drawable; + } + + /** + * Gets the activity or application icon for an activity. + * + * @param component Name of an activity. + * @return A drawable, or {@code null} if neither the acitivy or the application + * have an icon set. + */ + private Drawable getActivityIcon(ComponentName component) { + PackageManager pm = mContext.getPackageManager(); + final ActivityInfo activityInfo; + try { + activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA); + } catch (NameNotFoundException ex) { + Log.w(LOG_TAG, ex.toString()); + return null; + } + int iconId = activityInfo.getIconResource(); + if (iconId == 0) return null; + String pkg = component.getPackageName(); + Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo); + if (drawable == null) { + Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for " + + component.flattenToShortString()); + return null; + } + return drawable; + } + + /** + * Gets the value of a string column by name. + * + * @param cursor Cursor to read the value from. + * @param columnName The name of the column to read. + * @return The value of the given column, or <code>null</null> + * if the cursor does not contain the given column. + */ + public static String getColumnString(Cursor cursor, String columnName) { + int col = cursor.getColumnIndex(columnName); + return getStringOrNull(cursor, col); + } + + private static String getStringOrNull(Cursor cursor, int col) { + if (col == NONE) { + return null; + } + try { + return cursor.getString(col); + } catch (Exception e) { + Log.e(LOG_TAG, + "unexpected error retrieving valid column from cursor, " + + "did the remote process die?", e); + return null; + } + } +} diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index b8c9e72..2fb2d6d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5118,6 +5118,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private boolean compressText(float width) { + if (isHardwareAccelerated()) return false; + // Only compress the text if it hasn't been compressed by the previous pass if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && mTextPaint.getTextScaleX() == 1.0f) { diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 1620778..bd87a0d 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -18,6 +18,7 @@ package com.android.internal.app; import com.android.internal.os.BatteryStatsImpl; +import android.os.WorkSource; import android.telephony.SignalStrength; interface IBatteryStats { @@ -33,6 +34,9 @@ interface IBatteryStats { SensorService.cpp */ void noteStopSensor(int uid, int sensor); + void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type); + void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type); + void noteStartGps(int uid); void noteStopGps(int uid); void noteScreenOn(); @@ -57,6 +61,12 @@ interface IBatteryStats { void noteScanWifiLockReleased(int uid); void noteWifiMulticastEnabled(int uid); void noteWifiMulticastDisabled(int uid); + void noteFullWifiLockAcquiredFromSource(in WorkSource ws); + void noteFullWifiLockReleasedFromSource(in WorkSource ws); + void noteScanWifiLockAcquiredFromSource(in WorkSource ws); + void noteScanWifiLockReleasedFromSource(in WorkSource ws); + void noteWifiMulticastEnabledFromSource(in WorkSource ws); + void noteWifiMulticastDisabledFromSource(in WorkSource ws); void setBatteryState(int status, int health, int plugType, int level, int temp, int volt); long getAwakeTimeBattery(); long getAwakeTimePlugged(); diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 8b618c7..6e11cff 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -293,4 +293,34 @@ public class NativeLibraryHelper { inputStream.close(); } } + + // Remove the native binaries of a given package. This simply + // gets rid of the files in the 'lib' sub-directory. + public static void removeNativeBinariesLI(String nativeLibraryPath) { + if (DEBUG_NATIVE) { + Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryPath); + } + + /* + * Just remove any file in the directory. Since the directory is owned + * by the 'system' UID, the application is not supposed to have written + * anything there. + */ + File binaryDir = new File(nativeLibraryPath); + if (binaryDir.exists()) { + File[] binaries = binaryDir.listFiles(); + if (binaries != null) { + for (int nn = 0; nn < binaries.length; nn++) { + if (DEBUG_NATIVE) { + Slog.d(TAG, " Deleting " + binaries[nn].getName()); + } + if (!binaries[nn].delete()) { + Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath()); + } + } + } + // Do not delete 'lib' directory itself, or this will prevent + // installation of future updates. + } + } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index b9f0c61..4943531 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -29,6 +29,7 @@ import android.os.ParcelFormatException; import android.os.Parcelable; import android.os.Process; import android.os.SystemClock; +import android.os.WorkSource; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -68,12 +69,12 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int VERSION = 50; // Maximum number of items we will record in the history. - private static final int MAX_HISTORY_ITEMS = 1000; + private static final int MAX_HISTORY_ITEMS = 2000; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks // in to one common name. - private static final int MAX_WAKELOCKS_PER_UID = 20; + private static final int MAX_WAKELOCKS_PER_UID = 30; private static final String BATCHED_WAKELOCK_NAME = "*overflow*"; @@ -1173,7 +1174,7 @@ public final class BatteryStatsImpl extends BatteryStats { // If the current time is basically the same as the last time, // just collapse into one record. if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE - && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+100)) { + && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)) { // If the current is the same as the one before, then we no // longer need the entry. if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE @@ -1189,6 +1190,10 @@ public final class BatteryStatsImpl extends BatteryStats { return; } + if (mNumHistoryItems == MAX_HISTORY_ITEMS) { + addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW); + } + if (mNumHistoryItems >= MAX_HISTORY_ITEMS) { // Once we've reached the maximum number of items, we only // record changes to the battery level. @@ -1329,6 +1334,20 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteStartWakeLocked(ws.get(i), pid, name, type); + } + } + + public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteStopWakeLocked(ws.get(i), pid, name, type); + } + } + public int startAddingCpuLocked() { mHandler.removeMessages(MSG_UPDATE_WAKELOCKS); @@ -1949,6 +1968,48 @@ public final class BatteryStatsImpl extends BatteryStats { getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(); } + public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteFullWifiLockAcquiredLocked(ws.get(i)); + } + } + + public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteFullWifiLockReleasedLocked(ws.get(i)); + } + } + + public void noteScanWifiLockAcquiredFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteScanWifiLockAcquiredLocked(ws.get(i)); + } + } + + public void noteScanWifiLockReleasedFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteScanWifiLockReleasedLocked(ws.get(i)); + } + } + + public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiMulticastEnabledLocked(ws.get(i)); + } + } + + public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiMulticastDisabledLocked(ws.get(i)); + } + } + @Override public long getScreenOnTime(long batteryRealtime, int which) { return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 4501bd7..e884af8 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -30,5 +30,6 @@ oneway interface IStatusBar void disable(int state); void animateExpand(); void animateCollapse(); + void setLightsOn(boolean on); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 2307669..0763f5e 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -30,15 +30,18 @@ interface IStatusBarService void setIcon(String slot, String iconPackage, int iconId, int iconLevel); void setIconVisibility(String slot, boolean visible); void removeIcon(String slot); + void setActiveWindowIsFullscreen(boolean fullscreen); // ---- 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); + out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications, + out boolean[] lightsOn); void onPanelRevealed(); void onNotificationClick(String pkg, String tag, int id); void onNotificationError(String pkg, String tag, int id, int uid, int initialPid, String message); void onClearAllNotifications(); void onNotificationClear(String pkg, String tag, int id); + void setLightsOn(boolean on); } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java index 035875a..d2851a9 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItem.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java @@ -19,9 +19,10 @@ package com.android.internal.view.menu; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuItem; import android.view.SubMenu; -import android.view.ContextMenu.ContextMenuInfo; +import android.view.View; /** * @hide @@ -222,4 +223,12 @@ public class ActionMenuItem implements MenuItem { public void setShowAsAction(int show) { // Do nothing. ActionMenuItems always show as action buttons. } + + public MenuItem setActionView(View actionView) { + throw new UnsupportedOperationException(); + } + + public View getActionView() { + return null; + } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java index c4b6214..20939ab 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuView.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Gravity; +import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.LinearLayout; @@ -158,8 +159,13 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo for (int i = 0; i < itemCount; i++) { final MenuItemImpl itemData = itemsToShow.get(i); - addItemView((ActionMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, - this)); + final View actionView = itemData.getActionView(); + if (actionView != null) { + addView(actionView); + } else { + addItemView((ActionMenuItemView) itemData.getItemView( + MenuBuilder.TYPE_ACTION_BUTTON, this)); + } } if (reserveOverflow) { diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index d160fec..749257a 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -78,12 +78,15 @@ public class MenuBuilder implements Menu { private static final String VIEWS_TAG = "android:views"; // Order must be the same order as the TYPE_* + // Special values: + // 0: Use the system default theme + // -1: Use the app's own theme static final int THEME_RES_FOR_TYPE[] = new int[] { com.android.internal.R.style.Theme_IconMenu, com.android.internal.R.style.Theme_ExpandedMenu, 0, - 0, - 0, + -1, + -1, }; // Order must be the same order as the TYPE_* @@ -220,8 +223,9 @@ public class MenuBuilder implements Menu { LayoutInflater getInflater() { // Create an inflater that uses the given theme for the Views it inflates if (mInflater == null) { - Context wrappedContext = new ContextThemeWrapper(mContext, - THEME_RES_FOR_TYPE[mMenuType]); + int themeResForType = THEME_RES_FOR_TYPE[mMenuType]; + Context wrappedContext = themeResForType < 0 ? mContext : + new ContextThemeWrapper(mContext, themeResForType); mInflater = (LayoutInflater) wrappedContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 07a2a94..42f9771 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -78,6 +78,8 @@ public final class MenuItemImpl implements MenuItem { private int mShowAsAction = SHOW_AS_ACTION_NEVER; + private View mActionView; + /** Used for the icon resource ID if this item does not have an icon */ static final int NO_ICON = 0; @@ -666,4 +668,13 @@ public final class MenuItemImpl implements MenuItem { mShowAsAction = actionEnum; mMenu.onItemActionRequestChanged(this); } + + public MenuItem setActionView(View view) { + mActionView = view; + return this; + } + + public View getActionView() { + return mActionView; + } } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 6b7b946..b5e57c2 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -48,7 +48,6 @@ public class ActionBarContextView extends ViewGroup { private LinearLayout mTitleLayout; private TextView mTitleView; private TextView mSubtitleView; - private int mCloseButtonStyle; private int mTitleStyleRes; private int mSubtitleStyleRes; private ActionMenuView mMenuView; @@ -69,8 +68,6 @@ public class ActionBarContextView extends ViewGroup { com.android.internal.R.styleable.ActionMode_itemPadding, 0); setBackgroundDrawable(a.getDrawable( com.android.internal.R.styleable.ActionMode_background)); - mCloseButtonStyle = a.getResourceId( - com.android.internal.R.styleable.ActionMode_closeButtonStyle, 0); mTitleStyleRes = a.getResourceId( com.android.internal.R.styleable.ActionMode_titleTextStyle, 0); mSubtitleStyleRes = a.getResourceId( @@ -156,7 +153,8 @@ public class ActionBarContextView extends ViewGroup { public void initForMode(final ActionMode mode) { if (mCloseButton == null) { - mCloseButton = new ImageButton(getContext(), null, mCloseButtonStyle); + mCloseButton = new ImageButton(getContext(), null, + com.android.internal.R.attr.actionModeCloseButtonStyle); } mCloseButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index 939f118..12cf853 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -84,6 +84,7 @@ public class PointerLocationView extends View { private boolean mCurDown; private int mCurNumPointers; private int mMaxNumPointers; + private int mActivePointerId; private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>(); private final VelocityTracker mVelocity; @@ -123,6 +124,7 @@ public class PointerLocationView extends View { PointerState ps = new PointerState(); mPointers.add(ps); + mActivePointerId = 0; mVelocity = VelocityTracker.obtain(); @@ -183,14 +185,15 @@ public class PointerLocationView extends View { final int NP = mPointers.size(); // Labels - if (NP > 0) { - final PointerState ps = mPointers.get(0); + if (mActivePointerId >= 0) { + final PointerState ps = mPointers.get(mActivePointerId); + canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint); canvas.drawText(mText.clear() .append("P: ").append(mCurNumPointers) .append(" / ").append(mMaxNumPointers) .toString(), 1, base, mTextPaint); - + final int N = ps.mTraceCount; if ((mCurDown && ps.mCurDown) || N == 0) { canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint); @@ -355,6 +358,11 @@ public class PointerLocationView extends View { NP++; } + if (mActivePointerId < 0 || + ! mPointers.get(mActivePointerId).mCurDown) { + mActivePointerId = id; + } + final PointerState ps = mPointers.get(id); ps.mCurDown = true; if (mPrintCoords) { @@ -396,6 +404,7 @@ public class PointerLocationView extends View { } if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP @@ -407,10 +416,14 @@ public class PointerLocationView extends View { Log.i(TAG, mText.clear().append("Pointer ") .append(id + 1).append(": UP").toString()); } - - if (action == MotionEvent.ACTION_UP) { + + if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL) { mCurDown = false; } else { + if (mActivePointerId == id) { + mActivePointerId = event.getPointerId(index == 0 ? 1 : 0); + } ps.addTrace(Float.NaN, Float.NaN); } } @@ -438,9 +451,9 @@ public class PointerLocationView extends View { // HACK // A quick and dirty string builder implementation optimized for GC. - // Using the basic StringBuilder implementation causes the application grind to a halt when - // more than a couple of pointers are down due to the number of temporary objects allocated - // while formatting strings for drawing or logging. + // Using String.format causes the application grind to a halt when + // more than a couple of pointers are down due to the number of + // temporary objects allocated while formatting strings for drawing or logging. private static final class FasterStringBuilder { private char[] mChars; private int mLength; diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index 1307ec3..459458b 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -63,6 +63,8 @@ static jmethodID method_onRequestPasskey; static jmethodID method_onRequestPasskeyConfirmation; static jmethodID method_onRequestPairingConsent; static jmethodID method_onDisplayPasskey; +static jmethodID method_onRequestOobData; +static jmethodID method_onAgentOutOfBandDataAvailable; static jmethodID method_onAgentAuthorize; static jmethodID method_onAgentCancel; @@ -116,6 +118,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onAgentAuthorize = env->GetMethodID(clazz, "onAgentAuthorize", "(Ljava/lang/String;Ljava/lang/String;)Z"); + method_onAgentOutOfBandDataAvailable = env->GetMethodID(clazz, "onAgentOutOfBandDataAvailable", + "(Ljava/lang/String;)Z"); method_onAgentCancel = env->GetMethodID(clazz, "onAgentCancel", "()V"); method_onRequestPinCode = env->GetMethodID(clazz, "onRequestPinCode", "(Ljava/lang/String;I)V"); @@ -135,6 +139,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) { "(Ljava/lang/String;[Ljava/lang/String;)V"); method_onPanDeviceConnectionResult = env->GetMethodID(clazz, "onPanDeviceConnectionResult", "(Ljava/lang/String;Z)V"); + method_onRequestOobData = env->GetMethodID(clazz, "onRequestOobData", + "(Ljava/lang/String;I)V"); field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I"); #endif @@ -345,6 +351,7 @@ static int register_agent(native_data_t *nat, { DBusMessage *msg, *reply; DBusError err; + bool oob = TRUE; if (!dbus_connection_register_object_path(nat->conn, agent_path, &agent_vtable, nat)) { @@ -366,6 +373,7 @@ static int register_agent(native_data_t *nat, } dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &agent_path, DBUS_TYPE_STRING, &capabilities, + DBUS_TYPE_BOOLEAN, &oob, DBUS_TYPE_INVALID); dbus_error_init(&err); @@ -1056,6 +1064,43 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, } goto success; } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "OutOfBandAvailable")) { + char *object_path; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for OutOfBandData available() method", __FUNCTION__); + goto failure; + } + + LOGV("... object_path = %s", object_path); + + bool available = + env->CallBooleanMethod(nat->me, method_onAgentOutOfBandDataAvailable, + env->NewStringUTF(object_path)); + + + // reply + if (available) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) { + LOGE("%s: Cannot create message reply\n", __FUNCTION__); + goto failure; + } + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(reply); + } else { + DBusMessage *reply = dbus_message_new_error(msg, + "org.bluez.Error.DoesNotExist", "OutofBand data not available"); + if (!reply) { + LOGE("%s: Cannot create message reply\n", __FUNCTION__); + goto failure; + } + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(reply); + } + goto success; + } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "RequestPinCode")) { char *object_path; if (!dbus_message_get_args(msg, NULL, @@ -1086,6 +1131,21 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, int(msg)); goto success; } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "RequestOobData")) { + char *object_path; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for RequestOobData() method", __FUNCTION__); + goto failure; + } + + dbus_message_ref(msg); // increment refcount because we pass to java + env->CallVoidMethod(nat->me, method_onRequestOobData, + env->NewStringUTF(object_path), + int(msg)); + goto success; + } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "DisplayPasskey")) { char *object_path; uint32_t passkey; diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp index 74127cf..337dccc 100644 --- a/core/jni/android_server_BluetoothService.cpp +++ b/core/jni/android_server_BluetoothService.cpp @@ -296,6 +296,46 @@ done: #endif } +static jbyteArray readAdapterOutOfBandDataNative(JNIEnv *env, jobject object) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + DBusError err; + jbyte *hash, *randomizer; + jbyteArray byteArray = NULL; + int hash_len, r_len; + if (nat) { + DBusMessage *reply = dbus_func_args(env, nat->conn, + get_adapter_path(env, object), + DBUS_ADAPTER_IFACE, "ReadLocalOutOfBandData", + DBUS_TYPE_INVALID); + if (!reply) return NULL; + + dbus_error_init(&err); + if (dbus_message_get_args(reply, &err, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &hash, &hash_len, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &randomizer, &r_len, + DBUS_TYPE_INVALID)) { + if (hash_len == 16 && r_len == 16) { + byteArray = env->NewByteArray(32); + if (byteArray) { + env->SetByteArrayRegion(byteArray, 0, 16, hash); + env->SetByteArrayRegion(byteArray, 16, 16, randomizer); + } + } else { + LOGE("readAdapterOutOfBandDataNative: Hash len = %d, R len = %d", + hash_len, r_len); + } + } else { + LOG_AND_FREE_DBUS_ERROR(&err); + } + dbus_message_unref(reply); + return byteArray; + } +#endif + return NULL; +} + static jboolean createPairedDeviceNative(JNIEnv *env, jobject object, jstring address, jint timeout_ms) { LOGV(__FUNCTION__); @@ -332,6 +372,41 @@ static jboolean createPairedDeviceNative(JNIEnv *env, jobject object, return JNI_FALSE; } +static jboolean createPairedDeviceOutOfBandNative(JNIEnv *env, jobject object, + jstring address, jint timeout_ms) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + jobject eventLoop = env->GetObjectField(object, field_mEventLoop); + struct event_loop_native_data_t *eventLoopNat = + get_EventLoop_native_data(env, eventLoop); + + if (nat && eventLoopNat) { + const char *c_address = env->GetStringUTFChars(address, NULL); + LOGV("... address = %s", c_address); + char *context_address = (char *)calloc(BTADDR_SIZE, sizeof(char)); + const char *capabilities = "DisplayYesNo"; + const char *agent_path = "/android/bluetooth/remote_device_agent"; + + strlcpy(context_address, c_address, BTADDR_SIZE); // for callback + bool ret = dbus_func_args_async(env, nat->conn, (int)timeout_ms, + onCreatePairedDeviceResult, // callback + context_address, + eventLoopNat, + get_adapter_path(env, object), + DBUS_ADAPTER_IFACE, + "CreatePairedDeviceOutOfBand", + DBUS_TYPE_STRING, &c_address, + DBUS_TYPE_OBJECT_PATH, &agent_path, + DBUS_TYPE_STRING, &capabilities, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(address, c_address); + return ret ? JNI_TRUE : JNI_FALSE; + } +#endif + return JNI_FALSE; +} + static jint getDeviceServiceChannelNative(JNIEnv *env, jobject object, jstring path, jstring pattern, jint attr_id) { @@ -498,6 +573,40 @@ static jboolean setPasskeyNative(JNIEnv *env, jobject object, jstring address, return JNI_FALSE; } +static jboolean setRemoteOutOfBandDataNative(JNIEnv *env, jobject object, jstring address, + jbyteArray hash, jbyteArray randomizer, int nativeData) { +#ifdef HAVE_BLUETOOTH + LOGV(__FUNCTION__); + native_data_t *nat = get_native_data(env, object); + if (nat) { + DBusMessage *msg = (DBusMessage *)nativeData; + DBusMessage *reply = dbus_message_new_method_return(msg); + jbyte *h_ptr = env->GetByteArrayElements(hash, NULL); + jbyte *r_ptr = env->GetByteArrayElements(randomizer, NULL); + if (!reply) { + LOGE("%s: Cannot create message reply to return remote OOB data to " + "D-Bus\n", __FUNCTION__); + dbus_message_unref(msg); + return JNI_FALSE; + } + + dbus_message_append_args(reply, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &h_ptr, 16, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &r_ptr, 16, + DBUS_TYPE_INVALID); + + env->ReleaseByteArrayElements(hash, h_ptr, 0); + env->ReleaseByteArrayElements(randomizer, r_ptr, 0); + + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(msg); + dbus_message_unref(reply); + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + static jboolean setPinNative(JNIEnv *env, jobject object, jstring address, jstring pin, int nativeData) { #ifdef HAVE_BLUETOOTH @@ -1069,7 +1178,10 @@ static JNINativeMethod sMethods[] = { {"startDiscoveryNative", "()Z", (void*)startDiscoveryNative}, {"stopDiscoveryNative", "()Z", (void *)stopDiscoveryNative}, + {"readAdapterOutOfBandDataNative", "()[B", (void *)readAdapterOutOfBandDataNative}, {"createPairedDeviceNative", "(Ljava/lang/String;I)Z", (void *)createPairedDeviceNative}, + {"createPairedDeviceOutOfBandNative", "(Ljava/lang/String;I)Z", + (void *)createPairedDeviceOutOfBandNative}, {"cancelDeviceCreationNative", "(Ljava/lang/String;)Z", (void *)cancelDeviceCreationNative}, {"removeDeviceNative", "(Ljava/lang/String;)Z", (void *)removeDeviceNative}, {"getDeviceServiceChannelNative", "(Ljava/lang/String;Ljava/lang/String;I)I", @@ -1078,6 +1190,7 @@ static JNINativeMethod sMethods[] = { {"setPairingConfirmationNative", "(Ljava/lang/String;ZI)Z", (void *)setPairingConfirmationNative}, {"setPasskeyNative", "(Ljava/lang/String;II)Z", (void *)setPasskeyNative}, + {"setRemoteOutOfBandDataNative", "(Ljava/lang/String;[B[BI)Z", (void *)setRemoteOutOfBandDataNative}, {"setPinNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setPinNative}, {"cancelPairingUserInputNative", "(Ljava/lang/String;I)Z", (void *)cancelPairingUserInputNative}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 89298ea..4d02b60 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -939,7 +939,7 @@ <permission android:name="android.permission.UPDATE_DEVICE_STATS" android:label="@string/permlab_batteryStats" android:description="@string/permdesc_batteryStats" - android:protectionLevel="signature" /> + android:protectionLevel="signatureOrSystem" /> <!-- Allows an application to open windows that are for use by parts of the system user interface. Not for use by third party apps. --> diff --git a/core/res/res/drawable-hdpi/menu_background.9.png b/core/res/res/drawable-hdpi/menu_background.9.png Binary files differindex cbe62af..60f0731 100644 --- a/core/res/res/drawable-hdpi/menu_background.9.png +++ b/core/res/res/drawable-hdpi/menu_background.9.png diff --git a/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png b/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png Binary files differindex 0812487..09eac9b 100644 --- a/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png +++ b/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png diff --git a/core/res/res/drawable-mdpi/menu_background.9.png b/core/res/res/drawable-mdpi/menu_background.9.png Binary files differindex ee99583..9f16df9 100644 --- a/core/res/res/drawable-mdpi/menu_background.9.png +++ b/core/res/res/drawable-mdpi/menu_background.9.png diff --git a/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png b/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png Binary files differindex d368983..da3011b 100644 --- a/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png +++ b/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png diff --git a/core/res/res/drawable-xlarge/default_wallpaper.jpg b/core/res/res/drawable-xlarge/default_wallpaper.jpg Binary files differindex afb0acc..5acad94 100644 --- a/core/res/res/drawable-xlarge/default_wallpaper.jpg +++ b/core/res/res/drawable-xlarge/default_wallpaper.jpg diff --git a/core/res/res/layout/always_use_checkbox.xml b/core/res/res/layout/always_use_checkbox.xml index baa4bee..a955352 100644 --- a/core/res/res/layout/always_use_checkbox.xml +++ b/core/res/res/layout/always_use_checkbox.xml @@ -26,14 +26,14 @@ <CheckBox android:id="@+id/alwaysUse" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:focusable="true" android:clickable="true" /> <TextView android:id="@+id/clearDefaultHint" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:paddingLeft="36dip" diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml new file mode 100644 index 0000000..c229b59 --- /dev/null +++ b/core/res/res/layout/search_view.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * 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. + */ + +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/search_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:focusable="true" + android:descendantFocusability="afterDescendants"> + + <!-- This is actually used for the badge icon *or* the badge label (or neither) --> + <TextView + android:id="@+id/search_badge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="2dip" + android:drawablePadding="0dip" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?android:attr/textColorPrimaryInverse" + android:visibility="gone" + /> + + <ImageView + android:id="@+id/search_button" + android:layout_height="36dip" + android:layout_width="36dip" + android:layout_marginRight="7dip" + android:layout_gravity="center_vertical" + android:src="@android:drawable/ic_btn_search" + /> + + <!-- Inner layout contains the app icon, button(s) and EditText --> + <LinearLayout + android:id="@+id/search_edit_frame" + android:layout_width="300dp" + android:layout_height="wrap_content" + android:visibility="gone" + android:orientation="horizontal" + android:background="?android:attr/editTextBackground"> + + <ImageView + android:id="@+id/search_app_icon" + android:layout_height="24dip" + android:layout_width="24dip" + android:layout_marginRight="7dip" + android:layout_gravity="bottom" + android:src="@android:drawable/ic_btn_search" + /> + + <AutoCompleteTextView + android:id="@+id/search_src_text" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_weight="1" + android:paddingLeft="8dip" + android:paddingRight="6dip" + android:drawablePadding="2dip" + android:singleLine="true" + android:ellipsize="end" + android:background="@null" + android:inputType="text|textAutoComplete" + android:imeOptions="actionSearch" + android:dropDownWidth="300dp" + android:dropDownHeight="wrap_content" + android:dropDownAnchor="@id/search_edit_frame" + android:dropDownVerticalOffset="0dip" + android:dropDownHorizontalOffset="0dip" + android:popupBackground="@android:drawable/search_dropdown_background" + /> + + <ImageView + android:id="@+id/search_close_btn" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="bottom" + android:src="@android:drawable/btn_close" + /> + </LinearLayout> + + <ImageView + android:id="@+id/search_go_btn" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginLeft="4dip" + android:layout_marginRight="4dip" + android:src="@android:drawable/ic_btn_find_next" + /> + + <ImageButton + android:id="@+id/search_voice_btn" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginLeft="0dip" + android:layout_marginTop="-6.5dip" + android:layout_marginBottom="-7dip" + android:layout_marginRight="-5dip" + android:background="@drawable/btn_search_dialog_voice" + android:src="@android:drawable/ic_btn_speak_now" + android:visibility="gone" + /> +</LinearLayout> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 05e9faa..94cc393 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Umožňuje aplikaci načíst všechna data kontaktů (adresy) uložená ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat vaše data."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"zápis dat kontaktů"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Umožňuje aplikaci změnit kontaktní údaje (adresu) uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit kontaktní údaje."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"zápis informací o vlastníkovi"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Umožňuje aplikaci změnit informace o vlastníkovi telefonu uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit informace o vlastníkovi."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"čtení informací o vlastníkovi"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Umožňuje aplikaci číst informace o vlastníkovi telefonu uložená v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení načíst informace o vlastníkovi."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"Čtení událostí v kalendáři"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Umožňuje aplikaci načíst všechny události kalendáře uložené ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat události z vašeho kalendáře."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"Přidávání nebo úprava událostí v kalendáři a odesílání e-mailů hostům"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopírovat"</string> <string name="paste" msgid="5629880836805036433">"Vložit"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopírovat adresu URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Vybrat text..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Výběr textu"</string> <string name="inputMethod" msgid="1653630062304567879">"Metoda zadávání dat"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Operace s textem"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Málo paměti"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"V telefonu zbývá málo místa pro ukládání dat."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Ukončit aplikaci"</string> <string name="report" msgid="4060218260984795706">"Nahlásit"</string> <string name="wait" msgid="7147118217226317732">"Počkat"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) porušila své vlastní vynucené zásady StrictMode."</string> <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své vlastní vynucené zásady StrictMode."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotykem zobrazíte další informace o využití mobilních dat"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Byl překročen limit mobilních dat"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Dotykem zobrazíte další informace o využití mobilních dat"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index f5331e5..d64b548 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillader, at et program læser alle kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine data til andre mennesker."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"skriv kontaktdata"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillader, at et program ændrer kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kontaktdata."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skriv ejerdata"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillader, at et program ændrer rtelefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre ejerdata."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"læs ejerdata"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillader, at et program læser telefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at læse ejerdata."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"læs kalenderbegivenheder"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillader, at et program læser alle kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine kalenderbegivenheder til andre mennesker."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"tilføj eller rediger kalenderbegivenheder, og send e-mail til gæster"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopier"</string> <string name="paste" msgid="5629880836805036433">"Indsæt"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Marker tekst..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Tekstmarkering"</string> <string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksthandlinger"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Der er ikke så meget plads tilbage"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Der er næsten ikke mere plads på telefonen."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Tving til at lukke"</string> <string name="report" msgid="4060218260984795706">"Rapporter"</string> <string name="wait" msgid="7147118217226317732">"Vent"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) har overtrådt sin egen StrictMode-politik."</string> <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har overtrådt sin egen StrictMode-politik."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> er i gang"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryk for oplysninger om brug af mobildata"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Grænsen for mobildata er overskredet"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Tryk for oplysninger om brug af mobildata"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 52ae3b4..1fe084c 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu lesen. Schädliche Anwendungen können so Ihre Daten an andere Personen senden."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"Kontaktdaten schreiben"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu ändern. Schädliche Anwendungen können so Ihre Kontaktdaten löschen oder verändern."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"Eigentümerdaten schreiben"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu ändern. Schädliche Anwendungen können so Eigentümerdaten löschen oder verändern."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"Eigentümerdaten lesen"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu lesen. Schädliche Anwendungen können so Eigentümerdaten lesen."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"Kalendereinträge lesen"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kalenderereignisse zu lesen. Schädliche Anwendungen können so Ihre Kalenderereignisse an andere Personen senden."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"Kalendereinträge hinzufügen oder ändern und E-Mails an Gäste senden"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopieren"</string> <string name="paste" msgid="5629880836805036433">"Einfügen"</string> <string name="copyUrl" msgid="2538211579596067402">"URL kopieren"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Text auswählen..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Textauswahl"</string> <string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Textaktionen"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Geringer Speicher"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Kaum noch freier Telefonspeicher verfügbar."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Schließen erzwingen"</string> <string name="report" msgid="4060218260984795706">"Bericht"</string> <string name="wait" msgid="7147118217226317732">"Warten"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Die Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) hat gegen ihre selbsterzwungene StrictMode-Richtlinie verstoßen."</string> <string name="smv_process" msgid="5120397012047462446">"Der Prozess <xliff:g id="PROCESS">%1$s</xliff:g> hat gegen seine selbsterzwungene StrictMode-Richtlinie verstoßen."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> läuft"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Weitere Informationen über die Mobildatennutzung durch Berühren aufrufen"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Mobildatenlimit überschritten"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Weitere Informationen über die Mobildatennutzung durch Berühren aufrufen"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index c21321d..2d54f44 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Επιτρέπει σε μια εφαρμογή την ανάγνωση όλων των δεδομένων επαφής (διεύθυνσης) που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να αποστείλουν τα δεδομένα σας σε τρίτους."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"εγγραφή δεδομένων επαφής"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Επιτρέπει σε μια εφαρμογή να τροποποιεί τα δεδομένα επαφής (διεύθυνσης) που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να διαγράψουν ή να τροποποιήσουν τα δεδομένα επαφών σας."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"εγγραφή δεδομένων κατόχου"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Επιτρέπει σε μια εφαρμογή να τροποποιήσει τα δεδομένα κατόχου τηλεφώνου στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να τροποποιήσουν τα δεδομένα κατόχου."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"ανάγνωση δεδομένων κατόχου"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Επιτρέπει σε μια εφαρμογή την ανάγνωση των δεδομένων κατόχου τηλεφώνου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για την ανάγνωση δεδομένων κατόχου τηλεφώνου."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"ανάγνωση συμβάντων ημερολογίου"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Επιτρέπει σε μια εφαρμογή να αναγνώσει όλα τα συμβάντα ημερολογίου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να αποστείλουν συμβάντα ημερολογίου σε άλλους χρήστες."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"προσθήκη ή τροποποίηση συμβάντων του ημερολογίου και αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου στους προσκεκλημένους"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string> <string name="paste" msgid="5629880836805036433">"Επικόλληση"</string> <string name="copyUrl" msgid="2538211579596067402">"Αντιγραφή διεύθυνσης URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Επιλογή κειμένου..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Επιλογή κειμένου"</string> <string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Ενέργειες κειμένου"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Απομένει λίγος ελεύθερος χώρος"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Έχει απομείνει λίγος ελεύθερος αποθηκευτικός χώρος στο τηλέφωνο."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Αναγκαστικό κλείσιμο"</string> <string name="report" msgid="4060218260984795706">"Αναφορά"</string> <string name="wait" msgid="7147118217226317732">"Αναμονή"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Η εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g> (διεργασία <xliff:g id="PROCESS">%2$s</xliff:g>) παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string> <string name="smv_process" msgid="5120397012047462446">"Η διεργασία <xliff:g id="PROCESS">%1$s</xliff:g> παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Αγγίξτε για να μάθετε περισσότερα σχετικά με τη χρήση δεδομένων κινητής τηλεφωνίας"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Ξεπεράστηκε το όριο δεδομένων κινητής τηλεφωνίας"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Αγγίξτε για να μάθετε περισσότερα σχετικά με τη χρήση δεδομένων κινητής τηλεφωνίας"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 38ad404..efd2885 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Admite una aplicación que lee todos los datos de (direcciones) de contactos almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para enviar tus eventos de calendario a otras personas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"escribir datos de contacto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Admite una aplicación que modifica los datos de (dirección de) contacto guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar los datos de contacto."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escribir datos del propietario"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Admite una aplicación que modifica los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar los datos del propietario."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Admite una aplicación que lee los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para leer los datos del propietario del teléfono."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"Leer eventos del calendario"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Admite que una aplicación lea todos los eventos de calendario almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para enviar tus eventos de calendario a otras personas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"Agregar o cambiar eventos del calendario y enviar un correo electrónico a los invitados"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Copiar"</string> <string name="paste" msgid="5629880836805036433">"Pegar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Seleccionar texto..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selección de texto "</string> <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Acciones de texto"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Poco espacio de almacenamiento"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Hay poco espacio de almacenamiento en el teléfono."</string> <string name="ok" msgid="5970060430562524910">"Aceptar"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Provocar acercamiento"</string> <string name="report" msgid="4060218260984795706">"Notificar"</string> <string name="wait" msgid="7147118217226317732">"Espera"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha violado su política StrictMode autoimpuesta."</string> <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha violado su política StrictMode autoimpuesta."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> Correr"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toca para obtener más información acerca de la utilización de datos móviles."</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Límite de datos móviles excedido "</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Toca para obtener más información acerca de la utilización de datos móviles."</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index bb2dfd8..b12f8be 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -144,7 +144,7 @@ <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"El sonido está desactivado. Activar."</string> <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"El sonido está activado. Desactivar."</string> <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Modo avión"</string> - <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avión desactivado. Activar."</string> + <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avión activado. Desactivar."</string> <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modo avión desactivado. Activar."</string> <string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string> <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string> @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite que una aplicación lea todos los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus datos a otras personas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"escribir datos de contacto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite que una aplicación modifique los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar tus datos de contacto."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escribir datos de propietario"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que una aplicación modifique los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar los datos del propietario."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que una aplicación lea los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para leer los datos del propietario del teléfono."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"leer eventos de calendario"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que una aplicación lea todos los eventos de calendario almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus eventos de calendario a otras personas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"añadir o modificar eventos de calendario y enviar mensajes de correo electrónico a los invitados"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Copiar"</string> <string name="paste" msgid="5629880836805036433">"Pegar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Seleccionar texto..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selección de texto"</string> <string name="inputMethod" msgid="1653630062304567879">"Método de introducción de texto"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Acciones de texto"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Poco espacio"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Se está agotando el espacio de almacenamiento del teléfono."</string> <string name="ok" msgid="5970060430562524910">"Aceptar"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Forzar cierre"</string> <string name="report" msgid="4060218260984795706">"Informe"</string> <string name="wait" msgid="7147118217226317732">"Esperar"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha infringido su política StrictMode autoaplicable."</string> <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha infringido su política StrictMode autoaplicable."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Más información sobre uso de datos"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Límite datos superado"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Más información sobre uso de datos"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 343829a..5347a45 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permet à une application de lire toutes les données des contacts (adresses) enregistrées sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer vos données à d\'autres personnes."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"Édition des données d\'un contact"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permet à une application de modifier toutes les données de contact (adresses) enregistrées sur le téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier vos données de contact."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"Édition les données du propriétaire"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permet à une application de modifier les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier ces données."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"Lecture des données du propriétaire"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permet à une application de lire les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour lire ces données."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"lire des événements de l\'agenda"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permet à une application de lire tous les événements de l\'agenda enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer les événements de votre agenda à d\'autres personnes."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"ajouter ou modifier des événements d\'agenda et envoyer des e-mails aux invités"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Copier"</string> <string name="paste" msgid="5629880836805036433">"Coller"</string> <string name="copyUrl" msgid="2538211579596067402">"Copier l\'URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Sélect. le texte..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Sélection de texte"</string> <string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Actions sur le texte"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Espace disponible faible"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"La mémoire du téléphone commence à être pleine."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Forcer la fermeture"</string> <string name="report" msgid="4060218260984795706">"Rapport"</string> <string name="wait" msgid="7147118217226317732">"Attendre"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles du mode strict."</string> <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles du mode strict."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Touchez pour en savoir plus sur l\'utilisation des données mobiles"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Quota d\'utilisation des données mobiles dépassé"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Touchez pour en savoir plus sur l\'utilisation des données mobiles"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 58a34eb..713c983 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Consente la lettura da parte di un\'applicazione di tutti i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i dati ad altre persone."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"scrittura dati di contatto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Consente a un\'applicazione di modificare i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati di contatto."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"scrittura dati proprietario"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Consente a un\'applicazione di modificare i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare tali dati."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"lettura dati proprietario"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Consente a un\'applicazione di leggere i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per leggere tali dati."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"leggere eventi di calendario"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Consente la lettura da parte di un\'applicazione di tutti gli eventi di calendario memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i tuoi eventi di calendario ad altre persone."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"aggiungere o modificare eventi di calendario e inviare email agli invitati"</string> @@ -443,8 +439,8 @@ <string-array name="phoneTypes"> <item msgid="8901098336658710359">"Casa"</item> <item msgid="869923650527136615">"Cellulare"</item> - <item msgid="7897544654242874543">"Ufficio"</item> - <item msgid="1103601433382158155">"Fax ufficio"</item> + <item msgid="7897544654242874543">"Lavoro"</item> + <item msgid="1103601433382158155">"Fax lavoro"</item> <item msgid="1735177144948329370">"Fax casa"</item> <item msgid="603878674477207394">"Cercapersone"</item> <item msgid="1650824275177931637">"Altro"</item> @@ -452,24 +448,24 @@ </string-array> <string-array name="emailAddressTypes"> <item msgid="8073994352956129127">"Casa"</item> - <item msgid="7084237356602625604">"Ufficio"</item> + <item msgid="7084237356602625604">"Lavoro"</item> <item msgid="1112044410659011023">"Altro"</item> <item msgid="2374913952870110618">"Personalizzato"</item> </string-array> <string-array name="postalAddressTypes"> <item msgid="6880257626740047286">"Casa"</item> - <item msgid="5629153956045109251">"Ufficio"</item> + <item msgid="5629153956045109251">"Lavoro"</item> <item msgid="4966604264500343469">"Altro"</item> <item msgid="4932682847595299369">"Personalizzato"</item> </string-array> <string-array name="imAddressTypes"> <item msgid="1738585194601476694">"Casa"</item> - <item msgid="1359644565647383708">"Uffico"</item> + <item msgid="1359644565647383708">"Lavoro"</item> <item msgid="7868549401053615677">"Altro"</item> <item msgid="3145118944639869809">"Personalizzato"</item> </string-array> <string-array name="organizationTypes"> - <item msgid="7546335612189115615">"Ufficio"</item> + <item msgid="7546335612189115615">"Lavoro"</item> <item msgid="4378074129049520373">"Altro"</item> <item msgid="3455047468583965104">"Personalizzato"</item> </string-array> @@ -486,8 +482,8 @@ <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizzato"</string> <string name="phoneTypeHome" msgid="2570923463033985887">"Casa"</string> <string name="phoneTypeMobile" msgid="6501463557754751037">"Cellulare"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Ufficio"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax ufficio"</string> + <string name="phoneTypeWork" msgid="8863939667059911633">"Lavoro"</string> + <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax lavoro"</string> <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax casa"</string> <string name="phoneTypePager" msgid="7582359955394921732">"Cercapersone"</string> <string name="phoneTypeOther" msgid="1544425847868765990">"Altro"</string> @@ -501,7 +497,7 @@ <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Cellulare lavoro"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Cercapersone ufficio"</string> + <string name="phoneTypeWorkPager" msgid="649938731231157056">"Cercapersone lavoro"</string> <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistente"</string> <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> <string name="eventTypeBirthday" msgid="2813379844211390740">"Compleanno"</string> @@ -509,16 +505,16 @@ <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string> <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizzato"</string> <string name="emailTypeHome" msgid="449227236140433919">"Casa"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Ufficio"</string> + <string name="emailTypeWork" msgid="3548058059601149973">"Lavoro"</string> <string name="emailTypeOther" msgid="2923008695272639549">"Altro"</string> <string name="emailTypeMobile" msgid="119919005321166205">"Cellulare"</string> <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizzato"</string> <string name="postalTypeHome" msgid="8165756977184483097">"Casa"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Ufficio"</string> + <string name="postalTypeWork" msgid="5268172772387694495">"Lavoro"</string> <string name="postalTypeOther" msgid="2726111966623584341">"Altro"</string> <string name="imTypeCustom" msgid="2074028755527826046">"Personalizzato"</string> <string name="imTypeHome" msgid="6241181032954263892">"Casa"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Ufficio"</string> + <string name="imTypeWork" msgid="1371489290242433090">"Lavoro"</string> <string name="imTypeOther" msgid="5377007495735915478">"Altro"</string> <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizzato"</string> <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> @@ -530,7 +526,7 @@ <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Ufficio"</string> + <string name="orgTypeWork" msgid="29268870505363872">"Lavoro"</string> <string name="orgTypeOther" msgid="3951781131570124082">"Altro"</string> <string name="orgTypeCustom" msgid="225523415372088322">"Personalizzato"</string> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Inserisci il PIN"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Copia"</string> <string name="paste" msgid="5629880836805036433">"Incolla"</string> <string name="copyUrl" msgid="2538211579596067402">"Copia URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Seleziona testo..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selezione testo"</string> <string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Azioni testo"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Spazio in esaurimento"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Spazio di archiviazione del telefono in esaurimento."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Termina"</string> <string name="report" msgid="4060218260984795706">"Segnala"</string> <string name="wait" msgid="7147118217226317732">"Attendi"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"L\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) ha violato la norma StrictMode autoimposta."</string> <string name="smv_process" msgid="5120397012047462446">"Il processo <xliff:g id="PROCESS">%1$s</xliff:g> ha violato la norma StrictMode autoimposta."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> in esecuzione"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tocca per informazioni sull\'utilizzo dati cell."</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Limite dati cell. superato"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Tocca per informazioni sull\'utilizzo dati cell."</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 7da30f7..ea9bf9f 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"端末に保存した連絡先(アドレス)データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションがデータを他人に送信する恐れがあります。"</string> <string name="permlab_writeContacts" msgid="644616215860933284">"連絡先データの書き込み"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"端末に保存した連絡先(アドレス)データの変更をアプリケーションに許可します。悪意のあるアプリケーションが連絡先データを消去/変更する恐れがあります。"</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"所有者データの書き込み"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"端末に保存した所有者のデータの変更をアプリケーションに許可します。悪意のあるアプリケーションが所有者のデータを消去/変更する恐れがあります。"</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"所有者データの読み取り"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"携帯電話に保存した所有者データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションが所有者データを読み取る恐れがあります。"</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"カレンダーの予定の読み取り"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"端末に保存したカレンダーの予定の読み取りをアプリケーションに許可します。悪意のあるアプリケーションがカレンダーの予定を他人に送信する恐れがあります。"</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"カレンダーの予定の追加や変更を行い、ゲストにメールを送信する"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"コピー"</string> <string name="paste" msgid="5629880836805036433">"貼り付け"</string> <string name="copyUrl" msgid="2538211579596067402">"URLをコピー"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"テキストを選択..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"テキスト選択"</string> <string name="inputMethod" msgid="1653630062304567879">"入力方法"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"テキスト操作"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"空き容量低下"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"携帯電話の空き容量が少なくなっています。"</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"強制終了"</string> <string name="report" msgid="4060218260984795706">"レポート"</string> <string name="wait" msgid="7147118217226317732">"待機"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"アプリケーション<xliff:g id="APPLICATION">%1$s</xliff:g>(プロセス<xliff:g id="PROCESS">%2$s</xliff:g>)でStrictModeポリシー違反がありました。"</string> <string name="smv_process" msgid="5120397012047462446">"プロセス<xliff:g id="PROCESS">%1$s</xliff:g>でStrictModeポリシー違反がありました。"</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>を実行中"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"タップしてモバイルデータ利用の詳細を表示します"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"モバイルデータの制限を超えました"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"タップしてモバイルデータ利用の詳細を表示します"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 144efa0..a2309da 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"애플리케이션이 휴대전화에 저장된 모든 연락처(주소) 데이터를 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 데이터를 다른 사람에게 보낼 수 있습니다."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"연락처 데이터 작성"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"애플리케이션이 휴대전화에 저장된 연락처(주소) 데이터를 수정할 수 있도록 합니다. 이 경우 악성 애플리케이션이 연락처 데이터를 지우거나 수정할 수 있습니다."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"소유자 데이터 작성"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"애플리케이션이 휴대전화에 저장된 소유자 데이터를 수정할 수 있도록 합니다. 단, 악성 애플리케이션이 이 기능을 이용하여 소유자 데이터를 지우거나 수정할 수 있습니다."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"소유자 데이터 읽기"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"애플리케이션이 휴대전화에 저장된 휴대전화 소유자 데이터를 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 휴대전화 소유자 데이터를 읽을 수 있습니다."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"캘린더 일정 읽기"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"애플리케이션이 휴대전화에 저장된 모든 캘린더 일정을 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 캘린더 일정을 다른 사람에게 보낼 수 있습니다."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"캘린더 일정 추가/수정 및 참석자에게 이메일 전송"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"복사"</string> <string name="paste" msgid="5629880836805036433">"붙여넣기"</string> <string name="copyUrl" msgid="2538211579596067402">"URL 복사"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"텍스트 선택..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"텍스트 선택"</string> <string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"텍스트 작업"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"저장공간 부족"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"휴대전화 저장공간이 부족합니다."</string> <string name="ok" msgid="5970060430562524910">"확인"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"닫기"</string> <string name="report" msgid="4060218260984795706">"신고"</string> <string name="wait" msgid="7147118217226317732">"대기"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"애플리케이션 <xliff:g id="APPLICATION">%1$s</xliff:g>(프로세스 <xliff:g id="PROCESS">%2$s</xliff:g>)이(가) 자체 시행 StrictMode 정책을 위반했습니다."</string> <string name="smv_process" msgid="5120397012047462446">"프로세스(<xliff:g id="PROCESS">%1$s</xliff:g>)가 자체 시행 StrictMode 정책을 위반했습니다."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 실행 중"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"모바일 데이터 사용에 대해 자세히 알아보려면 터치하세요."</string> <string name="throttled_notification_title" msgid="6269541897729781332">"모바일 데이터 제한을 초과했습니다."</string> <string name="throttled_notification_message" msgid="4712369856601275146">"모바일 데이터 사용에 대해 자세히 알아보려면 터치하세요."</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index b4aa661..43e5597 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Lar applikasjonen lese all kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende personlige data til andre."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"skrive kontaktinformasjon"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Lar applikasjonen endre kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å redigere eller endre kontaktinformasjonen."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skrive eierinformasjon"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Lar applikasjonen endre dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å slette eller redigere telefonens eierdata."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"lese eierinformasjon"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Lar applikasjonen lese dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å lese telefonens eierdata."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"les kalenderaktiviteter"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Lar applikasjonen lese alle kalenderhendelser lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende kalenderhendelser til andre."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"legg til eller endre kalenderaktiviteter og send e-postmelding til gjestene"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopier"</string> <string name="paste" msgid="5629880836805036433">"Lim inn"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopier URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Marker tekst"</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Merket tekst"</string> <string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksthandlinger"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Lite plass"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Det begynner å bli lite lagringsplass på telefonen."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Tving avslutning"</string> <string name="report" msgid="4060218260984795706">"Rapportér"</string> <string name="wait" msgid="7147118217226317732">"Vent"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (prosessen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutt de selvpålagte StrictMode-retningslinjene."</string> <string name="smv_process" msgid="5120397012047462446">"Prosessen<xliff:g id="PROCESS">%1$s</xliff:g> har brutt de selvpålagte StrictMode-retningslinjene."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> kjører"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Berør for å lese mer om bruk av mobildata"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Grensen for mobildatabruk er overskredet"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Berør for å lese mer om bruk av mobildata"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index cc60101..7aded96 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Hiermee kan een toepassing alle contactgegevens (adresgegevens) zien die op uw telefoon zijn opgeslagen. Schadelijke toepassingen kunnen hiervan gebruik maken om uw gegevens te verzenden naar andere personen."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"contactgegevens schrijven"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Hiermee kan een toepassing de op uw telefoon opgeslagen contactgegevens (adresgegevens) wijzigen. Schadelijke toepassingen kunnen hiermee uw contactgegevens verwijderen of wijzigen."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"gegevens eigenaar schrijven"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar wijzigen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar verwijderen of wijzigen."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"gegevens eigenaar lezen"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar lezen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar lezen."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"agendagebeurtenissen lezen"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Hiermee kan een toepassing alle agendagebeurtenissen lezen die zijn opgeslagen op uw telefoon. Schadelijke toepassingen kunnen hiervan gebruik maken om uw agendagebeurtenissen te verzenden naar andere personen."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"agendagebeurtenissen toevoegen of aanpassen en e-mail verzenden naar gasten"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopiëren"</string> <string name="paste" msgid="5629880836805036433">"Plakken"</string> <string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Tekst selecteren..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Tekstselectie"</string> <string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstacties"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Weinig ruimte"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Opslagruimte van telefoon raakt op."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Nu sluiten"</string> <string name="report" msgid="4060218260984795706">"Rapport"</string> <string name="wait" msgid="7147118217226317732">"Wachten"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"De toepassing <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) heeft het zelf afgedwongen StrictMode-beleid geschonden."</string> <string name="smv_process" msgid="5120397012047462446">"Het proces <xliff:g id="PROCESS">%1$s</xliff:g> heeft het zelf afgedwongen StrictMode-beleid geschonden."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> wordt uitgevoerd"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Raak aan voor meer informatie over mobiel gegevensgebruik"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Mobiele gegevenslimiet overschreden"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Raak aan voor meer informatie over mobiel gegevensgebruik"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index a965316..c6a0d9c 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Pozwala aplikacji na czytanie wszystkich danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wysyłać dane użytkownika do innych ludzi."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"zapisywanie danych kontaktowych"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Pozwala aplikacji na zmianę danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby usunąć lub zmienić dane kontaktowe."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"zapisywanie danych właściciela"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Pozwala aplikacji na zmianę danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wymazać lub zmienić dane właściciela."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"czytanie danych właściciela"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Pozwala aplikacji na czytanie danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do odczytania danych właściciela."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"odczytywanie wydarzeń w kalendarzu"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Pozwala aplikacji na odczytywanie wszystkich wydarzeń z kalendarza, zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do rozsyłania wydarzeń z kalendarza do innych ludzi."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"dodawanie i modyfikowanie wydarzeń w kalendarzu oraz wysyłanie wiadomości e-mail do gości"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopiuj"</string> <string name="paste" msgid="5629880836805036433">"Wklej"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopiuj adres URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Zaznacz tekst"</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Zaznaczanie tekstu"</string> <string name="inputMethod" msgid="1653630062304567879">"Metoda wprowadzania"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Działania na tekście"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Mało miejsca"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Maleje ilość dostępnej pamięci telefonu."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Wymuś zamknięcie"</string> <string name="report" msgid="4060218260984795706">"Zgłoś"</string> <string name="wait" msgid="7147118217226317732">"Czekaj"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Aplikacja <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) naruszyła wymuszone przez siebie zasady StrictMode."</string> <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> naruszył wymuszone przez siebie zasady StrictMode."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"Działa <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotknij, aby zobaczyć statystyki przesyłu danych"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Przekroczono limit danych"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Dotknij, aby zobaczyć statystyki przesyłu danych"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 360b911..638da34 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite a uma aplicação ler todos os dados de contactos (endereços) armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para enviar os seus dados a outras pessoas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"escrever dados de contacto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite a uma aplicação modificar os dados de contacto (endereço) armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar estes dados para apagar ou modificar os dados dos seus contactos."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escrever dados do proprietário"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para apagar ou modificar dados do proprietário."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para ler dados do proprietário do telefone."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite a uma aplicação ler todos os eventos do calendário armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para enviar os eventos do seu calendário a outras pessoas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou alterar eventos da agenda e enviar e-mails para os convidados"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Copiar"</string> <string name="paste" msgid="5629880836805036433">"Colar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Seleccionar texto..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selecção de texto"</string> <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Acções de texto"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Pouco espaço livre"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"O espaço de armazenamento do telefone está a ficar reduzido."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Forçar fecho"</string> <string name="report" msgid="4060218260984795706">"Relatório"</string> <string name="wait" msgid="7147118217226317732">"Esperar"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"A aplicação <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode auto-imposta."</string> <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode auto-imposta."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre a utilização de dados móveis"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados móveis excedido"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Toque para saber mais sobre a utilização de dados móveis"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index dc5ee14..1ec1346 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite que um aplicativo leia todos os dados de contato (endereço) armazenados no seu telefone. Aplicativos maliciosos podem usar isso para enviar os seus dados para outras pessoas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"gravar dados de contato"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite que um aplicativo modifique os dados de contato (endereço) armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar os seus dados de contato."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"gravar dados do proprietário"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que um aplicativo modifique os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar dados do proprietário."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que um aplicativo leia os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para ler os dados de proprietário do telefone."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que um aplicativo leia todos os eventos da agenda armazenados no seu telefone. Aplicativos maliciosos podem usar isso para enviar eventos da sua agenda para outras pessoas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou modificar eventos da agenda e enviar e-mail aos convidados"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Copiar"</string> <string name="paste" msgid="5629880836805036433">"Colar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Selecionar texto..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Seleção de texto"</string> <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Ações de texto"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Pouco espaço"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"O espaço de armazenamento do telefone está ficando baixo."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Forçar fechamento"</string> <string name="report" msgid="4060218260984795706">"Informar"</string> <string name="wait" msgid="7147118217226317732">"Aguardar"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"O aplicativo <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode imposta automaticamente."</string> <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode imposta automaticamente."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre uso de dados do celular"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados do celular excedido"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Toque para saber mais sobre o uso de dados do celular"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 8405760..df2456c 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Позволяет приложению считывать все данные контактов (адресов), сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для передачи данных посторонним лицам."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"перезаписывать данные контакта"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Позволяет приложению изменять данные (адрес) контакта, сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для удаления или изменения данных контакта."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"перезаписывать данные о владельце"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Позволяет приложению изменять сведения о владельце, сохраненные на телефоне. Вредоносные приложения могут использовать эту возможность для удаления или изменения данных владельца."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"считывать данные о владельце"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Позволяет приложению считывать сведения о владельце, сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для считывания данных владельца."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"считывать мероприятия в календаре"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Позволяет приложению считывать все события календаря, сохраненные на телефоне. Вредоносные приложения могут использовать эту возможность для передачи ваших событий календаря посторонним лицам."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"добавлять и изменять мероприятия в календаре и отправлять письма гостям"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Копировать"</string> <string name="paste" msgid="5629880836805036433">"Вставить"</string> <string name="copyUrl" msgid="2538211579596067402">"Копировать URL"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Выбрать текст..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Выбор текста"</string> <string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Операции с текстом"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Недостаточно места"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Заканчивается место в памяти телефона."</string> <string name="ok" msgid="5970060430562524910">"ОК"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Закрыть"</string> <string name="report" msgid="4060218260984795706">"Отчет"</string> <string name="wait" msgid="7147118217226317732">"Подождать"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (процесс <xliff:g id="PROCESS">%2$s</xliff:g>) нарушило собственную политику StrictMode."</string> <string name="smv_process" msgid="5120397012047462446">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> нарушил собственную политику StrictMode."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"Приложение <xliff:g id="APP">%1$s</xliff:g> запущено"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Нажмите, чтобы узнать больше о мобильной передаче данных"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Превышен лимит на мобильные данные"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Нажмите, чтобы узнать больше о мобильной передаче данных"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 46806ab..4ba0e91 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillåter att ett program läser alla kontaktuppgifter (adresser) som har lagrats på din telefon. Skadliga program kan använda detta för att skicka dina data till andra personer."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"skriva kontaktuppgifter"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillåter att ett program ändrar kontaktuppgifter (adress) som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra kontaktuppgifter."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skriva ägarinformation"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillåter att ett program ändrar information om telefonens ägare som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra ägaruppgifter."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"läsa information om ägare"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillåter att ett program läser information om telefonens ägare som har lagrats på telefonen. Skadliga program kan använda detta för att läsa telefonens ägaruppgifter."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"läsa kalenderhändelser"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillåter att ett program läser alla händelser i kalendern som har lagrats på din telefon. Skadliga program kan använda detta för att skicka din kalender till andra personer."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"lägg till och ändra kalenderhändelser och skicka e-post till gäster"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopiera"</string> <string name="paste" msgid="5629880836805036433">"Klistra in"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopiera webbadress"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Markera text..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Textmarkering"</string> <string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Textåtgärder"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Dåligt med utrymme"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Telefonens lagringsutrymme håller på att ta slut."</string> <string name="ok" msgid="5970060430562524910">"OK"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Tvinga fram en stängning"</string> <string name="report" msgid="4060218260984795706">"Rapportera"</string> <string name="wait" msgid="7147118217226317732">"Vänta"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (processen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutit mot sin egen StrictMode-policy."</string> <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har brutit mot sin egen StrictMode-policy."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> körs"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryck om du vill veta mer om mobildataanvändning"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Gränsen för mobildata har överskridits"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Tryck om du vill veta mer om mobildataanvändning"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index fffbcd7..78a1a3e 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Uygulamaların telefonunuzda depolanan tüm kişi (adres) verilerini okumasına izin verir. Kötü amaçlı uygulamalar bu işlevi verilerinizi başkalarına göndermek için kullanabilir."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"kişi verileri yaz"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Uygulamaların telefonunuzda depolanan kişi (adres) verilerini değiştirmesine izin verir. Kötü amaçlı uygulamalar bu işlevi kişi verilerinizi silmek veya değiştirmek için kullanabilir."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"sahip verilerini yaz"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini değiştirmesine izin verir. Kötü amaçlı uygulamalar bu işlevi kullanıcı verilerini silmek veya değiştirmek için kullanabilir."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"sahip verilerini oku"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini okumasına izin verir. Kötü amaçlı uygulamalar bunu telefon sahibi verilerini okumak için kullanabilir."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"takvim etkinliklerini oku"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Uygulamaların telefonunuzda depolanan takvim etkinliklerinin tümünü okumasına izin verir. Kötü amaçlı uygulamalar bunu, takvim etkinliklerinizi başkalarına göndermek için kullanabilir."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"takvim etkinlikleri ekle veya değiştir ve konuklara e-posta gönder"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"Kopyala"</string> <string name="paste" msgid="5629880836805036433">"Yapıştır"</string> <string name="copyUrl" msgid="2538211579596067402">"URL\'yi kopyala"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"Metin seç..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"Metin seçimi"</string> <string name="inputMethod" msgid="1653630062304567879">"Giriş yöntemi"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"Metin eylemleri"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Yer az"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Telefonun depolama alanı azalıyor."</string> <string name="ok" msgid="5970060430562524910">"Tamam"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string> <string name="report" msgid="4060218260984795706">"Rapor"</string> <string name="wait" msgid="7147118217226317732">"Bekle"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string> <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Mobil veri kullanımı hakkında daha fazla bilgi edinmek için dokunun"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"Mobil veri limiti aşıldı"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"Mobil veri kullanımı hakkında daha fazla bilgi edinmek için dokunun"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index bff82b9..9ad8c3e 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"允许应用程序读取您手机上存储的所有联系人(地址)数据。恶意应用程序可借此将您的数据发送给其他人。"</string> <string name="permlab_writeContacts" msgid="644616215860933284">"写入联系数据"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"允许应用程序修改您手机上存储的联系人(地址)数据。恶意应用程序可借此清除或修改您的联系人数据。"</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"写入所有者数据"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"允许应用程序修改您手机上存储的手机所有者数据。恶意应用程序可借此清除或修改所有者数据。"</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"读取所有者数据"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"允许应用程序读取您手机上存储的手机所有者数据。恶意应用程序可借此读取手机所有者数据。"</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"读取日历活动"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"允许应用程序读取您手机上存储的所有日历活动。恶意应用程序可借此将您的日历活动发送给其他人。"</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"添加或修改日历活动以及向邀请对象发送电子邮件"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"复制"</string> <string name="paste" msgid="5629880836805036433">"粘贴"</string> <string name="copyUrl" msgid="2538211579596067402">"复制网址"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"选择文字..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"文字选择"</string> <string name="inputMethod" msgid="1653630062304567879">"输入法"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"文字操作"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"存储空间不足"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"手机内存空间所剩不多了。"</string> <string name="ok" msgid="5970060430562524910">"确定"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"强行关闭"</string> <string name="report" msgid="4060218260984795706">"报告"</string> <string name="wait" msgid="7147118217226317732">"等待"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string> <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策。"</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"轻触以了解有关手机流量详情"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"已超出手机数据上限"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"轻触以了解有关手机流量详情"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 300cf20..ead49a7 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -288,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"允許應用程式讀取手機上所有聯絡人 (地址)。請注意:惡意程式可能利用此功能將您的資料傳送給其他人。"</string> <string name="permlab_writeContacts" msgid="644616215860933284">"輸入聯絡人資料"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"允許應用程式更改聯絡資訊 (地址)。請注意:惡意程式可能利用此功能,清除或修改聯絡資料。"</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"寫入持有者的資料"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"允許應用程式更改手機持有者的資料。請注意:惡意程式可能利用此功能,清除或修改持有者的資料。"</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"讀取持有者的資料"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"允許應用程式讀取手機持有者資料。請注意:惡意程式可能利用此功能讀取持有者的資料。"</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"讀取日曆活動"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"允許應用程式讀取手機上所有日曆資料。請注意:惡意程式可能利用此功能將您的日曆資料傳送給其他人。"</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"新增或修改日曆活動,並傳送電子郵件給他人"</string> @@ -713,13 +709,10 @@ <string name="copy" msgid="2681946229533511987">"複製"</string> <string name="paste" msgid="5629880836805036433">"貼上"</string> <string name="copyUrl" msgid="2538211579596067402">"複製網址"</string> - <!-- no translation found for selectTextMode (6738556348861347240) --> - <skip /> - <!-- no translation found for textSelectionCABTitle (5236850394370820357) --> - <skip /> + <string name="selectTextMode" msgid="6738556348861347240">"選取文字..."</string> + <string name="textSelectionCABTitle" msgid="5236850394370820357">"選取文字"</string> <string name="inputMethod" msgid="1653630062304567879">"輸入方式"</string> - <!-- no translation found for editTextMenuTitle (4909135564941815494) --> - <skip /> + <string name="editTextMenuTitle" msgid="4909135564941815494">"文字動作"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"儲存空間即將不足"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"手機儲存空間即將不足。"</string> <string name="ok" msgid="5970060430562524910">"確定"</string> @@ -746,6 +739,12 @@ <string name="force_close" msgid="3653416315450806396">"強制關閉"</string> <string name="report" msgid="4060218260984795706">"回報"</string> <string name="wait" msgid="7147118217226317732">"等待"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string> <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 執行中"</string> @@ -876,4 +875,10 @@ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"輕觸即可瞭解更多有關行動資料用量的詳細資訊"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"已達行動資料上限"</string> <string name="throttled_notification_message" msgid="4712369856601275146">"輕觸即可瞭解更多有關行動資料用量的詳細資訊"</string> + <!-- no translation found for no_matches (8129421908915840737) --> + <skip /> + <!-- no translation found for find_on_page (1946799233822820384) --> + <skip /> + <!-- no translation found for matches_found:one (8167147081136579439) --> + <!-- no translation found for matches_found:other (4641872797067609177) --> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7d54ea1..cda997a 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -469,7 +469,6 @@ <attr name="actionBarTabBarStyle" format="reference" /> <attr name="actionBarTabTextStyle" format="reference" /> <attr name="actionOverflowButtonStyle" format="reference" /> - <attr name="actionModeStyle" format="reference" /> <!-- Reference to a style for the Action Bar --> <attr name="actionBarStyle" format="reference" /> <!-- Size of the Action Bar, including the contextual @@ -483,6 +482,8 @@ <!-- Action mode styles --> <!-- =================== --> <eat-comment /> + <attr name="actionModeStyle" format="reference" /> + <attr name="actionModeCloseButtonStyle" format="reference" /> <!-- Background drawable to use for action mode UI --> <attr name="actionModeBackground" format="reference" /> <!-- Drawable to use for the close action mode button --> @@ -3501,6 +3502,16 @@ <enum name="always" value="2" /> </attr> + <!-- An optional layout to be used as an action view. + See {@link android.view.MenuItem#setActionView(android.view.View)} + for more info. --> + <attr name="actionLayout" format="reference" /> + + <!-- The name of an optional View class to instantiate and use as an + action view. See {@link android.view.MenuItem#setActionView(android.view.View)} + for more info. --> + <attr name="actionViewClass" format="string" /> + </declare-styleable> <!-- **************************************************************** --> @@ -3519,6 +3530,8 @@ <!-- Attribute for a header describing the item shown in the top-level list from which the selects the set of preference to dig in to. --> <declare-styleable name="PreferenceHeader"> + <!-- Identifier value for the header. --> + <attr name="id" /> <!-- The title of the item that is shown to the user. --> <attr name="title" /> <!-- The summary for the item. --> @@ -4090,8 +4103,11 @@ <attr name="height" /> <!-- Specifies a padding to use between elements on the bar. --> <attr name="itemPadding" format="dimension" /> - <!-- Specifies a style for the mode's close button. --> - <attr name="closeButtonStyle" format="reference" /> </declare-styleable> + <declare-styleable name="SearchView"> + <!-- The default state of the SearchView. If true, it will be iconified when not in + use and expanded when clicked. --> + <attr name="iconifiedByDefault" format="boolean"/> + </declare-styleable> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index bff22a6..86e5579 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1340,9 +1340,12 @@ <public type="attr" name="actionBarTabTextStyle" /> <public type="attr" name="actionOverflowButtonStyle" /> <public type="attr" name="itemPadding" /> - <public type="attr" name="closeButtonStyle" /> + <public type="attr" name="actionModeCloseButtonStyle" /> <public type="attr" name="titleTextStyle" /> <public type="attr" name="subtitleTextStyle" /> + <public type="attr" name="iconifiedByDefault" /> + <public type="attr" name="actionLayout" /> + <public type="attr" name="actionViewClass" /> <public type="anim" name="animator_fade_in" /> <public type="anim" name="animator_fade_out" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1ebb085..433c005 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1602,7 +1602,7 @@ <!-- Do not translate. WebView User Agent string --> <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>) - AppleWebKit/534.6 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.6</string> + AppleWebKit/534.7 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.7</string> <!-- Do not translate. WebView User Agent targeted content --> <string name="web_user_agent_target_content" translatable="false">"Mobile "</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index fa5c70b..31aa49a 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -443,14 +443,8 @@ <item name="android:background">@android:drawable/btn_default</item> </style> - <style name="Widget.AutoCompleteTextView"> - <item name="android:focusable">true</item> - <item name="android:focusableInTouchMode">true</item> - <item name="android:clickable">true</item> - <item name="android:background">@android:drawable/edit_text</item> + <style name="Widget.AutoCompleteTextView" parent="Widget.EditText"> <item name="android:completionHintView">@android:layout/simple_dropdown_hint</item> - <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item> - <item name="android:gravity">center_vertical</item> <item name="android:completionThreshold">2</item> <item name="android:dropDownSelector">@android:drawable/list_selector_background</item> <item name="android:popupBackground">@android:drawable/spinner_dropdown_background</item> @@ -529,7 +523,7 @@ <item name="android:fadingEdge">vertical</item> <item name="listSelector">@android:drawable/menu_selector</item> <!-- Light background for the list in menus, so the divider for bright themes --> - <item name="android:divider">@android:drawable/divider_horizontal_bright</item> + <item name="android:divider">@android:drawable/divider_horizontal_dark</item> </style> <style name="Widget.GridView" parent="Widget.AbsListView"> @@ -700,7 +694,7 @@ </style> <style name="TextAppearance.Widget.IconMenu.Item" parent="TextAppearance.Small"> - <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColor">?textColorPrimary</item> </style> <style name="TextAppearance.Widget.EditText"> @@ -896,7 +890,6 @@ <item name="android:background">?android:attr/actionModeBackground</item> <item name="android:height">?android:attr/actionBarSize</item> <item name="android:itemPadding">?android:attr/actionButtonPadding</item> - <item name="android:closeButtonStyle">@android:style/Widget.ActionButton.CloseMode</item> <item name="android:titleTextStyle">@android:style/TextAppearance.Widget.ActionMode.Title</item> <item name="android:subtitleTextStyle">@android:style/TextAppearance.Widget.ActionMode.Subtitle</item> </style> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index e66e52a..8a9ebf0 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -133,9 +133,10 @@ <!-- Panel attributes --> <item name="panelBackground">@android:drawable/menu_background</item> <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item> - <item name="panelColorBackground">#fff</item> - <item name="panelColorForeground">?android:attr/textColorPrimaryInverse</item> - <item name="panelTextAppearance">?android:attr/textAppearanceInverse</item> + <!-- These three attributes do not seems to be used by the framework. Declared public though --> + <item name="panelColorBackground">#000</item> + <item name="panelColorForeground">?android:attr/textColorPrimary</item> + <item name="panelTextAppearance">?android:attr/textAppearance</item> <!-- Scrollbar attributes --> <item name="scrollbarFadeDuration">250</item> @@ -179,9 +180,6 @@ <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="actionDropDownStyle">@android:style/Widget.Spinner.DropDown</item> - <item name="actionButtonStyle">@android:style/Widget.ActionButton</item> - <item name="actionOverflowButtonStyle">@android:style/Widget.ActionButton.Overflow</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> @@ -215,6 +213,9 @@ <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item> <!-- Action bar styles --> + <item name="actionDropDownStyle">@android:style/Widget.Spinner.DropDown</item> + <item name="actionButtonStyle">@android:style/Widget.ActionButton</item> + <item name="actionOverflowButtonStyle">@android:style/Widget.ActionButton.Overflow</item> <item name="actionButtonPadding">12dip</item> <item name="actionModeBackground">@android:drawable/action_bar_context_background</item> <item name="actionModeCloseDrawable">@android:drawable/ic_menu_close_clear_cancel</item> @@ -222,6 +223,7 @@ <item name="actionBarTabBarStyle">@style/Widget.ActionBarView_TabBar</item> <item name="actionBarTabTextStyle">@style/Widget.ActionBarView_TabText</item> <item name="actionModeStyle">@style/Widget.ActionMode</item> + <item name="actionModeCloseButtonStyle">@style/Widget.ActionButton.CloseMode</item> <item name="actionBarStyle">@android:style/Widget.ActionBar</item> <item name="actionBarSize">50dip</item> </style> @@ -533,8 +535,8 @@ <item name="android:itemTextAppearance">@android:style/TextAppearance.Widget.IconMenu.Item</item> <item name="android:itemBackground">@android:drawable/menu_selector</item> <item name="android:itemIconDisabledAlpha">?android:attr/disabledAlpha</item> - <item name="android:horizontalDivider">@android:drawable/divider_horizontal_bright</item> - <item name="android:verticalDivider">@android:drawable/divider_vertical_bright</item> + <item name="android:horizontalDivider">@android:drawable/divider_horizontal_dark</item> + <item name="android:verticalDivider">@android:drawable/divider_vertical_dark</item> <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item> <item name="android:moreIcon">@android:drawable/ic_menu_more</item> <item name="android:background">@null</item> @@ -542,7 +544,7 @@ <style name="Theme.ExpandedMenu"> <!-- Menu/item attributes --> - <item name="android:itemTextAppearance">?android:attr/textAppearanceLargeInverse</item> + <item name="android:itemTextAppearance">?android:attr/textAppearanceLarge</item> <item name="android:listViewStyle">@android:style/Widget.ListView.Menu</item> <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item> <item name="android:background">@null</item> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f09421b..e449ddb 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1022,7 +1022,13 @@ <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> </service> - + + <service android:name="android.webkit.AccessibilityInjectorTest$MockAccessibilityService"> + <intent-filter> + <action android:name="android.accessibilityservice.AccessibilityService" /> + </intent-filter> + </service> + <activity android:name="android.widget.RadioGroupActivity" android:label="RadioGroupActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCacheTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCacheTest.java new file mode 100644 index 0000000..2459815 --- /dev/null +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCacheTest.java @@ -0,0 +1,176 @@ +/* + * 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 android.database.sqlite; + + +import android.content.Context; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.File; +import java.util.ArrayList; + +public class SQLiteCacheTest extends AndroidTestCase { + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + private static final String TABLE_NAME = "testCache"; + private SQLiteCache mCache; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); + mDatabaseFile = new File(dbDir, "test.db"); + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + assertNotNull(mDatabase); + mCache = mDatabase.mCache; + assertNotNull(mCache); + + // create a test table + mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);"); + } + + @Override + protected void tearDown() throws Exception { + mDatabase.lock(); + // flush the above statement from cache and close all the pending statements to be released + mCache.dealloc(); + mDatabase.closePendingStatements(); + mDatabase.unlock(); + assertEquals(0, mDatabase.getQueuedUpStmtList().size()); + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + /** + * do N+1 queries - and when the 0th entry is removed from LRU cache due to the + * insertion of (N+1)th entry, make sure 0th entry is closed + */ + @SmallTest + public void testLruCaching() { + mDatabase.disableWriteAheadLogging(); + // set cache size + int N = 25; + mDatabase.setMaxSqlCacheSize(N); + + ArrayList<Integer> stmtObjs = new ArrayList<Integer>(); + ArrayList<String> sqlStrings = new ArrayList<String>(); + int stmt0 = 0; + for (int i = 0; i < N+1; i++) { + String s = "insert into " + TABLE_NAME + " values(" + i + ",?);"; + sqlStrings.add(s); + ClassToTestSqlCompilationAndCaching c = + ClassToTestSqlCompilationAndCaching.create(mDatabase, s); + int n = c.getSqlStatementId(); + stmtObjs.add(i, n); + if (i == 0) { + // save the statementId of this obj. we want to make sure it is thrown out of + // the cache at the end of this test. + stmt0 = n; + } + c.close(); + } + assertEquals(N, mCache.getCachesize()); + // is 0'th entry out of the cache? it should be in the list of statementIds + // corresponding to the pre-compiled sql statements to be finalized. + assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0)); + for (int i = 1; i < N+1; i++) { + SQLiteCompiledSql compSql = + mDatabase.mCache.getCompiledStatementForSql(sqlStrings.get(i)); + assertNotNull(compSql); + assertTrue(stmtObjs.contains(compSql.nStatement)); + } + assertEquals(N, mCache.getCachesize()); + + } + + /** + * Cache should only have Select / Insert / Update / Delete / Replace. + */ + @SmallTest + public void testCachingOfCRUDstatementsOnly() { + ClassToTestSqlCompilationAndCaching c; + // do some CRUD sql + int crudSqlNum = 7 * 4; + mDatabase.setMaxSqlCacheSize(crudSqlNum); + for (int i = 0; i < crudSqlNum / 4; i++) { + c= ClassToTestSqlCompilationAndCaching.create(mDatabase, + "insert into " + TABLE_NAME + " values(" + i + ",?);"); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "update " + TABLE_NAME + " set i = " + i); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "select * from " + TABLE_NAME + " where i = " + i); + c.close(); + c= ClassToTestSqlCompilationAndCaching.create(mDatabase, + "delete from " + TABLE_NAME + " where i = " + i); + c.close(); + } + // do some non-CRUD sql + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "create table j (i int);"); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "pragma database_list;"); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "begin transaction;"); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "commit;"); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "attach database \"blah\" as blah_db;"); + c.close(); + // cache size should be crudSqlNum + assertEquals(crudSqlNum, mCache.getCachesize()); + // everything in the cache should be a CRUD sql statement + for (String s : mCache.getKeys()) { + int type = DatabaseUtils.getSqlStatementType(s); + assertTrue(type == DatabaseUtils.STATEMENT_SELECT || + type == DatabaseUtils.STATEMENT_UPDATE); + } + } + + /** + * calling SQLiteCache.getCompiledStatementForSql() should reserve the cached-entry + * for the caller, if the entry exists + */ + @SmallTest + public void testGetShouldReserveEntry() { + String sql = "insert into " + TABLE_NAME + " values(1,?);"; + ClassToTestSqlCompilationAndCaching c = + ClassToTestSqlCompilationAndCaching.create(mDatabase, sql); + c.close(); + SQLiteCompiledSql compiledSql = mCache.getCompiledStatementForSql(sql); + assertNotNull(compiledSql); + assertTrue(compiledSql.isInUse()); + // get entry for the same sql again. should get null and a warning in log + assertNull(mCache.getCompiledStatementForSql(sql)); + compiledSql.free(); + assertFalse(compiledSql.isInUse()); + } +} diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCompiledSqlTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCompiledSqlTest.java new file mode 100644 index 0000000..1606cf6 --- /dev/null +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCompiledSqlTest.java @@ -0,0 +1,99 @@ +/* + * 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 android.database.sqlite; + + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.File; + +public class SQLiteCompiledSqlTest extends AndroidTestCase { + private SQLiteDatabase mDatabase; + private File mDatabaseFile; + private static final String TABLE_NAME = "testCache"; + private SQLiteCache mCache; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); + mDatabaseFile = new File(dbDir, "sqlitecursor_test.db"); + if (mDatabaseFile.exists()) { + mDatabaseFile.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); + assertNotNull(mDatabase); + mCache = mDatabase.mCache; + assertNotNull(mCache); + + // create a test table + mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);"); + } + + @Override + protected void tearDown() throws Exception { + mDatabase.close(); + mDatabaseFile.delete(); + super.tearDown(); + } + + /** + * releaseIfNotInUse() should release only if it is not in use + */ + @SmallTest + public void testReleaseIfNotInUse() { + ClassToTestSqlCompilationAndCaching c; + // do some CRUD sql + int crudSqlNum = 20 * 4; + mDatabase.setMaxSqlCacheSize(crudSqlNum); + for (int i = 0; i < crudSqlNum / 4; i++) { + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "insert into " + TABLE_NAME + " values(" + i + ",?);"); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "update " + TABLE_NAME + " set i = " + i); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "select * from " + TABLE_NAME + " where i = " + i); + c.close(); + c = ClassToTestSqlCompilationAndCaching.create(mDatabase, + "delete from " + TABLE_NAME + " where i = " + i); + c.close(); + } + assertEquals(crudSqlNum, mCache.getCachesize()); + String sql = "insert into " + TABLE_NAME + " values(1,?);"; + SQLiteCompiledSql compiledSql = mCache.getCompiledStatementForSql(sql); + assertNotNull(compiledSql); + assertTrue(compiledSql.isInUse()); + // the object is in use. try to release it + compiledSql.releaseIfNotInUse(); + // compiledSql should not be released yet + int stmtId = compiledSql.nStatement; + assertTrue(stmtId > 0); + // free the object and call releaseIfNotInUse() again - and it should work this time + compiledSql.free(); + assertFalse(compiledSql.isInUse()); + compiledSql.releaseIfNotInUse(); + assertEquals(0, compiledSql.nStatement); + assertTrue(mDatabase.getQueuedUpStmtList().contains(stmtId)); + } +} diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index 86eda71..3249cb6 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -447,42 +447,6 @@ public class SQLiteDatabaseTest extends AndroidTestCase { } } - @SmallTest - public void testLruCachingOfSqliteCompiledSqlObjs() { - createTableAndClearCache(); - // set cache size - int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE; - mDatabase.setMaxSqlCacheSize(N); - - // do N+1 queries - and when the 0th entry is removed from LRU cache due to the - // insertion of (N+1)th entry, make sure 0th entry is closed - ArrayList<Integer> stmtObjs = new ArrayList<Integer>(); - ArrayList<String> sqlStrings = new ArrayList<String>(); - int stmt0 = 0; - for (int i = 0; i < N+1; i++) { - String s = "insert into test values(" + i + ",?);"; - sqlStrings.add(s); - ClassToTestSqlCompilationAndCaching c = - ClassToTestSqlCompilationAndCaching.create(mDatabase, s); - int n = c.getSqlStatementId(); - stmtObjs.add(i, n); - if (i == 0) { - // save the statementId of this obj. we want to make sure it is thrown out of - // the cache at the end of this test. - stmt0 = n; - } - c.close(); - } - // is 0'th entry out of the cache? it should be in the list of statementIds - // corresponding to the pre-compiled sql statements to be finalized. - assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0)); - for (int i = 1; i < N+1; i++) { - SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i)); - assertNotNull(compSql); - assertTrue(stmtObjs.contains(compSql.nStatement)); - } - } - @MediumTest public void testDbCloseReleasingAllCachedSql() { mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " + @@ -503,7 +467,7 @@ public class SQLiteDatabaseTest extends AndroidTestCase { mDatabase.enableWriteAheadLogging(); mDatabase.lock(); // flush the above statement from cache and close all the pending statements to be released - mDatabase.deallocCachedSqlStatements(); + mDatabase.mCache.dealloc(); mDatabase.closePendingStatements(); mDatabase.unlock(); assertEquals(0, mDatabase.getQueuedUpStmtList().size()); diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java index 217545f..bdd6d5c 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java @@ -53,6 +53,12 @@ public class SQLiteStatementTest extends AndroidTestCase { * Start 2 threads to repeatedly execute the above SQL statement. * Even though 2 threads are executing the same SQL, they each should get their own copy of * prepared SQL statement id and there SHOULD NOT be an error from sqlite or android. + *<p> + * This method will produce a lot of the following warnings: + * Possible bug: Either using the same SQL in 2 threads at the same time, or + * previous instance of this SQL statement was never close()d. + * That is expected behavior. + * * @throws InterruptedException thrown if the test threads started by this test are interrupted */ @LargeTest diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java index a5229cc..1beba53 100644 --- a/core/tests/coretests/src/android/text/TextUtilsTest.java +++ b/core/tests/coretests/src/android/text/TextUtilsTest.java @@ -349,6 +349,26 @@ public class TextUtilsTest extends TestCase { } } + @SmallTest + public void testDelimitedStringContains() { + assertFalse(TextUtils.delimitedStringContains("", ',', null)); + assertFalse(TextUtils.delimitedStringContains(null, ',', "")); + // Whole match + assertTrue(TextUtils.delimitedStringContains("gps", ',', "gps")); + // At beginning. + assertTrue(TextUtils.delimitedStringContains("gps,gpsx,network,mock", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("gps,network,mock", ',', "gps")); + // In middle, both without, before & after a false match. + assertTrue(TextUtils.delimitedStringContains("network,gps,mock", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("network,gps,gpsx,mock", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("network,gpsx,gps,mock", ',', "gps")); + // At the end. + assertTrue(TextUtils.delimitedStringContains("network,mock,gps", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("network,mock,gpsx,gps", ',', "gps")); + // Not present (but with a false match) + assertFalse(TextUtils.delimitedStringContains("network,mock,gpsx", ',', "gps")); + } + /** * CharSequence wrapper for testing the cases where text is copied into * a char array instead of working from a String or a Spanned. diff --git a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java new file mode 100644 index 0000000..9c9d9fe --- /dev/null +++ b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java @@ -0,0 +1,945 @@ +/* + * 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 android.webkit; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.KeyEvent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +/** + * This is a test for the behavior of the {@link AccessibilityInjector} + * which is used by {@link WebView} to provide basic accessibility support + * in case JavaScript is disabled. + * </p> + * Note: This test works against the generated {@link AccessibilityEvent}s + * to so it also checks if the test for announcing navigation axis and + * status messages as appropriate. + */ +public class AccessibilityInjectorTest extends AndroidTestCase { + + /** The timeout to wait for the expected selection. */ + private static final long TIMEOUT_WAIT_FOR_SELECTION_STRING = 1000; + + /** The timeout to wait for accessibility and the mock service to be enabled. */ + private static final long TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE = 500; + + /** The count of tests to detect when to shut down the service. */ + private static final int TEST_CASE_COUNT = 8; + + /** The meta state for pressed left ALT. */ + private static final int META_STATE_ALT_LEFT_ON = KeyEvent.META_ALT_ON + | KeyEvent.META_ALT_LEFT_ON; + + /** The value for not specified selection string since null is a valid value. */ + private static final String SELECTION_STRING_UNKNOWN = "Unknown"; + + /** Lock for locking the test. */ + private static final Object sTestLock = new Object(); + + /** Handle to the test for use by the mock service. */ + private static AccessibilityInjectorTest sInstance; + + /** Flag indicating if the accessibility service is ready to receive events. */ + private static boolean sIsAccessibilityServiceReady; + + /** The count of executed tests to detect when to toggle accessibility and the service. */ + private static int sExecutedTestCount; + + /** Worker thread with a handler to perform non test thread processing. */ + private Worker mWorker; + + /** Handle to the {@link WebView} to load data in. */ + private WebView mWebView; + + /** The received selection string for assertion checking. */ + private static String sReceivedSelectionString = SELECTION_STRING_UNKNOWN; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mWorker = new Worker(); + sInstance = this; + if (sExecutedTestCount == 0) { + // until JUnit4 comes to play with @BeforeTest + disableAccessibilityAndMockAccessibilityService(); + enableAccessibilityAndMockAccessibilityService(); + } + } + + @Override + protected void tearDown() throws Exception { + if (mWorker != null) { + mWorker.stop(); + } + if (sExecutedTestCount == TEST_CASE_COUNT) { + // until JUnit4 comes to play with @AfterTest + disableAccessibilityAndMockAccessibilityService(); + } + super.tearDown(); + } + + /** + * Tests navigation by character. + */ + @LargeTest + public void testNavigationByCharacter() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<p>" + + "a <b>b</b> c" + + "</p>" + + "<p>" + + "d" + + "<input>e</input>" + + "</p>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // change navigation axis to word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON); + assertSelectionString("1"); // expect the word navigation axis + + // change navigation axis to character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON); + assertSelectionString("0"); // expect the character navigation axis + + // go to the first character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("a"); + + // go to the second character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<b>b</b>"); + + // go to the third character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("c"); + + // go to the fourth character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("d"); + + // go to the fifth character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("e"); + + // try to go past the last character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString(null); + + // go to the fourth character (reverse) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("d"); + + // go to the third character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("c"); + + // go to the second character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<b>b</b>"); + + // go to the first character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("a"); + + // try to go before the first character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString(null); + + // go to the second character (reverse again) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<b>b</b>"); + } + + /** + * Tests navigation by word. + */ + @LargeTest + public void testNavigationByWord() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<p>" + + "This is <b>a</b> sentence" + + "</p>" + + "<p>" + + " scattered " + + "<input>all</input>" + + " over " + + "</p>" + + "<div>" + + "<button>the place.</button>" + + "</div>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // change navigation axis to word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON); + assertSelectionString("1"); // expect the word navigation axis + + // go to the first word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("This"); + + // go to the second word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("is"); + + // go to the third word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<b>a</b>"); + + // go to the fourth word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("sentence"); + + // go to the fifth word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("scattered"); + + // go to the sixth word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("all"); + + // go to the seventh word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("over"); + + // go to the eight word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("the"); + + // go to the ninth word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("place"); + + // NOTE: WebKit selection returns the dot as a word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("."); + + // try to go past the last word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString(null); + + // go to the last word (reverse) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("place"); + + // go to the eight word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("the"); + + // go to the seventh word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("over"); + + // go to the sixth word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("all"); + + // go to the fifth word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("scattered"); + + // go to the fourth word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("sentence"); + + // go to the third word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<b>a</b>"); + + // go to the second word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("is"); + + // go to the first word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("This"); + + // try to go before the first word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString(null); + + // go to the second word (reverse again) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("is"); + } + + /** + * Tests navigation by sentence. + */ + @LargeTest + public void testNavigationBySentence() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<div>" + + "<p>" + + "This is the first sentence of the first paragraph and has an <b>inline bold tag</b>." + + "This is the second sentence of the first paragraph." + + "</p>" + + "<h1>This is a heading</h1>" + + "<p>" + + "This is the first sentence of the second paragraph." + + "This is the second sentence of the second paragraph." + + "</p>" + + "</div>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // Sentence axis is the default + + // go to the first sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("This is the first sentence of the first paragraph and has an " + + "<b>inline bold tag</b>."); + + // go to the second sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("This is the second sentence of the first paragraph."); + + // go to the third sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("This is a heading"); + + // go to the fourth sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("This is the first sentence of the second paragraph."); + + // go to the fifth sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("This is the second sentence of the second paragraph."); + + // try to go past the last sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString(null); + + // go to the fourth sentence (reverse) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("This is the first sentence of the second paragraph."); + + // go to the third sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("This is a heading"); + + // go to the second sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("This is the second sentence of the first paragraph."); + + // go to the first sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("This is the first sentence of the first paragraph and has an " + + "<b>inline bold tag</b>."); + + // try to go before the first sentence + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString(null); + + // go to the second sentence (reverse again) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("This is the second sentence of the first paragraph."); + } + + /** + * Tests navigation by heading. + */ + @LargeTest + public void testNavigationByHeading() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<!DOCTYPE html>" + + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<h1>Heading one</h1>" + + "<p>" + + "This is some text" + + "</p>" + + "<h2>Heading two</h2>" + + "<p>" + + "This is some text" + + "</p>" + + "<h3>Heading three</h3>" + + "<p>" + + "This is some text" + + "</p>" + + "<h4>Heading four</h4>" + + "<p>" + + "This is some text" + + "</p>" + + "<h5>Heading five</h5>" + + "<p>" + + "This is some text" + + "</p>" + + "<h6>Heading six</h6>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // change navigation axis to heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON); + assertSelectionString("3"); // expect the heading navigation axis + + // go to the first heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h1>Heading one</h1>"); + + // go to the second heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h2>Heading two</h2>"); + + // go to the third heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h3>Heading three</h3>"); + + // go to the fourth heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h4>Heading four</h4>"); + + // go to the fifth heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h5>Heading five</h5>"); + + // go to the sixth heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h6>Heading six</h6>"); + + // try to go past the last heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString(null); + + // go to the fifth heading (reverse) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<h5>Heading five</h5>"); + + // go to the fourth heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<h4>Heading four</h4>"); + + // go to the third heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<h3>Heading three</h3>"); + + // go to the second heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<h2>Heading two</h2>"); + + // go to the first heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<h1>Heading one</h1>"); + + // try to go before the first heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString(null); + + // go to the second heading (reverse again) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h2>Heading two</h2>"); + } + + /** + * Tests navigation by sibling. + */ + @LargeTest + public void testNavigationBySibing() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<!DOCTYPE html>" + + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<h1>Heading one</h1>" + + "<p>" + + "This is some text" + + "</p>" + + "<div>" + + "<button>Input</button>" + + "</div>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // change navigation axis to heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON); + assertSelectionString("3"); // expect the heading navigation axis + + // change navigation axis to sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON); + assertSelectionString("4"); // expect the sibling navigation axis + + // change navigation axis to parent/first child + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON); + assertSelectionString("5"); // expect the parent/first child navigation axis + + // go to the first child of the body + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<h1>Heading one</h1>"); + + // change navigation axis to sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON); + assertSelectionString("4"); // expect the sibling navigation axis + + // go to the next sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<p>This is some text</p>"); + + // go to the next sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<div><button>Input</button></div>"); + + // try to go past the last sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString(null); + + // go to the previous sibling (reverse) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<p>This is some text</p>"); + + // go to the previous sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<h1>Heading one</h1>"); + + // try to go before the previous sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString(null); + + // go to the next sibling (reverse again) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<p>This is some text</p>"); + } + + /** + * Tests navigation by parent/first child. + */ + @LargeTest + public void testNavigationByParentFirstChild() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<!DOCTYPE html>" + + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<div>" + + "<button>Input</button>" + + "</div>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // change navigation axis to document + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON); + assertSelectionString("6"); // expect the document navigation axis + + // change navigation axis to parent/first child + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON); + assertSelectionString("5"); // expect the parent/first child navigation axis + + // go to the first child + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<div><button>Input</button></div>"); + + // go to the first child + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<button>Input</button>"); + + // try to go to the first child of a leaf element + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString(null); + + // go to the parent (reverse) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<div><button>Input</button></div>"); + + // go to the parent + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<body><div><button>Input</button></div></body>"); + + // try to go to the body parent + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString(null); + + // go to the first child (reverse again) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<div><button>Input</button></div>"); + } + + /** + * Tests navigation by document. + */ + @LargeTest + public void testNavigationByDocument() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<!DOCTYPE html>" + + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<button>Click</button>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // change navigation axis to document + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON); + assertSelectionString("6"); // expect the document navigation axis + + // go to the bottom of the document + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("Click"); + + // go to the top of the document (reverse) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); + assertSelectionString("<body><button>Click</button></body>"); + + // go to the bottom of the document (reverse again) + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("Click"); + } + + /** + * Tests the sync between the text navigation and navigation by DOM elements. + */ + @LargeTest + public void testSyncBetweenTextAndDomNodeNavigation() throws Exception { + // a bit ugly but helps detect beginning and end of all tests so accessibility + // and the mock service are not toggled on every test (expensive) + sExecutedTestCount++; + + String html = + "<!DOCTYPE html>" + + "<html>" + + "<head>" + + "</head>" + + "<body>" + + "<p>" + + "First" + + "</p>" + + "<button>Second</button>" + + "<p>" + + "Third" + + "</p>" + + "</body>" + + "</html>"; + + WebView webView = createWebVewWithHtml(html); + + // change navigation axis to word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON); + assertSelectionString("1"); // expect the word navigation axis + + // go to the first word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("First"); + + // change navigation axis to heading + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON); + assertSelectionString("3"); // expect the heading navigation axis + + // change navigation axis to sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON); + assertSelectionString("4"); // expect the sibling navigation axis + + // go to the next sibling + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("<button>Second</button>"); + + // change navigation axis to character + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON); + assertSelectionString("0"); // expect the character navigation axis + + // change navigation axis to word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON); + assertSelectionString("1"); // expect the word navigation axis + + // go to the next word + sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); + assertSelectionString("Third"); + } + + /** + * Enable accessibility and the mock accessibility service. + */ + private void enableAccessibilityAndMockAccessibilityService() { + // make sure the manager is instantiated so the system initializes it + AccessibilityManager.getInstance(getContext()); + + // enable accessibility and the mock accessibility service + Settings.Secure.putInt(getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 1); + String enabledServices = new ComponentName(getContext().getPackageName(), + MockAccessibilityService.class.getName()).flattenToShortString(); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices); + + // poll within a timeout and let be interrupted in case of success + long incrementStep = TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE / 5; + long start = SystemClock.uptimeMillis(); + while (SystemClock.uptimeMillis() - start < TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE && + !sIsAccessibilityServiceReady) { + synchronized (sTestLock) { + try { + sTestLock.wait(incrementStep); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + + if (!sIsAccessibilityServiceReady) { + throw new IllegalStateException("MockAccessibilityService not ready. Did you add " + + "tests and forgot to update AccessibilityInjectorTest#TEST_CASE_COUNT?"); + } + } + + @Override + protected void scrubClass(Class<?> testCaseClass) { + /* do nothing - avoid superclass behavior */ + } + + /** + * Disables accessibility and the mock accessibility service. + */ + private void disableAccessibilityAndMockAccessibilityService() { + // disable accessibility and the mock accessibility service + Settings.Secure.putInt(getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0); + Settings.Secure.putString(getContext().getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ""); + } + + /** + * Asserts the next <code>expectedSelectionString</code> to be received. + */ + private void assertSelectionString(String expectedSelectionString) { + assertTrue("MockAccessibilityService not ready", sIsAccessibilityServiceReady); + + long incrementStep = TIMEOUT_WAIT_FOR_SELECTION_STRING / 5; + long start = SystemClock.uptimeMillis(); + while (SystemClock.uptimeMillis() - start < TIMEOUT_WAIT_FOR_SELECTION_STRING && + sReceivedSelectionString == SELECTION_STRING_UNKNOWN) { + synchronized (sTestLock) { + try { + sTestLock.wait(incrementStep); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + try { + if (sReceivedSelectionString == SELECTION_STRING_UNKNOWN) { + fail("No selection string received. Expected: " + expectedSelectionString); + } + assertEquals(expectedSelectionString, sReceivedSelectionString); + } finally { + sReceivedSelectionString = SELECTION_STRING_UNKNOWN; + } + } + + /** + * Sends a {@link KeyEvent} (up and down) to the {@link WebView}. + * + * @param keyCode The event key code. + */ + private void sendKeyEvent(WebView webView, int keyCode, int metaState) { + webView.onKeyDown(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 1, metaState)); + webView.onKeyUp(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 1, metaState)); + } + + /** + * Creates a {@link WebView} with with a given HTML content. + * + * @param html The HTML content; + * @return The created view. + */ + private WebView createWebVewWithHtml(final String html) { + mWorker.getHandler().post(new Runnable() { + public void run() { + mWebView = new WebView(getContext()); + mWebView.loadData(html, "text/html", "utf-8"); + mWebView.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView view, String url) { + mWorker.getHandler().post(new Runnable() { + public void run() { + synchronized (sTestLock) { + sTestLock.notifyAll(); + } + } + }); + } + }); + } + }); + synchronized (sTestLock) { + try { + sTestLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + return mWebView; + } + + /** + * This is a worker thread responsible for creating the {@link WebView}. + */ + private class Worker implements Runnable { + private final Object mWorkerLock = new Object(); + private Handler mHandler; + + public Worker() { + new Thread(this).start(); + synchronized (mWorkerLock) { + while (mHandler == null) { + try { + mWorkerLock.wait(); + } catch (InterruptedException ex) { + /* ignore */ + } + } + } + } + + public void run() { + synchronized (mWorkerLock) { + Looper.prepare(); + mHandler = new Handler(); + mWorkerLock.notifyAll(); + } + Looper.loop(); + } + + public Handler getHandler() { + return mHandler; + } + + public void stop() { + mHandler.getLooper().quit(); + } + } + + /** + * Mock accessibility service to receive the accessibility events + * with the current {@link WebView} selection. + */ + public static class MockAccessibilityService extends AccessibilityService { + private boolean mIsServiceInfoSet; + + @Override + protected void onServiceConnected() { + if (mIsServiceInfoSet) { + return; + } + AccessibilityServiceInfo info = new AccessibilityServiceInfo(); + info.eventTypes = AccessibilityEvent.TYPE_VIEW_SELECTED; + info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; + setServiceInfo(info); + mIsServiceInfoSet = true; + + sIsAccessibilityServiceReady = true; + + if (sInstance == null) { + return; + } + synchronized (sTestLock) { + sTestLock.notifyAll(); + } + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (sInstance == null) { + return; + } + if (!event.getText().isEmpty()) { + CharSequence text = event.getText().get(0); + sReceivedSelectionString = (text != null) ? text.toString() : null; + } + synchronized (sTestLock) { + sTestLock.notifyAll(); + } + } + + @Override + public void onInterrupt() { + /* do nothing */ + } + + @Override + public boolean onUnbind(Intent intent) { + sIsAccessibilityServiceReady = false; + return false; + } + } +} diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java index c5ea9c1..0f7351a 100644 --- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java +++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java @@ -580,7 +580,7 @@ public class PackageManagerHostTestUtils extends Assert { private boolean mAllTestsPassed = true; private String mTestRunErrorMessage = null; - public void testEnded(TestIdentifier test) { + public void testEnded(TestIdentifier test, Map<String, String> metrics) { // ignore } diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd index ec47796..92d4e15 100644 --- a/docs/html/resources/dashboard/platform-versions.jd +++ b/docs/html/resources/dashboard/platform-versions.jd @@ -52,7 +52,7 @@ Android Market within a 14-day period ending on the data collection date noted b <div class="dashboard-panel"> <img alt="" height="250" width="460" -src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:15.3,20.3,0.2,59.7,4.5&chl= +src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:12.0,17.5,0.1,41.7,28.7&chl= Android%201.5|Android%201.6|Other*|Android%202.1|Android%202.2&chco=c4df9b, 6fad0c" /> @@ -62,14 +62,14 @@ Android%201.5|Android%201.6|Other*|Android%202.1|Android%202.2&chco=c4df9b, <th>API Level</th> <th>Distribution</th> </tr> -<tr><td>Android 1.5</td><td>3</td><td>15.3%</td></tr> -<tr><td>Android 1.6</td><td>4</td><td>20.3%</td></tr> -<tr><td>Android 2.1</td><td>7</td><td>59.7%</td></tr> -<tr><td>Android 2.2</td><td>8</td><td>4.5%</td></tr> +<tr><td>Android 1.5</td><td>3</td><td>12.0%</td></tr> +<tr><td>Android 1.6</td><td>4</td><td>17.5%</td></tr> +<tr><td>Android 2.1</td><td>7</td><td>41.7%</td></tr> +<tr><td>Android 2.2</td><td>8</td><td>28.7%</td></tr> </table> -<p><em>Data collected during two weeks ending on August 2, 2010</em></p> -<p style="font-size:.9em">* <em>Other: 0.2% of devices running obsolete versions</em></p> +<p><em>Data collected during two weeks ending on September 1, 2010</em></p> +<p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p> </div><!-- end dashboard-panel --> @@ -97,18 +97,18 @@ Android Market within a 14-day period ending on the date indicated on the x-axis <img alt="" height="250" width="660" style="padding:5px;background:#fff" src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,y,r&chxr=0,0,12|1,0,100|2,0,100& -chxl=0%3A%7C2010/02/01%7C02/15%7C03/01%7C03/15%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/ -01%7C07/15%7C2010/08/01%7C1%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C2%3A%7C0%25%7C25%25%7C50%25 -%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.0,99.2,99.4,99.5,99.6,99.6, -99.6,99.7,100.6,101.1,99.9,100.0,100.0|63.4,62.5,61.6,60.6,61.5,61.7,62.3,63.5,73.0,76.4,78.6,81.1, -84.5|22.6,23.2,24.3,25.4,29.4,30.2,32.7,35.3,46.2,51.3,55.1,59.0,64.1|0.0,0.0,0.0,0.0,4.0,28.3,32.0, -34.9,45.9,51.0,54.9,58.8,64.0|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.8,1.2,1.8,3.3,4.3&chm=tAndroid%201.5 -,7caa36,0,0,15,,t::-5|b,c3df9b,0,1,0|tAndroid%201.6,638d23,1,0,15,,t::-5|b,b0db6e,1,2,0|tAndroid%202 -.0.1,496c13,2,0,15,,t::-5|b,9ddb3d,2,3,0|tAndroid%202.1,2f4708,3,5,15,,t::-5|b,89cf19,3,4,0|B,6fad0c -,4,5,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.0.1|Android%202.1|Android%202.2&chco= -add274,9ad145,84c323,6ba213,507d08" /> - -<p><em>Last historical dataset collected during two weeks ending on August 2, 2010</em></p> +chxl=0%3A%7C2010/03/01%7C03/15%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/ +01%7C08/15%7C2010/09/01%7C1%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C2%3A%7C0%25%7C25%25%7C50%25 +%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.4,99.5,99.6,99.6,99.6,99.7, +100.6,101.1,99.9,100.0,100.0,99.8,99.9|61.6,60.6,61.5,61.7,62.3,63.5,73.0,76.4,78.6,81.1,84.5,86.6, +88.0|24.3,25.4,29.4,30.2,32.7,35.3,46.2,51.3,55.1,59.0,64.1,68.2,70.4|0.0,0.0,4.0,28.3,32.0,34.9,45. +9,51.0,54.9,58.8,64.0,68.1,70.3|0.0,0.0,0.0,0.0,0.0,0.0,0.8,1.2,1.8,3.3,4.3,11.3,27.8&chm=tAndroid% +201.5,7caa36,0,0,15,,t::-5|b,c3df9b,0,1,0|tAndroid%201.6,638d23,1,0,15,,t::-5|b,b0db6e,1,2,0| +tAndroid%202.0.1,496c13,2,0,15,,t::-5|b,9ddb3d,2,3,0|tAndroid%202.1,2f4708,3,3,15,,t::-5|b,89cf19,3, +4,0|tAndroid%202.2,131d02,4,11,15,,t::-5|B,6fad0c,4,5,0&chg=7,25&chdl=Android%201.5|Android%201.6| +Android%202.0.1|Android%202.1|Android%202.2&chco=add274,9ad145,84c323,6ba213,507d08" /> + +<p><em>Last historical dataset collected during two weeks ending on September 1, 2010</em></p> </div><!-- end dashboard-panel --> diff --git a/drm/common/Android.mk b/drm/common/Android.mk index 249fe8e..808b2c2 100644 --- a/drm/common/Android.mk +++ b/drm/common/Android.mk @@ -38,4 +38,6 @@ LOCAL_C_INCLUDES := \ LOCAL_MODULE:= libdrmframeworkcommon +LOCAL_MODULE_TAGS := optional + include $(BUILD_STATIC_LIBRARY) diff --git a/drm/drmioserver/Android.mk b/drm/drmioserver/Android.mk index c40ce26..11571c7 100644 --- a/drm/drmioserver/Android.mk +++ b/drm/drmioserver/Android.mk @@ -22,8 +22,13 @@ LOCAL_SRC_FILES:= \ LOCAL_SHARED_LIBRARIES := \ libutils \ - libbinder \ - libdl + libbinder + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif LOCAL_STATIC_LIBRARIES := libdrmframeworkcommon @@ -33,4 +38,6 @@ LOCAL_C_INCLUDES := \ LOCAL_MODULE:= drmioserver +LOCAL_MODULE_TAGS := optional + include $(BUILD_EXECUTABLE) diff --git a/drm/drmserver/Android.mk b/drm/drmserver/Android.mk index d2ebe8e..5df2ff8 100644 --- a/drm/drmserver/Android.mk +++ b/drm/drmserver/Android.mk @@ -24,8 +24,13 @@ LOCAL_SRC_FILES:= \ LOCAL_SHARED_LIBRARIES := \ libutils \ - libbinder \ - libdl + libbinder + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif LOCAL_STATIC_LIBRARIES := libdrmframeworkcommon @@ -36,4 +41,6 @@ LOCAL_C_INCLUDES := \ LOCAL_MODULE:= drmserver +LOCAL_MODULE_TAGS := optional + include $(BUILD_EXECUTABLE) diff --git a/drm/jni/Android.mk b/drm/jni/Android.mk index 0731755..b65e4da 100644 --- a/drm/jni/Android.mk +++ b/drm/jni/Android.mk @@ -26,8 +26,13 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libandroid_runtime \ libnativehelper \ - libbinder \ - libdl + libbinder + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif LOCAL_STATIC_LIBRARIES := @@ -39,5 +44,7 @@ LOCAL_C_INCLUDES += \ LOCAL_PRELINK_MODULE := false +LOCAL_MODULE_TAGS := optional + include $(BUILD_SHARED_LIBRARY) diff --git a/drm/libdrmframework/Android.mk b/drm/libdrmframework/Android.mk index c25d79b..99133ba 100644 --- a/drm/libdrmframework/Android.mk +++ b/drm/libdrmframework/Android.mk @@ -25,8 +25,13 @@ LOCAL_MODULE:= libdrmframework LOCAL_SHARED_LIBRARIES := \ libutils \ - libbinder \ - libdl + libbinder + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif LOCAL_STATIC_LIBRARIES := \ libdrmframeworkcommon @@ -37,6 +42,8 @@ LOCAL_C_INCLUDES += \ LOCAL_PRELINK_MODULE := false +LOCAL_MODULE_TAGS := optional + include $(BUILD_SHARED_LIBRARY) include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/drm/libdrmframework/plugins/passthru/Android.mk b/drm/libdrmframework/plugins/passthru/Android.mk index a7bbf23..7856d37 100644 --- a/drm/libdrmframework/plugins/passthru/Android.mk +++ b/drm/libdrmframework/plugins/passthru/Android.mk @@ -24,8 +24,13 @@ LOCAL_MODULE := libdrmpassthruplugin LOCAL_STATIC_LIBRARIES := libdrmframeworkcommon LOCAL_SHARED_LIBRARIES := \ - libutils \ - libdl + libutils + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif LOCAL_PRELINK_MODULE := false @@ -38,7 +43,6 @@ LOCAL_C_INCLUDES += \ # Set the following flag to enable the decryption passthru flow #LOCAL_CFLAGS += -DENABLE_PASSTHRU_DECRYPTION -PRODUCT_COPY_FILES += \ - $(TARGET_OUT_SHARED_LIBRARIES)/libdrmpassthruplugin.so:system/lib/drm/plugins/native/libdrmpassthruplugin.so +LOCAL_MODULE_TAGS := optional include $(BUILD_SHARED_LIBRARY) diff --git a/include/media/MediaProfiles.h b/include/media/MediaProfiles.h index c3cd361..0ec7eec 100644 --- a/include/media/MediaProfiles.h +++ b/include/media/MediaProfiles.h @@ -25,7 +25,20 @@ namespace android { enum camcorder_quality { CAMCORDER_QUALITY_LOW = 0, - CAMCORDER_QUALITY_HIGH = 1 + CAMCORDER_QUALITY_HIGH = 1, + CAMCORDER_QUALITY_QCIF = 2, + CAMCORDER_QUALITY_CIF = 3, + CAMCORDER_QUALITY_480P = 4, + CAMCORDER_QUALITY_720P = 5, + CAMCORDER_QUALITY_1080P = 6, + + CAMCORDER_QUALITY_TIME_LAPSE_LOW = 1000, + CAMCORDER_QUALITY_TIME_LAPSE_HIGH = 1001, + CAMCORDER_QUALITY_TIME_LAPSE_QCIF = 1002, + CAMCORDER_QUALITY_TIME_LAPSE_CIF = 1003, + CAMCORDER_QUALITY_TIME_LAPSE_480P = 1004, + CAMCORDER_QUALITY_TIME_LAPSE_720P = 1005, + CAMCORDER_QUALITY_TIME_LAPSE_1080P = 1006 }; enum video_decoder { @@ -68,6 +81,12 @@ public: camcorder_quality quality) const; /** + * Returns true if a profile for the given camera at the given quality exists, + * or false if not. + */ + bool hasCamcorderProfile(int cameraId, camcorder_quality quality) const; + + /** * Returns the output file formats supported. */ Vector<output_format> getOutputFileFormats() const; @@ -252,6 +271,8 @@ private: Vector<int> mLevels; }; + int getCamcorderProfileIndex(int cameraId, camcorder_quality quality) const; + // Debug static void logVideoCodec(const VideoCodec& codec); static void logAudioCodec(const AudioCodec& codec); @@ -283,6 +304,8 @@ private: static MediaProfiles* createDefaultInstance(); static CamcorderProfile *createDefaultCamcorderLowProfile(); static CamcorderProfile *createDefaultCamcorderHighProfile(); + static CamcorderProfile *createDefaultCamcorderTimeLapseLowProfile(); + static CamcorderProfile *createDefaultCamcorderTimeLapseHighProfile(); static void createDefaultCamcorderProfiles(MediaProfiles *profiles); static void createDefaultVideoEncoders(MediaProfiles *profiles); static void createDefaultAudioEncoders(MediaProfiles *profiles); diff --git a/include/media/mediascanner.h b/include/media/mediascanner.h index 0d397ac..74c9d5d 100644 --- a/include/media/mediascanner.h +++ b/include/media/mediascanner.h @@ -38,8 +38,7 @@ struct MediaScanner { typedef bool (*ExceptionCheck)(void* env); virtual status_t processDirectory( - const char *path, const char *extensions, - MediaScannerClient &client, + const char *path, MediaScannerClient &client, ExceptionCheck exceptionCheck, void *exceptionEnv); void setLocale(const char *locale); @@ -55,9 +54,8 @@ private: char *mLocale; status_t doProcessDirectory( - char *path, int pathRemaining, const char *extensions, - MediaScannerClient &client, ExceptionCheck exceptionCheck, - void *exceptionEnv); + char *path, int pathRemaining, MediaScannerClient &client, + ExceptionCheck exceptionCheck, void *exceptionEnv); MediaScanner(const MediaScanner &); MediaScanner &operator=(const MediaScanner &); diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h index f00f2db..a06208a 100644 --- a/include/ui/InputDispatcher.h +++ b/include/ui/InputDispatcher.h @@ -30,6 +30,7 @@ #include <stddef.h> #include <unistd.h> +#include <limits.h> namespace android { @@ -108,9 +109,12 @@ struct InputTarget { // Flags for the input target. int32_t flags; - // The timeout for event delivery to this target in nanoseconds. Or -1 if none. + // The timeout for event delivery to this target in nanoseconds, or -1 to wait indefinitely. nsecs_t timeout; + // The time already spent waiting for this target in nanoseconds, or 0 if none. + nsecs_t timeSpentWaitingForApplication; + // The x and y offset to add to a MotionEvent as it is delivered. // (ignored for KeyEvents) float xOffset, yOffset; @@ -118,6 +122,122 @@ struct InputTarget { /* + * An input window describes the bounds of a window that can receive input. + */ +struct InputWindow { + // Window flags from WindowManager.LayoutParams + enum { + FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001, + FLAG_DIM_BEHIND = 0x00000002, + FLAG_BLUR_BEHIND = 0x00000004, + FLAG_NOT_FOCUSABLE = 0x00000008, + FLAG_NOT_TOUCHABLE = 0x00000010, + FLAG_NOT_TOUCH_MODAL = 0x00000020, + FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040, + FLAG_KEEP_SCREEN_ON = 0x00000080, + FLAG_LAYOUT_IN_SCREEN = 0x00000100, + FLAG_LAYOUT_NO_LIMITS = 0x00000200, + FLAG_FULLSCREEN = 0x00000400, + FLAG_FORCE_NOT_FULLSCREEN = 0x00000800, + FLAG_DITHER = 0x00001000, + FLAG_SECURE = 0x00002000, + FLAG_SCALED = 0x00004000, + FLAG_IGNORE_CHEEK_PRESSES = 0x00008000, + FLAG_LAYOUT_INSET_DECOR = 0x00010000, + FLAG_ALT_FOCUSABLE_IM = 0x00020000, + FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000, + FLAG_SHOW_WHEN_LOCKED = 0x00080000, + FLAG_SHOW_WALLPAPER = 0x00100000, + FLAG_TURN_SCREEN_ON = 0x00200000, + FLAG_DISMISS_KEYGUARD = 0x00400000, + FLAG_IMMERSIVE = 0x00800000, + FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000, + FLAG_COMPATIBLE_WINDOW = 0x20000000, + FLAG_SYSTEM_ERROR = 0x40000000, + }; + + // Window types from WindowManager.LayoutParams + enum { + FIRST_APPLICATION_WINDOW = 1, + TYPE_BASE_APPLICATION = 1, + TYPE_APPLICATION = 2, + TYPE_APPLICATION_STARTING = 3, + LAST_APPLICATION_WINDOW = 99, + FIRST_SUB_WINDOW = 1000, + TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW, + TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1, + TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2, + TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3, + TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4, + LAST_SUB_WINDOW = 1999, + FIRST_SYSTEM_WINDOW = 2000, + TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW, + TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1, + TYPE_PHONE = FIRST_SYSTEM_WINDOW+2, + TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3, + TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4, + TYPE_TOAST = FIRST_SYSTEM_WINDOW+5, + TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6, + TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7, + TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8, + TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9, + TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10, + TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11, + TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12, + TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13, + TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14, + LAST_SYSTEM_WINDOW = 2999, + }; + + sp<InputChannel> inputChannel; + int32_t layoutParamsFlags; + int32_t layoutParamsType; + nsecs_t dispatchingTimeout; + int32_t frameLeft; + int32_t frameTop; + int32_t frameRight; + int32_t frameBottom; + int32_t visibleFrameLeft; + int32_t visibleFrameTop; + int32_t visibleFrameRight; + int32_t visibleFrameBottom; + int32_t touchableAreaLeft; + int32_t touchableAreaTop; + int32_t touchableAreaRight; + int32_t touchableAreaBottom; + bool visible; + bool hasFocus; + bool hasWallpaper; + bool paused; + int32_t ownerPid; + int32_t ownerUid; + + bool visibleFrameIntersects(const InputWindow* other) const; + bool touchableAreaContainsPoint(int32_t x, int32_t y) const; +}; + + +/* + * A private handle type used by the input manager to track the window. + */ +class InputApplicationHandle : public RefBase { +protected: + InputApplicationHandle() { } + virtual ~InputApplicationHandle() { } +}; + + +/* + * An input application describes properties of an application that can receive input. + */ +struct InputApplication { + String8 name; + nsecs_t dispatchingTimeout; + sp<InputApplicationHandle> handle; +}; + + +/* * Input dispatcher policy interface. * * The input reader policy is used by the input reader to interact with the Window Manager @@ -135,14 +255,16 @@ public: /* Notifies the system that a configuration change has occurred. */ virtual void notifyConfigurationChanged(nsecs_t when) = 0; + /* Notifies the system that an application is not responding. + * Returns a new timeout to continue waiting, or 0 to abort dispatch. */ + virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle) = 0; + /* Notifies the system that an input channel is unrecoverably broken. */ virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) = 0; /* Notifies the system that an input channel is not responding. - * Returns true and a new timeout value if the dispatcher should keep waiting. - * Otherwise returns false. */ - virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel, - nsecs_t& outNewTimeout) = 0; + * Returns a new timeout to continue waiting, or 0 to abort dispatch. */ + virtual nsecs_t notifyInputChannelANR(const sp<InputChannel>& inputChannel) = 0; /* Notifies the system that an input channel recovered from ANR. */ virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) = 0; @@ -153,29 +275,27 @@ public: /* Gets the key repeat inter-key delay. */ virtual nsecs_t getKeyRepeatDelay() = 0; - /* Waits for key event input targets to become available. - * If the event is being injected, injectorPid and injectorUid should specify the - * process id and used id of the injecting application, otherwise they should both - * be -1. - * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */ - virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, - Vector<InputTarget>& outTargets) = 0; - - /* Waits for motion event targets to become available. - * If the event is being injected, injectorPid and injectorUid should specify the - * process id and used id of the injecting application, otherwise they should both - * be -1. - * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */ - virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, - Vector<InputTarget>& outTargets) = 0; - /* Gets the maximum suggested event delivery rate per second. * This value is used to throttle motion event movement actions on a per-device * basis. It is not intended to be a hard limit. */ virtual int32_t getMaxEventsPerSecond() = 0; + + /* Allows the policy a chance to intercept a key before dispatching. */ + virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, + const KeyEvent* keyEvent, uint32_t policyFlags) = 0; + + /* Poke user activity for an event dispatched to a window. */ + virtual void pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType) = 0; + + /* Checks whether a given application pid/uid has permission to inject input events + * into other applications. + * + * This method is special in that its implementation promises to be non-reentrant and + * is safe to call while holding other locks. (Most other methods make no such guarantees!) + */ + virtual bool checkInjectEventsPermissionNonReentrant( + int32_t injectorPid, int32_t injectorUid) = 0; }; @@ -187,6 +307,11 @@ protected: virtual ~InputDispatcherInterface() { } public: + /* Dumps the state of the input dispatcher. + * + * This method may be called on any thread (usually by the input manager). */ + virtual void dump(String8& dump) = 0; + /* Runs a single iteration of the dispatch loop. * Nominally processes one queued event, a timeout, or a response from an input consumer. * @@ -199,7 +324,6 @@ public: * These methods should only be called on the input reader thread. */ virtual void notifyConfigurationChanged(nsecs_t eventTime) = 0; - virtual void notifyAppSwitchComing(nsecs_t eventTime) = 0; virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0; @@ -219,6 +343,24 @@ public: virtual int32_t injectInputEvent(const InputEvent* event, int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0; + /* Sets the list of input windows. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void setInputWindows(const Vector<InputWindow>& inputWindows) = 0; + + /* Sets the focused application. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void setFocusedApplication(const InputApplication* inputApplication) = 0; + + /* Sets the input dispatching mode. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void setInputDispatchMode(bool enabled, bool frozen) = 0; + /* Preempts input dispatch in progress by making pending synchronous * dispatches asynchronous instead. This method is generally called during a focus * transition from one application to the next so as to enable the new application @@ -230,10 +372,11 @@ public: virtual void preemptInputDispatch() = 0; /* Registers or unregister input channels that may be used as targets for input events. + * If monitor is true, the channel will receive a copy of all input events. * * These methods may be called on any thread (usually by the input manager). */ - virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0; + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) = 0; virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0; }; @@ -261,10 +404,11 @@ protected: public: explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy); + virtual void dump(String8& dump); + virtual void dispatchOnce(); virtual void notifyConfigurationChanged(nsecs_t eventTime); - virtual void notifyAppSwitchComing(nsecs_t eventTime); virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime); @@ -277,9 +421,12 @@ public: virtual int32_t injectInputEvent(const InputEvent* event, int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); + virtual void setInputWindows(const Vector<InputWindow>& inputWindows); + virtual void setFocusedApplication(const InputApplication* inputApplication); + virtual void setInputDispatchMode(bool enabled, bool frozen); virtual void preemptInputDispatch(); - virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel); + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor); virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); private: @@ -310,6 +457,8 @@ private: int32_t pendingSyncDispatches; // the number of synchronous dispatches in progress inline bool isInjected() { return injectorPid >= 0; } + + void recycle(); }; struct ConfigurationChangedEntry : EventEntry { @@ -326,6 +475,17 @@ private: int32_t metaState; int32_t repeatCount; nsecs_t downTime; + + bool syntheticRepeat; // set to true for synthetic key repeats + + enum InterceptKeyResult { + INTERCEPT_KEY_RESULT_UNKNOWN, + INTERCEPT_KEY_RESULT_SKIP, + INTERCEPT_KEY_RESULT_CONTINUE, + }; + InterceptKeyResult interceptKeyResult; // set based on the interception result + + void recycle(); }; struct MotionSample { @@ -380,9 +540,13 @@ private: // will be set to NULL. MotionSample* tailMotionSample; - inline bool isSyncTarget() { + inline bool isSyncTarget() const { return targetFlags & InputTarget::FLAG_SYNC; } + + inline void preemptSyncTarget() { + targetFlags &= ~ InputTarget::FLAG_SYNC; + } }; // A command entry captures state and behavior for an action to be performed in the @@ -413,37 +577,43 @@ private: // parameters for the command (usage varies by command) sp<Connection> connection; + nsecs_t eventTime; + KeyEntry* keyEntry; + sp<InputChannel> inputChannel; + sp<InputApplicationHandle> inputApplicationHandle; + int32_t windowType; + int32_t userActivityEventType; }; // Generic queue implementation. template <typename T> struct Queue { - T head; - T tail; + T headSentinel; + T tailSentinel; inline Queue() { - head.prev = NULL; - head.next = & tail; - tail.prev = & head; - tail.next = NULL; + headSentinel.prev = NULL; + headSentinel.next = & tailSentinel; + tailSentinel.prev = & headSentinel; + tailSentinel.next = NULL; } - inline bool isEmpty() { - return head.next == & tail; + inline bool isEmpty() const { + return headSentinel.next == & tailSentinel; } inline void enqueueAtTail(T* entry) { - T* last = tail.prev; + T* last = tailSentinel.prev; last->next = entry; entry->prev = last; - entry->next = & tail; - tail.prev = entry; + entry->next = & tailSentinel; + tailSentinel.prev = entry; } inline void enqueueAtHead(T* entry) { - T* first = head.next; - head.next = entry; - entry->prev = & head; + T* first = headSentinel.next; + headSentinel.next = entry; + entry->prev = & headSentinel; entry->next = first; first->prev = entry; } @@ -454,7 +624,7 @@ private: } inline T* dequeueAtHead() { - T* first = head.next; + T* first = headSentinel.next; dequeue(first); return first; } @@ -476,7 +646,8 @@ private: float xPrecision, float yPrecision, nsecs_t downTime, uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords); - DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry); + DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry, + int32_t targetFlags, float xOffset, float yOffset, nsecs_t timeout); CommandEntry* obtainCommandEntry(Command command); void releaseEventEntry(EventEntry* entry); @@ -500,6 +671,85 @@ private: void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime); }; + /* Tracks dispatched key and motion event state so that cancelation events can be + * synthesized when events are dropped. */ + class InputState { + public: + // Specifies whether a given event will violate input state consistency. + enum Consistency { + // The event is consistent with the current input state. + CONSISTENT, + // The event is inconsistent with the current input state but applications + // will tolerate it. eg. Down followed by another down. + TOLERABLE, + // The event is inconsistent with the current input state and will probably + // cause applications to crash. eg. Up without prior down, move with + // unexpected number of pointers. + BROKEN + }; + + InputState(); + ~InputState(); + + // Returns true if there is no state to be canceled. + bool isNeutral() const; + + // Returns true if the input state believes it is out of sync. + bool isOutOfSync() const; + + // Sets the input state to be out of sync if it is not neutral. + void setOutOfSync(); + + // Resets the input state out of sync flag. + void resetOutOfSync(); + + // Records tracking information for an event that has just been published. + // Returns whether the event is consistent with the current input state. + Consistency trackEvent(const EventEntry* entry); + + // Records tracking information for a key event that has just been published. + // Returns whether the event is consistent with the current input state. + Consistency trackKey(const KeyEntry* entry); + + // Records tracking information for a motion event that has just been published. + // Returns whether the event is consistent with the current input state. + Consistency trackMotion(const MotionEntry* entry); + + // Synthesizes cancelation events for the current state. + void synthesizeCancelationEvents(Allocator* allocator, + Vector<EventEntry*>& outEvents) const; + + // Clears the current state. + void clear(); + + private: + bool mIsOutOfSync; + + struct KeyMemento { + int32_t deviceId; + int32_t source; + int32_t keyCode; + int32_t scanCode; + nsecs_t downTime; + }; + + struct MotionMemento { + int32_t deviceId; + int32_t source; + float xPrecision; + float yPrecision; + nsecs_t downTime; + uint32_t pointerCount; + int32_t pointerIds[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + + void setPointers(const MotionEntry* entry); + }; + + Vector<KeyMemento> mKeyMementos; + Vector<MotionMemento> mMotionMementos; + }; + /* Manages the dispatch state associated with a single input channel. */ class Connection : public RefBase { protected: @@ -520,6 +770,7 @@ private: Status status; sp<InputChannel> inputChannel; InputPublisher inputPublisher; + InputState inputState; Queue<DispatchEntry> outboundQueue; nsecs_t nextTimeoutTime; // next timeout time (LONG_LONG_MAX if none) @@ -540,28 +791,34 @@ private: // Determine whether this connection has a pending synchronous dispatch target. // Since there can only ever be at most one such target at a time, if there is one, // it must be at the tail because nothing else can be enqueued after it. - inline bool hasPendingSyncTarget() { - return ! outboundQueue.isEmpty() && outboundQueue.tail.prev->isSyncTarget(); + inline bool hasPendingSyncTarget() const { + return ! outboundQueue.isEmpty() && outboundQueue.tailSentinel.prev->isSyncTarget(); + } + + // Assuming there is a pending sync target, make it async. + inline void preemptSyncTarget() { + outboundQueue.tailSentinel.prev->preemptSyncTarget(); } // Gets the time since the current event was originally obtained from the input driver. - inline double getEventLatencyMillis(nsecs_t currentTime) { + inline double getEventLatencyMillis(nsecs_t currentTime) const { return (currentTime - lastEventTime) / 1000000.0; } // Gets the time since the current event entered the outbound dispatch queue. - inline double getDispatchLatencyMillis(nsecs_t currentTime) { + inline double getDispatchLatencyMillis(nsecs_t currentTime) const { return (currentTime - lastDispatchTime) / 1000000.0; } // Gets the time since the current event ANR was declared, if applicable. - inline double getANRLatencyMillis(nsecs_t currentTime) { + inline double getANRLatencyMillis(nsecs_t currentTime) const { return (currentTime - lastANRTime) / 1000000.0; } status_t initialize(); void setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout); + void resetTimeout(nsecs_t currentTime); }; sp<InputDispatcherPolicyInterface> mPolicy; @@ -571,9 +828,26 @@ private: Allocator mAllocator; sp<PollLoop> mPollLoop; + EventEntry* mPendingEvent; Queue<EventEntry> mInboundQueue; Queue<CommandEntry> mCommandQueue; + Vector<EventEntry*> mTempCancelationEvents; + + void dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay, + nsecs_t* nextWakeupTime); + + // Enqueues an inbound event. Returns true if mPollLoop->wake() should be called. + bool enqueueInboundEventLocked(EventEntry* entry); + + // App switch latency optimization. + nsecs_t mAppSwitchDueTime; + + static bool isAppSwitchKey(int32_t keyCode); + bool isAppSwitchPendingLocked(); + bool detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry); + void resetPendingAppSwitchLocked(bool handled); + // All registered connections mapped by receive pipe file descriptor. KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd; @@ -591,20 +865,15 @@ private: // the duration. Vector<Connection*> mTimedOutConnections; - // Preallocated key and motion event objects used only to ask the input dispatcher policy - // for the targets of an event that is to be dispatched. - KeyEvent mReusableKeyEvent; - MotionEvent mReusableMotionEvent; + // Input channels that will receive a copy of all input events. + Vector<sp<InputChannel> > mMonitoringChannels; - // The input targets that were most recently identified for dispatch. - // If there is a synchronous event dispatch in progress, the current input targets will - // remain unchanged until the dispatch has completed or been aborted. - Vector<InputTarget> mCurrentInputTargets; - bool mCurrentInputTargetsValid; // false while targets are being recomputed + // Preallocated key event object used for policy inquiries. + KeyEvent mReusableKeyEvent; // Event injection and synchronization. Condition mInjectionResultAvailableCondition; - EventEntry* createEntryFromInputEventLocked(const InputEvent* event); + EventEntry* createEntryFromInjectedInputEventLocked(const InputEvent* event); void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult); Condition mInjectionSyncFinishedCondition; @@ -622,36 +891,108 @@ private: } mThrottleState; // Key repeat tracking. - // XXX Move this up to the input reader instead. struct KeyRepeatState { KeyEntry* lastKeyEntry; // or null if no repeat nsecs_t nextRepeatTime; } mKeyRepeatState; void resetKeyRepeatLocked(); + KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime, nsecs_t keyRepeatTimeout); // Deferred command processing. bool runCommandsLockedInterruptible(); CommandEntry* postCommandLocked(Command command); - // Process events that have just been dequeued from the head of the input queue. - void processConfigurationChangedLockedInterruptible( + // Inbound event processing. + void drainInboundQueueLocked(); + void releasePendingEventLocked(bool wasDropped); + void releaseInboundEventLocked(EventEntry* entry, bool wasDropped); + bool isEventFromReliableSourceLocked(EventEntry* entry); + + // Dispatch state. + bool mDispatchEnabled; + bool mDispatchFrozen; + Vector<InputWindow> mWindows; + Vector<InputWindow*> mWallpaperWindows; + + // Focus tracking for keys, trackball, etc. + InputWindow* mFocusedWindow; + + // Focus tracking for touch. + bool mTouchDown; + InputWindow* mTouchedWindow; // primary target for current down + bool mTouchedWindowIsObscured; // true if other windows may obscure the target + Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets + struct OutsideTarget { + InputWindow* window; + bool obscured; + }; + Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets + Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets + + // Focused application. + InputApplication* mFocusedApplication; + InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication + void releaseFocusedApplicationLocked(); + + // Dispatch inbound events. + bool dispatchConfigurationChangedLocked( nsecs_t currentTime, ConfigurationChangedEntry* entry); - void processKeyLockedInterruptible( - nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout); - void processKeyRepeatLockedInterruptible( - nsecs_t currentTime, nsecs_t keyRepeatTimeout); - void processMotionLockedInterruptible( - nsecs_t currentTime, MotionEntry* entry); - - // Identify input targets for an event and dispatch to them. - void identifyInputTargetsAndDispatchKeyLockedInterruptible( - nsecs_t currentTime, KeyEntry* entry); - void identifyInputTargetsAndDispatchMotionLockedInterruptible( - nsecs_t currentTime, MotionEntry* entry); + bool dispatchKeyLocked( + nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout, + nsecs_t* nextWakeupTime); + bool dispatchMotionLocked( + nsecs_t currentTime, MotionEntry* entry, + nsecs_t* nextWakeupTime); void dispatchEventToCurrentInputTargetsLocked( nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample); + void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry); + void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry); + + // The input targets that were most recently identified for dispatch. + // If there is a synchronous event dispatch in progress, the current input targets will + // remain unchanged until the dispatch has completed or been aborted. + bool mCurrentInputTargetsValid; // false while targets are being recomputed + Vector<InputTarget> mCurrentInputTargets; + int32_t mCurrentInputWindowType; + sp<InputChannel> mCurrentInputChannel; + + enum InputTargetWaitCause { + INPUT_TARGET_WAIT_CAUSE_NONE, + INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY, + INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY, + }; + + InputTargetWaitCause mInputTargetWaitCause; + nsecs_t mInputTargetWaitStartTime; + nsecs_t mInputTargetWaitTimeoutTime; + bool mInputTargetWaitTimeoutExpired; + + // Finding targets for input events. + void startFindingTargetsLocked(); + void finishFindingTargetsLocked(const InputWindow* window); + int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, + const InputApplication* application, const InputWindow* window, + nsecs_t* nextWakeupTime); + void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout); + nsecs_t getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(nsecs_t currentTime); + void resetANRTimeoutsLocked(); + + int32_t findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry, + nsecs_t* nextWakeupTime, InputWindow** outWindow); + int32_t findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry, + nsecs_t* nextWakeupTime, InputWindow** outWindow); + + void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, + nsecs_t timeSpentWaitingForApplication); + void addMonitoringTargetsLocked(); + void pokeUserActivityLocked(nsecs_t eventTime, int32_t windowType, int32_t eventType); + bool checkInjectionPermission(const InputWindow* window, + int32_t injectorPid, int32_t injectorUid); + bool isWindowObscuredLocked(const InputWindow* window); + void releaseTouchedWindowLocked(); + // Manage the dispatch cycle for a single connection. // These methods are deliberately not Interruptible because doing all of the work // with the mutex held makes it easier to ensure that connection invariants are maintained. @@ -659,15 +1000,25 @@ private: void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, bool resumeWithAppendedMotionSample); - void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); + void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, + nsecs_t timeSpentWaitingForApplication); void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); + void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); void timeoutDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); void resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, nsecs_t newTimeout); void abortDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, bool broken); + void drainOutboundQueueLocked(Connection* connection, DispatchEntry* firstDispatchEntryToDrain); static bool handleReceiveCallback(int receiveFd, int events, void* data); + // Preempting input dispatch. + bool preemptInputDispatchInnerLocked(); + + // Dump state. + void dumpDispatchStateLocked(String8& dump); + void logDispatchStateLocked(); + // Add or remove a connection to the mActiveConnections vector. void activateConnectionLocked(Connection* connection); void deactivateConnectionLocked(Connection* connection); @@ -683,9 +1034,13 @@ private: nsecs_t currentTime, const sp<Connection>& connection); // Outbound policy interactions. + void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry); void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry); void doNotifyInputChannelANRLockedInterruptible(CommandEntry* commandEntry); void doNotifyInputChannelRecoveredFromANRLockedInterruptible(CommandEntry* commandEntry); + void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry); + void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry); + void doTargetsNotReadyTimeoutLockedInterruptible(CommandEntry* commandEntry); }; /* Enqueues and dispatches input events, endlessly. */ @@ -702,4 +1057,4 @@ private: } // namespace android -#endif // _UI_INPUT_DISPATCHER_PRIV_H +#endif // _UI_INPUT_DISPATCHER_H diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h index 4012c69..568568b 100644 --- a/include/ui/InputManager.h +++ b/include/ui/InputManager.h @@ -72,51 +72,11 @@ public: /* Stops the input manager threads and waits for them to exit. */ virtual status_t stop() = 0; - /* Registers an input channel prior to using it as the target of an event. */ - virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0; - - /* Unregisters an input channel. */ - virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0; - - /* Injects an input event and optionally waits for sync. - * The synchronization mode determines whether the method blocks while waiting for - * input injection to proceed. - * Returns one of the INPUT_EVENT_INJECTION_XXX constants. - */ - virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0; - - /* Preempts input dispatch in progress by making pending synchronous - * dispatches asynchronous instead. This method is generally called during a focus - * transition from one application to the next so as to enable the new application - * to start receiving input as soon as possible without having to wait for the - * old application to finish up. - */ - virtual void preemptInputDispatch() = 0; - - /* Gets input device configuration. */ - virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0; - - /* Gets information about the specified input device. - * Returns OK if the device information was obtained or NAME_NOT_FOUND if there - * was no such device. - */ - virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0; - - /* Gets the list of all registered device ids. */ - virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0; - - /* Queries current input state. */ - virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t scanCode) = 0; - virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t keyCode) = 0; - virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, - int32_t sw) = 0; - - /* Determines whether physical keys exist for the given framework-domain key codes. */ - virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, - size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0; + /* Gets the input reader. */ + virtual sp<InputReaderInterface> getReader() = 0; + + /* Gets the input dispatcher. */ + virtual sp<InputDispatcherInterface> getDispatcher() = 0; }; class InputManager : public InputManagerInterface { @@ -137,25 +97,8 @@ public: virtual status_t start(); virtual status_t stop(); - virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel); - virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); - - virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); - - virtual void preemptInputDispatch(); - - virtual void getInputConfiguration(InputConfiguration* outConfiguration); - virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo); - virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds); - virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t scanCode); - virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t keyCode); - virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, - int32_t sw); - virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, - size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags); + virtual sp<InputReaderInterface> getReader(); + virtual sp<InputDispatcherInterface> getDispatcher(); private: sp<InputReaderInterface> mReader; diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h index 7a089a4..903c3c4 100644 --- a/include/ui/InputReader.h +++ b/include/ui/InputReader.h @@ -95,10 +95,6 @@ public: // The input dispatcher should dispatch the input to the application. ACTION_DISPATCH = 0x00000001, - - // The input dispatcher should perform special filtering in preparation for - // a pending app switch. - ACTION_APP_SWITCH_COMING = 0x00000002, }; /* Gets information about the display with the specified id. @@ -168,6 +164,11 @@ protected: virtual ~InputReaderInterface() { } public: + /* Dumps the state of the input reader. + * + * This method may be called on any thread (usually by the input manager). */ + virtual void dump(String8& dump) = 0; + /* Runs a single iteration of the processing loop. * Nominally reads and processes one incoming message from the EventHub. * @@ -240,6 +241,8 @@ public: const sp<InputDispatcherInterface>& dispatcher); virtual ~InputReader(); + virtual void dump(String8& dump); + virtual void loopOnce(); virtual void getInputConfiguration(InputConfiguration* outConfiguration); @@ -305,6 +308,9 @@ private: GetStateFunc getStateFunc); bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags); + + // dump state + void dumpDeviceInfo(String8& dump); }; @@ -759,9 +765,11 @@ protected: } mLocked; virtual void configureParameters(); + virtual void logParameters(); virtual void configureRawAxes(); virtual void logRawAxes(); virtual bool configureSurfaceLocked(); + virtual void logMotionRangesLocked(); virtual void configureVirtualKeysLocked(); virtual void parseCalibration(); virtual void resolveCalibration(); diff --git a/include/ui/PowerManager.h b/include/ui/PowerManager.h new file mode 100644 index 0000000..5434b4f --- /dev/null +++ b/include/ui/PowerManager.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#ifndef _UI_POWER_MANAGER_H +#define _UI_POWER_MANAGER_H + + +namespace android { + +enum { + POWER_MANAGER_OTHER_EVENT = 0, + POWER_MANAGER_CHEEK_EVENT = 1, + POWER_MANAGER_TOUCH_EVENT = 2, // touch events are TOUCH for 300ms, and then either + // up events or LONG_TOUCH events. + POWER_MANAGER_LONG_TOUCH_EVENT = 3, + POWER_MANAGER_TOUCH_UP_EVENT = 4, + POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events. + + POWER_MANAGER_LAST_EVENT = POWER_MANAGER_BUTTON_EVENT, // Last valid event code. +}; + +} // namespace android + +#endif // _UI_POWER_MANAGER_H diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h index bc616eb..c2dfe5d 100644 --- a/include/utils/PollLoop.h +++ b/include/utils/PollLoop.h @@ -172,22 +172,36 @@ private: void* data; }; - const bool mAllowNonCallbacks; - + const bool mAllowNonCallbacks; // immutable + + int mWakeReadPipeFd; // immutable + int mWakeWritePipeFd; // immutable + + // The lock guards state used to track whether there is a poll() in progress and whether + // there are any other threads waiting in wakeAndLock(). The condition variables + // are used to transfer control among these threads such that all waiters are + // serviced before a new poll can begin. + // The wakeAndLock() method increments mWaiters, wakes the poll, blocks on mAwake + // until mPolling becomes false, then decrements mWaiters again. + // The poll() method blocks on mResume until mWaiters becomes 0, then sets + // mPolling to true, blocks until the poll completes, then resets mPolling to false + // and signals mResume if there are waiters. Mutex mLock; - bool mPolling; - uint32_t mWaiters; - Condition mAwake; - Condition mResume; - - int mWakeReadPipeFd; - int mWakeWritePipeFd; - + bool mPolling; // guarded by mLock + uint32_t mWaiters; // guarded by mLock + Condition mAwake; // guarded by mLock + Condition mResume; // guarded by mLock + + // The next two vectors are only mutated when mPolling is false since they must + // not be changed while the poll() system call is in progress. To mutate these + // vectors, the poll() must first be awoken then the lock acquired. Vector<struct pollfd> mRequestedFds; Vector<RequestedCallback> mRequestedCallbacks; - Vector<PendingCallback> mPendingCallbacks; // used privately by pollOnce - Vector<PendingCallback> mPendingFds; // used privately by pollOnce + // This state is only used privately by pollOnce and does not require a lock since + // it runs on a single thread. + Vector<PendingCallback> mPendingCallbacks; + Vector<PendingCallback> mPendingFds; size_t mPendingFdsPos; void openWakePipe(); diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 5d7f8bf..2959814 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -166,6 +166,8 @@ void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds); } +#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16) + void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,Rect *bounds) { @@ -173,12 +175,16 @@ void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t return; } - int penX = x, penY = y; + SkFixed penX = SkIntToFixed(x); + int penY = y; int glyphsLeft = 1; if (numGlyphs > 0) { glyphsLeft = numGlyphs; } + SkFixed prevRsbDelta = 0; + penX += SK_Fixed1 / 2; + text += start; while (glyphsLeft > 0) { @@ -190,23 +196,25 @@ void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t } CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar); + penX += SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta); + prevRsbDelta = cachedGlyph->mRsbDelta; // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage if (cachedGlyph->mIsValid) { switch(mode) { case FRAMEBUFFER: - drawCachedGlyph(cachedGlyph, penX, penY); + drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY); break; case BITMAP: - drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH); + drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bitmap, bitmapW, bitmapH); break; case MEASURE: - measureCachedGlyph(cachedGlyph, penX, penY, bounds); + measureCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bounds); break; } } - penX += SkFixedFloor(cachedGlyph->mAdvanceX); + penX += cachedGlyph->mAdvanceX; // If we were given a specific number of glyphs, decrement if (numGlyphs > 0) { @@ -220,6 +228,8 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp glyph->mAdvanceY = skiaGlyph.fAdvanceY; glyph->mBitmapLeft = skiaGlyph.fLeft; glyph->mBitmapTop = skiaGlyph.fTop; + glyph->mLsbDelta = skiaGlyph.fLsbDelta; + glyph->mRsbDelta = skiaGlyph.fRsbDelta; uint32_t startX = 0; uint32_t startY = 0; @@ -352,7 +362,7 @@ void FontRenderer::flushAllAndInvalidate() { bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { // If the glyph is too tall, don't cache it - if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { + if (glyph.fHeight > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { LOGE("Font size to large to fit in cache. width, height = %i, %i", (int) glyph.fWidth, (int) glyph.fHeight); return false; @@ -616,7 +626,7 @@ void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { const float maxPrecacheFontSize = 40.0f; bool isNewFont = currentNumFonts != mActiveFonts.size(); - if(isNewFont && fontSize <= maxPrecacheFontSize ){ + if (isNewFont && fontSize <= maxPrecacheFontSize) { precacheLatin(paint); } } diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index a03ea92..de5c019 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -94,6 +94,9 @@ protected: // Values below contain a glyph's origin in the bitmap int32_t mBitmapLeft; int32_t mBitmapTop; + // Auto-kerning + SkFixed mLsbDelta; + SkFixed mRsbDelta; }; Font(FontRenderer* state, uint32_t fontId, float fontSize); diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index d4db782..c527038 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -63,28 +63,19 @@ struct LayerSize { */ struct Layer { /** - * Coordinates of the layer corresponding to this snapshot. - * Only set when the flag kFlagIsLayer is set. + * Coordinates of the layer. */ Rect layer; /** * Name of the texture used to render the layer. - * Only set when the flag kFlagIsLayer is set. */ GLuint texture; /** - * Name of the FBO used to render the layer. - * Only set when the flag kFlagIsLayer is set. - */ - GLuint fbo; - /** * Opacity of the layer. - * Only set when the flag kFlagIsLayer is set. */ - float alpha; + int alpha; /** * Blending mode of the layer. - * Only set when the flag kFlagIsLayer is set. */ SkXfermode::Mode mode; /** diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp index a204778..1a18766 100644 --- a/libs/hwui/LayerCache.cpp +++ b/libs/hwui/LayerCache.cpp @@ -87,7 +87,6 @@ void LayerCache::deleteLayer(Layer* layer) { if (layer) { mSize -= layer->layer.getWidth() * layer->layer.getHeight() * 4; - glDeleteFramebuffers(1, &layer->fbo); glDeleteTextures(1, &layer->texture); delete layer; } @@ -99,7 +98,7 @@ void LayerCache::clear() { mCache.setOnEntryRemovedListener(NULL); } -Layer* LayerCache::get(LayerSize& size, GLuint previousFbo) { +Layer* LayerCache::get(LayerSize& size) { Layer* layer = mCache.remove(size); if (layer) { LAYER_LOGD("Reusing layer"); @@ -111,10 +110,6 @@ Layer* LayerCache::get(LayerSize& size, GLuint previousFbo) { layer = new Layer; layer->blend = true; - // Generate the FBO and attach the texture - glGenFramebuffers(1, &layer->fbo); - glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo); - // Generate the texture in which the FBO will draw glGenTextures(1, &layer->texture); glBindTexture(GL_TEXTURE_2D, layer->texture); @@ -128,23 +123,6 @@ Layer* LayerCache::get(LayerSize& size, GLuint previousFbo) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - - // Bind texture to FBO - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - layer->texture, 0); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - LOGE("Framebuffer incomplete (GL error code 0x%x)", status); - - glBindFramebuffer(GL_FRAMEBUFFER, previousFbo); - - glDeleteFramebuffers(1, &layer->fbo); - glDeleteTextures(1, &layer->texture); - delete layer; - - return NULL; - } } return layer; diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h index 9860994..c0c7542 100644 --- a/libs/hwui/LayerCache.h +++ b/libs/hwui/LayerCache.h @@ -62,10 +62,8 @@ public: * size of the cache goes down. * * @param size The dimensions of the desired layer - * @param previousFbo The name of the FBO to bind to if creating a new - * layer fails */ - Layer* get(LayerSize& size, GLuint previousFbo); + Layer* get(LayerSize& size); /** * Adds the layer to the cache. The layer will not be added if there is * not enough space available. diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 6c90704..b1f5f6b 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -80,6 +80,24 @@ static const Blender gBlends[] = { { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA } }; +// This array contains the swapped version of each SkXfermode. For instance +// this array's SrcOver blending mode is actually DstOver. You can refer to +// createLayer() for more information on the purpose of this array. +static const Blender gBlendsSwap[] = { + { SkXfermode::kClear_Mode, GL_ZERO, GL_ZERO }, + { SkXfermode::kSrc_Mode, GL_ZERO, GL_ONE }, + { SkXfermode::kDst_Mode, GL_ONE, GL_ZERO }, + { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE }, + { SkXfermode::kDstOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kSrcIn_Mode, GL_ZERO, GL_SRC_ALPHA }, + { SkXfermode::kDstIn_Mode, GL_DST_ALPHA, GL_ZERO }, + { SkXfermode::kSrcOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kDstOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, + { SkXfermode::kSrcATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA }, + { SkXfermode::kDstATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA } +}; + static const GLenum gTextureUnits[] = { GL_TEXTURE0, GL_TEXTURE1, @@ -122,8 +140,6 @@ void OpenGLRenderer::setViewport(int width, int height) { mWidth = width; mHeight = height; - mFirstSnapshot->height = height; - mFirstSnapshot->viewport.set(0, 0, width, height); } void OpenGLRenderer::prepare() { @@ -155,14 +171,19 @@ void OpenGLRenderer::acquireContext() { } void OpenGLRenderer::releaseContext() { - glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight()); + glViewport(0, 0, mWidth, mHeight); glEnable(GL_SCISSOR_TEST); setScissorFromClip(); + glDisable(GL_DITHER); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (mCaches.blend) { glEnable(GL_BLEND); glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode); + glBlendEquation(GL_FUNC_ADD); } else { glDisable(GL_BLEND); } @@ -202,17 +223,10 @@ int OpenGLRenderer::saveSnapshot(int flags) { bool OpenGLRenderer::restoreSnapshot() { bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet; bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer; - bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho; sp<Snapshot> current = mSnapshot; sp<Snapshot> previous = mSnapshot->previous; - if (restoreOrtho) { - Rect& r = previous->viewport; - glViewport(r.left, r.top, r.right, r.bottom); - mOrthoMatrix.load(current->orthoMatrix); - } - mSaveCount--; mSnapshot = previous; @@ -253,11 +267,7 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom, mode = SkXfermode::kSrcOver_Mode; } - if (alpha > 0 && !mSnapshot->invisible) { - createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags); - } else { - mSnapshot->invisible = true; - } + createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags); return count; } @@ -273,79 +283,123 @@ int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bot } } +/** + * Layers are viewed by Skia are slightly different than layers in image editing + * programs (for instance.) When a layer is created, previously created layers + * and the frame buffer still receive every drawing command. For instance, if a + * layer is created and a shape intersecting the bounds of the layers and the + * framebuffer is draw, the shape will be drawn on both (unless the layer was + * created with the SkCanvas::kClipToLayer_SaveFlag flag.) + * + * A way to implement layers is to create an FBO for each layer, backed by an RGBA + * texture. Unfortunately, this is inefficient as it requires every primitive to + * be drawn n + 1 times, where n is the number of active layers. In practice this + * means, for every primitive: + * - Switch active frame buffer + * - Change viewport, clip and projection matrix + * - Issue the drawing + * + * Switching rendering target n + 1 times per drawn primitive is extremely costly. + * To avoid this, layers are implemented in a different way here. + * + * This implementation relies on the frame buffer being at least RGBA 8888. When + * a layer is created, only a texture is created, not an FBO. The content of the + * frame buffer contained within the layer's bounds is copied into this texture + * using glCopyTexImage2D(). The layer's region is then cleared(1) in the frame + * buffer and drawing continues as normal. This technique therefore treats the + * frame buffer as a scratch buffer for the layers. + * + * To compose the layers back onto the frame buffer, each layer texture + * (containing the original frame buffer data) is drawn as a simple quad over + * the frame buffer. The trick is that the quad is set as the composition + * destination in the blending equation, and the frame buffer becomes the source + * of the composition. + * + * Drawing layers with an alpha value requires an extra step before composition. + * An empty quad is drawn over the layer's region in the frame buffer. This quad + * is drawn with the rgba color (0,0,0,alpha). The alpha value offered by the + * quad is used to multiply the colors in the frame buffer. This is achieved by + * changing the GL blend functions for the GL_FUNC_ADD blend equation to + * GL_ZERO, GL_SRC_ALPHA. + * + * Because glCopyTexImage2D() can be slow, an alternative implementation might + * be use to draw a single clipped layer. The implementation described above + * is correct in every case. + * + * (1) The frame buffer is actually not cleared right away. To allow the GPU + * to potentially optimize series of calls to glCopyTexImage2D, the frame + * buffer is left untouched until the first drawing operation. Only when + * something actually gets drawn are the layers regions cleared. + */ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) { LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top); LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize()); + // Window coordinates of the layer Rect bounds(left, top, right, bottom); - // TODO: Apply transformations and treat layers in screen coordinates - // mSnapshot->transform->mapRect(bounds); + mSnapshot->transform->mapRect(bounds); - GLuint previousFbo = snapshot->previous.get() ? snapshot->previous->fbo : 0; - LayerSize size(bounds.getWidth(), bounds.getHeight()); + // Layers only make sense if they are in the framebuffer's bounds + bounds.intersect(*mSnapshot->clipRect); + if (bounds.isEmpty()) return false; - Layer* layer = mCaches.layerCache.get(size, previousFbo); + LayerSize size(bounds.getWidth(), bounds.getHeight()); + Layer* layer = mCaches.layerCache.get(size); if (!layer) { return false; } - glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo); - - // Clear the FBO - glDisable(GL_SCISSOR_TEST); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - glEnable(GL_SCISSOR_TEST); - layer->mode = mode; - layer->alpha = alpha / 255.0f; + layer->alpha = alpha; layer->layer.set(bounds); // Save the layer in the snapshot snapshot->flags |= Snapshot::kFlagIsLayer; snapshot->layer = layer; - snapshot->fbo = layer->fbo; - // TODO: Temporary until real layer support is implemented - snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f); - // TODO: Temporary until real layer support is implemented - snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight()); - snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight()); - snapshot->height = bounds.getHeight(); - snapshot->flags |= Snapshot::kFlagDirtyOrtho; - snapshot->orthoMatrix.load(mOrthoMatrix); - setScissorFromClip(); + // Copy the framebuffer into the layer + glBindTexture(GL_TEXTURE_2D, layer->texture); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom, + bounds.getWidth(), bounds.getHeight(), 0); + + if (flags & SkCanvas::kClipToLayer_SaveFlag) { + if (mSnapshot->clipTransformed(bounds)) setScissorFromClip(); + } - // Change the ortho projection - glViewport(0, 0, bounds.getWidth(), bounds.getHeight()); - mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f); + // Enqueue the buffer coordinates to clear the corresponding region later + mLayers.push(new Rect(bounds)); return true; } +/** + * Read the documentation of createLayer() before doing anything in this method. + */ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { if (!current->layer) { LOGE("Attempting to compose a layer that does not exist"); return; } - // Unbind current FBO and restore previous one - // Most of the time, previous->fbo will be 0 to bind the default buffer - glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo); - // Restore the clip from the previous snapshot const Rect& clip = *previous->clipRect; - glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight()); + glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight()); Layer* layer = current->layer; const Rect& rect = layer->layer; - // FBOs are already drawn with a top-left origin, don't flip the texture + if (layer->alpha < 255) { + drawColorRect(rect.left, rect.top, rect.right, rect.bottom, + layer->alpha << 24, SkXfermode::kDstIn_Mode, true); + } + + // Layers are already drawn with a top-left origin, don't flip the texture resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f); - drawTextureRect(rect.left, rect.top, rect.right, rect.bottom, - layer->texture, layer->alpha, layer->mode, layer->blend); + drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture, + 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0], + &mMeshVertices[0].texture[0], NULL, 0, true, true); resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f); @@ -355,13 +409,32 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { if (!mCaches.layerCache.put(size, layer)) { LAYER_LOGD("Deleting layer"); - glDeleteFramebuffers(1, &layer->fbo); glDeleteTextures(1, &layer->texture); delete layer; } } +void OpenGLRenderer::clearLayerRegions() { + if (mLayers.size() == 0) return; + + for (uint32_t i = 0; i < mLayers.size(); i++) { + Rect* bounds = mLayers.itemAt(i); + + // Clear the framebuffer where the layer will draw + glScissor(bounds->left, mHeight - bounds->bottom, + bounds->getWidth(), bounds->getHeight()); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + delete bounds; + } + mLayers.clear(); + + // Restore the clip + setScissorFromClip(); +} + /////////////////////////////////////////////////////////////////////////////// // Transforms /////////////////////////////////////////////////////////////////////////////// @@ -397,7 +470,7 @@ void OpenGLRenderer::concatMatrix(SkMatrix* matrix) { void OpenGLRenderer::setScissorFromClip() { const Rect& clip = *mSnapshot->clipRect; - glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight()); + glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight()); } const Rect& OpenGLRenderer::getClipBounds() { @@ -405,8 +478,6 @@ const Rect& OpenGLRenderer::getClipBounds() { } bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) { - if (mSnapshot->invisible) return true; - Rect r(left, top, right, bottom); mSnapshot->transform->mapRect(r); return !mSnapshot->clipRect->intersects(r); @@ -503,6 +574,7 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, Patch* mesh = mCaches.patchCache.get(patch); mesh->updateVertices(bitmap, left, top, right, bottom, &patch->xDivs[0], &patch->yDivs[0], patch->numXDivs, patch->numYDivs); + mesh->dump(); // Specify right and bottom as +1.0f from left/top to prevent scaling since the // patch mesh already defines the final size @@ -512,7 +584,6 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, } void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) { - if (mSnapshot->invisible) return; const Rect& clip = *mSnapshot->clipRect; drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true); } @@ -545,18 +616,10 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint) { - if (mSnapshot->invisible || text == NULL || count == 0 || - (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) { + if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) { return; } - - float scaleX = paint->getTextScaleX(); - bool applyScaleX = scaleX < 0.9999f || scaleX > 1.0001f; - if (applyScaleX) { - save(SkCanvas::kMatrix_SaveFlag); - translate(x - (x * scaleX), 0.0f); - scale(scaleX, 1.0f); - } + paint->setAntiAlias(true); float length = -1.0f; switch (paint->getTextAlign()) { @@ -606,21 +669,16 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, mode, false, true); const Rect& clip = mSnapshot->getLocalClip(); + clearLayerRegions(); fontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords")); drawTextDecorations(text, bytesCount, length, x, y, paint); - - if (applyScaleX) { - restore(); - } } void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { - if (mSnapshot->invisible) return; - GLuint textureUnit = 0; glActiveTexture(gTextureUnits[textureUnit]); @@ -647,6 +705,8 @@ void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { setupTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true, true); + clearLayerRegions(); + // Draw the mesh glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords")); @@ -778,6 +838,7 @@ void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t } } +// Same values used by Skia #define kStdStrikeThru_Offset (-6.0f / 21.0f) #define kStdUnderline_Offset (1.0f / 9.0f) #define kStdUnderline_Thickness (1.0f / 18.0f) @@ -831,6 +892,8 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom, int color, SkXfermode::Mode mode, bool ignoreTransform) { + clearLayerRegions(); + // If a shader is set, preserve only the alpha if (mShader) { color |= 0x00ffffff; @@ -905,7 +968,10 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom, GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, - GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount) { + GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount, + bool swapSrcDst, bool ignoreTransform) { + clearLayerRegions(); + ProgramDescription description; description.hasTexture = true; if (mColorFilter) { @@ -915,10 +981,15 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b mModelView.loadTranslate(left, top, 0.0f); mModelView.scale(right - left, bottom - top, 1.0f); - chooseBlending(blend || alpha < 1.0f, mode, description); + chooseBlending(blend || alpha < 1.0f, mode, description, swapSrcDst); useProgram(mCaches.programCache.get(description)); - mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform); + if (!ignoreTransform) { + mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform); + } else { + mat4 m; + mCaches.currentProgram->set(mOrthoMatrix, mModelView, m); + } // Texture bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0); @@ -948,7 +1019,7 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b } void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, - ProgramDescription& description) { + ProgramDescription& description, bool swapSrcDst) { blend = blend || mode != SkXfermode::kSrcOver_Mode; if (blend) { if (mode < SkXfermode::kPlus_Mode) { @@ -956,8 +1027,8 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, glEnable(GL_BLEND); } - GLenum sourceMode = gBlends[mode].src; - GLenum destMode = gBlends[mode].dst; + GLenum sourceMode = swapSrcDst ? gBlendsSwap[mode].src : gBlends[mode].src; + GLenum destMode = swapSrcDst ? gBlendsSwap[mode].dst : gBlends[mode].dst; if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) { glBlendFunc(sourceMode, destMode); @@ -970,6 +1041,7 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, // the blending, turn blending off here if (mExtensions.hasFramebufferFetch()) { description.framebufferMode = mode; + description.swapSrcDst = swapSrcDst; } if (mCaches.blend) { diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 50f42c2..3126754 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -29,6 +29,7 @@ #include <utils/RefBase.h> #include <utils/ResourceTypes.h> +#include <utils/Vector.h> #include "Extensions.h" #include "Matrix.h" @@ -156,6 +157,12 @@ private: int alpha, SkXfermode::Mode mode, int flags); /** + * Clears all the regions corresponding to the current list of layers. + * This method MUST be invoked before any drawing operation. + */ + void clearLayerRegions(); + + /** * Draws a colored rectangle with the specified color. The specified coordinates * are transformed by the current snapshot's transform matrix. * @@ -166,9 +173,10 @@ private: * @param color The rectangle's ARGB color, defined as a packed 32 bits word * @param mode The Skia xfermode to use * @param ignoreTransform True if the current transform should be ignored + * @paran ignoreBlending True if the blending is set by the caller */ void drawColorRect(float left, float top, float right, float bottom, - int color, SkXfermode::Mode mode, bool ignoreTransform = false); + int color, SkXfermode::Mode mode, bool ignoreTransform = false); /** * Draws a textured rectangle with the specified texture. The specified coordinates @@ -216,10 +224,13 @@ private: * @param texCoords The texture coordinates of each vertex * @param indices The indices of the vertices, can be NULL * @param elementsCount The number of elements in the mesh, required by indices + * @param swapSrcDst Whether or not the src and dst blending operations should be swapped + * @param ignoreTransform True if the current transform should be ignored */ void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, - GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0); + GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0, + bool swapSrcDst = false, bool ignoreTransform = false); /** * Prepares the renderer to draw the specified shadow. @@ -322,8 +333,13 @@ private: * Enable or disable blending as necessary. This function sets the appropriate * blend function based on the specified xfermode. */ - inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description); + inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description, + bool swapSrcDst = false); + /** + * Safely retrieves the mode from the specified xfermode. If the specified + * xfermode is null, the mode is assumed to be SkXfermode::kSrcOver_Mode. + */ inline SkXfermode::Mode getXfermode(SkXfermode* mode); /** @@ -375,6 +391,10 @@ private: // Various caches Caches& mCaches; + + // List of rectangles to clear due to calls to saveLayer() + Vector<Rect*> mLayers; + }; // class OpenGLRenderer }; // namespace uirenderer diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index ff65c1b..becbc22 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -119,6 +119,8 @@ const char* gFS_Main_FragColor = " gl_FragColor = fragColor;\n"; const char* gFS_Main_FragColor_Blend = " gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n"; +const char* gFS_Main_FragColor_Blend_Swap = + " gl_FragColor = blendFramebuffer(gl_LastFragColor, fragColor);\n"; const char* gFS_Main_ApplyColorOp[4] = { // None "", @@ -376,7 +378,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (!blendFramebuffer) { shader.append(gFS_Main_FragColor); } else { - shader.append(gFS_Main_FragColor_Blend); + shader.append(!description.swapSrcDst ? + gFS_Main_FragColor_Blend : gFS_Main_FragColor_Blend_Swap); } } // End the shader diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h index 8f5304d..0a17052 100644 --- a/libs/hwui/ProgramCache.h +++ b/libs/hwui/ProgramCache.h @@ -35,7 +35,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Debug -#define DEBUG_PROGRAM_CACHE 1 +#define DEBUG_PROGRAM_CACHE 0 // Debug #if DEBUG_PROGRAM_CACHE @@ -44,6 +44,9 @@ namespace uirenderer { #define PROGRAM_LOGD(...) #endif +/* + * IMPORTANT: All 32 bits are used, switch to a long. + */ #define PROGRAM_KEY_TEXTURE 0x1 #define PROGRAM_KEY_A8_TEXTURE 0x2 #define PROGRAM_KEY_BITMAP 0x4 @@ -53,6 +56,7 @@ namespace uirenderer { #define PROGRAM_KEY_COLOR_LIGHTING 0x40 #define PROGRAM_KEY_COLOR_BLEND 0x80 #define PROGRAM_KEY_BITMAP_NPOT 0x100 +#define PROGRAM_KEY_SWAP_SRC_DST 0x2000 #define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600 #define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800 @@ -70,6 +74,9 @@ namespace uirenderer { // Types /////////////////////////////////////////////////////////////////////////////// +/* + * IMPORTANT: All 32 bits are used, switch to a long. + */ typedef uint32_t programid; /////////////////////////////////////////////////////////////////////////////// @@ -95,7 +102,7 @@ struct ProgramDescription { shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false), bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE), colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode), - framebufferMode(SkXfermode::kClear_Mode) { + framebufferMode(SkXfermode::kClear_Mode), swapSrcDst(false) { } // Texturing @@ -118,6 +125,7 @@ struct ProgramDescription { // Framebuffer blending (requires Extensions.hasFramebufferFetch()) // Ignored for all values < SkXfermode::kPlus_Mode SkXfermode::Mode framebufferMode; + bool swapSrcDst; inline uint32_t getEnumForWrap(GLenum wrap) const { switch (wrap) { @@ -163,6 +171,7 @@ struct ProgramDescription { break; } key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; + if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST; return key; } }; // struct ProgramDescription diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index da48243..062c986 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -43,7 +43,7 @@ namespace uirenderer { */ class Snapshot: public LightRefBase<Snapshot> { public: - Snapshot(): invisible(false), flags(0), previous(NULL), layer(NULL), fbo(0) { + Snapshot(): flags(0), previous(NULL), layer(NULL) { transform = &mTransformRoot; clipRect = &mClipRectRoot; } @@ -53,13 +53,7 @@ public: * the previous snapshot. */ Snapshot(const sp<Snapshot>& s, int saveFlags): - height(s->height), - invisible(s->invisible), - flags(0), - previous(s), - layer(NULL), - fbo(s->fbo), - viewport(s->viewport) { + flags(0), previous(s), layer(NULL) { if (saveFlags & SkCanvas::kMatrix_SaveFlag) { mTransformRoot.load(*s->transform); transform = &mTransformRoot; @@ -97,24 +91,36 @@ public: */ kFlagIsLayer = 0x2, /** - * Indicates that this snapshot has changed the ortho matrix. - */ - kFlagDirtyOrtho = 0x4, - /** * Indicates that the local clip should be recomputed. */ - kFlagDirtyLocalClip = 0x8, + kFlagDirtyLocalClip = 0x4, }; /** - * Intersects the current clip with the new clip rectangle. + * Modifies the current clip with the new clip rectangle and + * the specified operation. The specified rectangle is transformed + * by this snapshot's trasnformation. */ - bool clip(float left, float top, float right, float bottom, SkRegion::Op op) { - bool clipped = false; - + bool clip(float left, float top, float right, float bottom, + SkRegion::Op op = SkRegion::kIntersect_Op) { Rect r(left, top, right, bottom); transform->mapRect(r); + return clipTransformed(r, op); + } + /** + * Modifies the current clip with the new clip rectangle and + * the specified operation. The specified rectangle is considered + * already transformed. + */ + bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op) { + bool clipped = false; + + // NOTE: The unimplemented operations require support for regions + // Supporting regions would require using a stencil buffer instead + // of the scissor. The stencil buffer itself is not too expensive + // (memory cost excluded) but on fillrate limited devices, managing + // the stencil might have a negative impact on the framerate. switch (op) { case SkRegion::kDifference_Op: break; @@ -162,29 +168,6 @@ public: return mLocalClip; } - // TODO: Temporary - void resetTransform(float x, float y, float z) { - transform = &mTransformRoot; - transform->loadTranslate(x, y, z); - } - - // TODO: Temporary - void resetClip(float left, float top, float right, float bottom) { - clipRect = &mClipRectRoot; - clipRect->set(left, top, right, bottom); - flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip; - } - - /** - * Height of the framebuffer the snapshot is rendering into. - */ - int height; - - /** - * If true, the layer won't be rendered. - */ - bool invisible; - /** * Dirty flags. */ @@ -199,17 +182,6 @@ public: * Only set when the flag kFlagIsLayer is set. */ Layer* layer; - GLuint fbo; - - /** - * Current viewport. - */ - Rect viewport; - - /** - * Contains the previous ortho matrix. - */ - mat4 orthoMatrix; /** * Local transformation. Holds the current translation, scale and diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp index df232d4..b8a26b0 100644 --- a/libs/ui/InputDispatcher.cpp +++ b/libs/ui/InputDispatcher.cpp @@ -31,8 +31,15 @@ // Log debug messages about input event throttling. #define DEBUG_THROTTLING 0 +// Log debug messages about input focus tracking. +#define DEBUG_FOCUS 0 + +// Log debug messages about the app switch latency optimization. +#define DEBUG_APP_SWITCH 0 + #include <cutils/log.h> #include <ui/InputDispatcher.h> +#include <ui/PowerManager.h> #include <stddef.h> #include <unistd.h> @@ -41,31 +48,62 @@ namespace android { -// TODO, this needs to be somewhere else, perhaps in the policy -static inline bool isMovementKey(int32_t keyCode) { - return keyCode == AKEYCODE_DPAD_UP - || keyCode == AKEYCODE_DPAD_DOWN - || keyCode == AKEYCODE_DPAD_LEFT - || keyCode == AKEYCODE_DPAD_RIGHT; -} +// Delay between reporting long touch events to the power manager. +const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms + +// Default input dispatching timeout if there is no focused application or paused window +// from which to determine an appropriate dispatching timeout. +const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec + +// Amount of time to allow for all pending events to be processed when an app switch +// key is on the way. This is used to preempt input dispatch and drop input events +// when an application takes too long to respond and the user has pressed an app switch key. +const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec + static inline nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); } +static inline const char* toString(bool value) { + return value ? "true" : "false"; +} + + +// --- InputWindow --- + +bool InputWindow::visibleFrameIntersects(const InputWindow* other) const { + return visibleFrameRight > other->visibleFrameLeft + && visibleFrameLeft < other->visibleFrameRight + && visibleFrameBottom > other->visibleFrameTop + && visibleFrameTop < other->visibleFrameBottom; +} + +bool InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const { + return x >= touchableAreaLeft && x <= touchableAreaRight + && y >= touchableAreaTop && y <= touchableAreaBottom; +} + + // --- InputDispatcher --- InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) : - mPolicy(policy) { + mPolicy(policy), + mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX), + mDispatchEnabled(true), mDispatchFrozen(false), + mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL), + mFocusedApplication(NULL), + mCurrentInputTargetsValid(false), + mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) { mPollLoop = new PollLoop(false); - mInboundQueue.head.refCount = -1; - mInboundQueue.head.type = EventEntry::TYPE_SENTINEL; - mInboundQueue.head.eventTime = LONG_LONG_MIN; + mInboundQueue.headSentinel.refCount = -1; + mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN; - mInboundQueue.tail.refCount = -1; - mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL; - mInboundQueue.tail.eventTime = LONG_LONG_MAX; + mInboundQueue.tailSentinel.refCount = -1; + mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX; mKeyRepeatState.lastKeyEntry = NULL; @@ -77,21 +115,19 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic mThrottleState.originalSampleCount = 0; LOGD("Throttling - Max events per second = %d", maxEventsPerSecond); #endif - - mCurrentInputTargetsValid = false; } InputDispatcher::~InputDispatcher() { - resetKeyRepeatLocked(); + { // acquire lock + AutoMutex _l(mLock); - while (mConnectionsByReceiveFd.size() != 0) { - unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel); + resetKeyRepeatLocked(); + releasePendingEventLocked(true); + drainInboundQueueLocked(); } - for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) { - EventEntry* next = entry->next; - mAllocator.releaseEventEntry(next); - entry = next; + while (mConnectionsByReceiveFd.size() != 0) { + unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel); } } @@ -99,167 +135,282 @@ void InputDispatcher::dispatchOnce() { nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout(); nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay(); - bool skipPoll = false; - nsecs_t currentTime; nsecs_t nextWakeupTime = LONG_LONG_MAX; { // acquire lock AutoMutex _l(mLock); - currentTime = now(); + dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime); - // Reset the key repeat timer whenever we disallow key events, even if the next event - // is not a key. This is to ensure that we abort a key repeat if the device is just coming - // out of sleep. - // XXX we should handle resetting input state coming out of sleep more generally elsewhere - if (keyRepeatTimeout < 0) { - resetKeyRepeatLocked(); + if (runCommandsLockedInterruptible()) { + nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately } + } // release lock - // Detect and process timeouts for all connections and determine if there are any - // synchronous event dispatches pending. This step is entirely non-interruptible. - bool hasPendingSyncTarget = false; - size_t activeConnectionCount = mActiveConnections.size(); - for (size_t i = 0; i < activeConnectionCount; i++) { - Connection* connection = mActiveConnections.itemAt(i); + // Wait for callback or timeout or wake. (make sure we round up, not down) + nsecs_t currentTime = now(); + int32_t timeoutMillis; + if (nextWakeupTime > currentTime) { + uint64_t timeout = uint64_t(nextWakeupTime - currentTime); + timeout = (timeout + 999999LL) / 1000000LL; + timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout); + } else { + timeoutMillis = 0; + } - if (connection->hasPendingSyncTarget()) { - hasPendingSyncTarget = true; - } + mPollLoop->pollOnce(timeoutMillis); +} + +void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, + nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) { + nsecs_t currentTime = now(); + + // Reset the key repeat timer whenever we disallow key events, even if the next event + // is not a key. This is to ensure that we abort a key repeat if the device is just coming + // out of sleep. + if (keyRepeatTimeout < 0) { + resetKeyRepeatLocked(); + } + + // If dispatching is disabled, drop all events in the queue. + if (! mDispatchEnabled) { + if (mPendingEvent || ! mInboundQueue.isEmpty()) { + LOGI("Dropping pending events because input dispatch is disabled."); + releasePendingEventLocked(true); + drainInboundQueueLocked(); + } + return; + } + + // If dispatching is frozen, do not process timeouts or try to deliver any new events. + if (mDispatchFrozen) { +#if DEBUG_FOCUS + LOGD("Dispatch frozen. Waiting some more."); +#endif + return; + } - nsecs_t connectionTimeoutTime = connection->nextTimeoutTime; - if (connectionTimeoutTime <= currentTime) { - mTimedOutConnections.add(connection); - } else if (connectionTimeoutTime < nextWakeupTime) { - nextWakeupTime = connectionTimeoutTime; + // Optimize latency of app switches. + // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has + // been pressed. When it expires, we preempt dispatch and drop all other pending events. + bool isAppSwitchDue = mAppSwitchDueTime <= currentTime; + if (mAppSwitchDueTime < *nextWakeupTime) { + *nextWakeupTime = mAppSwitchDueTime; + } + + // Detect and process timeouts for all connections and determine if there are any + // synchronous event dispatches pending. This step is entirely non-interruptible. + bool havePendingSyncTarget = false; + size_t activeConnectionCount = mActiveConnections.size(); + for (size_t i = 0; i < activeConnectionCount; i++) { + Connection* connection = mActiveConnections.itemAt(i); + + if (connection->hasPendingSyncTarget()) { + if (isAppSwitchDue) { + connection->preemptSyncTarget(); + } else { + havePendingSyncTarget = true; } } - size_t timedOutConnectionCount = mTimedOutConnections.size(); - for (size_t i = 0; i < timedOutConnectionCount; i++) { - Connection* connection = mTimedOutConnections.itemAt(i); - timeoutDispatchCycleLocked(currentTime, connection); - skipPoll = true; - } - mTimedOutConnections.clear(); - - // If we don't have a pending sync target, then we can begin delivering a new event. - // (Otherwise we wait for dispatch to complete for that target.) - if (! hasPendingSyncTarget) { - if (mInboundQueue.isEmpty()) { - if (mKeyRepeatState.lastKeyEntry) { - if (currentTime >= mKeyRepeatState.nextRepeatTime) { - processKeyRepeatLockedInterruptible(currentTime, keyRepeatDelay); - skipPoll = true; - } else { - if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) { - nextWakeupTime = mKeyRepeatState.nextRepeatTime; - } + nsecs_t connectionTimeoutTime = connection->nextTimeoutTime; + if (connectionTimeoutTime <= currentTime) { + mTimedOutConnections.add(connection); + } else if (connectionTimeoutTime < *nextWakeupTime) { + *nextWakeupTime = connectionTimeoutTime; + } + } + + size_t timedOutConnectionCount = mTimedOutConnections.size(); + for (size_t i = 0; i < timedOutConnectionCount; i++) { + Connection* connection = mTimedOutConnections.itemAt(i); + timeoutDispatchCycleLocked(currentTime, connection); + *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately + } + mTimedOutConnections.clear(); + + // If we have a pending synchronous target, skip dispatch. + if (havePendingSyncTarget) { + return; + } + + // Ready to start a new event. + // If we don't already have a pending event, go grab one. + if (! mPendingEvent) { + if (mInboundQueue.isEmpty()) { + if (isAppSwitchDue) { + // The inbound queue is empty so the app switch key we were waiting + // for will never arrive. Stop waiting for it. + resetPendingAppSwitchLocked(false); + isAppSwitchDue = false; + } + + // Synthesize a key repeat if appropriate. + if (mKeyRepeatState.lastKeyEntry) { + if (currentTime >= mKeyRepeatState.nextRepeatTime) { + mPendingEvent = synthesizeKeyRepeatLocked(currentTime, keyRepeatDelay); + } else { + if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) { + *nextWakeupTime = mKeyRepeatState.nextRepeatTime; } } - } else { - // Inbound queue has at least one entry. - EventEntry* entry = mInboundQueue.head.next; - - // Consider throttling the entry if it is a move event and there are no - // other events behind it in the queue. Due to movement batching, additional - // samples may be appended to this event by the time the throttling timeout - // expires. - // TODO Make this smarter and consider throttling per device independently. - if (entry->type == EventEntry::TYPE_MOTION) { - MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); - int32_t deviceId = motionEntry->deviceId; - uint32_t source = motionEntry->source; - if (motionEntry->next == & mInboundQueue.tail - && motionEntry->action == AMOTION_EVENT_ACTION_MOVE - && deviceId == mThrottleState.lastDeviceId - && source == mThrottleState.lastSource) { - nsecs_t nextTime = mThrottleState.lastEventTime - + mThrottleState.minTimeBetweenEvents; - if (currentTime < nextTime) { - // Throttle it! + } + if (! mPendingEvent) { + return; + } + } else { + // Inbound queue has at least one entry. + EventEntry* entry = mInboundQueue.headSentinel.next; + + // Throttle the entry if it is a move event and there are no + // other events behind it in the queue. Due to movement batching, additional + // samples may be appended to this event by the time the throttling timeout + // expires. + // TODO Make this smarter and consider throttling per device independently. + if (entry->type == EventEntry::TYPE_MOTION) { + MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); + int32_t deviceId = motionEntry->deviceId; + uint32_t source = motionEntry->source; + if (! isAppSwitchDue + && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event + && motionEntry->action == AMOTION_EVENT_ACTION_MOVE + && deviceId == mThrottleState.lastDeviceId + && source == mThrottleState.lastSource) { + nsecs_t nextTime = mThrottleState.lastEventTime + + mThrottleState.minTimeBetweenEvents; + if (currentTime < nextTime) { + // Throttle it! #if DEBUG_THROTTLING - LOGD("Throttling - Delaying motion event for " - "device 0x%x, source 0x%08x by up to %0.3fms.", - deviceId, source, (nextTime - currentTime) * 0.000001); + LOGD("Throttling - Delaying motion event for " + "device 0x%x, source 0x%08x by up to %0.3fms.", + deviceId, source, (nextTime - currentTime) * 0.000001); #endif - if (nextTime < nextWakeupTime) { - nextWakeupTime = nextTime; - } - if (mThrottleState.originalSampleCount == 0) { - mThrottleState.originalSampleCount = - motionEntry->countSamples(); - } - goto Throttle; + if (nextTime < *nextWakeupTime) { + *nextWakeupTime = nextTime; + } + if (mThrottleState.originalSampleCount == 0) { + mThrottleState.originalSampleCount = + motionEntry->countSamples(); } + return; } + } #if DEBUG_THROTTLING - if (mThrottleState.originalSampleCount != 0) { - uint32_t count = motionEntry->countSamples(); - LOGD("Throttling - Motion event sample count grew by %d from %d to %d.", - count - mThrottleState.originalSampleCount, - mThrottleState.originalSampleCount, count); - mThrottleState.originalSampleCount = 0; - } + if (mThrottleState.originalSampleCount != 0) { + uint32_t count = motionEntry->countSamples(); + LOGD("Throttling - Motion event sample count grew by %d from %d to %d.", + count - mThrottleState.originalSampleCount, + mThrottleState.originalSampleCount, count); + mThrottleState.originalSampleCount = 0; + } #endif - mThrottleState.lastEventTime = entry->eventTime < currentTime - ? entry->eventTime : currentTime; - mThrottleState.lastDeviceId = deviceId; - mThrottleState.lastSource = source; - } + mThrottleState.lastEventTime = entry->eventTime < currentTime + ? entry->eventTime : currentTime; + mThrottleState.lastDeviceId = deviceId; + mThrottleState.lastSource = source; + } - // Start processing the entry but leave it on the queue until later so that the - // input reader can keep appending samples onto a motion event between the - // time we started processing it and the time we finally enqueue dispatch - // entries for it. - switch (entry->type) { - case EventEntry::TYPE_CONFIGURATION_CHANGED: { - ConfigurationChangedEntry* typedEntry = - static_cast<ConfigurationChangedEntry*>(entry); - processConfigurationChangedLockedInterruptible(currentTime, typedEntry); - break; - } + mInboundQueue.dequeue(entry); + mPendingEvent = entry; + } + } - case EventEntry::TYPE_KEY: { - KeyEntry* typedEntry = static_cast<KeyEntry*>(entry); - processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout); - break; - } + // Now we have an event to dispatch. + assert(mPendingEvent != NULL); + bool wasDispatched = false; + bool wasDropped = false; + switch (mPendingEvent->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: { + ConfigurationChangedEntry* typedEntry = + static_cast<ConfigurationChangedEntry*>(mPendingEvent); + wasDispatched = dispatchConfigurationChangedLocked(currentTime, typedEntry); + break; + } - case EventEntry::TYPE_MOTION: { - MotionEntry* typedEntry = static_cast<MotionEntry*>(entry); - processMotionLockedInterruptible(currentTime, typedEntry); - break; - } + case EventEntry::TYPE_KEY: { + KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent); + if (isAppSwitchPendingLocked()) { + if (isAppSwitchKey(typedEntry->keyCode)) { + resetPendingAppSwitchLocked(true); + } else if (isAppSwitchDue) { + LOGI("Dropping key because of pending overdue app switch."); + wasDropped = true; + break; + } + } + wasDispatched = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout, + nextWakeupTime); + break; + } - default: - assert(false); - break; - } + case EventEntry::TYPE_MOTION: { + MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent); + if (isAppSwitchDue) { + LOGI("Dropping motion because of pending overdue app switch."); + wasDropped = true; + break; + } + wasDispatched = dispatchMotionLocked(currentTime, typedEntry, nextWakeupTime); + break; + } - // Dequeue and release the event entry that we just processed. - mInboundQueue.dequeue(entry); - mAllocator.releaseEventEntry(entry); - skipPoll = true; + default: + assert(false); + wasDropped = true; + break; + } - Throttle: ; - } - } + if (wasDispatched || wasDropped) { + releasePendingEventLocked(wasDropped); + *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately + } +} - // Run any deferred commands. - skipPoll |= runCommandsLockedInterruptible(); - } // release lock +bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { + bool needWake = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(entry); - // If we dispatched anything, don't poll just now. Wait for the next iteration. - // Contents may have shifted during flight. - if (skipPoll) { - return; + switch (entry->type) { + case EventEntry::TYPE_KEY: + needWake |= detectPendingAppSwitchLocked(static_cast<KeyEntry*>(entry)); + break; } - // Wait for callback or timeout or wake. (make sure we round up, not down) - nsecs_t timeout = (nextWakeupTime - currentTime + 999999LL) / 1000000LL; - int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0; - mPollLoop->pollOnce(timeoutMillis); + return needWake; +} + +bool InputDispatcher::isAppSwitchKey(int32_t keyCode) { + return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL; +} + +bool InputDispatcher::isAppSwitchPendingLocked() { + return mAppSwitchDueTime != LONG_LONG_MAX; +} + +bool InputDispatcher::detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry) { + if (inboundKeyEntry->action == AKEY_EVENT_ACTION_UP + && ! (inboundKeyEntry->flags & AKEY_EVENT_FLAG_CANCELED) + && isAppSwitchKey(inboundKeyEntry->keyCode) + && isEventFromReliableSourceLocked(inboundKeyEntry)) { +#if DEBUG_APP_SWITCH + LOGD("App switch is pending!"); +#endif + mAppSwitchDueTime = inboundKeyEntry->eventTime + APP_SWITCH_TIMEOUT; + return true; // need wake + } + return false; +} + +void InputDispatcher::resetPendingAppSwitchLocked(bool handled) { + mAppSwitchDueTime = LONG_LONG_MAX; + +#if DEBUG_APP_SWITCH + if (handled) { + LOGD("App switch has arrived."); + } else { + LOGD("App switch was abandoned."); + } +#endif } bool InputDispatcher::runCommandsLockedInterruptible() { @@ -285,78 +436,52 @@ InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command comman return commandEntry; } -void InputDispatcher::processConfigurationChangedLockedInterruptible( - nsecs_t currentTime, ConfigurationChangedEntry* entry) { -#if DEBUG_OUTBOUND_EVENT_DETAILS - LOGD("processConfigurationChanged - eventTime=%lld", entry->eventTime); -#endif - - // Reset key repeating in case a keyboard device was added or removed or something. - resetKeyRepeatLocked(); - - mLock.unlock(); - - mPolicy->notifyConfigurationChanged(entry->eventTime); +void InputDispatcher::drainInboundQueueLocked() { + while (! mInboundQueue.isEmpty()) { + EventEntry* entry = mInboundQueue.dequeueAtHead(); + releaseInboundEventLocked(entry, true /*wasDropped*/); + } +} - mLock.lock(); +void InputDispatcher::releasePendingEventLocked(bool wasDropped) { + if (mPendingEvent) { + releaseInboundEventLocked(mPendingEvent, wasDropped); + mPendingEvent = NULL; + } } -void InputDispatcher::processKeyLockedInterruptible( - nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout) { -#if DEBUG_OUTBOUND_EVENT_DETAILS - LOGD("processKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, " - "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld", - entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action, - entry->flags, entry->keyCode, entry->scanCode, entry->metaState, - entry->downTime); +void InputDispatcher::releaseInboundEventLocked(EventEntry* entry, bool wasDropped) { + if (wasDropped) { +#if DEBUG_DISPATCH_CYCLE + LOGD("Pending event was dropped."); #endif - - if (entry->action == AKEY_EVENT_ACTION_DOWN && ! entry->isInjected()) { - if (mKeyRepeatState.lastKeyEntry - && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { - // We have seen two identical key downs in a row which indicates that the device - // driver is automatically generating key repeats itself. We take note of the - // repeat here, but we disable our own next key repeat timer since it is clear that - // we will not need to synthesize key repeats ourselves. - entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1; - resetKeyRepeatLocked(); - mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves - } else { - // Not a repeat. Save key down state in case we do see a repeat later. - resetKeyRepeatLocked(); - mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout; - } - mKeyRepeatState.lastKeyEntry = entry; - entry->refCount += 1; - } else { - resetKeyRepeatLocked(); + setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED); } + mAllocator.releaseEventEntry(entry); +} - identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry); +bool InputDispatcher::isEventFromReliableSourceLocked(EventEntry* entry) { + return ! entry->isInjected() + || entry->injectorUid == 0 + || mPolicy->checkInjectEventsPermissionNonReentrant( + entry->injectorPid, entry->injectorUid); } -void InputDispatcher::processKeyRepeatLockedInterruptible( +void InputDispatcher::resetKeyRepeatLocked() { + if (mKeyRepeatState.lastKeyEntry) { + mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry); + mKeyRepeatState.lastKeyEntry = NULL; + } +} + +InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked( nsecs_t currentTime, nsecs_t keyRepeatDelay) { KeyEntry* entry = mKeyRepeatState.lastKeyEntry; - // Search the inbound queue for a key up corresponding to this device. - // It doesn't make sense to generate a key repeat event if the key is already up. - for (EventEntry* queuedEntry = mInboundQueue.head.next; - queuedEntry != & mInboundQueue.tail; queuedEntry = entry->next) { - if (queuedEntry->type == EventEntry::TYPE_KEY) { - KeyEntry* queuedKeyEntry = static_cast<KeyEntry*>(queuedEntry); - if (queuedKeyEntry->deviceId == entry->deviceId - && entry->action == AKEY_EVENT_ACTION_UP) { - resetKeyRepeatLocked(); - return; - } - } - } - - // Synthesize a key repeat. // Reuse the repeated key entry if it is otherwise unreferenced. uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK; if (entry->refCount == 1) { + entry->recycle(); entry->eventTime = currentTime; entry->policyFlags = policyFlags; entry->repeatCount += 1; @@ -371,31 +496,194 @@ void InputDispatcher::processKeyRepeatLockedInterruptible( entry = newEntry; } + entry->syntheticRepeat = true; + + // Increment reference count since we keep a reference to the event in + // mKeyRepeatState.lastKeyEntry in addition to the one we return. + entry->refCount += 1; if (entry->repeatCount == 1) { entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS; } mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay; + return entry; +} + +bool InputDispatcher::dispatchConfigurationChangedLocked( + nsecs_t currentTime, ConfigurationChangedEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime); +#endif + + // Reset key repeating in case a keyboard device was added or removed or something. + resetKeyRepeatLocked(); + + // Enqueue a command to run outside the lock to tell the policy that the configuration changed. + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doNotifyConfigurationChangedInterruptible); + commandEntry->eventTime = entry->eventTime; + return true; +} +bool InputDispatcher::dispatchKeyLocked( + nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout, + nsecs_t* nextWakeupTime) { + // Preprocessing. + if (! entry->dispatchInProgress) { + logOutboundKeyDetailsLocked("dispatchKey - ", entry); + + if (entry->repeatCount == 0 + && entry->action == AKEY_EVENT_ACTION_DOWN + && ! entry->isInjected()) { + if (mKeyRepeatState.lastKeyEntry + && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { + // We have seen two identical key downs in a row which indicates that the device + // driver is automatically generating key repeats itself. We take note of the + // repeat here, but we disable our own next key repeat timer since it is clear that + // we will not need to synthesize key repeats ourselves. + entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1; + resetKeyRepeatLocked(); + mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves + } else { + // Not a repeat. Save key down state in case we do see a repeat later. + resetKeyRepeatLocked(); + mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout; + } + mKeyRepeatState.lastKeyEntry = entry; + entry->refCount += 1; + } else if (! entry->syntheticRepeat) { + resetKeyRepeatLocked(); + } + + entry->dispatchInProgress = true; + startFindingTargetsLocked(); + } + + // Identify targets. + if (! mCurrentInputTargetsValid) { + InputWindow* window = NULL; + int32_t injectionResult = findFocusedWindowLocked(currentTime, + entry, nextWakeupTime, & window); + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } + + addMonitoringTargetsLocked(); + finishFindingTargetsLocked(window); + } + + // Give the policy a chance to intercept the key. + if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible); + commandEntry->inputChannel = mCurrentInputChannel; + commandEntry->keyEntry = entry; + entry->refCount += 1; + return false; // wait for the command to run + } + if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { + return true; + } + + // Dispatch the key. + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + + // Poke user activity. + pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, POWER_MANAGER_BUTTON_EVENT); + return true; +} + +void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) { #if DEBUG_OUTBOUND_EVENT_DETAILS - LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, " + LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, " "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, " - "repeatCount=%d, downTime=%lld", + "downTime=%lld", + prefix, entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState, - entry->repeatCount, entry->downTime); + entry->downTime); #endif +} + +bool InputDispatcher::dispatchMotionLocked( + nsecs_t currentTime, MotionEntry* entry, nsecs_t* nextWakeupTime) { + // Preprocessing. + if (! entry->dispatchInProgress) { + logOutboundMotionDetailsLocked("dispatchMotion - ", entry); + + entry->dispatchInProgress = true; + startFindingTargetsLocked(); + } + + bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; + + // Identify targets. + if (! mCurrentInputTargetsValid) { + InputWindow* window = NULL; + int32_t injectionResult; + if (isPointerEvent) { + // Pointer event. (eg. touchscreen) + injectionResult = findTouchedWindowLocked(currentTime, + entry, nextWakeupTime, & window); + } else { + // Non touch event. (eg. trackball) + injectionResult = findFocusedWindowLocked(currentTime, + entry, nextWakeupTime, & window); + } + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } - identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry); + addMonitoringTargetsLocked(); + finishFindingTargetsLocked(window); + } + + // Dispatch the motion. + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + + // Poke user activity. + int32_t eventType; + if (isPointerEvent) { + switch (entry->action) { + case AMOTION_EVENT_ACTION_DOWN: + eventType = POWER_MANAGER_TOUCH_EVENT; + break; + case AMOTION_EVENT_ACTION_UP: + eventType = POWER_MANAGER_TOUCH_UP_EVENT; + break; + default: + if (entry->eventTime - entry->downTime >= EVENT_IGNORE_DURATION) { + eventType = POWER_MANAGER_TOUCH_EVENT; + } else { + eventType = POWER_MANAGER_LONG_TOUCH_EVENT; + } + break; + } + } else { + eventType = POWER_MANAGER_BUTTON_EVENT; + } + pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, eventType); + return true; } -void InputDispatcher::processMotionLockedInterruptible( - nsecs_t currentTime, MotionEntry* entry) { + +void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) { #if DEBUG_OUTBOUND_EVENT_DETAILS - LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, " + LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, " "action=0x%x, flags=0x%x, " "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld", + prefix, entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action, entry->flags, entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision, @@ -403,7 +691,7 @@ void InputDispatcher::processMotionLockedInterruptible( // Print the most recent sample that we have available, this may change due to batching. size_t sampleCount = 1; - MotionSample* sample = & entry->firstSample; + const MotionSample* sample = & entry->firstSample; for (; sample->next != NULL; sample = sample->next) { sampleCount += 1; } @@ -425,94 +713,541 @@ void InputDispatcher::processMotionLockedInterruptible( LOGD(" ... Total movement samples currently batched %d ...", sampleCount); } #endif - - identifyInputTargetsAndDispatchMotionLockedInterruptible(currentTime, entry); } -void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible( - nsecs_t currentTime, KeyEntry* entry) { +void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, + EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { #if DEBUG_DISPATCH_CYCLE - LOGD("identifyInputTargetsAndDispatchKey"); + LOGD("dispatchEventToCurrentInputTargets - " + "resumeWithAppendedMotionSample=%s", + toString(resumeWithAppendedMotionSample)); #endif - entry->dispatchInProgress = true; - mCurrentInputTargetsValid = false; - mLock.unlock(); + assert(eventEntry->dispatchInProgress); // should already have been set to true - mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags, - entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, - entry->downTime, entry->eventTime); + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); + ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget, + resumeWithAppendedMotionSample); + } else { + LOGW("Framework requested delivery of an input event to channel '%s' but it " + "is not registered with the input dispatcher.", + inputTarget.inputChannel->getName().string()); + } + } +} + +void InputDispatcher::startFindingTargetsLocked() { + mCurrentInputTargetsValid = false; mCurrentInputTargets.clear(); - int32_t injectionResult = mPolicy->waitForKeyEventTargets(& mReusableKeyEvent, - entry->policyFlags, entry->injectorPid, entry->injectorUid, - mCurrentInputTargets); + mCurrentInputChannel.clear(); + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; +} - mLock.lock(); +void InputDispatcher::finishFindingTargetsLocked(const InputWindow* window) { + mCurrentInputWindowType = window->layoutParamsType; + mCurrentInputChannel = window->inputChannel; mCurrentInputTargetsValid = true; +} + +int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, + const EventEntry* entry, const InputApplication* application, const InputWindow* window, + nsecs_t* nextWakeupTime) { + if (application == NULL && window == NULL) { + if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) { +#if DEBUG_FOCUS + LOGD("Waiting for system to become ready for input."); +#endif + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY; + mInputTargetWaitStartTime = currentTime; + mInputTargetWaitTimeoutTime = LONG_LONG_MAX; + mInputTargetWaitTimeoutExpired = false; + } + } else { + if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { +#if DEBUG_FOCUS + LOGD("Waiting for application to become ready for input: name=%s, window=%s", + application ? application->name.string() : "<unknown>", + window ? window->inputChannel->getName().string() : "<unknown>"); +#endif + nsecs_t timeout = window ? window->dispatchingTimeout : + application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT; + + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY; + mInputTargetWaitStartTime = currentTime; + mInputTargetWaitTimeoutTime = currentTime + timeout; + mInputTargetWaitTimeoutExpired = false; + } + } + + if (mInputTargetWaitTimeoutExpired) { + return INPUT_EVENT_INJECTION_TIMED_OUT; + } + + if (currentTime >= mInputTargetWaitTimeoutTime) { + LOGI("Application is not ready for input: name=%s, window=%s," + "%01.1fms since event, %01.1fms since wait started", + application ? application->name.string() : "<unknown>", + window ? window->inputChannel->getName().string() : "<unknown>", + (currentTime - entry->eventTime) / 1000000.0, + (currentTime - mInputTargetWaitStartTime) / 1000000.0); + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible); + if (application) { + commandEntry->inputApplicationHandle = application->handle; + } + if (window) { + commandEntry->inputChannel = window->inputChannel; + } + + // Force poll loop to wake up immediately on next iteration once we get the + // ANR response back from the policy. + *nextWakeupTime = LONG_LONG_MIN; + return INPUT_EVENT_INJECTION_PENDING; + } else { + // Force poll loop to wake up when timeout is due. + if (mInputTargetWaitTimeoutTime < *nextWakeupTime) { + *nextWakeupTime = mInputTargetWaitTimeoutTime; + } + return INPUT_EVENT_INJECTION_PENDING; + } +} - setInjectionResultLocked(entry, injectionResult); +void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout) { + if (newTimeout > 0) { + // Extend the timeout. + mInputTargetWaitTimeoutTime = now() + newTimeout; + } else { + // Give up. + mInputTargetWaitTimeoutExpired = true; + } +} - if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { - dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); +nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationWhileFindingTargetsLocked( + nsecs_t currentTime) { + if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { + return currentTime - mInputTargetWaitStartTime; } + return 0; } -void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible( - nsecs_t currentTime, MotionEntry* entry) { -#if DEBUG_DISPATCH_CYCLE - LOGD("identifyInputTargetsAndDispatchMotion"); +void InputDispatcher::resetANRTimeoutsLocked() { +#if DEBUG_FOCUS + LOGD("Resetting ANR timeouts."); #endif - entry->dispatchInProgress = true; - mCurrentInputTargetsValid = false; - mLock.unlock(); + // Reset timeouts for all active connections. + nsecs_t currentTime = now(); + for (size_t i = 0; i < mActiveConnections.size(); i++) { + Connection* connection = mActiveConnections[i]; + connection->resetTimeout(currentTime); + } - mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags, - entry->edgeFlags, entry->metaState, - 0, 0, entry->xPrecision, entry->yPrecision, - entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds, - entry->firstSample.pointerCoords); + // Reset input target wait timeout. + mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; +} +int32_t InputDispatcher::findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry, + nsecs_t* nextWakeupTime, InputWindow** outWindow) { + *outWindow = NULL; mCurrentInputTargets.clear(); - int32_t injectionResult = mPolicy->waitForMotionEventTargets(& mReusableMotionEvent, - entry->policyFlags, entry->injectorPid, entry->injectorUid, - mCurrentInputTargets); - mLock.lock(); - mCurrentInputTargetsValid = true; + int32_t injectionResult; - setInjectionResultLocked(entry, injectionResult); + // If there is no currently focused window and no focused application + // then drop the event. + if (! mFocusedWindow) { + if (mFocusedApplication) { +#if DEBUG_FOCUS + LOGD("Waiting because there is no focused window but there is a " + "focused application that may eventually add a window: '%s'.", + mFocusedApplication->name.string()); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplication, NULL, nextWakeupTime); + goto Unresponsive; + } - if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { - dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + LOGI("Dropping event because there is no focused window or focused application."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + goto Failed; } + + // Check permissions. + if (! checkInjectionPermission(mFocusedWindow, entry->injectorPid, entry->injectorUid)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + goto Failed; + } + + // If the currently focused window is paused then keep waiting. + if (mFocusedWindow->paused) { +#if DEBUG_FOCUS + LOGD("Waiting because focused window is paused."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplication, mFocusedWindow, nextWakeupTime); + goto Unresponsive; + } + + // Success! Output targets. + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + *outWindow = mFocusedWindow; + addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_SYNC, + getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime)); + + // Done. +Failed: +Unresponsive: +#if DEBUG_FOCUS + LOGD("findFocusedWindow finished: injectionResult=%d", + injectionResult); + logDispatchStateLocked(); +#endif + return injectionResult; } -void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, - EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { -#if DEBUG_DISPATCH_CYCLE - LOGD("dispatchEventToCurrentInputTargets - " - "resumeWithAppendedMotionSample=%s", - resumeWithAppendedMotionSample ? "true" : "false"); +int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry, + nsecs_t* nextWakeupTime, InputWindow** outWindow) { + enum InjectionPermission { + INJECTION_PERMISSION_UNKNOWN, + INJECTION_PERMISSION_GRANTED, + INJECTION_PERMISSION_DENIED + }; + + *outWindow = NULL; + mCurrentInputTargets.clear(); + + nsecs_t startTime = now(); + + // For security reasons, we defer updating the touch state until we are sure that + // event injection will be allowed. + // + // FIXME In the original code, screenWasOff could never be set to true. + // The reason is that the POLICY_FLAG_WOKE_HERE + // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw + // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was + // actually enqueued using the policyFlags that appeared in the final EV_SYN + // events upon which no preprocessing took place. So policyFlags was always 0. + // In the new native input dispatcher we're a bit more careful about event + // preprocessing so the touches we receive can actually have non-zero policyFlags. + // Unfortunately we obtain undesirable behavior. + // + // Here's what happens: + // + // When the device dims in anticipation of going to sleep, touches + // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause + // the device to brighten and reset the user activity timer. + // Touches on other windows (such as the launcher window) + // are dropped. Then after a moment, the device goes to sleep. Oops. + // + // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE + // instead of POLICY_FLAG_WOKE_HERE... + // + bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE; + + int32_t action = entry->action; + + // Update the touch state as needed based on the properties of the touch event. + int32_t injectionResult; + InjectionPermission injectionPermission; + if (action == AMOTION_EVENT_ACTION_DOWN) { + /* Case 1: ACTION_DOWN */ + + InputWindow* newTouchedWindow = NULL; + mTempTouchedOutsideTargets.clear(); + + int32_t x = int32_t(entry->firstSample.pointerCoords[0].x); + int32_t y = int32_t(entry->firstSample.pointerCoords[0].y); + InputWindow* topErrorWindow = NULL; + bool obscured = false; + + // Traverse windows from front to back to find touched window and outside targets. + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + InputWindow* window = & mWindows.editItemAt(i); + int32_t flags = window->layoutParamsFlags; + + if (flags & InputWindow::FLAG_SYSTEM_ERROR) { + if (! topErrorWindow) { + topErrorWindow = window; + } + } + + if (window->visible) { + if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) { + bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE + | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0; + if (isTouchModal || window->touchableAreaContainsPoint(x, y)) { + if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) { + newTouchedWindow = window; + obscured = isWindowObscuredLocked(window); + } + break; // found touched window, exit window loop + } + } + + if (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH) { + OutsideTarget outsideTarget; + outsideTarget.window = window; + outsideTarget.obscured = isWindowObscuredLocked(window); + mTempTouchedOutsideTargets.push(outsideTarget); + } + } + } + + // If there is an error window but it is not taking focus (typically because + // it is invisible) then wait for it. Any other focused window may in + // fact be in ANR state. + if (topErrorWindow && newTouchedWindow != topErrorWindow) { +#if DEBUG_FOCUS + LOGD("Waiting because system error window is pending."); #endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, NULL, nextWakeupTime); + injectionPermission = INJECTION_PERMISSION_UNKNOWN; + goto Unresponsive; + } - assert(eventEntry->dispatchInProgress); // should already have been set to true + // If we did not find a touched window then fail. + if (! newTouchedWindow) { + if (mFocusedApplication) { +#if DEBUG_FOCUS + LOGD("Waiting because there is no touched window but there is a " + "focused application that may eventually add a new window: '%s'.", + mFocusedApplication->name.string()); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplication, NULL, nextWakeupTime); + injectionPermission = INJECTION_PERMISSION_UNKNOWN; + goto Unresponsive; + } - for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { - const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); + LOGI("Dropping event because there is no touched window or focused application."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + injectionPermission = INJECTION_PERMISSION_UNKNOWN; + goto Failed; + } - ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel); - if (connectionIndex >= 0) { - sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); - prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget, - resumeWithAppendedMotionSample); + // Check permissions. + if (! checkInjectionPermission(newTouchedWindow, entry->injectorPid, entry->injectorUid)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + injectionPermission = INJECTION_PERMISSION_DENIED; + goto Failed; + } + + // If the touched window is paused then keep waiting. + if (newTouchedWindow->paused) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Waiting because touched window is paused."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, newTouchedWindow, nextWakeupTime); + injectionPermission = INJECTION_PERMISSION_GRANTED; + goto Unresponsive; + } + + // Success! Update the touch dispatch state for real. + releaseTouchedWindowLocked(); + + mTouchedWindow = newTouchedWindow; + mTouchedWindowIsObscured = obscured; + + if (newTouchedWindow->hasWallpaper) { + mTouchedWallpaperWindows.appendVector(mWallpaperWindows); + } + } else { + /* Case 2: Everything but ACTION_DOWN */ + + // Check permissions. + if (! checkInjectionPermission(mTouchedWindow, entry->injectorPid, entry->injectorUid)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + injectionPermission = INJECTION_PERMISSION_DENIED; + goto Failed; + } + + // If the pointer is not currently down, then ignore the event. + if (! mTouchDown) { + LOGI("Dropping event because the pointer is not down."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + injectionPermission = INJECTION_PERMISSION_GRANTED; + goto Failed; + } + + // If there is no currently touched window then fail. + if (! mTouchedWindow) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Dropping event because there is no touched window to receive it."); +#endif + injectionResult = INPUT_EVENT_INJECTION_FAILED; + injectionPermission = INJECTION_PERMISSION_GRANTED; + goto Failed; + } + + // If the touched window is paused then keep waiting. + if (mTouchedWindow->paused) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Waiting because touched window is paused."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, mTouchedWindow, nextWakeupTime); + injectionPermission = INJECTION_PERMISSION_GRANTED; + goto Unresponsive; + } + } + + // Success! Output targets. + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + injectionPermission = INJECTION_PERMISSION_GRANTED; + + { + size_t numWallpaperWindows = mTouchedWallpaperWindows.size(); + for (size_t i = 0; i < numWallpaperWindows; i++) { + addWindowTargetLocked(mTouchedWallpaperWindows[i], + InputTarget::FLAG_WINDOW_IS_OBSCURED, 0); + } + + size_t numOutsideTargets = mTempTouchedOutsideTargets.size(); + for (size_t i = 0; i < numOutsideTargets; i++) { + const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i]; + int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE; + if (outsideTarget.obscured) { + outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } + addWindowTargetLocked(outsideTarget.window, outsideTargetFlags, 0); + } + mTempTouchedOutsideTargets.clear(); + + int32_t targetFlags = InputTarget::FLAG_SYNC; + if (mTouchedWindowIsObscured) { + targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + } + addWindowTargetLocked(mTouchedWindow, targetFlags, + getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime)); + *outWindow = mTouchedWindow; + } + +Failed: + // Check injection permission once and for all. + if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) { + if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow, + entry->injectorPid, entry->injectorUid)) { + injectionPermission = INJECTION_PERMISSION_GRANTED; } else { - LOGW("Framework requested delivery of an input event to channel '%s' but it " - "is not registered with the input dispatcher.", - inputTarget.inputChannel->getName().string()); + injectionPermission = INJECTION_PERMISSION_DENIED; } } + + // Update final pieces of touch state if the injector had permission. + if (injectionPermission == INJECTION_PERMISSION_GRANTED) { + if (action == AMOTION_EVENT_ACTION_DOWN) { + if (mTouchDown) { + // This is weird. We got a down but we thought it was already down! + LOGW("Pointer down received while already down."); + } else { + mTouchDown = true; + } + + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + // Since we failed to identify a target for this touch down, we may still + // be holding on to an earlier target from a previous touch down. Release it. + releaseTouchedWindowLocked(); + } + } else if (action == AMOTION_EVENT_ACTION_UP) { + mTouchDown = false; + releaseTouchedWindowLocked(); + } + } else { + LOGW("Not updating touch focus because injection was denied."); + } + +Unresponsive: +#if DEBUG_FOCUS + LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d", + injectionResult, injectionPermission); + logDispatchStateLocked(); +#endif + return injectionResult; +} + +void InputDispatcher::releaseTouchedWindowLocked() { + mTouchedWindow = NULL; + mTouchedWindowIsObscured = false; + mTouchedWallpaperWindows.clear(); +} + +void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, + nsecs_t timeSpentWaitingForApplication) { + mCurrentInputTargets.push(); + + InputTarget& target = mCurrentInputTargets.editTop(); + target.inputChannel = window->inputChannel; + target.flags = targetFlags; + target.timeout = window->dispatchingTimeout; + target.timeSpentWaitingForApplication = timeSpentWaitingForApplication; + target.xOffset = - window->frameLeft; + target.yOffset = - window->frameTop; +} + +void InputDispatcher::addMonitoringTargetsLocked() { + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + mCurrentInputTargets.push(); + + InputTarget& target = mCurrentInputTargets.editTop(); + target.inputChannel = mMonitoringChannels[i]; + target.flags = 0; + target.timeout = -1; + target.timeSpentWaitingForApplication = 0; + target.xOffset = 0; + target.yOffset = 0; + } +} + +bool InputDispatcher::checkInjectionPermission(const InputWindow* window, + int32_t injectorPid, int32_t injectorUid) { + if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) { + bool result = mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid); + if (! result) { + if (window) { + LOGW("Permission denied: injecting event from pid %d uid %d to window " + "with input channel %s owned by uid %d", + injectorPid, injectorUid, window->inputChannel->getName().string(), + window->ownerUid); + } else { + LOGW("Permission denied: injecting event from pid %d uid %d", + injectorPid, injectorUid); + } + return false; + } + } + return true; +} + +bool InputDispatcher::isWindowObscuredLocked(const InputWindow* window) { + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + const InputWindow* other = & mWindows.itemAt(i); + if (other == window) { + break; + } + if (other->visible && window->visibleFrameIntersects(other)) { + return true; + } + } + return false; +} + +void InputDispatcher::pokeUserActivityLocked(nsecs_t eventTime, + int32_t windowType, int32_t eventType) { + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doPokeUserActivityLockedInterruptible); + commandEntry->eventTime = eventTime; + commandEntry->windowType = windowType; + commandEntry->userActivityEventType = eventType; } void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, @@ -523,15 +1258,21 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, "xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s", connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout, inputTarget->xOffset, inputTarget->yOffset, - resumeWithAppendedMotionSample ? "true" : "false"); + toString(resumeWithAppendedMotionSample)); #endif // Skip this event if the connection status is not normal. - // We don't want to queue outbound events at all if the connection is broken or + // We don't want to enqueue additional outbound events if the connection is broken or // not responding. if (connection->status != Connection::STATUS_NORMAL) { - LOGV("channel '%s' ~ Dropping event because the channel status is %s", - connection->getStatusLabel()); + LOGW("channel '%s' ~ Dropping event because the channel status is %s", + connection->getInputChannelName(), connection->getStatusLabel()); + + // If the connection is not responding but the user is poking the application anyways, + // retrigger the original timeout. + if (connection->status == Connection::STATUS_NOT_RESPONDING) { + timeoutDispatchCycleLocked(currentTime, connection); + } return; } @@ -612,17 +1353,45 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, } } + // Bring the input state back in line with reality in case it drifted off during an ANR. + if (connection->inputState.isOutOfSync()) { + mTempCancelationEvents.clear(); + connection->inputState.synthesizeCancelationEvents(& mAllocator, mTempCancelationEvents); + connection->inputState.resetOutOfSync(); + + if (! mTempCancelationEvents.isEmpty()) { + LOGI("channel '%s' ~ Generated %d cancelation events to bring channel back in sync " + "with reality.", + connection->getInputChannelName(), mTempCancelationEvents.size()); + + for (size_t i = 0; i < mTempCancelationEvents.size(); i++) { + EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i); + switch (cancelationEventEntry->type) { + case EventEntry::TYPE_KEY: + logOutboundKeyDetailsLocked(" ", + static_cast<KeyEntry*>(cancelationEventEntry)); + break; + case EventEntry::TYPE_MOTION: + logOutboundMotionDetailsLocked(" ", + static_cast<MotionEntry*>(cancelationEventEntry)); + break; + } + + DispatchEntry* cancelationDispatchEntry = + mAllocator.obtainDispatchEntry(cancelationEventEntry, + 0, inputTarget->xOffset, inputTarget->yOffset, inputTarget->timeout); + connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry); + + mAllocator.releaseEventEntry(cancelationEventEntry); + } + } + } + // This is a new event. // Enqueue a new dispatch entry onto the outbound queue for this connection. - DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref - dispatchEntry->targetFlags = inputTarget->flags; - dispatchEntry->xOffset = inputTarget->xOffset; - dispatchEntry->yOffset = inputTarget->yOffset; - dispatchEntry->timeout = inputTarget->timeout; - dispatchEntry->inProgress = false; - dispatchEntry->headMotionSample = NULL; - dispatchEntry->tailMotionSample = NULL; - + DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref + inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset, + inputTarget->timeout); if (dispatchEntry->isSyncTarget()) { eventEntry->pendingSyncDispatches += 1; } @@ -647,12 +1416,13 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, // If the outbound queue was previously empty, start the dispatch cycle going. if (wasEmpty) { activateConnectionLocked(connection.get()); - startDispatchCycleLocked(currentTime, connection); + startDispatchCycleLocked(currentTime, connection, + inputTarget->timeSpentWaitingForApplication); } } void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, - const sp<Connection>& connection) { + const sp<Connection>& connection, nsecs_t timeSpentWaitingForApplication) { #if DEBUG_DISPATCH_CYCLE LOGD("channel '%s' ~ startDispatchCycle", connection->getInputChannelName()); @@ -661,12 +1431,37 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, assert(connection->status == Connection::STATUS_NORMAL); assert(! connection->outboundQueue.isEmpty()); - DispatchEntry* dispatchEntry = connection->outboundQueue.head.next; + DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; assert(! dispatchEntry->inProgress); - // TODO throttle successive ACTION_MOVE motion events for the same device - // possible implementation could set a brief poll timeout here and resume starting the - // dispatch cycle when elapsed + // Mark the dispatch entry as in progress. + dispatchEntry->inProgress = true; + + // Update the connection's input state. + InputState::Consistency consistency = connection->inputState.trackEvent( + dispatchEntry->eventEntry); + +#if FILTER_INPUT_EVENTS + // Filter out inconsistent sequences of input events. + // The input system may drop or inject events in a way that could violate implicit + // invariants on input state and potentially cause an application to crash + // or think that a key or pointer is stuck down. Technically we make no guarantees + // of consistency but it would be nice to improve on this where possible. + // XXX: This code is a proof of concept only. Not ready for prime time. + if (consistency == InputState::TOLERABLE) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's " + "current input state but that is likely to be tolerated by the application.", + connection->getInputChannelName()); +#endif + } else if (consistency == InputState::BROKEN) { + LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's " + "current input state and that is likely to cause the application to crash.", + connection->getInputChannelName()); + startNextDispatchCycleLocked(currentTime, connection); + return; + } +#endif // Publish the event. status_t status; @@ -790,12 +1585,10 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, } // Record information about the newly started dispatch cycle. - dispatchEntry->inProgress = true; - connection->lastEventTime = dispatchEntry->eventEntry->eventTime; connection->lastDispatchTime = currentTime; - nsecs_t timeout = dispatchEntry->timeout; + nsecs_t timeout = dispatchEntry->timeout - timeSpentWaitingForApplication; connection->setNextTimeoutTime(currentTime, timeout); // Notify other system components. @@ -844,9 +1637,14 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, return; } + startNextDispatchCycleLocked(currentTime, connection); +} + +void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime, + const sp<Connection>& connection) { // Start the next dispatch cycle for this connection. while (! connection->outboundQueue.isEmpty()) { - DispatchEntry* dispatchEntry = connection->outboundQueue.head.next; + DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; if (dispatchEntry->inProgress) { // Finish or resume current event in progress. if (dispatchEntry->tailMotionSample) { @@ -855,7 +1653,7 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, dispatchEntry->inProgress = false; dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample; dispatchEntry->tailMotionSample = NULL; - startDispatchCycleLocked(currentTime, connection); + startDispatchCycleLocked(currentTime, connection, 0); return; } // Finished. @@ -868,7 +1666,7 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, // If the head is not in progress, then we must have already dequeued the in // progress event, which means we actually aborted it (due to ANR). // So just start the next event for this connection. - startDispatchCycleLocked(currentTime, connection); + startDispatchCycleLocked(currentTime, connection, 0); return; } } @@ -884,58 +1682,74 @@ void InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime, connection->getInputChannelName()); #endif - if (connection->status != Connection::STATUS_NORMAL) { + if (connection->status == Connection::STATUS_NORMAL) { + // Enter the not responding state. + connection->status = Connection::STATUS_NOT_RESPONDING; + connection->lastANRTime = currentTime; + } else if (connection->status != Connection::STATUS_NOT_RESPONDING) { + // Connection is broken or dead. return; } - // Enter the not responding state. - connection->status = Connection::STATUS_NOT_RESPONDING; - connection->lastANRTime = currentTime; - // Notify other system components. - // This enqueues a command which will eventually either call - // resumeAfterTimeoutDispatchCycleLocked or abortDispatchCycleLocked. + // This enqueues a command which will eventually call resumeAfterTimeoutDispatchCycleLocked. onDispatchCycleANRLocked(currentTime, connection); } void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, nsecs_t newTimeout) { #if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked", - connection->getInputChannelName()); + LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked - newTimeout=%lld", + connection->getInputChannelName(), newTimeout); #endif if (connection->status != Connection::STATUS_NOT_RESPONDING) { return; } - // Resume normal dispatch. - connection->status = Connection::STATUS_NORMAL; - connection->setNextTimeoutTime(currentTime, newTimeout); + if (newTimeout > 0) { + // The system has decided to give the application some more time. + // Keep waiting synchronously and resume normal dispatch. + connection->status = Connection::STATUS_NORMAL; + connection->setNextTimeoutTime(currentTime, newTimeout); + } else { + // The system is about to throw up an ANR dialog and has requested that we abort dispatch. + // Reset the timeout. + connection->nextTimeoutTime = LONG_LONG_MAX; + + // Input state will no longer be realistic. + connection->inputState.setOutOfSync(); + + if (! connection->outboundQueue.isEmpty()) { + // Make the current pending dispatch asynchronous (if it isn't already) so that + // subsequent events can be delivered to the ANR dialog or to another application. + DispatchEntry* currentDispatchEntry = connection->outboundQueue.headSentinel.next; + currentDispatchEntry->preemptSyncTarget(); + + // Drain all but the first entry in the outbound queue. We keep the first entry + // since that is the one that dispatch is stuck on. We throw away the others + // so that we don't spam the application with stale messages if it eventually + // wakes up and recovers from the ANR. + drainOutboundQueueLocked(connection.get(), currentDispatchEntry->next); + } + } } void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, bool broken) { #if DEBUG_DISPATCH_CYCLE LOGD("channel '%s' ~ abortDispatchCycle - broken=%s", - connection->getInputChannelName(), broken ? "true" : "false"); + connection->getInputChannelName(), toString(broken)); #endif // Clear the pending timeout. connection->nextTimeoutTime = LONG_LONG_MAX; - // Clear the outbound queue. - if (! connection->outboundQueue.isEmpty()) { - do { - DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead(); - if (dispatchEntry->isSyncTarget()) { - decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry); - } - mAllocator.releaseDispatchEntry(dispatchEntry); - } while (! connection->outboundQueue.isEmpty()); + // Input state will no longer be realistic. + connection->inputState.setOutOfSync(); - deactivateConnectionLocked(connection.get()); - } + // Clear the outbound queue. + drainOutboundQueueLocked(connection.get(), connection->outboundQueue.headSentinel.next); // Handle the case where the connection appears to be unrecoverably broken. // Ignore already broken or zombie connections. @@ -950,6 +1764,26 @@ void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, } } +void InputDispatcher::drainOutboundQueueLocked(Connection* connection, + DispatchEntry* firstDispatchEntryToDrain) { + for (DispatchEntry* dispatchEntry = firstDispatchEntryToDrain; + dispatchEntry != & connection->outboundQueue.tailSentinel;) { + DispatchEntry* next = dispatchEntry->next; + connection->outboundQueue.dequeue(dispatchEntry); + + if (dispatchEntry->isSyncTarget()) { + decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry); + } + mAllocator.releaseDispatchEntry(dispatchEntry); + + dispatchEntry = next; + } + + if (connection->outboundQueue.isEmpty()) { + deactivateConnectionLocked(connection); + } +} + bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) { InputDispatcher* d = static_cast<InputDispatcher*>(data); @@ -1000,57 +1834,19 @@ void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) { LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime); #endif - bool wasEmpty; + bool needWake; { // acquire lock AutoMutex _l(mLock); ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime); - - wasEmpty = mInboundQueue.isEmpty(); - mInboundQueue.enqueueAtTail(newEntry); + needWake = enqueueInboundEventLocked(newEntry); } // release lock - if (wasEmpty) { + if (needWake) { mPollLoop->wake(); } } -void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) { -#if DEBUG_INBOUND_EVENT_DETAILS - LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime); -#endif - - // Remove movement keys from the queue from most recent to least recent, stopping at the - // first non-movement key. - // TODO: Include a detailed description of why we do this... - - { // acquire lock - AutoMutex _l(mLock); - - for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) { - EventEntry* prev = entry->prev; - - if (entry->type == EventEntry::TYPE_KEY) { - KeyEntry* keyEntry = static_cast<KeyEntry*>(entry); - if (isMovementKey(keyEntry->keyCode)) { - LOGV("Dropping movement key during app switch: keyCode=%d, action=%d", - keyEntry->keyCode, keyEntry->action); - mInboundQueue.dequeue(keyEntry); - - setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED); - - mAllocator.releaseKeyEntry(keyEntry); - } else { - // stop at last non-movement key - break; - } - } - - entry = prev; - } - } // release lock -} - void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) { @@ -1061,7 +1857,7 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t sou keyCode, scanCode, metaState, downTime); #endif - bool wasEmpty; + bool needWake; { // acquire lock AutoMutex _l(mLock); @@ -1070,11 +1866,10 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t sou deviceId, source, policyFlags, action, flags, keyCode, scanCode, metaState, repeatCount, downTime); - wasEmpty = mInboundQueue.isEmpty(); - mInboundQueue.enqueueAtTail(newEntry); + needWake = enqueueInboundEventLocked(newEntry); } // release lock - if (wasEmpty) { + if (needWake) { mPollLoop->wake(); } } @@ -1101,7 +1896,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t } #endif - bool wasEmpty; + bool needWake; { // acquire lock AutoMutex _l(mLock); @@ -1112,8 +1907,8 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t // Try to append a move sample to the tail of the inbound queue for this device. // Give up if we encounter a non-move motion event for this device since that // means we cannot append any new samples until a new motion event has started. - for (EventEntry* entry = mInboundQueue.tail.prev; - entry != & mInboundQueue.head; entry = entry->prev) { + for (EventEntry* entry = mInboundQueue.tailSentinel.prev; + entry != & mInboundQueue.headSentinel; entry = entry->prev) { if (entry->type != EventEntry::TYPE_MOTION) { // Keep looking for motion events. continue; @@ -1140,18 +1935,6 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t LOGD("Appended motion sample onto batch for most recent " "motion event for this device in the inbound queue."); #endif - - // Sanity check for special case because dispatch is interruptible. - // The dispatch logic is partially interruptible and releases its lock while - // identifying targets. However, as soon as the targets have been identified, - // the dispatcher proceeds to write a dispatch entry into all relevant outbound - // queues and then promptly removes the motion entry from the queue. - // - // Consequently, we should never observe the case where the inbound queue contains - // an in-progress motion entry unless the current input targets are invalid - // (currently being computed). Check for this! - assert(! (motionEntry->dispatchInProgress && mCurrentInputTargetsValid)); - return; // done! } @@ -1178,7 +1961,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t for (size_t i = 0; i < mActiveConnections.size(); i++) { Connection* connection = mActiveConnections.itemAt(i); if (! connection->outboundQueue.isEmpty()) { - DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev; + DispatchEntry* dispatchEntry = connection->outboundQueue.tailSentinel.prev; if (dispatchEntry->isSyncTarget()) { if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) { goto NoBatchingOrStreaming; @@ -1220,11 +2003,10 @@ NoBatchingOrStreaming:; xPrecision, yPrecision, downTime, pointerCount, pointerIds, pointerCoords); - wasEmpty = mInboundQueue.isEmpty(); - mInboundQueue.enqueueAtTail(newEntry); + needWake = enqueueInboundEventLocked(newEntry); } // release lock - if (wasEmpty) { + if (needWake) { mPollLoop->wake(); } } @@ -1240,11 +2022,15 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); EventEntry* injectedEntry; - bool wasEmpty; + bool needWake; { // acquire lock AutoMutex _l(mLock); - injectedEntry = createEntryFromInputEventLocked(event); + injectedEntry = createEntryFromInjectedInputEventLocked(event); + if (! injectedEntry) { + return INPUT_EVENT_INJECTION_FAILED; + } + injectedEntry->refCount += 1; injectedEntry->injectorPid = injectorPid; injectedEntry->injectorUid = injectorUid; @@ -1253,12 +2039,10 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, injectedEntry->injectionIsAsync = true; } - wasEmpty = mInboundQueue.isEmpty(); - mInboundQueue.enqueueAtTail(injectedEntry); - + needWake = enqueueInboundEventLocked(injectedEntry); } // release lock - if (wasEmpty) { + if (needWake) { mPollLoop->wake(); } @@ -1361,11 +2145,42 @@ void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) { } } -InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked( +static bool isValidKeyAction(int32_t action) { + switch (action) { + case AKEY_EVENT_ACTION_DOWN: + case AKEY_EVENT_ACTION_UP: + return true; + default: + return false; + } +} + +static bool isValidMotionAction(int32_t action) { + switch (action & AMOTION_EVENT_ACTION_MASK) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_MOVE: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_OUTSIDE: + return true; + default: + return false; + } +} + +InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventLocked( const InputEvent* event) { switch (event->getType()) { case AINPUT_EVENT_TYPE_KEY: { const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event); + if (! isValidKeyAction(keyEvent->getAction())) { + LOGE("Dropping injected key event since it has invalid action code 0x%x", + keyEvent->getAction()); + return NULL; + } + uint32_t policyFlags = POLICY_FLAG_INJECTED; KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(), @@ -1378,6 +2193,17 @@ InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked( case AINPUT_EVENT_TYPE_MOTION: { const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event); + if (! isValidMotionAction(motionEvent->getAction())) { + LOGE("Dropping injected motion event since it has invalid action code 0x%x.", + motionEvent->getAction()); + return NULL; + } + if (motionEvent->getPointerCount() == 0 + || motionEvent->getPointerCount() > MAX_POINTERS) { + LOGE("Dropping injected motion event since it has an invalid pointer count %d.", + motionEvent->getPointerCount()); + } + uint32_t policyFlags = POLICY_FLAG_INJECTED; const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes(); @@ -1405,33 +2231,143 @@ InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked( } } -void InputDispatcher::resetKeyRepeatLocked() { - if (mKeyRepeatState.lastKeyEntry) { - mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry); - mKeyRepeatState.lastKeyEntry = NULL; +void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { +#if DEBUG_FOCUS + LOGD("setInputWindows"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + sp<InputChannel> touchedWindowChannel; + if (mTouchedWindow) { + touchedWindowChannel = mTouchedWindow->inputChannel; + mTouchedWindow = NULL; + } + size_t numTouchedWallpapers = mTouchedWallpaperWindows.size(); + if (numTouchedWallpapers != 0) { + for (size_t i = 0; i < numTouchedWallpapers; i++) { + mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel); + } + mTouchedWallpaperWindows.clear(); + } + + bool hadFocusedWindow = mFocusedWindow != NULL; + + mFocusedWindow = NULL; + mWallpaperWindows.clear(); + + mWindows.clear(); + mWindows.appendVector(inputWindows); + + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + InputWindow* window = & mWindows.editItemAt(i); + if (window->hasFocus) { + mFocusedWindow = window; + } + + if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) { + mWallpaperWindows.push(window); + + for (size_t j = 0; j < numTouchedWallpapers; j++) { + if (window->inputChannel == mTempTouchedWallpaperChannels[i]) { + mTouchedWallpaperWindows.push(window); + } + } + } + + if (window->inputChannel == touchedWindowChannel) { + mTouchedWindow = window; + } + } + + mTempTouchedWallpaperChannels.clear(); + + if ((hadFocusedWindow && ! mFocusedWindow) + || (mFocusedWindow && ! mFocusedWindow->visible)) { + preemptInputDispatchInnerLocked(); + } + +#if DEBUG_FOCUS + logDispatchStateLocked(); +#endif + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mPollLoop->wake(); +} + +void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) { +#if DEBUG_FOCUS + LOGD("setFocusedApplication"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + releaseFocusedApplicationLocked(); + + if (inputApplication) { + mFocusedApplicationStorage = *inputApplication; + mFocusedApplication = & mFocusedApplicationStorage; + } + +#if DEBUG_FOCUS + logDispatchStateLocked(); +#endif + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mPollLoop->wake(); +} + +void InputDispatcher::releaseFocusedApplicationLocked() { + if (mFocusedApplication) { + mFocusedApplication = NULL; + mFocusedApplicationStorage.handle.clear(); } } -void InputDispatcher::preemptInputDispatch() { -#if DEBUG_DISPATCH_CYCLE - LOGD("preemptInputDispatch"); +void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { +#if DEBUG_FOCUS + LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen); #endif - bool preemptedOne = false; + bool changed; { // acquire lock AutoMutex _l(mLock); - for (size_t i = 0; i < mActiveConnections.size(); i++) { - Connection* connection = mActiveConnections[i]; - if (connection->hasPendingSyncTarget()) { -#if DEBUG_DISPATCH_CYCLE - LOGD("channel '%s' ~ Preempted pending synchronous dispatch", - connection->getInputChannelName()); -#endif - connection->outboundQueue.tail.prev->targetFlags &= ~ InputTarget::FLAG_SYNC; - preemptedOne = true; + if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) { + if (mDispatchFrozen && ! frozen) { + resetANRTimeoutsLocked(); } + + mDispatchEnabled = enabled; + mDispatchFrozen = frozen; + changed = true; + } else { + changed = false; } + +#if DEBUG_FOCUS + logDispatchStateLocked(); +#endif + } // release lock + + if (changed) { + // Wake up poll loop since it may need to make new input dispatching choices. + mPollLoop->wake(); + } +} + +void InputDispatcher::preemptInputDispatch() { +#if DEBUG_FOCUS + LOGD("preemptInputDispatch"); +#endif + + bool preemptedOne; + { // acquire lock + AutoMutex _l(mLock); + preemptedOne = preemptInputDispatchInnerLocked(); } // release lock if (preemptedOne) { @@ -1440,9 +2376,99 @@ void InputDispatcher::preemptInputDispatch() { } } -status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) { +bool InputDispatcher::preemptInputDispatchInnerLocked() { + bool preemptedOne = false; + for (size_t i = 0; i < mActiveConnections.size(); i++) { + Connection* connection = mActiveConnections[i]; + if (connection->hasPendingSyncTarget()) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Preempted pending synchronous dispatch", + connection->getInputChannelName()); +#endif + connection->preemptSyncTarget(); + preemptedOne = true; + } + } + return preemptedOne; +} + +void InputDispatcher::logDispatchStateLocked() { + String8 dump; + dumpDispatchStateLocked(dump); + LOGD("%s", dump.string()); +} + +void InputDispatcher::dumpDispatchStateLocked(String8& dump) { + dump.appendFormat(" dispatchEnabled: %d\n", mDispatchEnabled); + dump.appendFormat(" dispatchFrozen: %d\n", mDispatchFrozen); + + if (mFocusedApplication) { + dump.appendFormat(" focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n", + mFocusedApplication->name.string(), + mFocusedApplication->dispatchingTimeout / 1000000.0); + } else { + dump.append(" focusedApplication: <null>\n"); + } + dump.appendFormat(" focusedWindow: '%s'\n", + mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>"); + dump.appendFormat(" touchedWindow: '%s', touchDown=%d\n", + mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>", + mTouchDown); + for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) { + dump.appendFormat(" touchedWallpaperWindows[%d]: '%s'\n", + i, mTouchedWallpaperWindows[i]->inputChannel->getName().string()); + } + for (size_t i = 0; i < mWindows.size(); i++) { + dump.appendFormat(" windows[%d]: '%s', paused=%s, hasFocus=%s, hasWallpaper=%s, " + "visible=%s, flags=0x%08x, type=0x%08x, " + "frame=[%d,%d][%d,%d], " + "visibleFrame=[%d,%d][%d,%d], " + "touchableArea=[%d,%d][%d,%d], " + "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n", + i, mWindows[i].inputChannel->getName().string(), + toString(mWindows[i].paused), + toString(mWindows[i].hasFocus), + toString(mWindows[i].hasWallpaper), + toString(mWindows[i].visible), + mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType, + mWindows[i].frameLeft, mWindows[i].frameTop, + mWindows[i].frameRight, mWindows[i].frameBottom, + mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop, + mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom, + mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop, + mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom, + mWindows[i].ownerPid, mWindows[i].ownerUid, + mWindows[i].dispatchingTimeout / 1000000.0); + } + + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + const sp<InputChannel>& channel = mMonitoringChannels[i]; + dump.appendFormat(" monitoringChannel[%d]: '%s'\n", + i, channel->getName().string()); + } + + for (size_t i = 0; i < mActiveConnections.size(); i++) { + const Connection* connection = mActiveConnections[i]; + dump.appendFormat(" activeConnection[%d]: '%s', status=%s, hasPendingSyncTarget=%s, " + "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n", + i, connection->getInputChannelName(), connection->getStatusLabel(), + toString(connection->hasPendingSyncTarget()), + toString(connection->inputState.isNeutral()), + toString(connection->inputState.isOutOfSync())); + } + + if (isAppSwitchPendingLocked()) { + dump.appendFormat(" appSwitch: pending, due in %01.1fms\n", + (mAppSwitchDueTime - now()) / 1000000.0); + } else { + dump.append(" appSwitch: not pending\n"); + } +} + +status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) { #if DEBUG_REGISTRATION - LOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().string()); + LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(), + toString(monitor)); #endif { // acquire lock @@ -1465,6 +2491,10 @@ status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChan int32_t receiveFd = inputChannel->getReceivePipeFd(); mConnectionsByReceiveFd.add(receiveFd, connection); + if (monitor) { + mMonitoringChannels.push(inputChannel); + } + mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); runCommandsLockedInterruptible(); @@ -1492,6 +2522,13 @@ status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputCh connection->status = Connection::STATUS_ZOMBIE; + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + if (mMonitoringChannels[i] == inputChannel) { + mMonitoringChannels.removeAt(i); + break; + } + } + mPollLoop->removeCallback(inputChannel->getReceivePipeFd()); nsecs_t currentTime = now(); @@ -1578,6 +2615,15 @@ void InputDispatcher::onDispatchCycleBrokenLocked( commandEntry->connection = connection; } +void InputDispatcher::doNotifyConfigurationChangedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->notifyConfigurationChanged(commandEntry->eventTime); + + mLock.lock(); +} + void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible( CommandEntry* commandEntry) { sp<Connection> connection = commandEntry->connection; @@ -1598,17 +2644,12 @@ void InputDispatcher::doNotifyInputChannelANRLockedInterruptible( if (connection->status != Connection::STATUS_ZOMBIE) { mLock.unlock(); - nsecs_t newTimeout; - bool resume = mPolicy->notifyInputChannelANR(connection->inputChannel, newTimeout); + nsecs_t newTimeout = mPolicy->notifyInputChannelANR(connection->inputChannel); mLock.lock(); nsecs_t currentTime = now(); - if (resume) { - resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout); - } else { - abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/); - } + resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout); } } @@ -1625,6 +2666,57 @@ void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible( } } +void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( + CommandEntry* commandEntry) { + KeyEntry* entry = commandEntry->keyEntry; + mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags, + entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, + entry->downTime, entry->eventTime); + + mLock.unlock(); + + bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputChannel, + & mReusableKeyEvent, entry->policyFlags); + + mLock.lock(); + + entry->interceptKeyResult = consumed + ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP + : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + mAllocator.releaseKeyEntry(entry); +} + +void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) { + mLock.unlock(); + + mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->windowType, + commandEntry->userActivityEventType); + + mLock.lock(); +} + +void InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible( + CommandEntry* commandEntry) { + mLock.unlock(); + + nsecs_t newTimeout; + if (commandEntry->inputChannel.get()) { + newTimeout = mPolicy->notifyInputChannelANR(commandEntry->inputChannel); + } else if (commandEntry->inputApplicationHandle.get()) { + newTimeout = mPolicy->notifyANR(commandEntry->inputApplicationHandle); + } else { + newTimeout = 0; + } + + mLock.lock(); + + resumeAfterTargetsNotReadyTimeoutLocked(newTimeout); +} + +void InputDispatcher::dump(String8& dump) { + dumpDispatchStateLocked(dump); +} + // --- InputDispatcher::Allocator --- @@ -1668,6 +2760,8 @@ InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t ev entry->metaState = metaState; entry->repeatCount = repeatCount; entry->downTime = downTime; + entry->syntheticRepeat = false; + entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; return entry; } @@ -1702,10 +2796,18 @@ InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsec } InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry( - EventEntry* eventEntry) { + EventEntry* eventEntry, + int32_t targetFlags, float xOffset, float yOffset, nsecs_t timeout) { DispatchEntry* entry = mDispatchEntryPool.alloc(); entry->eventEntry = eventEntry; eventEntry->refCount += 1; + entry->targetFlags = targetFlags; + entry->xOffset = xOffset; + entry->yOffset = yOffset; + entry->timeout = timeout; + entry->inProgress = false; + entry->headMotionSample = NULL; + entry->tailMotionSample = NULL; return entry; } @@ -1788,6 +2890,25 @@ void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, motionEntry->lastSample = sample; } + +// --- InputDispatcher::EventEntry --- + +void InputDispatcher::EventEntry::recycle() { + injectionResult = INPUT_EVENT_INJECTION_PENDING; + dispatchInProgress = false; + pendingSyncDispatches = 0; +} + + +// --- InputDispatcher::KeyEntry --- + +void InputDispatcher::KeyEntry::recycle() { + EventEntry::recycle(); + syntheticRepeat = false; + interceptKeyResult = INTERCEPT_KEY_RESULT_UNKNOWN; +} + + // --- InputDispatcher::MotionEntry --- uint32_t InputDispatcher::MotionEntry::countSamples() const { @@ -1798,6 +2919,189 @@ uint32_t InputDispatcher::MotionEntry::countSamples() const { return count; } + +// --- InputDispatcher::InputState --- + +InputDispatcher::InputState::InputState() : + mIsOutOfSync(false) { +} + +InputDispatcher::InputState::~InputState() { +} + +bool InputDispatcher::InputState::isNeutral() const { + return mKeyMementos.isEmpty() && mMotionMementos.isEmpty(); +} + +bool InputDispatcher::InputState::isOutOfSync() const { + return mIsOutOfSync; +} + +void InputDispatcher::InputState::setOutOfSync() { + if (! isNeutral()) { + mIsOutOfSync = true; + } +} + +void InputDispatcher::InputState::resetOutOfSync() { + mIsOutOfSync = false; +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent( + const EventEntry* entry) { + switch (entry->type) { + case EventEntry::TYPE_KEY: + return trackKey(static_cast<const KeyEntry*>(entry)); + + case EventEntry::TYPE_MOTION: + return trackMotion(static_cast<const MotionEntry*>(entry)); + + default: + return CONSISTENT; + } +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey( + const KeyEntry* entry) { + int32_t action = entry->action; + for (size_t i = 0; i < mKeyMementos.size(); i++) { + KeyMemento& memento = mKeyMementos.editItemAt(i); + if (memento.deviceId == entry->deviceId + && memento.source == entry->source + && memento.keyCode == entry->keyCode + && memento.scanCode == entry->scanCode) { + switch (action) { + case AKEY_EVENT_ACTION_UP: + mKeyMementos.removeAt(i); + if (isNeutral()) { + mIsOutOfSync = false; + } + return CONSISTENT; + + case AKEY_EVENT_ACTION_DOWN: + return TOLERABLE; + + default: + return BROKEN; + } + } + } + + switch (action) { + case AKEY_EVENT_ACTION_DOWN: { + mKeyMementos.push(); + KeyMemento& memento = mKeyMementos.editTop(); + memento.deviceId = entry->deviceId; + memento.source = entry->source; + memento.keyCode = entry->keyCode; + memento.scanCode = entry->scanCode; + memento.downTime = entry->downTime; + return CONSISTENT; + } + + default: + return BROKEN; + } +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion( + const MotionEntry* entry) { + int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK; + for (size_t i = 0; i < mMotionMementos.size(); i++) { + MotionMemento& memento = mMotionMementos.editItemAt(i); + if (memento.deviceId == entry->deviceId + && memento.source == entry->source) { + switch (action) { + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_CANCEL: + mMotionMementos.removeAt(i); + if (isNeutral()) { + mIsOutOfSync = false; + } + return CONSISTENT; + + case AMOTION_EVENT_ACTION_DOWN: + return TOLERABLE; + + case AMOTION_EVENT_ACTION_POINTER_DOWN: + if (entry->pointerCount == memento.pointerCount + 1) { + memento.setPointers(entry); + return CONSISTENT; + } + return BROKEN; + + case AMOTION_EVENT_ACTION_POINTER_UP: + if (entry->pointerCount == memento.pointerCount - 1) { + memento.setPointers(entry); + return CONSISTENT; + } + return BROKEN; + + case AMOTION_EVENT_ACTION_MOVE: + if (entry->pointerCount == memento.pointerCount) { + return CONSISTENT; + } + return BROKEN; + + default: + return BROKEN; + } + } + } + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: { + mMotionMementos.push(); + MotionMemento& memento = mMotionMementos.editTop(); + memento.deviceId = entry->deviceId; + memento.source = entry->source; + memento.xPrecision = entry->xPrecision; + memento.yPrecision = entry->yPrecision; + memento.downTime = entry->downTime; + memento.setPointers(entry); + return CONSISTENT; + } + + default: + return BROKEN; + } +} + +void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) { + pointerCount = entry->pointerCount; + for (uint32_t i = 0; i < entry->pointerCount; i++) { + pointerIds[i] = entry->pointerIds[i]; + pointerCoords[i] = entry->lastSample->pointerCoords[i]; + } +} + +void InputDispatcher::InputState::synthesizeCancelationEvents( + Allocator* allocator, Vector<EventEntry*>& outEvents) const { + for (size_t i = 0; i < mKeyMementos.size(); i++) { + const KeyMemento& memento = mKeyMementos.itemAt(i); + outEvents.push(allocator->obtainKeyEntry(now(), + memento.deviceId, memento.source, 0, + AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_CANCELED, + memento.keyCode, memento.scanCode, 0, 0, memento.downTime)); + } + + for (size_t i = 0; i < mMotionMementos.size(); i++) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + outEvents.push(allocator->obtainMotionEntry(now(), + memento.deviceId, memento.source, 0, + AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0, + memento.xPrecision, memento.yPrecision, memento.downTime, + memento.pointerCount, memento.pointerIds, memento.pointerCoords)); + } +} + +void InputDispatcher::InputState::clear() { + mKeyMementos.clear(); + mMotionMementos.clear(); + mIsOutOfSync = false; +} + + // --- InputDispatcher::Connection --- InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) : @@ -1818,6 +3122,14 @@ void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_ nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX; } +void InputDispatcher::Connection::resetTimeout(nsecs_t currentTime) { + if (outboundQueue.isEmpty()) { + nextTimeoutTime = LONG_LONG_MAX; + } else { + setNextTimeoutTime(currentTime, outboundQueue.headSentinel.next->timeout); + } +} + const char* InputDispatcher::Connection::getStatusLabel() const { switch (status) { case STATUS_NORMAL: @@ -1839,8 +3151,8 @@ const char* InputDispatcher::Connection::getStatusLabel() const { InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent( const EventEntry* eventEntry) const { - for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev; - dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) { + for (DispatchEntry* dispatchEntry = outboundQueue.tailSentinel.prev; + dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) { if (dispatchEntry->eventEntry == eventEntry) { return dispatchEntry; } @@ -1848,9 +3160,11 @@ InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchE return NULL; } + // --- InputDispatcher::CommandEntry --- -InputDispatcher::CommandEntry::CommandEntry() { +InputDispatcher::CommandEntry::CommandEntry() : + keyEntry(NULL) { } InputDispatcher::CommandEntry::~CommandEntry() { diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp index ed4f07b..09fce38 100644 --- a/libs/ui/InputManager.cpp +++ b/libs/ui/InputManager.cpp @@ -72,52 +72,12 @@ status_t InputManager::stop() { return OK; } -status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) { - return mDispatcher->registerInputChannel(inputChannel); +sp<InputReaderInterface> InputManager::getReader() { + return mReader; } -status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) { - return mDispatcher->unregisterInputChannel(inputChannel); -} - -int32_t InputManager::injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { - return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); -} - -void InputManager::preemptInputDispatch() { - mDispatcher->preemptInputDispatch(); -} - -void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) { - mReader->getInputConfiguration(outConfiguration); -} - -status_t InputManager::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) { - return mReader->getInputDeviceInfo(deviceId, outDeviceInfo); -} - -void InputManager::getInputDeviceIds(Vector<int32_t>& outDeviceIds) { - mReader->getInputDeviceIds(outDeviceIds); -} - -int32_t InputManager::getScanCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t scanCode) { - return mReader->getScanCodeState(deviceId, sourceMask, scanCode); -} - -int32_t InputManager::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t keyCode) { - return mReader->getKeyCodeState(deviceId, sourceMask, keyCode); -} - -int32_t InputManager::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) { - return mReader->getSwitchState(deviceId, sourceMask, sw); -} - -bool InputManager::hasKeys(int32_t deviceId, uint32_t sourceMask, - size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) { - return mReader->hasKeys(deviceId, sourceMask, numCodes, keyCodes, outFlags); +sp<InputDispatcherInterface> InputManager::getDispatcher() { + return mDispatcher; } } // namespace android diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp index d57b38c..88084c0 100644 --- a/libs/ui/InputReader.cpp +++ b/libs/ui/InputReader.cpp @@ -573,6 +573,60 @@ bool InputReader::markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, s } // release device registy reader lock } +void InputReader::dump(String8& dump) { + dumpDeviceInfo(dump); +} + +static void dumpMotionRange(String8& dump, + const char* name, const InputDeviceInfo::MotionRange* range) { + if (range) { + dump.appendFormat(" %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n", + name, range->min, range->max, range->flat, range->fuzz); + } +} + +#define DUMP_MOTION_RANGE(range) \ + dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range)); + +void InputReader::dumpDeviceInfo(String8& dump) { + Vector<int32_t> deviceIds; + getInputDeviceIds(deviceIds); + + InputDeviceInfo deviceInfo; + for (size_t i = 0; i < deviceIds.size(); i++) { + int32_t deviceId = deviceIds[i]; + + status_t result = getInputDeviceInfo(deviceId, & deviceInfo); + if (result == NAME_NOT_FOUND) { + continue; + } else if (result != OK) { + dump.appendFormat(" ** Unexpected error %d getting information about input devices.\n", + result); + continue; + } + + dump.appendFormat(" Device %d: '%s'\n", + deviceInfo.getId(), deviceInfo.getName().string()); + dump.appendFormat(" sources = 0x%08x\n", + deviceInfo.getSources()); + dump.appendFormat(" keyboardType = %d\n", + deviceInfo.getKeyboardType()); + + dump.append(" motion ranges:\n"); + DUMP_MOTION_RANGE(X); + DUMP_MOTION_RANGE(Y); + DUMP_MOTION_RANGE(PRESSURE); + DUMP_MOTION_RANGE(SIZE); + DUMP_MOTION_RANGE(TOUCH_MAJOR); + DUMP_MOTION_RANGE(TOUCH_MINOR); + DUMP_MOTION_RANGE(TOOL_MAJOR); + DUMP_MOTION_RANGE(TOOL_MINOR); + DUMP_MOTION_RANGE(ORIENTATION); + } +} + +#undef DUMP_MOTION_RANGE + // --- InputReaderThread --- @@ -740,10 +794,6 @@ int32_t InputMapper::getMetaState() { } bool InputMapper::applyStandardPolicyActions(nsecs_t when, int32_t policyActions) { - if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) { - getDispatcher()->notifyAppSwitchComing(when); - } - return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH; } @@ -1249,20 +1299,12 @@ void TouchInputMapper::initializeLocked() { mLocked.orientedRanges.haveOrientation = false; } -static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) { - if (axis.valid) { - LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d", - name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz); - } else { - LOGI(INDENT "Raw %s axis: unknown range", name); - } -} - void TouchInputMapper::configure() { InputMapper::configure(); // Configure basic parameters. configureParameters(); + logParameters(); // Configure absolute axis information. configureRawAxes(); @@ -1287,6 +1329,18 @@ void TouchInputMapper::configureParameters() { mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents(); } +void TouchInputMapper::logParameters() { + if (mParameters.useBadTouchFilter) { + LOGI(INDENT "Bad touch filter enabled."); + } + if (mParameters.useAveragingTouchFilter) { + LOGI(INDENT "Averaging touch filter enabled."); + } + if (mParameters.useJumpyTouchFilter) { + LOGI(INDENT "Jumpy touch filter enabled."); + } +} + void TouchInputMapper::configureRawAxes() { mRawAxes.x.clear(); mRawAxes.y.clear(); @@ -1298,6 +1352,15 @@ void TouchInputMapper::configureRawAxes() { mRawAxes.orientation.clear(); } +static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) { + if (axis.valid) { + LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d", + name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz); + } else { + LOGI(INDENT "Raw %s axis: unknown range", name); + } +} + void TouchInputMapper::logRawAxes() { logAxisInfo(mRawAxes.x, "x"); logAxisInfo(mRawAxes.y, "y"); @@ -1331,8 +1394,10 @@ bool TouchInputMapper::configureSurfaceLocked() { bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height; if (sizeChanged) { - LOGI("Device configured: id=0x%x, name=%s (display size was changed)", + LOGI("Device reconfigured (display size changed): id=0x%x, name=%s", getDeviceId(), getDeviceName().string()); + LOGI(INDENT "Width: %dpx", width); + LOGI(INDENT "Height: %dpx", height); mLocked.surfaceWidth = width; mLocked.surfaceHeight = height; @@ -1500,9 +1565,41 @@ bool TouchInputMapper::configureSurfaceLocked() { mLocked.orientedRanges.y.fuzz = orientedYScale; } + if (sizeChanged) { + logMotionRangesLocked(); + } + return true; } +static void logMotionRangeInfo(InputDeviceInfo::MotionRange* range, const char* name) { + if (range) { + LOGI(INDENT "Output %s range: min=%f, max=%f, flat=%f, fuzz=%f", + name, range->min, range->max, range->flat, range->fuzz); + } else { + LOGI(INDENT "Output %s range: unsupported", name); + } +} + +void TouchInputMapper::logMotionRangesLocked() { + logMotionRangeInfo(& mLocked.orientedRanges.x, "x"); + logMotionRangeInfo(& mLocked.orientedRanges.y, "y"); + logMotionRangeInfo(mLocked.orientedRanges.havePressure + ? & mLocked.orientedRanges.pressure : NULL, "pressure"); + logMotionRangeInfo(mLocked.orientedRanges.haveSize + ? & mLocked.orientedRanges.size : NULL, "size"); + logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea + ? & mLocked.orientedRanges.touchMajor : NULL, "touchMajor"); + logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea + ? & mLocked.orientedRanges.touchMinor : NULL, "touchMinor"); + logMotionRangeInfo(mLocked.orientedRanges.haveToolArea + ? & mLocked.orientedRanges.toolMajor : NULL, "toolMajor"); + logMotionRangeInfo(mLocked.orientedRanges.haveToolArea + ? & mLocked.orientedRanges.toolMinor : NULL, "toolMinor"); + logMotionRangeInfo(mLocked.orientedRanges.haveOrientation + ? & mLocked.orientedRanges.orientation : NULL, "orientation"); +} + void TouchInputMapper::configureVirtualKeysLocked() { assert(mRawAxes.x.valid && mRawAxes.y.valid); @@ -1768,16 +1865,18 @@ void TouchInputMapper::resolveCalibration() { } void TouchInputMapper::logCalibration() { + LOGI(INDENT "Calibration:"); + // Touch Area switch (mCalibration.touchAreaCalibration) { case Calibration::TOUCH_AREA_CALIBRATION_NONE: - LOGI(INDENT " touch.touchArea.calibration: none"); + LOGI(INDENT INDENT "touch.touchArea.calibration: none"); break; case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC: - LOGI(INDENT " touch.touchArea.calibration: geometric"); + LOGI(INDENT INDENT "touch.touchArea.calibration: geometric"); break; case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE: - LOGI(INDENT " touch.touchArea.calibration: pressure"); + LOGI(INDENT INDENT "touch.touchArea.calibration: pressure"); break; default: assert(false); @@ -1786,40 +1885,40 @@ void TouchInputMapper::logCalibration() { // Tool Area switch (mCalibration.toolAreaCalibration) { case Calibration::TOOL_AREA_CALIBRATION_NONE: - LOGI(INDENT " touch.toolArea.calibration: none"); + LOGI(INDENT INDENT "touch.toolArea.calibration: none"); break; case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC: - LOGI(INDENT " touch.toolArea.calibration: geometric"); + LOGI(INDENT INDENT "touch.toolArea.calibration: geometric"); break; case Calibration::TOOL_AREA_CALIBRATION_LINEAR: - LOGI(INDENT " touch.toolArea.calibration: linear"); + LOGI(INDENT INDENT "touch.toolArea.calibration: linear"); break; default: assert(false); } if (mCalibration.haveToolAreaLinearScale) { - LOGI(INDENT " touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale); + LOGI(INDENT INDENT "touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale); } if (mCalibration.haveToolAreaLinearBias) { - LOGI(INDENT " touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias); + LOGI(INDENT INDENT "touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias); } if (mCalibration.haveToolAreaIsSummed) { - LOGI(INDENT " touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed); + LOGI(INDENT INDENT "touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed); } // Pressure switch (mCalibration.pressureCalibration) { case Calibration::PRESSURE_CALIBRATION_NONE: - LOGI(INDENT " touch.pressure.calibration: none"); + LOGI(INDENT INDENT "touch.pressure.calibration: none"); break; case Calibration::PRESSURE_CALIBRATION_PHYSICAL: - LOGI(INDENT " touch.pressure.calibration: physical"); + LOGI(INDENT INDENT "touch.pressure.calibration: physical"); break; case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: - LOGI(INDENT " touch.pressure.calibration: amplitude"); + LOGI(INDENT INDENT "touch.pressure.calibration: amplitude"); break; default: assert(false); @@ -1827,10 +1926,10 @@ void TouchInputMapper::logCalibration() { switch (mCalibration.pressureSource) { case Calibration::PRESSURE_SOURCE_PRESSURE: - LOGI(INDENT " touch.pressure.source: pressure"); + LOGI(INDENT INDENT "touch.pressure.source: pressure"); break; case Calibration::PRESSURE_SOURCE_TOUCH: - LOGI(INDENT " touch.pressure.source: touch"); + LOGI(INDENT INDENT "touch.pressure.source: touch"); break; case Calibration::PRESSURE_SOURCE_DEFAULT: break; @@ -1839,16 +1938,16 @@ void TouchInputMapper::logCalibration() { } if (mCalibration.havePressureScale) { - LOGI(INDENT " touch.pressure.scale: %f", mCalibration.pressureScale); + LOGI(INDENT INDENT "touch.pressure.scale: %f", mCalibration.pressureScale); } // Size switch (mCalibration.sizeCalibration) { case Calibration::SIZE_CALIBRATION_NONE: - LOGI(INDENT " touch.size.calibration: none"); + LOGI(INDENT INDENT "touch.size.calibration: none"); break; case Calibration::SIZE_CALIBRATION_NORMALIZED: - LOGI(INDENT " touch.size.calibration: normalized"); + LOGI(INDENT INDENT "touch.size.calibration: normalized"); break; default: assert(false); @@ -1857,10 +1956,10 @@ void TouchInputMapper::logCalibration() { // Orientation switch (mCalibration.orientationCalibration) { case Calibration::ORIENTATION_CALIBRATION_NONE: - LOGI(INDENT " touch.orientation.calibration: none"); + LOGI(INDENT INDENT "touch.orientation.calibration: none"); break; case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: - LOGI(INDENT " touch.orientation.calibration: interpolated"); + LOGI(INDENT INDENT "touch.orientation.calibration: interpolated"); break; default: assert(false); diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp index 6d3eeee..fe76cd0 100644 --- a/libs/utils/PollLoop.cpp +++ b/libs/utils/PollLoop.cpp @@ -119,7 +119,8 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) { if (outData != NULL) *outData = pending.data; return pending.ident; } - + + // Wait for wakeAndLock() waiters to run then set mPolling to true. mLock.lock(); while (mWaiters != 0) { mResume.wait(mLock); @@ -127,6 +128,7 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) { mPolling = true; mLock.unlock(); + // Poll. int32_t result; size_t requestedCount = mRequestedFds.size(); @@ -168,6 +170,7 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) { } #endif + // Process the poll results. mPendingCallbacks.clear(); mPendingFds.clear(); mPendingFdsPos = 0; @@ -218,6 +221,7 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) { } Done: + // Set mPolling to false and wake up the wakeAndLock() waiters. mLock.lock(); mPolling = false; if (mWaiters != 0) { @@ -357,11 +361,13 @@ ssize_t PollLoop::getRequestIndexLocked(int fd) { void PollLoop::wakeAndLock() { mLock.lock(); + mWaiters += 1; while (mPolling) { wake(); mAwake.wait(mLock); } + mWaiters -= 1; if (mWaiters == 0) { mResume.signal(); diff --git a/location/java/android/location/ILocationProvider.aidl b/location/java/android/location/ILocationProvider.aidl index 2b9782a..ecf6789 100644 --- a/location/java/android/location/ILocationProvider.aidl +++ b/location/java/android/location/ILocationProvider.aidl @@ -20,6 +20,7 @@ import android.location.Criteria; import android.location.Location; import android.net.NetworkInfo; import android.os.Bundle; +import android.os.WorkSource; /** * Binder interface for services that implement location providers. @@ -43,7 +44,7 @@ interface ILocationProvider { long getStatusUpdateTime(); String getInternalState(); void enableLocationTracking(boolean enable); - void setMinTime(long minTime); + void setMinTime(long minTime, in WorkSource ws); void updateNetworkState(int state, in NetworkInfo info); void updateLocation(in Location location); boolean sendExtraCommand(String command, inout Bundle extras); diff --git a/location/java/android/location/provider/LocationProvider.java b/location/java/android/location/provider/LocationProvider.java index cf939de..95b4425 100644 --- a/location/java/android/location/provider/LocationProvider.java +++ b/location/java/android/location/provider/LocationProvider.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.WorkSource; import android.util.Log; /** @@ -106,8 +107,8 @@ public abstract class LocationProvider { LocationProvider.this.onEnableLocationTracking(enable); } - public void setMinTime(long minTime) { - LocationProvider.this.onSetMinTime(minTime); + public void setMinTime(long minTime, WorkSource ws) { + LocationProvider.this.onSetMinTime(minTime, ws); } public void updateNetworkState(int state, NetworkInfo info) { @@ -123,11 +124,11 @@ public abstract class LocationProvider { } public void addListener(int uid) { - LocationProvider.this.onAddListener(uid); + LocationProvider.this.onAddListener(uid, new WorkSource(uid)); } public void removeListener(int uid) { - LocationProvider.this.onRemoveListener(uid); + LocationProvider.this.onRemoveListener(uid, new WorkSource(uid)); } }; @@ -303,8 +304,9 @@ public abstract class LocationProvider { * the frequency of updates to match the requested frequency. * * @param minTime the smallest minTime value over all listeners for this provider. + * @param ws the source this work is coming from. */ - public abstract void onSetMinTime(long minTime); + public abstract void onSetMinTime(long minTime, WorkSource ws); /** * Updates the network state for the given provider. This function must @@ -340,14 +342,16 @@ public abstract class LocationProvider { * Notifies the location provider when a new client is listening for locations. * * @param uid user ID of the new client. + * @param ws a WorkSource representation of the client. */ - public abstract void onAddListener(int uid); + public abstract void onAddListener(int uid, WorkSource ws); /** * Notifies the location provider when a client is no longer listening for locations. * * @param uid user ID of the client no longer listening. + * @param ws a WorkSource representation of the client. */ - public abstract void onRemoveListener(int uid); + public abstract void onRemoveListener(int uid, WorkSource ws); } diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java index a27df57..daa976f 100644 --- a/media/java/android/media/CamcorderProfile.java +++ b/media/java/android/media/CamcorderProfile.java @@ -39,22 +39,80 @@ package android.media; */ public class CamcorderProfile { + // Do not change these values/ordinals without updating their counterpart + // in include/media/MediaProfiles.h! + /** - * The output from camcorder recording sessions can have different quality levels. - * - * Currently, we define two quality levels: high quality and low quality. - * A camcorder recording session with high quality level usually has higher output bit - * rate, better video and/or audio recording quality, larger video frame - * resolution and higher audio sampling rate, etc, than those with low quality - * level. - * - * Do not change these values/ordinals without updating their counterpart - * in include/media/MediaProfiles.h! + * Quality level corresponding to the lowest available resolution. */ public static final int QUALITY_LOW = 0; + + /** + * Quality level corresponding to the highest available resolution. + */ public static final int QUALITY_HIGH = 1; /** + * Quality level corresponding to the qcif (176 x 144) resolution. + */ + public static final int QUALITY_QCIF = 2; + + /** + * Quality level corresponding to the cif (352 x 288) resolution. + */ + public static final int QUALITY_CIF = 3; + + /** + * Quality level corresponding to the 480p (720 x 480) resolution. + */ + public static final int QUALITY_480P = 4; + + /** + * Quality level corresponding to the 720p (1280 x 720) resolution. + */ + public static final int QUALITY_720P = 5; + + /** + * Quality level corresponding to the 1080p (1920 x 1088) resolution. + */ + public static final int QUALITY_1080P = 6; + + /** + * Time lapse quality level corresponding to the lowest available resolution. + */ + public static final int QUALITY_TIME_LAPSE_LOW = 1000; + + /** + * Time lapse quality level corresponding to the highest available resolution. + */ + public static final int QUALITY_TIME_LAPSE_HIGH = 1001; + + /** + * Time lapse quality level corresponding to the qcif (176 x 144) resolution. + */ + public static final int QUALITY_TIME_LAPSE_QCIF = 1002; + + /** + * Time lapse quality level corresponding to the cif (352 x 288) resolution. + */ + public static final int QUALITY_TIME_LAPSE_CIF = 1003; + + /** + * Time lapse quality level corresponding to the 480p (720 x 480) resolution. + */ + public static final int QUALITY_TIME_LAPSE_480P = 1004; + + /** + * Time lapse quality level corresponding to the 720p (1280 x 720) resolution. + */ + public static final int QUALITY_TIME_LAPSE_720P = 1005; + + /** + * Time lapse quality level corresponding to the 1080p (1920 x 1088) resolution. + */ + public static final int QUALITY_TIME_LAPSE_1080P = 1006; + + /** * Default recording duration in seconds before the session is terminated. * This is useful for applications like MMS has limited file size requirement. */ @@ -122,25 +180,75 @@ public class CamcorderProfile * Returns the camcorder profile for the default camera at the given * quality level. * @param quality the target quality level for the camcorder profile + * @see #get(int, int) */ public static CamcorderProfile get(int quality) { - return get(0, quality); + return get(android.hardware.Camera.CAMERA_ID_DEFAULT, quality); } /** * Returns the camcorder profile for the given camera at the given * quality level. + * + * Quality levels QUALITY_LOW, QUALITY_HIGH are guaranteed to be supported, while + * other levels may or may not be supported. The supported levels can be checked using + * {@link #hasProfile(int, int)}. + * QUALITY_LOW refers to the lowest quality available, while QUALITY_HIGH refers to + * the highest quality available. + * QUALITY_LOW/QUALITY_HIGH have to match one of qcif, cif, 480p, 720p, or 1080p. + * E.g. if the device supports 480p, 720p, and 1080p, then low is 480p and high is + * 1080p. + * + * A camcorder recording session with higher quality level usually has higher output + * bit rate, better video and/or audio recording quality, larger video frame + * resolution and higher audio sampling rate, etc, than those with lower quality + * level. + * * @param cameraId the id for the camera - * @param quality the target quality level for the camcorder profile + * @param quality the target quality level for the camcorder profile. + * @see #QUALITY_LOW + * @see #QUALITY_HIGH + * @see #QUALITY_QCIF + * @see #QUALITY_CIF + * @see #QUALITY_480P + * @see #QUALITY_720P + * @see #QUALITY_1080P + * @see #QUALITY_TIME_LAPSE_LOW + * @see #QUALITY_TIME_LAPSE_HIGH + * @see #QUALITY_TIME_LAPSE_QCIF + * @see #QUALITY_TIME_LAPSE_CIF + * @see #QUALITY_TIME_LAPSE_480P + * @see #QUALITY_TIME_LAPSE_720P + * @see #QUALITY_TIME_LAPSE_1080P */ public static CamcorderProfile get(int cameraId, int quality) { - if (quality < QUALITY_LOW || quality > QUALITY_HIGH) { + if (!((quality >= QUALITY_LOW && quality <= QUALITY_1080P) || + (quality >= QUALITY_TIME_LAPSE_LOW && quality <= QUALITY_TIME_LAPSE_1080P))) { String errMessage = "Unsupported quality level: " + quality; throw new IllegalArgumentException(errMessage); } return native_get_camcorder_profile(cameraId, quality); } + /** + * Returns true if camcorder profile exists for the default camera at + * the given quality level. + * @param quality the target quality level for the camcorder profile + */ + public static boolean hasProfile(int quality) { + return hasProfile(android.hardware.Camera.CAMERA_ID_DEFAULT, quality); + } + + /** + * Returns true if camcorder profile exists for the given camera at + * the given quality level. + * @param cameraId the id for the camera + * @param quality the target quality level for the camcorder profile + */ + public static boolean hasProfile(int cameraId, int quality) { + return native_has_camcorder_profile(cameraId, quality); + } + static { System.loadLibrary("media_jni"); native_init(); @@ -178,4 +286,6 @@ public class CamcorderProfile private static native final void native_init(); private static native final CamcorderProfile native_get_camcorder_profile( int cameraId, int quality); + private static native final boolean native_has_camcorder_profile( + int cameraId, int quality); } diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index fb2480e..66a93f04 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -34,8 +34,6 @@ import java.util.List; * {@hide} */ public class MediaFile { - // comma separated list of all file extensions supported by the media scanner - public final static String sFileExtensions; // Audio file types public static final int FILE_TYPE_MP3 = 1; @@ -84,6 +82,17 @@ public class MediaFile { public static final int FILE_TYPE_WPL = 43; private static final int FIRST_PLAYLIST_FILE_TYPE = FILE_TYPE_M3U; private static final int LAST_PLAYLIST_FILE_TYPE = FILE_TYPE_WPL; + + // Other popular file types + public static final int FILE_TYPE_TEXT = 100; + public static final int FILE_TYPE_HTML = 101; + public static final int FILE_TYPE_PDF = 102; + public static final int FILE_TYPE_XML = 103; + public static final int FILE_TYPE_MS_WORD = 104; + public static final int FILE_TYPE_MS_EXCEL = 105; + public static final int FILE_TYPE_MS_POWERPOINT = 106; + public static final int FILE_TYPE_FLAC = 107; + public static final int FILE_TYPE_ZIP = 108; static class MediaFileType { @@ -132,16 +141,6 @@ public class MediaFile { return false; } - private static boolean isWMVEnabled() { - List<VideoDecoder> decoders = DecoderCapabilities.getVideoDecoders(); - for (VideoDecoder decoder: decoders) { - if (decoder == VideoDecoder.VIDEO_DECODER_WMV) { - return true; - } - } - return false; - } - static { addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg", MtpConstants.FORMAT_MP3); addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", MtpConstants.FORMAT_MPEG); @@ -176,10 +175,8 @@ public class MediaFile { addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska"); addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); - if (isWMVEnabled()) { - addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.FORMAT_WMV); - addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); - } + addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.FORMAT_WMV); + addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG); addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG); @@ -192,53 +189,74 @@ public class MediaFile { addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls", MtpConstants.FORMAT_PLS_PLAYLIST); addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl", MtpConstants.FORMAT_WPL_PLAYLIST); - // compute file extensions list for native Media Scanner - StringBuilder builder = new StringBuilder(); - Iterator<String> iterator = sFileTypeMap.keySet().iterator(); - - while (iterator.hasNext()) { - if (builder.length() > 0) { - builder.append(','); - } - builder.append(iterator.next()); - } - sFileExtensions = builder.toString(); + addFileType("TXT", FILE_TYPE_TEXT, "text/plain", MtpConstants.FORMAT_TEXT); + addFileType("HTM", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML); + addFileType("HTML", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML); + addFileType("PDF", FILE_TYPE_PDF, "application/pdf"); + addFileType("DOC", FILE_TYPE_MS_WORD, "application/msword", MtpConstants.FORMAT_MS_WORD_DOCUMENT); + addFileType("XLS", FILE_TYPE_MS_EXCEL, "application/vnd.ms-excel", MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET); + addFileType("PPT", FILE_TYPE_MS_POWERPOINT, "application/mspowerpoint", MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION); + addFileType("FLAC", FILE_TYPE_FLAC, "audio/flac", MtpConstants.FORMAT_FLAC); + addFileType("ZIP", FILE_TYPE_ZIP, "application/zip"); } - + public static boolean isAudioFileType(int fileType) { return ((fileType >= FIRST_AUDIO_FILE_TYPE && fileType <= LAST_AUDIO_FILE_TYPE) || (fileType >= FIRST_MIDI_FILE_TYPE && fileType <= LAST_MIDI_FILE_TYPE)); } - + public static boolean isVideoFileType(int fileType) { return (fileType >= FIRST_VIDEO_FILE_TYPE && fileType <= LAST_VIDEO_FILE_TYPE); } - + public static boolean isImageFileType(int fileType) { return (fileType >= FIRST_IMAGE_FILE_TYPE && fileType <= LAST_IMAGE_FILE_TYPE); } - + public static boolean isPlayListFileType(int fileType) { return (fileType >= FIRST_PLAYLIST_FILE_TYPE && fileType <= LAST_PLAYLIST_FILE_TYPE); } - + public static MediaFileType getFileType(String path) { int lastDot = path.lastIndexOf("."); if (lastDot < 0) return null; return sFileTypeMap.get(path.substring(lastDot + 1).toUpperCase()); } - + + // generates a title based on file name + public static String getFileTitle(String path) { + // extract file name after last slash + int lastSlash = path.lastIndexOf('/'); + if (lastSlash >= 0) { + lastSlash++; + if (lastSlash < path.length()) { + path = path.substring(lastSlash); + } + } + // truncate the file extension (if any) + int lastDot = path.lastIndexOf('.'); + if (lastDot > 0) { + path = path.substring(0, lastDot); + } + return path; + } + public static int getFileTypeForMimeType(String mimeType) { Integer value = sMimeTypeMap.get(mimeType); return (value == null ? 0 : value.intValue()); } + public static String getMimeTypeForFile(String path) { + MediaFileType mediaFileType = getFileType(path); + return (mediaFileType == null ? null : mediaFileType.mimeType); + } + public static int getFormatCode(String fileName, String mimeType) { if (mimeType != null) { Integer value = sMimeTypeToFormatMap.get(mimeType); diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 19b0c17..4c43baa 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -277,11 +277,17 @@ public class MediaRecorder setVideoFrameRate(profile.videoFrameRate); setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); setVideoEncodingBitRate(profile.videoBitRate); - setAudioEncodingBitRate(profile.audioBitRate); - setAudioChannels(profile.audioChannels); - setAudioSamplingRate(profile.audioSampleRate); setVideoEncoder(profile.videoCodec); - setAudioEncoder(profile.audioCodec); + if (profile.quality >= CamcorderProfile.QUALITY_TIME_LAPSE_LOW && + profile.quality <= CamcorderProfile.QUALITY_TIME_LAPSE_1080P) { + // Enable time lapse. Also don't set audio for time lapse. + setParameter(String.format("time-lapse-enable=1")); + } else { + setAudioEncodingBitRate(profile.audioBitRate); + setAudioChannels(profile.audioChannels); + setAudioSamplingRate(profile.audioSampleRate); + setAudioEncoder(profile.audioCodec); + } } /** @@ -305,16 +311,24 @@ public class MediaRecorder } /** - * Enables time lapse capture and sets its parameters. This method should - * be called after setProfile(). + * Set video frame capture rate. This can be used to set a different video frame capture + * rate than the recorded video's playback rate. Currently this works only for time lapse mode. * - * @param timeBetweenTimeLapseFrameCaptureMs time between two captures of time lapse frames. + * @param fps Rate at which frames should be captured in frames per second. + * The fps can go as low as desired. However the fastest fps will be limited by the hardware. + * For resolutions that can be captured by the video camera, the fastest fps can be computed using + * {@link android.hardware.Camera.Parameters#getPreviewFpsRange(int[])}. For higher + * resolutions the fastest fps may be more restrictive. + * Note that the recorder cannot guarantee that frames will be captured at the + * given rate due to camera/encoder limitations. However it tries to be as close as + * possible. * @hide */ - public void enableTimeLapse(int timeBetweenTimeLapseFrameCaptureMs) { - setParameter(String.format("time-lapse-enable=1")); + public void setCaptureRate(double fps) { + double timeBetweenFrameCapture = 1 / fps; + int timeBetweenFrameCaptureMs = (int) (1000 * timeBetweenFrameCapture); setParameter(String.format("time-between-time-lapse-frame-capture=%d", - timeBetweenTimeLapseFrameCaptureMs)); + timeBetweenFrameCaptureMs)); } /** diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 8ca6237..b2a0a46 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -34,6 +34,7 @@ import android.os.SystemProperties; import android.provider.MediaStore; import android.provider.Settings; import android.provider.MediaStore.Audio; +import android.provider.MediaStore.Files; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; import android.provider.MediaStore.Audio.Genres; @@ -109,41 +110,28 @@ public class MediaScanner private final static String TAG = "MediaScanner"; - private static final String[] AUDIO_PROJECTION = new String[] { - Audio.Media._ID, // 0 - Audio.Media.DATA, // 1 - Audio.Media.DATE_MODIFIED, // 2 + private static final String[] FILES_PRESCAN_PROJECTION = new String[] { + Files.FileColumns._ID, // 0 + Files.FileColumns.DATA, // 1 + Files.FileColumns.FORMAT, // 2 + Files.FileColumns.DATE_MODIFIED, // 3 }; - private static final int ID_AUDIO_COLUMN_INDEX = 0; - private static final int PATH_AUDIO_COLUMN_INDEX = 1; - private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2; + private static final int FILES_PRESCAN_ID_COLUMN_INDEX = 0; + private static final int FILES_PRESCAN_PATH_COLUMN_INDEX = 1; + private static final int FILES_PRESCAN_FORMAT_COLUMN_INDEX = 2; + private static final int FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 3; - private static final String[] VIDEO_PROJECTION = new String[] { - Video.Media._ID, // 0 - Video.Media.DATA, // 1 - Video.Media.DATE_MODIFIED, // 2 + private static final String[] MEDIA_PRESCAN_PROJECTION = new String[] { + MediaStore.MediaColumns._ID, // 0 + MediaStore.MediaColumns.DATA, // 1 + MediaStore.MediaColumns.DATE_MODIFIED, // 2 }; - private static final int ID_VIDEO_COLUMN_INDEX = 0; - private static final int PATH_VIDEO_COLUMN_INDEX = 1; - private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2; + private static final int MEDIA_PRESCAN_ID_COLUMN_INDEX = 0; + private static final int MEDIA_PRESCAN_PATH_COLUMN_INDEX = 1; + private static final int MEDIA_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 2; - private static final String[] IMAGES_PROJECTION = new String[] { - Images.Media._ID, // 0 - Images.Media.DATA, // 1 - Images.Media.DATE_MODIFIED, // 2 - }; - - private static final int ID_IMAGES_COLUMN_INDEX = 0; - private static final int PATH_IMAGES_COLUMN_INDEX = 1; - private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2; - - private static final String[] PLAYLISTS_PROJECTION = new String[] { - Audio.Playlists._ID, // 0 - Audio.Playlists.DATA, // 1 - Audio.Playlists.DATE_MODIFIED, // 2 - }; private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] { Audio.Playlists.Members.PLAYLIST_ID, // 0 @@ -304,6 +292,7 @@ public class MediaScanner private Uri mThumbsUri; private Uri mGenresUri; private Uri mPlaylistsUri; + private Uri mFilesUri; private boolean mProcessPlaylists, mProcessGenres; private int mMtpObjectHandle; @@ -340,21 +329,23 @@ public class MediaScanner long mRowId; String mPath; long mLastModified; + int mFormat; boolean mSeenInFileSystem; boolean mLastModifiedChanged; - FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) { + FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified, int format) { mTableUri = tableUri; mRowId = rowId; mPath = path; mLastModified = lastModified; + mFormat = format; mSeenInFileSystem = false; mLastModifiedChanged = false; } @Override public String toString() { - return mPath; + return mPath + " mTableUri: " + mTableUri + " mRowId: " + mRowId; } } @@ -432,6 +423,9 @@ public class MediaScanner } mMimeType = null; + mFileType = 0; + mFileSize = fileSize; + // try mimeType first, if it is specified if (mimeType != null) { mFileType = MediaFile.getFileTypeForMimeType(mimeType); @@ -439,7 +433,6 @@ public class MediaScanner mMimeType = mimeType; } } - mFileSize = fileSize; // if mimeType was not specified, compute file type based on file extension. if (mMimeType == null) { @@ -456,7 +449,17 @@ public class MediaScanner } FileCacheEntry entry = mFileCache.get(key); if (entry == null) { - entry = new FileCacheEntry(null, 0, path, 0); + Uri tableUri; + if (MediaFile.isVideoFileType(mFileType)) { + tableUri = mVideoUri; + } else if (MediaFile.isImageFileType(mFileType)) { + tableUri = mImagesUri; + } else if (MediaFile.isAudioFileType(mFileType)) { + tableUri = mAudioUri; + } else { + tableUri = mFilesUri; + } + entry = new FileCacheEntry(tableUri, 0, path, 0, 0); mFileCache.put(key, entry); } entry.mSeenInFileSystem = true; @@ -501,7 +504,8 @@ public class MediaScanner doScanFile(path, mimeType, lastModified, fileSize, false); } - public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) { + public Uri doScanFile(String path, String mimeType, long lastModified, + long fileSize, boolean scanAlways) { Uri result = null; // long t1 = System.currentTimeMillis(); try { @@ -516,7 +520,9 @@ public class MediaScanner boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) || (!ringtones && !notifications && !alarms && !podcasts); - if (!MediaFile.isImageFileType(mFileType)) { + // we only extract metadata for audio and video files + if (MediaFile.isAudioFileType(mFileType) + || MediaFile.isVideoFileType(mFileType)) { processFile(path, mimeType, this); } @@ -627,9 +633,6 @@ public class MediaScanner map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified); map.put(MediaStore.MediaColumns.SIZE, mFileSize); map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType); - if (mMtpObjectHandle != 0) { - map.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle); - } if (MediaFile.isVideoFileType(mFileType)) { map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaStore.UNKNOWN_STRING)); @@ -659,21 +662,6 @@ public class MediaScanner boolean alarms, boolean music, boolean podcasts) throws RemoteException { // update database - Uri tableUri; - boolean isAudio = MediaFile.isAudioFileType(mFileType); - boolean isVideo = MediaFile.isVideoFileType(mFileType); - boolean isImage = MediaFile.isImageFileType(mFileType); - if (isVideo) { - tableUri = mVideoUri; - } else if (isImage) { - tableUri = mImagesUri; - } else if (isAudio) { - tableUri = mAudioUri; - } else { - // don't add file to database if not audio, video or image - return null; - } - entry.mTableUri = tableUri; // use album artist if artist is missing if (mArtist == null || mArtist.length() == 0) { @@ -683,20 +671,7 @@ public class MediaScanner ContentValues values = toValues(); String title = values.getAsString(MediaStore.MediaColumns.TITLE); if (title == null || TextUtils.isEmpty(title.trim())) { - title = values.getAsString(MediaStore.MediaColumns.DATA); - // extract file name after last slash - int lastSlash = title.lastIndexOf('/'); - if (lastSlash >= 0) { - lastSlash++; - if (lastSlash < title.length()) { - title = title.substring(lastSlash); - } - } - // truncate the file extension (if any) - int lastDot = title.lastIndexOf('.'); - if (lastDot > 0) { - title = title.substring(0, lastDot); - } + title = MediaFile.getFileTitle(values.getAsString(MediaStore.MediaColumns.DATA)); values.put(MediaStore.MediaColumns.TITLE, title); } String album = values.getAsString(Audio.Media.ALBUM); @@ -720,7 +695,7 @@ public class MediaScanner } } long rowId = entry.mRowId; - if (isAudio && rowId == 0) { + if (MediaFile.isAudioFileType(mFileType) && rowId == 0) { // Only set these for new entries. For existing entries, they // may have been modified later, and we want to keep the current // values so that custom ringtones still show up in the ringtone @@ -773,8 +748,15 @@ public class MediaScanner } } + Uri tableUri = entry.mTableUri; Uri result = null; if (rowId == 0) { + if (mMtpObjectHandle != 0) { + values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle); + } + if (tableUri == mFilesUri) { + values.put(Files.FileColumns.FORMAT, MediaFile.getFormatCode(entry.mPath, mMimeType)); + } // new file, insert it result = mMediaProvider.insert(tableUri, values); if (result != null) { @@ -890,7 +872,7 @@ public class MediaScanner }; // end of anonymous MediaScannerClient instance - private void prescan(String filePath) throws RemoteException { + private void prescan(String filePath, boolean prescanFiles) throws RemoteException { Cursor c = null; String where = null; String[] selectionArgs = null; @@ -906,21 +888,26 @@ public class MediaScanner mPlayLists.clear(); } + if (filePath != null) { + // query for only one file + where = Files.FileColumns.DATA + "=?"; + selectionArgs = new String[] { filePath }; + } + // Build the list of files from the content provider try { - // Read existing files from the audio table - if (filePath != null) { - where = MediaStore.Audio.Media.DATA + "=?"; - selectionArgs = new String[] { filePath }; - } - c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null); + if (prescanFiles) { + // First read existing files from the files table - if (c != null) { - try { + c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, + where, selectionArgs, null); + + if (c != null) { while (c.moveToNext()) { - long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX); - String path = c.getString(PATH_AUDIO_COLUMN_INDEX); - long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX); + long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); + String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX); + int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); + long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); // Only consider entries with absolute path names. // This allows storing URIs in the database without the @@ -930,114 +917,144 @@ public class MediaScanner if (mCaseInsensitivePaths) { key = path.toLowerCase(); } - mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path, - lastModified)); + + FileCacheEntry entry = new FileCacheEntry(mFilesUri, rowId, path, + lastModified, format); + mFileCache.put(key, entry); } } - } finally { c.close(); c = null; } } - // Read existing files from the video table - if (filePath != null) { - where = MediaStore.Video.Media.DATA + "=?"; - } else { - where = null; + // Read existing files from the audio table and update FileCacheEntry + c = mMediaProvider.query(mAudioUri, MEDIA_PRESCAN_PROJECTION, + where, selectionArgs, null); + if (c != null) { + while (c.moveToNext()) { + long rowId = c.getLong(MEDIA_PRESCAN_ID_COLUMN_INDEX); + String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX); + long lastModified = c.getLong(MEDIA_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); + + // Only consider entries with absolute path names. + // This allows storing URIs in the database without the + // media scanner removing them. + if (path.startsWith("/")) { + String key = path; + if (mCaseInsensitivePaths) { + key = path.toLowerCase(); + } + FileCacheEntry entry = mFileCache.get(path); + if (entry == null) { + mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path, + lastModified, 0)); + } else { + // update the entry + entry.mTableUri = mAudioUri; + entry.mRowId = rowId; + } + } + } + c.close(); + c = null; } - c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null); + // Read existing files from the video table and update FileCacheEntry + c = mMediaProvider.query(mVideoUri, MEDIA_PRESCAN_PROJECTION, + where, selectionArgs, null); if (c != null) { - try { - while (c.moveToNext()) { - long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX); - String path = c.getString(PATH_VIDEO_COLUMN_INDEX); - long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX); - - // Only consider entries with absolute path names. - // This allows storing URIs in the database without the - // media scanner removing them. - if (path.startsWith("/")) { - String key = path; - if (mCaseInsensitivePaths) { - key = path.toLowerCase(); - } + while (c.moveToNext()) { + long rowId = c.getLong(MEDIA_PRESCAN_ID_COLUMN_INDEX); + String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX); + long lastModified = c.getLong(MEDIA_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); + + // Only consider entries with absolute path names. + // This allows storing URIs in the database without the + // media scanner removing them. + if (path.startsWith("/")) { + String key = path; + if (mCaseInsensitivePaths) { + key = path.toLowerCase(); + } + FileCacheEntry entry = mFileCache.get(path); + if (entry == null) { mFileCache.put(key, new FileCacheEntry(mVideoUri, rowId, path, - lastModified)); + lastModified, 0)); + } else { + // update the entry + entry.mTableUri = mVideoUri; + entry.mRowId = rowId; } } - } finally { - c.close(); - c = null; } + c.close(); + c = null; } - // Read existing files from the images table - if (filePath != null) { - where = MediaStore.Images.Media.DATA + "=?"; - } else { - where = null; - } - mOriginalCount = 0; - c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null); - + // Read existing files from the video table and update FileCacheEntry + c = mMediaProvider.query(mImagesUri, MEDIA_PRESCAN_PROJECTION, + where, selectionArgs, null); if (c != null) { - try { - mOriginalCount = c.getCount(); - while (c.moveToNext()) { - long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX); - String path = c.getString(PATH_IMAGES_COLUMN_INDEX); - long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX); - - // Only consider entries with absolute path names. - // This allows storing URIs in the database without the - // media scanner removing them. - if (path.startsWith("/")) { - String key = path; - if (mCaseInsensitivePaths) { - key = path.toLowerCase(); - } - mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path, - lastModified)); - } + while (c.moveToNext()) { + long rowId = c.getLong(MEDIA_PRESCAN_ID_COLUMN_INDEX); + String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX); + long lastModified = c.getLong(MEDIA_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); + + // Only consider entries with absolute path names. + // This allows storing URIs in the database without the + // media scanner removing them. + if (path.startsWith("/")) { + String key = path; + if (mCaseInsensitivePaths) { + key = path.toLowerCase(); + } + FileCacheEntry entry = mFileCache.get(path); + if (entry == null) { + mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path, + lastModified, 0)); + } else { + // update the entry + entry.mTableUri = mImagesUri; + entry.mRowId = rowId; + } } - } finally { - c.close(); - c = null; } + c.close(); + c = null; } if (mProcessPlaylists) { - // Read existing files from the playlists table - if (filePath != null) { - where = MediaStore.Audio.Playlists.DATA + "=?"; - } else { - where = null; - } - c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null); - + // Read existing files from the playlists table and update FileCacheEntry + c = mMediaProvider.query(mPlaylistsUri, MEDIA_PRESCAN_PROJECTION, + where, selectionArgs, null); if (c != null) { - try { - while (c.moveToNext()) { - String path = c.getString(PATH_PLAYLISTS_COLUMN_INDEX); - - if (path != null && path.length() > 0) { - long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX); - long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX); + while (c.moveToNext()) { + long rowId = c.getLong(MEDIA_PRESCAN_ID_COLUMN_INDEX); + String path = c.getString(MEDIA_PRESCAN_PATH_COLUMN_INDEX); + long lastModified = c.getLong(MEDIA_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); - String key = path; - if (mCaseInsensitivePaths) { - key = path.toLowerCase(); - } + // Only consider entries with absolute path names. + // This allows storing URIs in the database without the + // media scanner removing them. + if (path.startsWith("/")) { + String key = path; + if (mCaseInsensitivePaths) { + key = path.toLowerCase(); + } + FileCacheEntry entry = mFileCache.get(path); + if (entry == null) { mFileCache.put(key, new FileCacheEntry(mPlaylistsUri, rowId, path, - lastModified)); + lastModified, 0)); + } else { + // update the entry + entry.mTableUri = mPlaylistsUri; + entry.mRowId = rowId; } } - } finally { - c.close(); - c = null; } + c.close(); + c = null; } } } @@ -1112,12 +1129,14 @@ public class MediaScanner // remove database entries for files that no longer exist. boolean fileMissing = false; - if (!entry.mSeenInFileSystem) { - if (inScanDirectory(path, directories)) { + if (!entry.mSeenInFileSystem && !MtpConstants.isAbstractObject(entry.mFormat)) { + if (entry.mFormat != MtpConstants.FORMAT_ASSOCIATION && + inScanDirectory(path, directories)) { // we didn't see this file in the scan directory. fileMissing = true; } else { - // the file is outside of our scan directory, + // the file actually a directory or other abstract object + // or is outside of our scan directory, // so we need to check for file existence here. File testFile = new File(path); if (!testFile.exists()) { @@ -1137,9 +1156,11 @@ public class MediaScanner ContentValues values = new ContentValues(); values.put(MediaStore.Audio.Playlists.DATA, ""); values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, 0); - mMediaProvider.update(ContentUris.withAppendedId(mPlaylistsUri, entry.mRowId), values, null, null); + mMediaProvider.update(ContentUris.withAppendedId(mPlaylistsUri, entry.mRowId), + values, null, null); } else { - mMediaProvider.delete(ContentUris.withAppendedId(entry.mTableUri, entry.mRowId), null, null); + mMediaProvider.delete(ContentUris.withAppendedId(mFilesUri, entry.mRowId), + null, null); iterator.remove(); } } @@ -1167,6 +1188,7 @@ public class MediaScanner mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); mThumbsUri = Images.Thumbnails.getContentUri(volumeName); + mFilesUri = Files.getContentUri(volumeName); if (!volumeName.equals("internal")) { // we only support playlists on external media @@ -1189,11 +1211,11 @@ public class MediaScanner try { long start = System.currentTimeMillis(); initialize(volumeName); - prescan(null); + prescan(null, true); long prescan = System.currentTimeMillis(); for (int i = 0; i < directories.length; i++) { - processDirectory(directories[i], MediaFile.sFileExtensions, mClient); + processDirectory(directories[i], mClient); } long scan = System.currentTimeMillis(); postscan(directories); @@ -1220,7 +1242,7 @@ public class MediaScanner public Uri scanSingleFile(String path, String volumeName, String mimeType) { try { initialize(volumeName); - prescan(path); + prescan(path, true); File file = new File(path); @@ -1235,12 +1257,34 @@ public class MediaScanner } } - public Uri scanMtpFile(String path, String volumeName, int objectHandle, int format) { - String mimeType = MediaFile.getMimeTypeForFormatCode(format); + public void scanMtpFile(String path, String volumeName, int objectHandle, int format) { + MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); + int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType); + + if (!MediaFile.isAudioFileType(fileType) && !MediaFile.isVideoFileType(fileType) && + !MediaFile.isImageFileType(fileType) && !MediaFile.isPlayListFileType(fileType)) { + // nothing to do + return; + } + mMtpObjectHandle = objectHandle; - Uri result = scanSingleFile(path, volumeName, mimeType); - mMtpObjectHandle = 0; - return result; + try { + initialize(volumeName); + // MTP will create a file entry for us so we don't want to do it in prescan + prescan(path, false); + + File file = new File(path); + + // lastModified is in milliseconds on Files. + long lastModifiedSeconds = file.lastModified() / 1000; + + // always scan the file, so we can return the content://media Uri for existing files + mClient.doScanFile(path, mediaFileType.mimeType, lastModifiedSeconds, file.length(), true); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e); + } finally { + mMtpObjectHandle = 0; + } } // returns the number of matching file/directory names, starting from the right @@ -1522,7 +1566,7 @@ public class MediaScanner } } - private native void processDirectory(String path, String extensions, MediaScannerClient client); + private native void processDirectory(String path, MediaScannerClient client); private native void processFile(String path, String mimeType, MediaScannerClient client); public native void setLocale(String locale); diff --git a/media/java/android/media/MtpConstants.java b/media/java/android/media/MtpConstants.java index 153f64f..a7d33ce 100644 --- a/media/java/android/media/MtpConstants.java +++ b/media/java/android/media/MtpConstants.java @@ -138,6 +138,28 @@ public final class MtpConstants { public static final int FORMAT_ABSTRACT_CONTACT = 0xBB81; public static final int FORMAT_VCARD_2 = 0xBB82; + public static boolean isAbstractObject(int format) { + switch (format) { + case FORMAT_ABSTRACT_MULTIMEDIA_ALBUM: + case FORMAT_ABSTRACT_IMAGE_ALBUM: + case FORMAT_ABSTRACT_AUDIO_ALBUM: + case FORMAT_ABSTRACT_VIDEO_ALBUM: + case FORMAT_ABSTRACT_AV_PLAYLIST: + case FORMAT_ABSTRACT_CONTACT_GROUP: + case FORMAT_ABSTRACT_MESSAGE_FOLDER: + case FORMAT_ABSTRACT_CHAPTERED_PRODUCTION: + case FORMAT_ABSTRACT_AUDIO_PLAYLIST: + case FORMAT_ABSTRACT_VIDEO_PLAYLIST: + case FORMAT_ABSTRACT_MEDIACAST: + case FORMAT_ABSTRACT_DOCUMENT: + case FORMAT_ABSTRACT_MESSSAGE: + case FORMAT_ABSTRACT_CONTACT: + return true; + default: + return false; + } + } + // MTP object properties public static final int PROPERTY_STORAGE_ID = 0xDC01; public static final int PROPERTY_OBJECT_FORMAT = 0xDC02; diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java index ad029a6..b64299a 100644 --- a/media/java/android/media/MtpDatabase.java +++ b/media/java/android/media/MtpDatabase.java @@ -170,7 +170,7 @@ public class MtpDatabase { Log.e(TAG, "RemoteException in endSendObject", e); } } else { - Uri uri = mMediaScanner.scanMtpFile(path, mVolumeName, handle, format); + mMediaScanner.scanMtpFile(path, mVolumeName, handle, format); } } else { deleteFile(handle); @@ -478,12 +478,26 @@ public class MtpDatabase { } } + private int deleteRecursive(int handle) throws RemoteException { + int[] children = getObjectList(0 /* storageID */, 0 /* format */, handle); + Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); + // delete parent first, to avoid potential infinite recursion + int count = mMediaProvider.delete(uri, null, null); + if (count == 1) { + if (children != null) { + for (int i = 0; i < children.length; i++) { + count += deleteRecursive(children[i]); + } + } + } + return count; + } + private int deleteFile(int handle) { Log.d(TAG, "deleteFile: " + handle); mDatabaseModified = true; - Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); try { - if (mMediaProvider.delete(uri, null, null) == 1) { + if (deleteRecursive(handle) > 0) { return MtpConstants.RESPONSE_OK; } else { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp index cce9fd0..08a6de1 100644 --- a/media/jni/android_media_MediaProfiles.cpp +++ b/media/jni/android_media_MediaProfiles.cpp @@ -165,7 +165,9 @@ static jobject android_media_MediaProfiles_native_get_camcorder_profile(JNIEnv *env, jobject thiz, jint id, jint quality) { LOGV("native_get_camcorder_profile: %d %d", id, quality); - if (quality != CAMCORDER_QUALITY_HIGH && quality != CAMCORDER_QUALITY_LOW) { + if (!((quality >= CAMCORDER_QUALITY_LOW && quality <= CAMCORDER_QUALITY_1080P) || + (quality >= CAMCORDER_QUALITY_TIME_LAPSE_LOW && + quality <= CAMCORDER_QUALITY_TIME_LAPSE_1080P))) { jniThrowException(env, "java/lang/RuntimeException", "Unknown camcorder profile quality"); return NULL; } @@ -210,6 +212,20 @@ android_media_MediaProfiles_native_get_camcorder_profile(JNIEnv *env, jobject th audioChannels); } +static jboolean +android_media_MediaProfiles_native_has_camcorder_profile(JNIEnv *env, jobject thiz, jint id, jint quality) +{ + LOGV("native_has_camcorder_profile: %d %d", id, quality); + if (!((quality >= CAMCORDER_QUALITY_LOW && quality <= CAMCORDER_QUALITY_1080P) || + (quality >= CAMCORDER_QUALITY_TIME_LAPSE_LOW && + quality <= CAMCORDER_QUALITY_TIME_LAPSE_1080P))) { + return false; + } + + camcorder_quality q = static_cast<camcorder_quality>(quality); + return sProfiles->hasCamcorderProfile(id, q); +} + static jint android_media_MediaProfiles_native_get_num_video_decoders(JNIEnv *env, jobject thiz) { @@ -289,6 +305,8 @@ static JNINativeMethod gMethodsForCamcorderProfileClass[] = { {"native_init", "()V", (void *)android_media_MediaProfiles_native_init}, {"native_get_camcorder_profile", "(II)Landroid/media/CamcorderProfile;", (void *)android_media_MediaProfiles_native_get_camcorder_profile}, + {"native_has_camcorder_profile", "(II)Z", + (void *)android_media_MediaProfiles_native_has_camcorder_profile}, }; static JNINativeMethod gMethodsForDecoderCapabilitiesClass[] = { diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp index 273f1af..fd0b233 100644 --- a/media/jni/android_media_MediaScanner.cpp +++ b/media/jni/android_media_MediaScanner.cpp @@ -146,7 +146,7 @@ static bool ExceptionCheck(void* env) } static void -android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client) +android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jobject client) { MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); @@ -154,27 +154,16 @@ android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring p jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } - if (extensions == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return; - } - + const char *pathStr = env->GetStringUTFChars(path, NULL); if (pathStr == NULL) { // Out of memory jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; } - const char *extensionsStr = env->GetStringUTFChars(extensions, NULL); - if (extensionsStr == NULL) { // Out of memory - env->ReleaseStringUTFChars(path, pathStr); - jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); - return; - } MyMediaScannerClient myClient(env, client); - mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env); + mp->processDirectory(pathStr, myClient, ExceptionCheck, env); env->ReleaseStringUTFChars(path, pathStr); - env->ReleaseStringUTFChars(extensions, extensionsStr); } static void @@ -309,9 +298,9 @@ android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz) // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { - {"processDirectory", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", + {"processDirectory", "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processDirectory}, - {"processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", + {"processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processFile}, {"setLocale", "(Ljava/lang/String;)V", (void *)android_media_MediaScanner_setLocale}, {"extractAlbumArt", "(Ljava/io/FileDescriptor;)[B", (void *)android_media_MediaScanner_extractAlbumArt}, diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp index e86ed99..90756d0 100644 --- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp +++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp @@ -65,6 +65,7 @@ namespace { int LvmInitFlag = LVM_FALSE; int LvmSessionsActive = 0; SessionContext GlobalSessionMemory[LVM_MAX_SESSIONS]; + int SessionIndex[LVM_MAX_SESSIONS]; // NXP SW BassBoost UUID @@ -199,11 +200,6 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, return -EINVAL; } - if(sessionId < 0){ - LOGV("\tLVM_ERROR : EffectCreate sessionId is less than 0"); - return -EINVAL; - } - if(LvmInitFlag == LVM_FALSE){ LvmInitFlag = LVM_TRUE; LOGV("\tEffectCreate - Initializing all global memory"); @@ -214,7 +210,7 @@ extern "C" int EffectCreate(effect_uuid_t *uuid, // Find next available sessionNo for(i=0; i<LVM_MAX_SESSIONS; i++){ - if((SessionIndex[i] == -1)||(SessionIndex[i] == sessionId)){ + if((SessionIndex[i] == LVM_UNUSED_SESSION)||(SessionIndex[i] == sessionId)){ sessionNo = i; SessionIndex[i] = sessionId; LOGV("\tEffectCreate: Allocating SessionNo %d for SessionId %d\n", sessionNo,sessionId); @@ -398,7 +394,7 @@ extern "C" int EffectRelease(effect_interface_t interface){ // Clear the SessionIndex for(int i=0; i<LVM_MAX_SESSIONS; i++){ if(SessionIndex[i] == pContext->pBundledContext->SessionId){ - SessionIndex[i] = -1; + SessionIndex[i] = LVM_UNUSED_SESSION; LOGV("\tEffectRelease: Clearing SessionIndex SessionNo %d for SessionId %d\n", i, pContext->pBundledContext->SessionId); break; @@ -432,7 +428,7 @@ void LvmGlobalBundle_init(){ GlobalSessionMemory[i].bVirtualizerInstantiated = LVM_FALSE; GlobalSessionMemory[i].pBundledContext = LVM_NULL; - SessionIndex[i] = -1; + SessionIndex[i] = LVM_UNUSED_SESSION; } return; } diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h index 35e1114..91963af 100644 --- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h +++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h @@ -21,6 +21,7 @@ #include <media/EffectBassBoostApi.h> #include <media/EffectVirtualizerApi.h> #include <LVM.h> +#include <limits.h> #if __cplusplus extern "C" { @@ -30,6 +31,7 @@ extern "C" { #define MAX_NUM_BANDS 5 #define MAX_CALL_SIZE 256 #define LVM_MAX_SESSIONS 32 +#define LVM_UNUSED_SESSION INT_MAX #define BASS_BOOST_CUP_LOAD_ARM9E 150 // Expressed in 0.1 MIPS #define VIRTUALIZER_CUP_LOAD_ARM9E 120 // Expressed in 0.1 MIPS #define EQUALIZER_CUP_LOAD_ARM9E 220 // Expressed in 0.1 MIPS diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp index 60f4288..26c5aca 100755 --- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp +++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp @@ -59,17 +59,17 @@ const static t_reverb_settings sReverbPresets[] = { // REVERB_PRESET_NONE: values are unused {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // REVERB_PRESET_SMALLROOM - {-1000, -600, 1100, 830, -400, 5, 500, 10, 1000, 1000}, + {-400, -600, 1100, 830, -400, 5, 500, 10, 1000, 1000}, // REVERB_PRESET_MEDIUMROOM - {-1000, -600, 1300, 830, -1000, 20, -200, 20, 1000, 1000}, + {-400, -600, 1300, 830, -1000, 20, -200, 20, 1000, 1000}, // REVERB_PRESET_LARGEROOM - {-1000, -600, 1500, 830, -1600, 5, -1000, 40, 1000, 1000}, + {-400, -600, 1500, 830, -1600, 5, -1000, 40, 1000, 1000}, // REVERB_PRESET_MEDIUMHALL - {-1000, -600, 1800, 700, -1300, 15, -800, 30, 1000, 1000}, + {-400, -600, 1800, 700, -1300, 15, -800, 30, 1000, 1000}, // REVERB_PRESET_LARGEHALL - {-1000, -600, 1800, 700, -2000, 30, -1400, 60, 1000, 1000}, + {-400, -600, 1800, 700, -2000, 30, -1400, 60, 1000, 1000}, // REVERB_PRESET_PLATE - {-1000, -200, 1300, 900, 0, 2, 0, 10, 1000, 750}, + {-400, -200, 1300, 900, 0, 2, 0, 10, 1000, 750}, }; @@ -90,7 +90,7 @@ static const effect_descriptor_t gInsertEnvReverbDescriptor = { {0xc2e5d5f0, 0x94bd, 0x4763, 0x9cac, {0x4e, 0x23, 0x4d, 0x06, 0x83, 0x9e}}, {0xc7a511a0, 0xa3bb, 0x11df, 0x860e, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}, EFFECT_API_VERSION, - EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST, + EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST | EFFECT_FLAG_VOLUME_CTRL, LVREV_CUP_LOAD_ARM9E, LVREV_MEM_USAGE, "Insert Environmental Reverb", @@ -114,7 +114,7 @@ static const effect_descriptor_t gInsertPresetReverbDescriptor = { {0x47382d60, 0xddd8, 0x11db, 0xbf3a, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}, {0x172cdf00, 0xa3bc, 0x11df, 0xa72f, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}, EFFECT_API_VERSION, - EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST, + EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST | EFFECT_FLAG_VOLUME_CTRL, LVREV_CUP_LOAD_ARM9E, LVREV_MEM_USAGE, "Insert Preset Reverb", @@ -153,10 +153,25 @@ struct ReverbContext{ uint16_t curPreset; uint16_t nextPreset; int SamplesToExitCount; + LVM_INT16 leftVolume; + LVM_INT16 rightVolume; + LVM_INT16 prevLeftVolume; + LVM_INT16 prevRightVolume; + int volumeMode; +}; + +enum { + REVERB_VOLUME_OFF, + REVERB_VOLUME_FLAT, + REVERB_VOLUME_RAMP, }; #define REVERB_DEFAULT_PRESET REVERB_PRESET_MEDIUMROOM + +#define REVERB_SEND_LEVEL (0x0C00) // 0.75 in 4.12 format +#define REVERB_UNIT_VOLUME (0x1000) // 1.0 in 4.12 format + //--- local function prototypes int Reverb_init (ReverbContext *pContext); void Reverb_free (ReverbContext *pContext); @@ -426,9 +441,20 @@ int process( LVM_INT16 *pIn, if (pContext->preset && pContext->nextPreset != pContext->curPreset) { Reverb_LoadPreset(pContext); } + + + // Convert to Input 32 bits - for(int i=0; i<frameCount*samplesPerFrame; i++){ - pContext->InFrames32[i] = (LVM_INT32)pIn[i]<<8; + if (pContext->auxiliary) { + for(int i=0; i<frameCount*samplesPerFrame; i++){ + pContext->InFrames32[i] = (LVM_INT32)pIn[i]<<8; + } + } else { + // insert reverb input is always stereo + for (int i = 0; i < frameCount; i++) { + pContext->InFrames32[2*i] = (pIn[2*i] * REVERB_SEND_LEVEL) >> 4; // <<8 + >>12 + pContext->InFrames32[2*i+1] = (pIn[2*i+1] * REVERB_SEND_LEVEL) >> 4; // <<8 + >>12 + } } if (pContext->preset && pContext->curPreset == REVERB_PRESET_NONE) { @@ -458,6 +484,42 @@ int process( LVM_INT16 *pIn, for (int i=0; i < frameCount*2; i++) { //always stereo here OutFrames16[i] = clamp16((pContext->OutFrames32[i]>>8) + (LVM_INT32)pIn[i]); } + + // apply volume with ramp if needed + if ((pContext->leftVolume != pContext->prevLeftVolume || + pContext->rightVolume != pContext->prevRightVolume) && + pContext->volumeMode == REVERB_VOLUME_RAMP) { + LVM_INT32 vl = (LVM_INT32)pContext->prevLeftVolume << 16; + LVM_INT32 incl = (((LVM_INT32)pContext->leftVolume << 16) - vl) / frameCount; + LVM_INT32 vr = (LVM_INT32)pContext->prevRightVolume << 16; + LVM_INT32 incr = (((LVM_INT32)pContext->rightVolume << 16) - vr) / frameCount; + + for (int i = 0; i < frameCount; i++) { + OutFrames16[2*i] = + clamp16((LVM_INT32)((vl >> 16) * OutFrames16[2*i]) >> 12); + OutFrames16[2*i+1] = + clamp16((LVM_INT32)((vr >> 16) * OutFrames16[2*i+1]) >> 12); + + vl += incl; + vr += incr; + } + + pContext->prevLeftVolume = pContext->leftVolume; + pContext->prevRightVolume = pContext->rightVolume; + } else if (pContext->volumeMode != REVERB_VOLUME_OFF) { + if (pContext->leftVolume != REVERB_UNIT_VOLUME || + pContext->rightVolume != REVERB_UNIT_VOLUME) { + for (int i = 0; i < frameCount; i++) { + OutFrames16[2*i] = + clamp16((LVM_INT32)(pContext->leftVolume * OutFrames16[2*i]) >> 12); + OutFrames16[2*i+1] = + clamp16((LVM_INT32)(pContext->rightVolume * OutFrames16[2*i+1]) >> 12); + } + } + pContext->prevLeftVolume = pContext->leftVolume; + pContext->prevRightVolume = pContext->rightVolume; + pContext->volumeMode = REVERB_VOLUME_RAMP; + } } #ifdef LVM_PCM @@ -658,6 +720,12 @@ int Reverb_init(ReverbContext *pContext){ pContext->config.outputCfg.bufferProvider.cookie = NULL; pContext->config.outputCfg.mask = EFFECT_CONFIG_ALL; + pContext->leftVolume = REVERB_UNIT_VOLUME; + pContext->rightVolume = REVERB_UNIT_VOLUME; + pContext->prevLeftVolume = REVERB_UNIT_VOLUME; + pContext->prevRightVolume = REVERB_UNIT_VOLUME; + pContext->volumeMode = REVERB_VOLUME_FLAT; + LVREV_ReturnStatus_en LvmStatus=LVREV_SUCCESS; /* Function call status */ LVREV_ControlParams_st params; /* Control Parameters */ LVREV_InstanceParams_st InstParams; /* Instance parameters */ @@ -1781,15 +1849,6 @@ extern "C" int Reverb_process(effect_interface_t self, LOGV("\tLVM_ERROR : Reverb_process() ERROR NULL INPUT POINTER OR FRAME COUNT IS WRONG"); return -EINVAL; } - if (pContext->bEnabled == LVM_FALSE){ - if( pContext->SamplesToExitCount > 0){ - pContext->SamplesToExitCount -= outBuffer->frameCount; - LOGV("\tReverb_process() Effect is being stopped %d", pContext->SamplesToExitCount); - }else{ - LOGV("\tReverb_process() Effect is being stopped"); - return -ENODATA; - } - } //LOGV("\tReverb_process() Calling process with %d frames", outBuffer->frameCount); /* Process all the available frames, block processing is handled internalLY by the LVM bundle */ status = process( (LVM_INT16 *)inBuffer->raw, @@ -1797,6 +1856,14 @@ extern "C" int Reverb_process(effect_interface_t self, outBuffer->frameCount, pContext); + if (pContext->bEnabled == LVM_FALSE) { + if (pContext->SamplesToExitCount > 0) { + pContext->SamplesToExitCount -= outBuffer->frameCount; + } else { + status = -ENODATA; + } + } + return status; } /* end Reverb_process */ @@ -1943,6 +2010,8 @@ extern "C" int Reverb_command(effect_interface_t self, LVM_ERROR_CHECK(LvmStatus, "LVREV_GetControlParameters", "EFFECT_CMD_ENABLE") pContext->SamplesToExitCount = (ActiveParams.T60 * pContext->config.inputCfg.samplingRate)/1000; + // force no volume ramp for first buffer processed after enabling the effect + pContext->volumeMode = android::REVERB_VOLUME_FLAT; //LOGV("\tEFFECT_CMD_ENABLE SamplesToExitCount = %d", pContext->SamplesToExitCount); break; case EFFECT_CMD_DISABLE: @@ -1963,8 +2032,34 @@ extern "C" int Reverb_command(effect_interface_t self, pContext->bEnabled = LVM_FALSE; break; - case EFFECT_CMD_SET_DEVICE: case EFFECT_CMD_SET_VOLUME: + if (pCmdData == NULL || + cmdSize != 2 * sizeof(uint32_t)) { + LOGV("\tLVM_ERROR : Reverb_command cmdCode Case: " + "EFFECT_CMD_SET_VOLUME: ERROR"); + return -EINVAL; + } + + + if (pReplyData != NULL) { // we have volume control + pContext->leftVolume = (LVM_INT16)((*(uint32_t *)pCmdData + (1 << 11)) >> 12); + pContext->rightVolume = (LVM_INT16)((*((uint32_t *)pCmdData + 1) + (1 << 11)) >> 12); + *(uint32_t *)pReplyData = (1 << 24); + *((uint32_t *)pReplyData + 1) = (1 << 24); + if (pContext->volumeMode == android::REVERB_VOLUME_OFF) { + // force no volume ramp for first buffer processed after getting volume control + pContext->volumeMode = android::REVERB_VOLUME_FLAT; + } + } else { // we don't have volume control + pContext->leftVolume = REVERB_UNIT_VOLUME; + pContext->rightVolume = REVERB_UNIT_VOLUME; + pContext->volumeMode = android::REVERB_VOLUME_OFF; + } + LOGV("EFFECT_CMD_SET_VOLUME left %d, right %d mode %d", + pContext->leftVolume, pContext->rightVolume, pContext->volumeMode); + break; + + case EFFECT_CMD_SET_DEVICE: case EFFECT_CMD_SET_AUDIO_MODE: //LOGV("\tReverb_command cmdCode Case: " // "EFFECT_CMD_SET_DEVICE/EFFECT_CMD_SET_VOLUME/EFFECT_CMD_SET_AUDIO_MODE start"); diff --git a/media/libmedia/MediaProfiles.cpp b/media/libmedia/MediaProfiles.cpp index 3869389..645c163 100644 --- a/media/libmedia/MediaProfiles.cpp +++ b/media/libmedia/MediaProfiles.cpp @@ -59,8 +59,21 @@ const MediaProfiles::NameToTagMap MediaProfiles::sAudioDecoderNameMap[] = { }; const MediaProfiles::NameToTagMap MediaProfiles::sCamcorderQualityNameMap[] = { + {"low", CAMCORDER_QUALITY_LOW}, {"high", CAMCORDER_QUALITY_HIGH}, - {"low", CAMCORDER_QUALITY_LOW} + {"qcif", CAMCORDER_QUALITY_QCIF}, + {"cif", CAMCORDER_QUALITY_CIF}, + {"480p", CAMCORDER_QUALITY_480P}, + {"720p", CAMCORDER_QUALITY_720P}, + {"1080p", CAMCORDER_QUALITY_1080P}, + + {"timelapselow", CAMCORDER_QUALITY_TIME_LAPSE_LOW}, + {"timelapsehigh", CAMCORDER_QUALITY_TIME_LAPSE_HIGH}, + {"timelapseqcif", CAMCORDER_QUALITY_TIME_LAPSE_QCIF}, + {"timelapsecif", CAMCORDER_QUALITY_TIME_LAPSE_CIF}, + {"timelapse480p", CAMCORDER_QUALITY_TIME_LAPSE_480P}, + {"timelapse720p", CAMCORDER_QUALITY_TIME_LAPSE_720P}, + {"timelapse1080p", CAMCORDER_QUALITY_TIME_LAPSE_1080P} }; /*static*/ void @@ -411,6 +424,40 @@ MediaProfiles::createDefaultVideoEncoders(MediaProfiles *profiles) } /*static*/ MediaProfiles::CamcorderProfile* +MediaProfiles::createDefaultCamcorderTimeLapseHighProfile() +{ + MediaProfiles::VideoCodec *videoCodec = + new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 20000000, 720, 480, 20); + + AudioCodec *audioCodec = new AudioCodec(AUDIO_ENCODER_AMR_NB, 12200, 8000, 1); + CamcorderProfile *profile = new MediaProfiles::CamcorderProfile; + profile->mCameraId = 0; + profile->mFileFormat = OUTPUT_FORMAT_THREE_GPP; + profile->mQuality = CAMCORDER_QUALITY_TIME_LAPSE_HIGH; + profile->mDuration = 60; + profile->mVideoCodec = videoCodec; + profile->mAudioCodec = audioCodec; + return profile; +} + +/*static*/ MediaProfiles::CamcorderProfile* +MediaProfiles::createDefaultCamcorderTimeLapseLowProfile() +{ + MediaProfiles::VideoCodec *videoCodec = + new MediaProfiles::VideoCodec(VIDEO_ENCODER_H263, 1000000, 176, 144, 20); + + AudioCodec *audioCodec = new AudioCodec(AUDIO_ENCODER_AMR_NB, 12200, 8000, 1); + CamcorderProfile *profile = new MediaProfiles::CamcorderProfile; + profile->mCameraId = 0; + profile->mFileFormat = OUTPUT_FORMAT_THREE_GPP; + profile->mQuality = CAMCORDER_QUALITY_TIME_LAPSE_LOW; + profile->mDuration = 60; + profile->mVideoCodec = videoCodec; + profile->mAudioCodec = audioCodec; + return profile; +} + +/*static*/ MediaProfiles::CamcorderProfile* MediaProfiles::createDefaultCamcorderHighProfile() { MediaProfiles::VideoCodec *videoCodec = @@ -449,6 +496,8 @@ MediaProfiles::createDefaultCamcorderLowProfile() /*static*/ void MediaProfiles::createDefaultCamcorderProfiles(MediaProfiles *profiles) { + profiles->mCamcorderProfiles.add(createDefaultCamcorderTimeLapseHighProfile()); + profiles->mCamcorderProfiles.add(createDefaultCamcorderTimeLapseLowProfile()); profiles->mCamcorderProfiles.add(createDefaultCamcorderHighProfile()); profiles->mCamcorderProfiles.add(createDefaultCamcorderLowProfile()); } @@ -668,13 +717,8 @@ Vector<audio_decoder> MediaProfiles::getAudioDecoders() const return decoders; // copy out } -int MediaProfiles::getCamcorderProfileParamByName(const char *name, - int cameraId, - camcorder_quality quality) const +int MediaProfiles::getCamcorderProfileIndex(int cameraId, camcorder_quality quality) const { - LOGV("getCamcorderProfileParamByName: %s for camera %d, quality %d", - name, cameraId, quality); - int index = -1; for (size_t i = 0, n = mCamcorderProfiles.size(); i < n; ++i) { if (mCamcorderProfiles[i]->mCameraId == cameraId && @@ -683,6 +727,17 @@ int MediaProfiles::getCamcorderProfileParamByName(const char *name, break; } } + return index; +} + +int MediaProfiles::getCamcorderProfileParamByName(const char *name, + int cameraId, + camcorder_quality quality) const +{ + LOGV("getCamcorderProfileParamByName: %s for camera %d, quality %d", + name, cameraId, quality); + + int index = getCamcorderProfileIndex(cameraId, quality); if (index == -1) { LOGE("The given camcorder profile camera %d quality %d is not found", cameraId, quality); @@ -705,6 +760,11 @@ int MediaProfiles::getCamcorderProfileParamByName(const char *name, return -1; } +bool MediaProfiles::hasCamcorderProfile(int cameraId, camcorder_quality quality) const +{ + return (getCamcorderProfileIndex(cameraId, quality) != -1); +} + Vector<int> MediaProfiles::getImageEncodingQualityLevels(int cameraId) const { Vector<int> result; diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp index ba98f04..c31b622 100644 --- a/media/libmedia/MediaScanner.cpp +++ b/media/libmedia/MediaScanner.cpp @@ -48,8 +48,7 @@ const char *MediaScanner::locale() const { } status_t MediaScanner::processDirectory( - const char *path, const char *extensions, - MediaScannerClient &client, + const char *path, MediaScannerClient &client, ExceptionCheck exceptionCheck, void *exceptionEnv) { int pathLength = strlen(path); if (pathLength >= PATH_MAX) { @@ -72,35 +71,16 @@ status_t MediaScanner::processDirectory( status_t result = doProcessDirectory( - pathBuffer, pathRemaining, extensions, client, - exceptionCheck, exceptionEnv); + pathBuffer, pathRemaining, client, exceptionCheck, exceptionEnv); free(pathBuffer); return result; } -static bool fileMatchesExtension(const char* path, const char* extensions) { - const char* extension = strrchr(path, '.'); - if (!extension) return false; - ++extension; // skip the dot - if (extension[0] == 0) return false; - - while (extensions[0]) { - const char* comma = strchr(extensions, ','); - size_t length = (comma ? comma - extensions : strlen(extensions)); - if (length == strlen(extension) && strncasecmp(extension, extensions, length) == 0) return true; - extensions += length; - if (extensions[0] == ',') ++extensions; - } - - return false; -} - status_t MediaScanner::doProcessDirectory( - char *path, int pathRemaining, const char *extensions, - MediaScannerClient &client, ExceptionCheck exceptionCheck, - void *exceptionEnv) { + char *path, int pathRemaining, MediaScannerClient &client, + ExceptionCheck exceptionCheck, void *exceptionEnv) { // place to copy file or directory name char* fileSpot = path + strlen(path); struct dirent* entry; @@ -163,14 +143,14 @@ status_t MediaScanner::doProcessDirectory( if (name[0] == '.') continue; strcat(fileSpot, "/"); - int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv); + int err = doProcessDirectory(path, pathRemaining - nameLength - 1, client, exceptionCheck, exceptionEnv); if (err) { // pass exceptions up - ignore other errors if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure; LOGE("Error processing '%s' - skipping\n", path); continue; } - } else if (fileMatchesExtension(path, extensions)) { + } else { struct stat statbuf; stat(path, &statbuf); if (statbuf.st_size > 0) { diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index 3d3bd62..6332b4e 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -21,6 +21,8 @@ #include <sys/stat.h> #include <fcntl.h> #include <errno.h> +#include <sys/stat.h> +#include <dirent.h> #include <cutils/properties.h> @@ -686,21 +688,77 @@ done: return result; } +static void deleteRecursive(const char* path) { + char pathbuf[PATH_MAX]; + int pathLength = strlen(path); + if (pathLength >= sizeof(pathbuf) - 1) { + LOGE("path too long: %s\n", path); + } + strcpy(pathbuf, path); + if (pathbuf[pathLength - 1] != '/') { + pathbuf[pathLength++] = '/'; + } + char* fileSpot = pathbuf + pathLength; + int pathRemaining = sizeof(pathbuf) - pathLength - 1; + + DIR* dir = opendir(path); + if (!dir) { + LOGE("opendir %s failed: %s", path, strerror(errno)); + return; + } + + struct dirent* entry; + while ((entry = readdir(dir))) { + const char* name = entry->d_name; + + // ignore "." and ".." + if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) { + continue; + } + + int nameLength = strlen(name); + if (nameLength > pathRemaining) { + LOGE("path %s/%s too long\n", path, name); + continue; + } + strcpy(fileSpot, name); + + int type = entry->d_type; + if (entry->d_type == DT_DIR) { + deleteRecursive(pathbuf); + rmdir(pathbuf); + } else { + unlink(pathbuf); + } + } +} + +static void deletePath(const char* path) { + struct stat statbuf; + if (stat(path, &statbuf) == 0) { + if (S_ISDIR(statbuf.st_mode)) { + deleteRecursive(path); + rmdir(path); + } else { + unlink(path); + } + } else { + LOGE("deletePath stat failed for %s: %s", path, strerror(errno)); + } +} + MtpResponseCode MtpServer::doDeleteObject() { MtpObjectHandle handle = mRequest.getParameter(1); - MtpObjectFormat format = mRequest.getParameter(1); + MtpObjectFormat format = mRequest.getParameter(2); // FIXME - support deleting all objects if handle is 0xFFFFFFFF // FIXME - implement deleting objects by format - // FIXME - handle non-empty directories MtpString filePath; int64_t fileLength; int result = mDatabase->getObjectFilePath(handle, filePath, fileLength); if (result == MTP_RESPONSE_OK) { LOGV("deleting %s", (const char *)filePath); - // one of these should work - rmdir((const char *)filePath); - unlink((const char *)filePath); + deletePath((const char *)filePath); return mDatabase->deleteFile(handle); } else { return result; diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp index bd5fad2..66f9e2d 100644 --- a/opengl/libs/EGL/egl.cpp +++ b/opengl/libs/EGL/egl.cpp @@ -60,6 +60,7 @@ static char const * const gExtensionString = "EGL_KHR_image_base " "EGL_KHR_image_pixmap " "EGL_KHR_gl_texture_2D_image " + "EGL_KHR_fence_sync " "EGL_ANDROID_image_native_buffer " "EGL_ANDROID_swap_rectangle " ; @@ -244,9 +245,23 @@ struct egl_image_t : public egl_object_t EGLImageKHR images[IMPL_NUM_IMPLEMENTATIONS]; }; +struct egl_sync_t : public egl_object_t +{ + typedef egl_object_t::LocalRef<egl_sync_t, EGLSyncKHR> Ref; + + egl_sync_t(EGLDisplay dpy, EGLContext context, EGLSyncKHR sync) + : dpy(dpy), context(context), sync(sync) + { + } + EGLDisplay dpy; + EGLContext context; + EGLSyncKHR sync; +}; + typedef egl_surface_t::Ref SurfaceRef; typedef egl_context_t::Ref ContextRef; typedef egl_image_t::Ref ImageRef; +typedef egl_sync_t::Ref SyncRef; struct tls_t { @@ -482,6 +497,11 @@ egl_image_t* get_image(EGLImageKHR image) { return egl_to_native_cast<egl_image_t>(image); } +static inline +egl_sync_t* get_sync(EGLSyncKHR sync) { + return egl_to_native_cast<egl_sync_t>(sync); +} + static egl_connection_t* validate_display_config( EGLDisplay dpy, EGLConfig config, egl_display_t const*& dp) @@ -1787,6 +1807,111 @@ EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img) return EGL_TRUE; } +// ---------------------------------------------------------------------------- +// EGL_EGLEXT_VERSION 5 +// ---------------------------------------------------------------------------- + + +EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list) +{ + EGLContext ctx = eglGetCurrentContext(); + ContextRef _c(ctx); + if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_NO_IMAGE_KHR); + if (!validate_display_context(dpy, ctx)) + return EGL_NO_IMAGE_KHR; + egl_display_t const * const dp = get_display(dpy); + egl_context_t * const c = get_context(ctx); + EGLSyncKHR result = EGL_NO_IMAGE_KHR; + if (c->cnx->egl.eglCreateSyncKHR) { + EGLSyncKHR sync = c->cnx->egl.eglCreateSyncKHR( + dp->disp[c->impl].dpy, type, attrib_list); + if (sync == EGL_NO_IMAGE_KHR) + return sync; + result = (egl_sync_t*)new egl_sync_t(dpy, ctx, sync); + } + return (EGLSyncKHR)result; +} + +EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync) +{ + egl_display_t const * const dp = get_display(dpy); + if (dp == 0) { + return setError(EGL_BAD_DISPLAY, EGL_FALSE); + } + + SyncRef _s(sync); + if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE); + egl_sync_t* syncObject = get_sync(sync); + + EGLContext ctx = syncObject->context; + ContextRef _c(ctx); + if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE); + if (!validate_display_context(dpy, ctx)) + return EGL_FALSE; + + egl_context_t * const c = get_context(ctx); + + if (c->cnx->egl.eglDestroySyncKHR) { + return c->cnx->egl.eglDestroySyncKHR( + dp->disp[c->impl].dpy, syncObject->sync); + } + + return EGL_FALSE; +} + +EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout) +{ + egl_display_t const * const dp = get_display(dpy); + if (dp == 0) { + return setError(EGL_BAD_DISPLAY, EGL_FALSE); + } + + SyncRef _s(sync); + if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE); + egl_sync_t* syncObject = get_sync(sync); + + EGLContext ctx = syncObject->context; + ContextRef _c(ctx); + if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE); + if (!validate_display_context(dpy, ctx)) + return EGL_FALSE; + + egl_context_t * const c = get_context(ctx); + + if (c->cnx->egl.eglClientWaitSyncKHR) { + return c->cnx->egl.eglClientWaitSyncKHR( + dp->disp[c->impl].dpy, syncObject->sync, flags, timeout); + } + + return EGL_FALSE; +} + +EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value) +{ + egl_display_t const * const dp = get_display(dpy); + if (dp == 0) { + return setError(EGL_BAD_DISPLAY, EGL_FALSE); + } + + SyncRef _s(sync); + if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE); + egl_sync_t* syncObject = get_sync(sync); + + EGLContext ctx = syncObject->context; + ContextRef _c(ctx); + if (!_c.get()) return setError(EGL_BAD_CONTEXT, EGL_FALSE); + if (!validate_display_context(dpy, ctx)) + return EGL_FALSE; + + egl_context_t * const c = get_context(ctx); + + if (c->cnx->egl.eglGetSyncAttribKHR) { + return c->cnx->egl.eglGetSyncAttribKHR( + dp->disp[c->impl].dpy, syncObject->sync, attribute, value); + } + + return EGL_FALSE; +} // ---------------------------------------------------------------------------- // ANDROID extensions diff --git a/opengl/libs/EGL/egl_entries.in b/opengl/libs/EGL/egl_entries.in index 5d89287..63c3c19 100644 --- a/opengl/libs/EGL/egl_entries.in +++ b/opengl/libs/EGL/egl_entries.in @@ -51,6 +51,13 @@ EGL_ENTRY(EGLBoolean, eglUnlockSurfaceKHR, EGLDisplay, EGLSurface) EGL_ENTRY(EGLImageKHR, eglCreateImageKHR, EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *) EGL_ENTRY(EGLBoolean, eglDestroyImageKHR, EGLDisplay, EGLImageKHR) +/* EGL_EGLEXT_VERSION 5 */ + +EGL_ENTRY(EGLSyncKHR, eglCreateSyncKHR, EGLDisplay, EGLenum, const EGLint *) +EGL_ENTRY(EGLBoolean, eglDestroySyncKHR, EGLDisplay, EGLSyncKHR) +EGL_ENTRY(EGLint, eglClientWaitSyncKHR, EGLDisplay, EGLSyncKHR, EGLint, EGLTimeKHR) +EGL_ENTRY(EGLBoolean, eglGetSyncAttribKHR, EGLDisplay, EGLSyncKHR, EGLint, EGLint *) + /* ANDROID extensions */ EGL_ENTRY(EGLBoolean, eglSetSwapRectangleANDROID, EGLDisplay, EGLSurface, EGLint, EGLint, EGLint, EGLint) diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 2ae34e3..43bb26a 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -74,4 +74,32 @@ <bool name="def_vibrate_in_silent">true</bool> <bool name="def_use_ptp_interface">false</bool> + + <!-- Default for Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION --> + <bool name="def_accessibility_script_injection">false</bool> + + <!-- Default for Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS --> + <string name="def_accessibility_web_content_key_bindings"> + <!-- DPAD/Trackball UP maps to traverse previous on current axis and send an event. --> + 0x13=0x01000100; + <!-- DPAD/Trackball DOWN maps to traverse next on current axis and send an event. --> + 0x14=0x01010100; + <!-- DPAD/Trackball LEFT maps to action in non-android default navigation axis. --> + 0x15=0x04000000; + <!-- DPAD/Trackball RIGHT maps to no action in non-android default navigation axis. --> + 0x16=0x04000000; + <!-- Left Alt+DPAD/Trackball UP transitions from an axis to another and sends an event. --> + <!-- Axis transitions: 2 -> 7; 1 -> 2; 0 -> 1; 3 -> 0; 4 -> 0; 5 -> 0; 6 -> 0; --> + 0x120013=0x03020701:0x03010201:0x03000101:0x03030001:0x03040001:0x03050001:0x03060001; + <!-- Left Alt+DPAD/Trackball DOWN transitions from an axis to another and sends an event. --> + <!-- Axis transitions: 1 -> 0; 2 -> 1; 7 -> 2; 3 -> 7; 4 -> 7; 5 -> 7; 6 -> 7; --> + 0x120014=0x03010001:0x03020101:0x03070201:0x03030701:0x03040701:0x03050701:0x03060701; + <!-- Left Alt+DPAD/Trackball LEFT transitions from an axis to another and sends an event. --> + <!-- Axis transitions: 4 -> 3; 5 -> 4; 6 -> 5; 0 -> 6; 1 -> 6; 2 -> 6; 7 -> 6; --> + 0x120015=0x03040301:0x03050401:0x03060501:0x03000601:0x03010601:0x03020601:0x03070601; + <!-- Left Alt+DPAD/Trackball RIGHT transitions from an axis to another and sends an event. --> + <!-- Axis transitions: 5 -> 6; 4 -> 5; 3 -> 4; 2 -> 3; 7 -> 3; 1 -> 3; 0 -> 3; --> + 0x120016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301; + </string> + </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index c1ad1ca..8eb3fe6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -21,7 +21,6 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; import android.database.Cursor; @@ -35,10 +34,7 @@ import android.os.SystemProperties; import android.provider.Settings; import android.provider.Settings.Secure; import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Config; import android.util.Log; -import android.util.Xml; import com.android.internal.content.PackageHelper; import com.android.internal.telephony.RILConstants; @@ -64,7 +60,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion' // is properly propagated through your change. Not doing so will result in a loss of user // settings. - private static final int DATABASE_VERSION = 57; + private static final int DATABASE_VERSION = 58; private Context mContext; @@ -734,6 +730,33 @@ public class DatabaseHelper extends SQLiteOpenHelper { } upgradeVersion = 57; } + + if (upgradeVersion == 57) { + /* + * New settings to: + * 1. Enable injection of accessibility scripts in WebViews. + * 2. Define the key bindings for traversing web content in WebViews. + */ + db.beginTransaction(); + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement("INSERT INTO secure(name,value)" + + " VALUES(?,?);"); + loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, + R.bool.def_accessibility_script_injection); + stmt.close(); + stmt = db.compileStatement("INSERT INTO secure(name,value)" + + " VALUES(?,?);"); + loadStringSetting(stmt, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, + R.string.def_accessibility_web_content_key_bindings); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + if (stmt != null) stmt.close(); + } + upgradeVersion = 58; + } + // *** Remember to update DATABASE_VERSION above! if (upgradeVersion != currentVersion) { @@ -876,7 +899,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { String cls = parser.getAttributeValue(null, "class"); String shortcutStr = parser.getAttributeValue(null, "shortcut"); - int shortcutValue = (int) shortcutStr.charAt(0); + int shortcutValue = shortcutStr.charAt(0); if (TextUtils.isEmpty(shortcutStr)) { Log.w(TAG, "Unable to get shortcut for: " + pkg + "/" + cls); } @@ -1187,6 +1210,12 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadBooleanSetting(stmt, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED, R.bool.def_mount_ums_notify_enabled); + + loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, + R.bool.def_accessibility_script_injection); + + loadStringSetting(stmt, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, + R.string.def_accessibility_web_content_key_bindings); } finally { if (stmt != null) stmt.close(); } diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml index f4040d9..e0b34cc 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml @@ -159,7 +159,6 @@ android:layout_height="match_parent" android:visibility="invisible" android:clickable="true" - android:onClick="toggleLightsOut" /> </FrameLayout> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 2c0af65..3ef12f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -52,6 +52,8 @@ public class CommandQueue extends IStatusBar.Stub { private static final int OP_EXPAND = 1; private static final int OP_COLLAPSE = 2; + private static final int MSG_SET_LIGHTS_ON = 0x00070000; + private StatusBarIconList mList; private Callbacks mCallbacks; private Handler mHandler = new H(); @@ -75,6 +77,7 @@ public class CommandQueue extends IStatusBar.Stub { public void disable(int state); public void animateExpand(); public void animateCollapse(); + public void setLightsOn(boolean on); } public CommandQueue(Callbacks callbacks, StatusBarIconList list) { @@ -143,6 +146,13 @@ public class CommandQueue extends IStatusBar.Stub { } } + public void setLightsOn(boolean on) { + synchronized (mList) { + mHandler.removeMessages(MSG_SET_LIGHTS_ON); + mHandler.obtainMessage(MSG_SET_LIGHTS_ON, on ? 1 : 0, 0, null).sendToTarget(); + } + } + private final class H extends Handler { public void handleMessage(Message msg) { final int what = msg.what & MSG_MASK; @@ -194,6 +204,10 @@ public class CommandQueue extends IStatusBar.Stub { } else { mCallbacks.animateCollapse(); } + break; + case MSG_SET_LIGHTS_ON: + mCallbacks.setLightsOn(msg.arg1 != 0); + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java index 48243ff..e945981 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java @@ -1011,6 +1011,15 @@ public class PhoneStatusBarService extends StatusBarService { return false; } + public void setLightsOn(boolean on) { + if (!on) { + // All we do for "lights out" mode on a phone is hide the status bar, + // which the window manager does. But we do need to hide the windowshade + // on our own. + animateCollapse(); + } + } + private class Launcher implements View.OnClickListener { private PendingIntent mIntent; private String mPkg; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java index b4e0d3a..5dedcc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java @@ -718,7 +718,6 @@ public class StatusBarPolicy { iconId = sWifiSignalImages[mLastWifiInetConnectivityState] [mLastWifiSignalLevel]; } - mService.setIcon("wifi", iconId, 0); // Show the icon since wi-fi is connected mService.setIconVisibility("wifi", true); @@ -1101,7 +1100,7 @@ public class StatusBarPolicy { int iconId; final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, - sWifiSignalImages.length); + sWifiSignalImages[0].length); if (newSignalLevel != mLastWifiSignalLevel) { mLastWifiSignalLevel = newSignalLevel; if (mIsWifiConnected) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java index a64c3e7..695fdba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java @@ -72,12 +72,16 @@ public abstract class StatusBarService extends Service implements CommandQueue.C mCommandQueue = new CommandQueue(this, iconList); mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + boolean[] lightsOn = new boolean[1]; try { - mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications); + mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, + lightsOn); } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. } + setLightsOn(lightsOn[0]); + // Set up the initial icon state int N = iconList.size(); int viewIndex = 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java index 6f74924..5ba1fab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java @@ -56,8 +56,6 @@ public class TabletStatusBarService extends StatusBarService { private static final int MAX_IMAGE_LEVEL = 10000; - - int mIconSize; H mHandler = new H(); @@ -152,15 +150,12 @@ public class TabletStatusBarService extends StatusBarService { mBarContents = sb.findViewById(R.id.bar_contents); mCurtains = sb.findViewById(R.id.lights_out); View systemInfo = sb.findViewById(R.id.systemInfo); - View.OnLongClickListener toggle = new View.OnLongClickListener() { - public boolean onLongClick(View v) { - toggleLightsOut(v); - return true; - } - }; - - systemInfo.setOnLongClickListener(toggle); - mCurtains.setOnLongClickListener(toggle); + + systemInfo.setOnLongClickListener(new SetLightsOnListener(false)); + + SetLightsOnListener on = new SetLightsOnListener(true); + mCurtains.setOnClickListener(on); + mCurtains.setOnLongClickListener(on); // the more notifications icon mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); @@ -493,6 +488,25 @@ public class TabletStatusBarService extends StatusBarService { mHandler.sendEmptyMessage(H.MSG_CLOSE_SYSTEM_PANEL); } + public void setLightsOn(boolean on) { + if (on) { + mCurtains.setAnimation(AnimationUtils.loadAnimation((Context)this, + R.anim.lights_out_out)); + mCurtains.setVisibility(View.GONE); + mBarContents.setAnimation(AnimationUtils.loadAnimation((Context)this, + R.anim.status_bar_in)); + mBarContents.setVisibility(View.VISIBLE); + } else { + animateCollapse(); + mCurtains.setAnimation(AnimationUtils.loadAnimation((Context)this, + R.anim.lights_out_in)); + mCurtains.setVisibility(View.VISIBLE); + mBarContents.setAnimation(AnimationUtils.loadAnimation((Context)this, + R.anim.status_bar_out)); + mBarContents.setVisibility(View.GONE); + } + } + public void notificationIconsClicked(View v) { if (DEBUG) Slog.d(TAG, "clicked notification icons"); mHandler.removeMessages(H.MSG_CLOSE_SYSTEM_PANEL); @@ -732,26 +746,31 @@ public class TabletStatusBarService extends StatusBarService { return true; } - protected void setLightsOut(boolean out) { - if (out) { - mCurtains.setAnimation(AnimationUtils.loadAnimation((Context)this, - R.anim.lights_out_in)); - mCurtains.setVisibility(View.VISIBLE); - mBarContents.setAnimation(AnimationUtils.loadAnimation((Context)this, - R.anim.status_bar_out)); - mBarContents.setVisibility(View.GONE); - } else { - mCurtains.setAnimation(AnimationUtils.loadAnimation((Context)this, - R.anim.lights_out_out)); - mCurtains.setVisibility(View.GONE); - mBarContents.setAnimation(AnimationUtils.loadAnimation((Context)this, - R.anim.status_bar_in)); - mBarContents.setVisibility(View.VISIBLE); + public class SetLightsOnListener implements View.OnLongClickListener, + View.OnClickListener { + private boolean mOn; + + SetLightsOnListener(boolean on) { + mOn = on; + } + + public void onClick(View v) { + try { + mBarService.setLightsOn(mOn); + } catch (RemoteException ex) { + // system process + } + } + + public boolean onLongClick(View v) { + try { + mBarService.setLightsOn(mOn); + } catch (RemoteException ex) { + // system process + } + return true; } - } - public void toggleLightsOut(View v) { - setLightsOut(mCurtains.getVisibility() != View.VISIBLE); } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index ca9a484..c25df1d 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -1407,18 +1407,20 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: { - AudioManager audioManager = (AudioManager) getContext().getSystemService( - Context.AUDIO_SERVICE); - if (audioManager != null) { - /* - * Play a sound. This is done on key up since we don't want the - * sound to play when a user holds down volume down to mute. - */ - audioManager.adjustSuggestedStreamVolume( - AudioManager.ADJUST_SAME, - mVolumeControlStreamType, - AudioManager.FLAG_PLAY_SOUND); - mVolumeKeyUpTime = SystemClock.uptimeMillis(); + if (!event.isCanceled()) { + AudioManager audioManager = (AudioManager) getContext().getSystemService( + Context.AUDIO_SERVICE); + if (audioManager != null) { + /* + * Play a sound. This is done on key up since we don't want the + * sound to play when a user holds down volume down to mute. + */ + audioManager.adjustSuggestedStreamVolume( + AudioManager.ADJUST_SAME, + mVolumeControlStreamType, + AudioManager.FLAG_PLAY_SOUND); + mVolumeKeyUpTime = SystemClock.uptimeMillis(); + } } return true; } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 6e5db2b..7009c65 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -177,6 +177,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { Context mContext; IWindowManager mWindowManager; LocalPowerManager mPowerManager; + IStatusBarService mStatusBarService; Vibrator mVibrator; // Vibrator for giving feedback of orientation changes // Vibrator pattern for haptic feedback of a long press. @@ -263,6 +264,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final Rect mTmpVisibleFrame = new Rect(); WindowState mTopFullscreenOpaqueWindowState; + boolean mTopIsFullscreen; boolean mForceStatusBar; boolean mHideLockScreen; boolean mDismissKeyguard; @@ -1555,8 +1557,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ public int finishAnimationLw() { int changes = 0; - - boolean hiding = false; + + boolean topIsFullscreen = false; if (mStatusBar != null) { if (localLOGV) Log.i(TAG, "force=" + mForceStatusBar + " top=" + mTopFullscreenOpaqueWindowState); @@ -1564,18 +1566,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_LAYOUT) Log.v(TAG, "Showing status bar"); if (mStatusBar.showLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT; } else if (mTopFullscreenOpaqueWindowState != null) { - //Log.i(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() - // + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw()); - //Log.i(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()); - WindowManager.LayoutParams lp = - mTopFullscreenOpaqueWindowState.getAttrs(); - boolean hideStatusBar = - (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; - if (hideStatusBar) { + final WindowManager.LayoutParams lp = mTopFullscreenOpaqueWindowState.getAttrs(); + if (localLOGV) { + Log.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() + + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw()); + Log.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs() + + " lp.flags=0x" + Integer.toHexString(lp.flags)); + } + topIsFullscreen = (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; + // The subtle difference between the window for mTopFullscreenOpaqueWindowState + // and mTopIsFullscreen is that that mTopIsFullscreen is set only if the window + // has the FLAG_FULLSCREEN set. Not sure if there is another way that to be the + // case though. + if (topIsFullscreen) { if (mStatusBarCanHide) { if (DEBUG_LAYOUT) Log.v(TAG, "Hiding status bar"); if (mStatusBar.hideLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT; - hiding = true; } else if (localLOGV) { Log.v(TAG, "Preventing status bar from hiding by policy"); } @@ -1586,21 +1592,36 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - if (changes != 0 && hiding) { - IStatusBarService sbs = IStatusBarService.Stub.asInterface(ServiceManager.getService("statusbar")); - if (sbs != null) { - try { - // Make sure the window shade is hidden. - sbs.collapse(); - } catch (RemoteException e) { - } - } + if (topIsFullscreen != mTopIsFullscreen) { + final boolean topIsFullscreenF = topIsFullscreen; + mTopIsFullscreen = topIsFullscreen; + mHandler.post(new Runnable() { + public void run() { + if (mStatusBarService == null) { + // This is the one that can not go away, but it doesn't come up + // before the window manager does, so don't fail if it doesn't + // exist. This works as long as no fullscreen windows come up + // before the status bar service does. + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService("statusbar")); + } + final IStatusBarService sbs = mStatusBarService; + if (mStatusBarService != null) { + try { + sbs.setActiveWindowIsFullscreen(topIsFullscreenF); + } catch (RemoteException e) { + // This should be impossible because we're in the same process. + mStatusBarService = null; + } + } + } + }); } // Hide the key guard if a visible window explicitly specifies that it wants to be displayed // when the screen is locked if (mKeyguard != null) { - if (localLOGV) Log.v(TAG, "finishLayoutLw::mHideKeyguard="+mHideLockScreen); + if (localLOGV) Log.v(TAG, "finishAnimationLw::mHideKeyguard="+mHideLockScreen); if (mDismissKeyguard && !mKeyguardMediator.isSecure()) { if (mKeyguard.hideLw(true)) { changes |= FINISH_LAYOUT_REDO_LAYOUT diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 3770b55..886c25b 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -1752,14 +1752,15 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track } // compute volume for this track - int16_t left, right, aux; + uint32_t vl, vr, va; if (track->isMuted() || track->isPausing() || mStreamTypes[track->type()].mute) { - left = right = aux = 0; + vl = vr = va = 0; if (track->isPausing()) { track->setPaused(); } } else { + // read original volumes with volume control float typeVolume = mStreamTypes[track->type()].volume; #ifdef LVMX @@ -1774,36 +1775,37 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track } #endif float v = masterVolume * typeVolume; - uint32_t vl = (uint32_t)(v * cblk->volume[0]) << 12; - uint32_t vr = (uint32_t)(v * cblk->volume[1]) << 12; + vl = (uint32_t)(v * cblk->volume[0]) << 12; + vr = (uint32_t)(v * cblk->volume[1]) << 12; - // Delegate volume control to effect in track effect chain if needed - if (chain != 0 && chain->setVolume_l(&vl, &vr)) { - // Do not ramp volume is volume is controlled by effect + va = (uint32_t)(v * cblk->sendLevel); + } + // Delegate volume control to effect in track effect chain if needed + if (chain != 0 && chain->setVolume_l(&vl, &vr)) { + // Do not ramp volume if volume is controlled by effect + param = AudioMixer::VOLUME; + track->mHasVolumeController = true; + } else { + // force no volume ramp when volume controller was just disabled or removed + // from effect chain to avoid volume spike + if (track->mHasVolumeController) { param = AudioMixer::VOLUME; - track->mHasVolumeController = true; - } else { - // force no volume ramp when volume controller was just disabled or removed - // from effect chain to avoid volume spike - if (track->mHasVolumeController) { - param = AudioMixer::VOLUME; - } - track->mHasVolumeController = false; } - - // Convert volumes from 8.24 to 4.12 format - uint32_t v_clamped = (vl + (1 << 11)) >> 12; - if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; - left = int16_t(v_clamped); - v_clamped = (vr + (1 << 11)) >> 12; - if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; - right = int16_t(v_clamped); - - v_clamped = (uint32_t)(v * cblk->sendLevel); - if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; - aux = int16_t(v_clamped); + track->mHasVolumeController = false; } + // Convert volumes from 8.24 to 4.12 format + int16_t left, right, aux; + uint32_t v_clamped = (vl + (1 << 11)) >> 12; + if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; + left = int16_t(v_clamped); + v_clamped = (vr + (1 << 11)) >> 12; + if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; + right = int16_t(v_clamped); + + if (va > MAX_GAIN_INT) va = MAX_GAIN_INT; + aux = int16_t(va); + #ifdef LVMX if ( tracksConnectedChanged || stateChanged ) { @@ -2283,7 +2285,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // only one effect chain can be present on DirectOutputThread, so if // there is one, the track is connected to it if (!effectChains.isEmpty()) { - // Do not ramp volume is volume is controlled by effect + // Do not ramp volume if volume is controlled by effect if(effectChains[0]->setVolume_l(&vl, &vr)) { rampVolume = false; } @@ -4728,7 +4730,7 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid, // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects // that can only be created by audio policy manager (running in same process) if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE && - getpid() != IPCThreadState::self()->getCallingPid()) { + getpid() != pid) { lStatus = INVALID_OPERATION; goto Exit; } diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index e454c08..aa87f29 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -54,6 +54,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.WorkSource; import android.provider.Settings; import android.util.EventLog; import android.util.Slog; @@ -504,7 +505,7 @@ class BackupManagerService extends IBackupManager.Stub { parseLeftoverJournals(); // Power management - mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup"); + mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*"); // Start the backup passes going setBackupEnabled(areEnabled); @@ -1363,6 +1364,7 @@ class BackupManagerService extends IBackupManager.Stub { ? IApplicationThread.BACKUP_MODE_FULL : IApplicationThread.BACKUP_MODE_INCREMENTAL; try { + mWakelock.setWorkSource(new WorkSource(request.appInfo.uid)); agent = bindToAgentSynchronous(request.appInfo, mode); if (agent != null) { int result = processOneBackup(request, agent, transport); @@ -1378,6 +1380,8 @@ class BackupManagerService extends IBackupManager.Stub { } } + mWakelock.setWorkSource(null); + return BackupConstants.TRANSPORT_OK; } diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index 314dd8a..90d036a 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -433,11 +433,6 @@ public class InputManager { } @SuppressWarnings("unused") - public void notifyAppSwitchComing() { - mWindowManagerService.mInputMonitor.notifyAppSwitchComing(); - } - - @SuppressWarnings("unused") public boolean filterTouchEvents() { return mContext.getResources().getBoolean( com.android.internal.R.bool.config_filterTouchEvents); diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index c61baad..9efc708 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1751,24 +1751,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); } + p.println(" "); if (client != null) { - p.println(" "); pw.flush(); try { client.client.asBinder().dump(fd, args); } catch (RemoteException e) { p.println("Input method client dead: " + e); } + } else { + p.println("No input method client."); } + p.println(" "); if (method != null) { - p.println(" "); pw.flush(); try { method.asBinder().dump(fd, args); } catch (RemoteException e) { p.println("Input method service dead: " + e); } + } else { + p.println("No input method service."); } } } diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index a343c59..8452a9f 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -52,6 +52,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.WorkSource; import android.provider.Settings; import android.util.Log; import android.util.Slog; @@ -157,6 +158,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider = new HashMap<String,ArrayList<UpdateRecord>>(); + /** + * Temporary filled in when computing min time for a provider. Access is + * protected by global lock mLock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); + // Proximity listeners private Receiver mProximityReceiver = null; private ILocationListener mProximityListener = null; @@ -913,7 +920,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (enabled) { p.enable(); if (listeners > 0) { - p.setMinTime(getMinTimeLocked(provider)); + p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource); p.enableLocationTracking(true); } } else { @@ -925,9 +932,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private long getMinTimeLocked(String provider) { long minTime = Long.MAX_VALUE; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + mTmpWorkSource.clear(); if (records != null) { for (int i=records.size()-1; i>=0; i--) { - minTime = Math.min(minTime, records.get(i).mMinTime); + UpdateRecord ur = records.get(i); + long curTime = ur.mMinTime; + if (curTime < minTime) { + minTime = curTime; + } + } + long inclTime = (minTime*3)/2; + for (int i=records.size()-1; i>=0; i--) { + UpdateRecord ur = records.get(i); + if (ur.mMinTime <= inclTime) { + mTmpWorkSource.add(ur.mUid); + } } } return minTime; @@ -1125,7 +1144,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run boolean isProviderEnabled = isAllowedBySettingsLocked(provider); if (isProviderEnabled) { long minTimeForProvider = getMinTimeLocked(provider); - p.setMinTime(minTimeForProvider); + p.setMinTime(minTimeForProvider, mTmpWorkSource); // try requesting single shot if singleShot is true, and fall back to // regular location tracking if requestSingleShotFix() is not supported if (!singleShot || !p.requestSingleShotFix()) { @@ -1223,7 +1242,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface p = mProvidersByName.get(provider); if (p != null) { if (hasOtherListener) { - p.setMinTime(getMinTimeLocked(provider)); + p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource); } else { p.enableLocationTracking(false); } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index f6d92b5..86ea0a5 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -3026,7 +3026,8 @@ class PackageManagerService extends IPackageManager.Stub { // Just create the setting, don't add it yet. For already existing packages // the PkgSetting exists already and doesn't have to be created. pkgSetting = mSettings.getPackageLP(pkg, origPackage, realName, suid, destCodeFile, - destResourceFile, pkg.applicationInfo.flags, true, false); + destResourceFile, pkg.applicationInfo.nativeLibraryDir, + pkg.applicationInfo.flags, true, false); if (pkgSetting == null) { Slog.w(TAG, "Creating application package " + pkg.packageName + " failed"); mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; @@ -3244,14 +3245,21 @@ class PackageManagerService extends IPackageManager.Stub { } } + pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString; + /* * Set the data dir to the default "/data/data/<package name>/lib" * if we got here without anyone telling us different (e.g., apps * stored on SD card have their native libraries stored in the ASEC * container with the APK). + * + * This happens during an upgrade from a package settings file that + * doesn't have a native library path attribute at all. */ - if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) { - pkg.applicationInfo.nativeLibraryDir = new File(dataPath, LIB_DIR_NAME).getPath(); + if (pkgSetting.nativeLibraryPathString == null && pkg.applicationInfo.dataDir != null) { + final String nativeLibraryPath = new File(dataPath, LIB_DIR_NAME).getPath(); + pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath; + pkgSetting.nativeLibraryPathString = nativeLibraryPath; } pkgSetting.uidError = uidError; @@ -3273,7 +3281,6 @@ class PackageManagerService extends IPackageManager.Stub { if ((!isSystemApp(pkg) || isUpdatedSystemApp(pkg)) && !isExternal(pkg)) { Log.i(TAG, path + " changed; unpacking"); File sharedLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); - sharedLibraryDir.mkdir(); NativeLibraryHelper.copyNativeBinariesLI(scanFile, sharedLibraryDir); } pkg.mScanPath = path; @@ -3591,40 +3598,6 @@ class PackageManagerService extends IPackageManager.Stub { } } - // Convenience call for removeNativeBinariesLI(File) - private void removeNativeBinariesLI(PackageParser.Package pkg) { - File nativeLibraryDir = getNativeBinaryDirForPackage(pkg); - removeNativeBinariesLI(nativeLibraryDir); - } - - // Remove the native binaries of a given package. This simply - // gets rid of the files in the 'lib' sub-directory. - public void removeNativeBinariesLI(File binaryDir) { - if (DEBUG_NATIVE) { - Slog.w(TAG, "Deleting native binaries from: " + binaryDir.getPath()); - } - - // Just remove any file in the directory. Since the directory - // is owned by the 'system' UID, the application is not supposed - // to have written anything there. - // - if (binaryDir.exists()) { - File[] binaries = binaryDir.listFiles(); - if (binaries != null) { - for (int nn = 0; nn < binaries.length; nn++) { - if (DEBUG_NATIVE) { - Slog.d(TAG, " Deleting " + binaries[nn].getName()); - } - if (!binaries[nn].delete()) { - Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath()); - } - } - } - // Do not delete 'lib' directory itself, or this will prevent - // installation of future updates. - } - } - void removePackageLI(PackageParser.Package pkg, boolean chatty) { if (chatty && Config.LOGD) Log.d( TAG, "Removing package " + pkg.applicationInfo.packageName ); @@ -5128,7 +5101,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (libraryPath != null) { - removeNativeBinariesLI(new File(libraryPath)); + NativeLibraryHelper.removeNativeBinariesLI(libraryPath); } } @@ -6202,8 +6175,8 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { // Reinstate the old system package mSettings.enableSystemPackageLP(p.packageName); - // Remove any native libraries. XXX needed? - removeNativeBinariesLI(p); + // Remove any native libraries from the upgraded package. + NativeLibraryHelper.removeNativeBinariesLI(p.applicationInfo.nativeLibraryDir); } // Install the system package PackageParser.Package newPkg = scanPackageLI(ps.codePath, @@ -7583,18 +7556,20 @@ class PackageManagerService extends IPackageManager.Stub { String installerPackageName; PackageSettingBase(String name, String realName, File codePath, File resourcePath, - int pVersionCode, int pkgFlags) { + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { super(pkgFlags); this.name = name; this.realName = realName; - init(codePath, resourcePath, pVersionCode); + init(codePath, resourcePath, nativeLibraryPathString, pVersionCode); } - void init(File codePath, File resourcePath, int pVersionCode) { + void init(File codePath, File resourcePath, String nativeLibraryPathString, + int pVersionCode) { this.codePath = codePath; this.codePathString = codePath.toString(); this.resourcePath = resourcePath; this.resourcePathString = resourcePath.toString(); + this.nativeLibraryPathString = nativeLibraryPathString; this.versionCode = pVersionCode; } @@ -7687,8 +7662,9 @@ class PackageManagerService extends IPackageManager.Stub { SharedUserSetting sharedUser; PackageSetting(String name, String realName, File codePath, File resourcePath, - int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, pVersionCode, pkgFlags); + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, + pkgFlags); } @Override @@ -7800,8 +7776,9 @@ class PackageManagerService extends IPackageManager.Stub { final int sharedId; PendingPackage(String name, String realName, File codePath, File resourcePath, - int sharedId, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, pVersionCode, pkgFlags); + String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, + pVersionCode, pkgFlags); this.sharedId = sharedId; } } @@ -7830,10 +7807,10 @@ class PackageManagerService extends IPackageManager.Stub { PackageSetting getPackageLP(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - int pkgFlags, boolean create, boolean add) { + String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) { final String name = pkg.packageName; PackageSetting p = getPackageLP(name, origPackage, realName, sharedUser, codePath, - resourcePath, pkg.mVersionCode, pkgFlags, create, add); + resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add); return p; } @@ -7929,14 +7906,14 @@ class PackageManagerService extends IPackageManager.Stub { if((p.pkg != null) && (p.pkg.applicationInfo != null)) { p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } - PackageSetting ret = addPackageLP(name, p.realName, p.codePath, - p.resourcePath, p.userId, p.versionCode, p.pkgFlags); + PackageSetting ret = addPackageLP(name, p.realName, p.codePath, p.resourcePath, + p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags); mDisabledSysPackages.remove(name); return ret; } - PackageSetting addPackageLP(String name, String realName, File codePath, - File resourcePath, int uid, int vc, int pkgFlags) { + PackageSetting addPackageLP(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int uid, int vc, int pkgFlags) { PackageSetting p = mPackages.get(name); if (p != null) { if (p.userId == uid) { @@ -7946,7 +7923,8 @@ class PackageManagerService extends IPackageManager.Stub { "Adding duplicate package, keeping first: " + name); return null; } - p = new PackageSetting(name, realName, codePath, resourcePath, vc, pkgFlags); + p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, + vc, pkgFlags); p.userId = uid; if (addUserIdLP(uid, p, name)) { mPackages.put(name, p); @@ -8001,7 +7979,7 @@ class PackageManagerService extends IPackageManager.Stub { private PackageSetting getPackageLP(String name, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - int vc, int pkgFlags, boolean create, boolean add) { + String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) { PackageSetting p = mPackages.get(name); if (p != null) { if (!p.codePath.equals(codePath)) { @@ -8044,8 +8022,8 @@ class PackageManagerService extends IPackageManager.Stub { } if (origPackage != null) { // We are consuming the data from an existing package. - p = new PackageSetting(origPackage.name, name, codePath, - resourcePath, vc, pkgFlags); + p = new PackageSetting(origPackage.name, name, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); if (DEBUG_UPGRADE) Log.v(TAG, "Package " + name + " is adopting original package " + origPackage.name); // Note that we will retain the new package's signature so @@ -8061,7 +8039,8 @@ class PackageManagerService extends IPackageManager.Stub { // Update new package state. p.setTimeStamp(codePath.lastModified()); } else { - p = new PackageSetting(name, realName, codePath, resourcePath, vc, pkgFlags); + p = new PackageSetting(name, realName, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); p.setTimeStamp(codePath.lastModified()); p.sharedUser = sharedUser; if (sharedUser != null) { @@ -8782,8 +8761,8 @@ class PackageManagerService extends IPackageManager.Stub { Object idObj = getUserIdLP(pp.sharedId); if (idObj != null && idObj instanceof SharedUserSetting) { PackageSetting p = getPackageLP(pp.name, null, pp.realName, - (SharedUserSetting)idObj, pp.codePath, pp.resourcePath, - pp.versionCode, pp.pkgFlags, true, true); + (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, + pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true); if (p == null) { Slog.w(TAG, "Unable to create application package for " + pp.name); @@ -8888,6 +8867,7 @@ class PackageManagerService extends IPackageManager.Stub { String realName = parser.getAttributeValue(null, "realName"); String codePathStr = parser.getAttributeValue(null, "codePath"); String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); if (resourcePathStr == null) { resourcePathStr = codePathStr; } @@ -8902,9 +8882,8 @@ class PackageManagerService extends IPackageManager.Stub { int pkgFlags = 0; pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - PackageSetting ps = new PackageSetting(name, realName, - new File(codePathStr), - new File(resourcePathStr), versionCode, pkgFlags); + PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags); String timeStampStr = parser.getAttributeValue(null, "ts"); if (timeStampStr != null) { try { @@ -9023,9 +9002,9 @@ class PackageManagerService extends IPackageManager.Stub { "Error in package manager settings: <package> has no codePath at " + parser.getPositionDescription()); } else if (userId > 0) { - packageSetting = addPackageLP(name.intern(), realName, - new File(codePathStr), new File(resourcePathStr), - userId, versionCode, pkgFlags); + packageSetting = addPackageLP(name.intern(), realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode, + pkgFlags); if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name + ": userId=" + userId + " pkg=" + packageSetting); if (packageSetting == null) { @@ -9042,7 +9021,7 @@ class PackageManagerService extends IPackageManager.Stub { if (userId > 0) { packageSetting = new PendingPackage(name.intern(), realName, new File(codePathStr), new File(resourcePathStr), - userId, versionCode, pkgFlags); + nativeLibraryPathStr, userId, versionCode, pkgFlags); packageSetting.setTimeStamp(timeStamp, timeStampStr); mPendingPackages.add((PendingPackage) packageSetting); if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 2e32e60..4d68b52 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -50,6 +50,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.WorkSource; import android.provider.Settings.SettingNotFoundException; import android.provider.Settings; import android.util.EventLog; @@ -310,7 +311,7 @@ class PowerManagerService extends IPowerManager.Stub long ident = Binder.clearCallingIdentity(); try { PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken, - MY_UID, MY_PID, mTag); + MY_UID, MY_PID, mTag, null); mHeld = true; } finally { Binder.restoreCallingIdentity(ident); @@ -607,6 +608,7 @@ class PowerManagerService extends IPowerManager.Stub final int uid; final int pid; final int monitorType; + WorkSource ws; boolean activated = true; int minState; } @@ -630,35 +632,74 @@ class PowerManagerService extends IPowerManager.Stub || n == PowerManager.SCREEN_DIM_WAKE_LOCK; } - public void acquireWakeLock(int flags, IBinder lock, String tag) { + void enforceWakeSourcePermission(int uid, int pid) { + if (uid == Process.myUid()) { + return; + } + mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, + pid, uid, null); + } + + public void acquireWakeLock(int flags, IBinder lock, String tag, WorkSource ws) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); if (uid != Process.myUid()) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); } + if (ws != null) { + enforceWakeSourcePermission(uid, pid); + } long ident = Binder.clearCallingIdentity(); try { synchronized (mLocks) { - acquireWakeLockLocked(flags, lock, uid, pid, tag); + acquireWakeLockLocked(flags, lock, uid, pid, tag, ws); } } finally { Binder.restoreCallingIdentity(ident); } } - public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag) { - int acquireUid = -1; - int acquirePid = -1; - String acquireName = null; - int acquireType = -1; + void noteStartWakeLocked(WakeLock wl, WorkSource ws) { + try { + if (ws != null) { + mBatteryStats.noteStartWakelockFromSource(ws, wl.pid, wl.tag, + wl.monitorType); + } else { + mBatteryStats.noteStartWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType); + } + } catch (RemoteException e) { + // Ignore + } + } + void noteStopWakeLocked(WakeLock wl, WorkSource ws) { + try { + if (ws != null) { + mBatteryStats.noteStopWakelockFromSource(ws, wl.pid, wl.tag, + wl.monitorType); + } else { + mBatteryStats.noteStopWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType); + } + } catch (RemoteException e) { + // Ignore + } + } + + public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag, + WorkSource ws) { if (mSpew) { Slog.d(TAG, "acquireWakeLock flags=0x" + Integer.toHexString(flags) + " tag=" + tag); } + if (ws != null && ws.size() == 0) { + ws = null; + } + int index = mLocks.getIndex(lock); WakeLock wl; boolean newlock; + boolean diffsource; + WorkSource oldsource; if (index < 0) { wl = new WakeLock(flags, lock, tag, uid, pid); switch (wl.flags & LOCK_MASK) @@ -687,10 +728,31 @@ class PowerManagerService extends IPowerManager.Stub return; } mLocks.addLock(wl); + if (ws != null) { + wl.ws = new WorkSource(ws); + } newlock = true; + diffsource = false; + oldsource = null; } else { wl = mLocks.get(index); newlock = false; + oldsource = wl.ws; + if (oldsource != null) { + if (ws == null) { + wl.ws = null; + diffsource = true; + } else { + diffsource = oldsource.diff(ws); + } + } else if (ws != null) { + diffsource = true; + } else { + diffsource = false; + } + if (diffsource) { + wl.ws = new WorkSource(ws); + } } if (isScreenLock(flags)) { // if this causes a wakeup, we reactivate all of the locks and @@ -731,19 +793,41 @@ class PowerManagerService extends IPowerManager.Stub enableProximityLockLocked(); } } - if (newlock) { - acquireUid = wl.uid; - acquirePid = wl.pid; - acquireName = wl.tag; - acquireType = wl.monitorType; + + if (diffsource) { + // If the lock sources have changed, need to first release the + // old ones. + noteStopWakeLocked(wl, oldsource); + } + if (newlock || diffsource) { + noteStartWakeLocked(wl, ws); } + } - if (acquireType >= 0) { - try { - mBatteryStats.noteStartWakelock(acquireUid, acquirePid, acquireName, acquireType); - } catch (RemoteException e) { - // Ignore + public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + if (ws != null && ws.size() == 0) { + ws = null; + } + if (ws != null) { + enforceWakeSourcePermission(uid, pid); + } + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLocks) { + int index = mLocks.getIndex(lock); + if (index < 0) { + throw new IllegalArgumentException("Wake lock not active"); + } + WakeLock wl = mLocks.get(index); + WorkSource oldsource = wl.ws; + wl.ws = ws != null ? new WorkSource(ws) : null; + noteStopWakeLocked(wl, oldsource); + noteStartWakeLocked(wl, ws); } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -759,11 +843,6 @@ class PowerManagerService extends IPowerManager.Stub } private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) { - int releaseUid; - int releasePid; - String releaseName; - int releaseType; - WakeLock wl = mLocks.removeLock(lock); if (wl == null) { return; @@ -804,17 +883,11 @@ class PowerManagerService extends IPowerManager.Stub } // Unlink the lock from the binder. wl.binder.unlinkToDeath(wl, 0); - releaseUid = wl.uid; - releasePid = wl.pid; - releaseName = wl.tag; - releaseType = wl.monitorType; - if (releaseType >= 0) { + if (wl.monitorType >= 0) { long origId = Binder.clearCallingIdentity(); try { - mBatteryStats.noteStopWakelock(releaseUid, releasePid, releaseName, releaseType); - } catch (RemoteException e) { - // Ignore + noteStopWakeLocked(wl, wl.ws); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index 717c309..b1baec5 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -68,6 +68,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); int mDisabled = 0; + Object mLock = new Object(); + // We usually call it lights out mode, but double negatives are annoying + boolean mLightsOn = true; + private class DisableRecord implements IBinder.DeathRecipient { String pkg; int what; @@ -242,6 +246,56 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } + /** + * This is used for the automatic version of lights-out mode. Only call this from + * the window manager. + * + * @see setLightsOn(boolean) + */ + public void setActiveWindowIsFullscreen(boolean fullscreen) { + // We could get away with a separate permission here, but STATUS_BAR is + // signatureOrSystem which is probably good enough. There is no public API + // for this, so the question is a security issue, not an API compatibility issue. + enforceStatusBar(); + + synchronized (mLock) { + updateLightsOnLocked(!fullscreen); + } + } + + /** + * This is used for the user-controlled version of lights-out mode. Only call this from + * the status bar itself. + * + * We have two different functions here, because I think we're going to want to + * tweak the behavior when the user keeps turning lights-out mode off and the + * app keeps trying to turn it on. For now they can just fight it out. Having + * these two separte inputs will allow us to keep that change local to here. --joeo + */ + public void setLightsOn(boolean lightsOn) { + enforceStatusBarService(); + + synchronized (mLock) { + updateLightsOnLocked(lightsOn); + } + } + + private void updateLightsOnLocked(final boolean lightsOn) { + if (mLightsOn != lightsOn) { + mLightsOn = lightsOn; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setLightsOn(lightsOn); + } catch (RemoteException ex) { + } + } + } + }); + } + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); @@ -262,7 +316,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub // Callbacks from the status bar service. // ================================================================================ public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, - List<IBinder> notificationKeys, List<StatusBarNotification> notifications) { + List<IBinder> notificationKeys, List<StatusBarNotification> notifications, + boolean lightsOn[]) { enforceStatusBarService(); Slog.i(TAG, "registerStatusBar bar=" + bar); @@ -276,6 +331,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub notifications.add(e.getValue()); } } + synchronized (mLock) { + lightsOn[0] = mLightsOn; + } } /** diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index dffc31c..3abac0d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -33,9 +33,11 @@ import android.content.Context; import android.content.pm.IPackageManager; import android.database.ContentObserver; import android.media.AudioService; +import android.os.Build; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; @@ -532,6 +534,17 @@ class ServerThread extends Thread { } }); + // For debug builds, log event loop stalls to dropbox for analysis. + // Similar logic also appears in ActivityThread.java for system apps. + if (!"user".equals(Build.TYPE)) { + Slog.i(TAG, "Enabling StrictMode for system server."); + StrictMode.setThreadPolicy( + StrictMode.DISALLOW_DISK_WRITE | + StrictMode.DISALLOW_DISK_READ | + StrictMode.DISALLOW_NETWORK | + StrictMode.PENALTY_DROPBOX); + } + Looper.loop(); Slog.d(TAG, "System ServerThread is exiting!"); } diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index 2e7e3e1..f0b5955 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -29,6 +29,7 @@ import android.os.RemoteException; import android.os.IBinder; import android.os.Binder; import android.os.SystemClock; +import android.os.WorkSource; import android.util.Slog; import java.util.LinkedList; @@ -39,6 +40,7 @@ public class VibratorService extends IVibratorService.Stub { private final LinkedList<Vibration> mVibrations; private Vibration mCurrentVibration; + private final WorkSource mTmpWorkSource = new WorkSource(); private class Vibration implements IBinder.DeathRecipient { private final IBinder mToken; @@ -46,22 +48,24 @@ public class VibratorService extends IVibratorService.Stub { private final long mStartTime; private final long[] mPattern; private final int mRepeat; + private final int mUid; - Vibration(IBinder token, long millis) { - this(token, millis, null, 0); + Vibration(IBinder token, long millis, int uid) { + this(token, millis, null, 0, uid); } - Vibration(IBinder token, long[] pattern, int repeat) { - this(token, 0, pattern, repeat); + Vibration(IBinder token, long[] pattern, int repeat, int uid) { + this(token, 0, pattern, repeat, uid); } private Vibration(IBinder token, long millis, long[] pattern, - int repeat) { + int repeat, int uid) { mToken = token; mTimeout = millis; mStartTime = SystemClock.uptimeMillis(); mPattern = pattern; mRepeat = repeat; + mUid = uid; } public void binderDied() { @@ -98,7 +102,7 @@ public class VibratorService extends IVibratorService.Stub { mContext = context; PowerManager pm = (PowerManager)context.getSystemService( Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); mWakeLock.setReferenceCounted(true); mVibrations = new LinkedList<Vibration>(); @@ -113,6 +117,7 @@ public class VibratorService extends IVibratorService.Stub { != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires VIBRATE permission"); } + int uid = Binder.getCallingUid(); // We're running in the system server so we cannot crash. Check for a // timeout of 0 or negative. This will ensure that a vibration has // either a timeout of > 0 or a non-null pattern. @@ -122,7 +127,7 @@ public class VibratorService extends IVibratorService.Stub { // longer than milliseconds. return; } - Vibration vib = new Vibration(token, milliseconds); + Vibration vib = new Vibration(token, milliseconds, uid); synchronized (mVibrations) { removeVibrationLocked(token); doCancelVibrateLocked(); @@ -146,6 +151,7 @@ public class VibratorService extends IVibratorService.Stub { != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires VIBRATE permission"); } + int uid = Binder.getCallingUid(); // so wakelock calls will succeed long identity = Binder.clearCallingIdentity(); try { @@ -165,7 +171,7 @@ public class VibratorService extends IVibratorService.Stub { return; } - Vibration vib = new Vibration(token, pattern, repeat); + Vibration vib = new Vibration(token, pattern, repeat, uid); try { token.linkToDeath(vib, 0); } catch (RemoteException e) { @@ -280,6 +286,8 @@ public class VibratorService extends IVibratorService.Stub { VibrateThread(Vibration vib) { mVibration = vib; + mTmpWorkSource.set(vib.mUid); + mWakeLock.setWorkSource(mTmpWorkSource); mWakeLock.acquire(); } diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 6ecc511..e8502fa 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -50,6 +50,7 @@ import android.os.INetworkManagementService; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; @@ -1035,8 +1036,8 @@ public class WifiService extends IWifiManager.Stub { } private class WifiLock extends DeathRecipient { - WifiLock(int lockMode, String tag, IBinder binder) { - super(lockMode, tag, binder); + WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) { + super(lockMode, tag, binder, ws); } public void binderDied() { @@ -1106,33 +1107,70 @@ public class WifiService extends IWifiManager.Stub { } } - public boolean acquireWifiLock(IBinder binder, int lockMode, String tag) { + void enforceWakeSourcePermission(int uid, int pid) { + if (uid == android.os.Process.myUid()) { + return; + } + mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, + pid, uid, null); + } + + public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); if (lockMode != WifiManager.WIFI_MODE_FULL && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY) { return false; } - WifiLock wifiLock = new WifiLock(lockMode, tag, binder); + if (ws != null) { + enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid()); + } + if (ws != null && ws.size() == 0) { + ws = null; + } + if (ws == null) { + ws = new WorkSource(Binder.getCallingUid()); + } + WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws); synchronized (mLocks) { return acquireWifiLockLocked(wifiLock); } } + private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException { + switch(wifiLock.mMode) { + case WifiManager.WIFI_MODE_FULL: + mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); + break; + case WifiManager.WIFI_MODE_SCAN_ONLY: + mBatteryStats.noteScanWifiLockAcquiredFromSource(wifiLock.mWorkSource); + break; + } + } + + private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException { + switch(wifiLock.mMode) { + case WifiManager.WIFI_MODE_FULL: + mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); + break; + case WifiManager.WIFI_MODE_SCAN_ONLY: + mBatteryStats.noteScanWifiLockReleasedFromSource(wifiLock.mWorkSource); + break; + } + } + private boolean acquireWifiLockLocked(WifiLock wifiLock) { Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock); mLocks.addLock(wifiLock); - int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { + noteAcquireWifiLock(wifiLock); switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: ++mFullLocksAcquired; - mBatteryStats.noteFullWifiLockAcquired(uid); break; case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksAcquired; - mBatteryStats.noteScanWifiLockAcquired(uid); break; } } catch (RemoteException e) { @@ -1144,6 +1182,33 @@ public class WifiService extends IWifiManager.Stub { return true; } + public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + if (ws != null && ws.size() == 0) { + ws = null; + } + if (ws != null) { + enforceWakeSourcePermission(uid, pid); + } + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLocks) { + int index = mLocks.findLockByBinder(lock); + if (index < 0) { + throw new IllegalArgumentException("Wifi lock not active"); + } + WifiLock wl = mLocks.mList.get(index); + noteReleaseWifiLock(wl); + wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid); + noteAcquireWifiLock(wl); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + } + public boolean releaseWifiLock(IBinder lock) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); synchronized (mLocks) { @@ -1161,17 +1226,15 @@ public class WifiService extends IWifiManager.Stub { hadLock = (wifiLock != null); if (hadLock) { - int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { + noteAcquireWifiLock(wifiLock); switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: ++mFullLocksReleased; - mBatteryStats.noteFullWifiLockReleased(uid); break; case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksReleased; - mBatteryStats.noteScanWifiLockReleased(uid); break; } } catch (RemoteException e) { @@ -1189,12 +1252,14 @@ public class WifiService extends IWifiManager.Stub { String mTag; int mMode; IBinder mBinder; + WorkSource mWorkSource; - DeathRecipient(int mode, String tag, IBinder binder) { + DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) { super(); mTag = tag; mMode = mode; mBinder = binder; + mWorkSource = ws; try { mBinder.linkToDeath(this, 0); } catch (RemoteException e) { @@ -1209,7 +1274,7 @@ public class WifiService extends IWifiManager.Stub { private class Multicaster extends DeathRecipient { Multicaster(String tag, IBinder binder) { - super(Binder.getCallingUid(), tag, binder); + super(Binder.getCallingUid(), tag, binder, null); } public void binderDied() { diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 2ab8091..dacdd7d 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -1481,6 +1482,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState wb = localmWindows.get(foundI-1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && + wb.mAttachedWindow != foundW.mAttachedWindow && (wb.mAttrs.type != TYPE_APPLICATION_STARTING || wb.mToken != foundW.mToken)) { // This window is not related to the previous one in any @@ -5087,8 +5089,7 @@ public class WindowManagerService extends IWindowManager.Stub } /* Notifies the window manager about an input channel that is not responding. - * The method can either cause dispatching to be aborted by returning -2 or - * return a new timeout in nanoseconds. + * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. * * Called by the InputManager. */ @@ -5097,7 +5098,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); if (windowState == null) { - return -2; // irrelevant, abort dispatching (-2) + return 0; // window is unknown, abort dispatching } Slog.i(TAG, "Input event dispatching timed out sending to " @@ -5120,8 +5121,7 @@ public class WindowManagerService extends IWindowManager.Stub /* Notifies the window manager about an application that is not responding * in general rather than with respect to a particular input channel. - * The method can either cause dispatching to be aborted by returning -2 or - * return a new timeout in nanoseconds. + * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. * * Called by the InputManager. */ @@ -5147,7 +5147,7 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RemoteException ex) { } } - return -2; // abort dispatching + return 0; // abort dispatching } private WindowState getWindowStateForInputChannel(InputChannel inputChannel) { @@ -5261,15 +5261,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - /* Notifies that an app switch key (BACK / HOME) has just been pressed. - * This essentially starts a .5 second timeout for the application to process - * subsequent input events while waiting for the app switch to occur. If it takes longer - * than this, the pending events will be dropped. - */ - public void notifyAppSwitchComing() { - // TODO Not implemented yet. Should go in the native side. - } - /* Notifies that the lid switch changed state. */ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); @@ -7037,6 +7028,9 @@ public class WindowManagerService extends IWindowManager.Stub frame.right >= mCompatibleScreenFrame.right && frame.bottom >= mCompatibleScreenFrame.bottom; } else { + if ((mAttrs.flags & FLAG_FULLSCREEN) != 0) { + return true; + } return frame.left <= 0 && frame.top <= 0 && frame.right >= screenWidth && frame.bottom >= screenHeight; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 1a7e944..5432890 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -6021,12 +6021,18 @@ public final class ActivityManagerService extends ActivityManagerNative finisher = new IIntentReceiver.Stub() { public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, - boolean sticky) - throws RemoteException { - synchronized (ActivityManagerService.this) { - mDidUpdate = true; - } - systemReady(goingCallback); + boolean sticky) { + // The raw IIntentReceiver interface is called + // with the AM lock held, so redispatch to + // execute our code without the lock. + mHandler.post(new Runnable() { + public void run() { + synchronized (ActivityManagerService.this) { + mDidUpdate = true; + } + systemReady(goingCallback); + } + }); } }; } @@ -6592,13 +6598,19 @@ public final class ActivityManagerService extends ActivityManagerNative * Utility function for addErrorToDropBox and handleStrictModeViolation's logging * to append various headers to the dropbox log text. */ - private static void appendDropBoxProcessHeaders(ProcessRecord process, StringBuilder sb) { - if (process == null || process.pid == MY_PID) { - sb.append("Process: system_server\n"); - } else { - sb.append("Process: ").append(process.processName).append("\n"); - } - if (process != null) { + private void appendDropBoxProcessHeaders(ProcessRecord process, StringBuilder sb) { + // Note: ProcessRecord 'process' is guarded by the service + // instance. (notably process.pkgList, which could otherwise change + // concurrently during execution of this method) + synchronized (this) { + if (process == null || process.pid == MY_PID) { + sb.append("Process: system_server\n"); + } else { + sb.append("Process: ").append(process.processName).append("\n"); + } + if (process == null) { + return; + } int flags = process.info.flags; IPackageManager pm = AppGlobals.getPackageManager(); sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n"); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index a0c21dd..4fc8020 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -1104,7 +1104,7 @@ public class ActivityStack { // Okay we are now going to start a switch, to 'next'. We may first // have to pause the current activity, but this is an important point // where we have decided to go to 'next' so keep track of that. - if (mLastStartedActivity != null) { + if (mLastStartedActivity != null && !mLastStartedActivity.finishing) { long now = SystemClock.uptimeMillis(); final boolean inTime = mLastStartedActivity.startTime != 0 && (mLastStartedActivity.startTime + START_WARN_TIME) >= now; diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 7314e04..bb40967 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -23,6 +23,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; +import android.os.WorkSource; import android.telephony.SignalStrength; import android.util.Slog; @@ -107,6 +108,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } + public void noteStartWakelockFromSource(WorkSource ws, int pid, String name, int type) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteStartWakeFromSourceLocked(ws, pid, name, type); + } + } + + public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, int type) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteStopWakeFromSourceLocked(ws, pid, name, type); + } + } + public void noteStartSensor(int uid, int sensor) { enforceCallingPermission(); synchronized (mStats) { @@ -317,6 +332,48 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } + public void noteFullWifiLockAcquiredFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteFullWifiLockAcquiredFromSourceLocked(ws); + } + } + + public void noteFullWifiLockReleasedFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteFullWifiLockReleasedFromSourceLocked(ws); + } + } + + public void noteScanWifiLockAcquiredFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteScanWifiLockAcquiredFromSourceLocked(ws); + } + } + + public void noteScanWifiLockReleasedFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteScanWifiLockReleasedFromSourceLocked(ws); + } + } + + public void noteWifiMulticastEnabledFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiMulticastEnabledFromSourceLocked(ws); + } + } + + public void noteWifiMulticastDisabledFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiMulticastDisabledFromSourceLocked(ws); + } + } + public boolean isOnBattery() { return mStats.isOnBattery(); } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index c1165c7..3bf6ee4 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -44,6 +44,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.WorkSource; import android.provider.Settings; import android.util.Log; import android.util.SparseIntArray; @@ -736,7 +737,7 @@ public class GpsLocationProvider implements LocationProviderInterface { startNavigating(true); } - public void setMinTime(long minTime) { + public void setMinTime(long minTime, WorkSource ws) { if (DEBUG) Log.d(TAG, "setMinTime " + minTime); if (minTime >= 0) { @@ -779,7 +780,7 @@ public class GpsLocationProvider implements LocationProviderInterface { public void addListener(int uid) { synchronized (mWakeLock) { mPendingListenerMessages++; - mWakeLock.acquire(); + mWakeLock.acquire(); Message m = Message.obtain(mHandler, ADD_LISTENER); m.arg1 = uid; mHandler.sendMessage(m); diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java index 084ab81..858a582 100644 --- a/services/java/com/android/server/location/LocationProviderInterface.java +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -20,6 +20,7 @@ import android.location.Criteria; import android.location.Location; import android.net.NetworkInfo; import android.os.Bundle; +import android.os.WorkSource; /** * Location Manager's interface for location providers. @@ -47,7 +48,7 @@ public interface LocationProviderInterface { /* returns false if single shot is not supported */ boolean requestSingleShotFix(); String getInternalState(); - void setMinTime(long minTime); + void setMinTime(long minTime, WorkSource ws); void updateNetworkState(int state, NetworkInfo info); void updateLocation(Location location); boolean sendExtraCommand(String command, Bundle extras); diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java index 24d7737..7dc9920 100644 --- a/services/java/com/android/server/location/LocationProviderProxy.java +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; +import android.os.WorkSource; import android.util.Log; import com.android.internal.location.DummyLocationProvider; @@ -52,6 +53,7 @@ public class LocationProviderProxy implements LocationProviderInterface { private boolean mLocationTracking = false; private boolean mEnabled = false; private long mMinTime = -1; + private WorkSource mMinTimeSource = new WorkSource(); private int mNetworkState; private NetworkInfo mNetworkInfo; @@ -122,7 +124,7 @@ public class LocationProviderProxy implements LocationProviderInterface { provider.enableLocationTracking(true); } if (mMinTime >= 0) { - provider.setMinTime(mMinTime); + provider.setMinTime(mMinTime, mMinTimeSource); } if (mNetworkInfo != null) { provider.updateNetworkState(mNetworkState, mNetworkInfo); @@ -318,6 +320,7 @@ public class LocationProviderProxy implements LocationProviderInterface { mLocationTracking = enable; if (!enable) { mMinTime = -1; + mMinTimeSource.clear(); } ILocationProvider provider; synchronized (mServiceConnection) { @@ -339,15 +342,16 @@ public class LocationProviderProxy implements LocationProviderInterface { return mMinTime; } - public void setMinTime(long minTime) { - mMinTime = minTime; + public void setMinTime(long minTime, WorkSource ws) { + mMinTime = minTime; + mMinTimeSource.set(ws); ILocationProvider provider; synchronized (mServiceConnection) { provider = mProvider; } if (provider != null) { try { - provider.setMinTime(minTime); + provider.setMinTime(minTime, ws); } catch (RemoteException e) { } } diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java index 01b34b7..09d799f 100644 --- a/services/java/com/android/server/location/MockProvider.java +++ b/services/java/com/android/server/location/MockProvider.java @@ -23,6 +23,7 @@ import android.location.LocationProvider; import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; +import android.os.WorkSource; import android.util.Log; import android.util.PrintWriterPrinter; @@ -201,7 +202,7 @@ public class MockProvider implements LocationProviderInterface { return false; } - public void setMinTime(long minTime) { + public void setMinTime(long minTime, WorkSource ws) { } public void updateNetworkState(int state, NetworkInfo info) { diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java index 7fc93f8..ea0d1b0 100644 --- a/services/java/com/android/server/location/PassiveProvider.java +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -24,6 +24,7 @@ import android.location.LocationProvider; import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; +import android.os.WorkSource; import android.util.Log; /** @@ -123,7 +124,7 @@ public class PassiveProvider implements LocationProviderInterface { return false; } - public void setMinTime(long minTime) { + public void setMinTime(long minTime, WorkSource ws) { } public void updateNetworkState(int state, NetworkInfo info) { diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java index eee97c3..c3786f5 100644 --- a/services/java/com/android/server/sip/SipService.java +++ b/services/java/com/android/server/sip/SipService.java @@ -27,6 +27,7 @@ import android.net.NetworkInfo; import android.net.sip.ISipService; import android.net.sip.ISipSession; import android.net.sip.ISipSessionListener; +import android.net.sip.SipErrorCode; import android.net.sip.SipManager; import android.net.sip.SipProfile; import android.net.sip.SipSessionAdapter; @@ -528,6 +529,8 @@ public final class SipService extends ISipService.Stub { private int mBackoff = 1; private boolean mRegistered; private long mExpiryTime; + private SipErrorCode mErrorCode; + private String mErrorMessage; private String getAction() { return toString(); @@ -551,15 +554,25 @@ public final class SipService extends ISipService.Stub { } public void stop() { + stop(false); + } + + private void stopButKeepStates() { + stop(true); + } + + private void stop(boolean keepStates) { if (mSession == null) return; - if (mConnected) mSession.unregister(); + if (mConnected && mRegistered) mSession.unregister(); mTimer.cancel(this); if (mKeepAliveProcess != null) { mKeepAliveProcess.stop(); mKeepAliveProcess = null; } - mSession = null; - mRegistered = false; + if (!keepStates) { + mSession = null; + mRegistered = false; + } } private boolean isStopped() { @@ -568,20 +581,33 @@ public final class SipService extends ISipService.Stub { public void setListener(ISipSessionListener listener) { Log.v(TAG, "setListener(): " + listener); - mProxy.setListener(listener); - if (mSession == null) return; + synchronized (SipService.this) { + mProxy.setListener(listener); + if (mSession == null) return; - try { - if ((mSession != null) && SipSessionState.REGISTERING.equals( - mSession.getState())) { - mProxy.onRegistering(mSession); - } else if (mRegistered) { - int duration = (int) - (mExpiryTime - SystemClock.elapsedRealtime()); - mProxy.onRegistrationDone(mSession, duration); + try { + SipSessionState state = (mSession == null) + ? SipSessionState.READY_TO_CALL + : Enum.valueOf( + SipSessionState.class, mSession.getState()); + if ((state == SipSessionState.REGISTERING) + || (state == SipSessionState.DEREGISTERING)) { + mProxy.onRegistering(mSession); + } else if (mRegistered) { + int duration = (int) + (mExpiryTime - SystemClock.elapsedRealtime()); + mProxy.onRegistrationDone(mSession, duration); + } else if (mErrorCode != null) { + if (mErrorCode == SipErrorCode.TIME_OUT) { + mProxy.onRegistrationTimeout(mSession); + } else { + mProxy.onRegistrationFailed(mSession, + mErrorCode.toString(), mErrorMessage); + } + } + } catch (Throwable t) { + Log.w(TAG, "setListener(): " + t); } - } catch (Throwable t) { - Log.w(TAG, "setListener(): " + t); } } @@ -590,6 +616,8 @@ public final class SipService extends ISipService.Stub { } public void run() { + mErrorCode = null; + mErrorMessage = null; Log.v(TAG, " ~~~ registering"); synchronized (SipService.this) { if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME); @@ -634,11 +662,7 @@ public final class SipService extends ISipService.Stub { synchronized (SipService.this) { if (!isStopped() && (session != mSession)) return; mRegistered = false; - try { - mProxy.onRegistering(session); - } catch (Throwable t) { - Log.w(TAG, "onRegistering()", t); - } + mProxy.onRegistering(session); } } @@ -647,11 +671,9 @@ public final class SipService extends ISipService.Stub { Log.v(TAG, "onRegistrationDone(): " + session + ": " + mSession); synchronized (SipService.this) { if (!isStopped() && (session != mSession)) return; - try { - mProxy.onRegistrationDone(session, duration); - } catch (Throwable t) { - Log.w(TAG, "onRegistrationDone()", t); - } + + mProxy.onRegistrationDone(session, duration); + if (isStopped()) return; if (duration > 0) { @@ -687,19 +709,25 @@ public final class SipService extends ISipService.Stub { } @Override - public void onRegistrationFailed(ISipSession session, String className, - String message) { + public void onRegistrationFailed(ISipSession session, + String errorCodeString, String message) { + SipErrorCode errorCode = + Enum.valueOf(SipErrorCode.class, errorCodeString); Log.v(TAG, "onRegistrationFailed(): " + session + ": " + mSession - + ": " + className + ": " + message); + + ": " + errorCode + ": " + message); synchronized (SipService.this) { if (!isStopped() && (session != mSession)) return; - try { - mProxy.onRegistrationFailed(session, className, message); - } catch (Throwable t) { - Log.w(TAG, "onRegistrationFailed(): " + t); + mErrorCode = errorCode; + mErrorMessage = message; + mProxy.onRegistrationFailed(session, errorCode.toString(), + message); + + if (errorCode == SipErrorCode.INVALID_CREDENTIALS) { + Log.d(TAG, " pause auto-registration"); + stopButKeepStates(); + } else if (!isStopped()) { + onError(); } - - if (!isStopped()) onError(); } } @@ -708,11 +736,8 @@ public final class SipService extends ISipService.Stub { Log.v(TAG, "onRegistrationTimeout(): " + session + ": " + mSession); synchronized (SipService.this) { if (!isStopped() && (session != mSession)) return; - try { - mProxy.onRegistrationTimeout(session); - } catch (Throwable t) { - Log.w(TAG, "onRegistrationTimeout(): " + t); - } + mErrorCode = SipErrorCode.TIME_OUT; + mProxy.onRegistrationTimeout(session); if (!isStopped()) { mRegistered = false; diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java index 2f7ddc4..da8e9b8 100644 --- a/services/java/com/android/server/sip/SipSessionGroup.java +++ b/services/java/com/android/server/sip/SipSessionGroup.java @@ -412,13 +412,7 @@ class SipSessionGroup implements SipListener { processCommand(command); } catch (SipException e) { Log.w(TAG, "command error: " + command, e); - // TODO: find a better way to do this - if ((command instanceof RegisterCommand) - || (command == DEREGISTER)) { - onRegistrationFailed(e); - } else { - onError(e); - } + onError(e); } } }).start(); @@ -480,7 +474,9 @@ class SipSessionGroup implements SipListener { private void processCommand(EventObject command) throws SipException { if (!process(command)) { - throw new SipException("wrong state to execute: " + command); + onError(SipErrorCode.IN_PROGRESS, + "cannot initiate a new transaction to execute: " + + command); } } @@ -562,11 +558,8 @@ class SipSessionGroup implements SipListener { if (evt instanceof TimeoutEvent) { processTimeout((TimeoutEvent) evt); } else { - Log.d(TAG, "Transaction terminated:" + this); - if (!SipSessionState.IN_CALL.equals(mState)) { - removeSipSession(this); - } - return true; + processTransactionTerminated( + (TransactionTerminatedEvent) evt); } return true; } else if (evt instanceof DialogTerminatedEvent) { @@ -585,6 +578,20 @@ class SipSessionGroup implements SipListener { } } + private void processTransactionTerminated( + TransactionTerminatedEvent event) { + switch (mState) { + case IN_CALL: + case READY_TO_CALL: + Log.d(TAG, "Transaction terminated; do nothing"); + break; + default: + Log.d(TAG, "Transaction terminated early: " + this); + onError(SipErrorCode.TRANSACTION_TERMINTED, + "transaction terminated"); + } + } + private void processTimeout(TimeoutEvent event) { Log.d(TAG, "processing Timeout..." + event); Transaction current = event.isServerTransaction() @@ -600,25 +607,26 @@ class SipSessionGroup implements SipListener { return; } switch (mState) { - case REGISTERING: - case DEREGISTERING: - reset(); - mProxy.onRegistrationTimeout(this); - break; - case INCOMING_CALL: - case INCOMING_CALL_ANSWERING: - case OUTGOING_CALL_CANCELING: - endCallOnError(SipErrorCode.TIME_OUT, event.toString()); - break; - case PINGING: - reset(); - mReRegisterFlag = true; - mState = SipSessionState.READY_TO_CALL; - break; + case REGISTERING: + case DEREGISTERING: + reset(); + mProxy.onRegistrationTimeout(this); + break; + case INCOMING_CALL: + case INCOMING_CALL_ANSWERING: + case OUTGOING_CALL: + case OUTGOING_CALL_CANCELING: + onError(SipErrorCode.TIME_OUT, event.toString()); + break; + case PINGING: + reset(); + mReRegisterFlag = true; + mState = SipSessionState.READY_TO_CALL; + break; - default: - // do nothing - break; + default: + Log.d(TAG, " do nothing"); + break; } } @@ -665,7 +673,7 @@ class SipSessionGroup implements SipListener { } else { Log.w(TAG, "peer did not respect our rport request"); } - mState = SipSessionState.READY_TO_CALL; + reset(); return true; } return false; @@ -687,7 +695,6 @@ class SipSessionGroup implements SipListener { switch (statusCode) { case Response.OK: SipSessionState state = mState; - reset(); onRegistrationDone((state == SipSessionState.REGISTERING) ? getExpiryTime(((ResponseEvent) evt).getResponse()) : -1); @@ -698,15 +705,13 @@ class SipSessionGroup implements SipListener { case Response.PROXY_AUTHENTICATION_REQUIRED: if (!handleAuthentication(event)) { Log.v(TAG, "Incorrect username/password"); - reset(); onRegistrationFailed(SipErrorCode.INVALID_CREDENTIALS, "incorrect username or password"); } return true; default: if (statusCode >= 500) { - reset(); - onRegistrationFailed(createCallbackException(response)); + onRegistrationFailed(response); return true; } } @@ -720,7 +725,6 @@ class SipSessionGroup implements SipListener { String nonce = getNonceFromResponse(response); if (((nonce != null) && nonce.equals(mLastNonce)) || (nonce == mLastNonce)) { - Log.v(TAG, "Incorrect username/password"); return false; } else { mClientTransaction = mSipHelper.handleChallenge( @@ -906,7 +910,7 @@ class SipSessionGroup implements SipListener { } if (statusCode >= 400) { - onError(createCallbackException(response)); + onError(response); return true; } } else if (evt instanceof TransactionTerminatedEvent) { @@ -954,10 +958,6 @@ class SipSessionGroup implements SipListener { response.getReasonPhrase(), response.getStatusCode()); } - private Exception createCallbackException(Response response) { - return new SipException(createErrorMessage(response)); - } - private void establishCall() { mState = SipSessionState.IN_CALL; mInCall = true; @@ -965,22 +965,22 @@ class SipSessionGroup implements SipListener { } private void fallbackToPreviousInCall(Throwable exception) { - mState = SipSessionState.IN_CALL; exception = getRootCause(exception); - mProxy.onCallChangeFailed(this, getErrorCode(exception).toString(), + fallbackToPreviousInCall(getErrorCode(exception), exception.toString()); } + private void fallbackToPreviousInCall(SipErrorCode errorCode, + String message) { + mState = SipSessionState.IN_CALL; + mProxy.onCallChangeFailed(this, errorCode.toString(), message); + } + private void endCallNormally() { reset(); mProxy.onCallEnded(this); } - private void endCallOnError(Throwable exception) { - exception = getRootCause(exception); - endCallOnError(getErrorCode(exception), exception.toString()); - } - private void endCallOnError(SipErrorCode errorCode, String message) { reset(); mProxy.onError(this, errorCode.toString(), message); @@ -991,26 +991,34 @@ class SipSessionGroup implements SipListener { mProxy.onCallBusy(this); } - private void onError(Throwable exception) { - if (mInCall) { - fallbackToPreviousInCall(exception); - } else { - endCallOnError(exception); + private void onError(SipErrorCode errorCode, String message) { + switch (mState) { + case REGISTERING: + case DEREGISTERING: + onRegistrationFailed(errorCode, message); + break; + default: + if (mInCall) { + fallbackToPreviousInCall(errorCode, message); + } else { + endCallOnError(errorCode, message); + } } } + + private void onError(Throwable exception) { + exception = getRootCause(exception); + onError(getErrorCode(exception), exception.toString()); + } + private void onError(Response response) { - if (mInCall) { - fallbackToPreviousInCall(createCallbackException(response)); + int statusCode = response.getStatusCode(); + if (!mInCall && ((statusCode == Response.TEMPORARILY_UNAVAILABLE) + || (statusCode == Response.BUSY_HERE))) { + endCallOnBusy(); } else { - int statusCode = response.getStatusCode(); - if ((statusCode == Response.TEMPORARILY_UNAVAILABLE) - || (statusCode == Response.BUSY_HERE)) { - endCallOnBusy(); - } else { - endCallOnError(getErrorCode(statusCode), - createErrorMessage(response)); - } + onError(getErrorCode(statusCode), createErrorMessage(response)); } } @@ -1053,19 +1061,29 @@ class SipSessionGroup implements SipListener { } private void onRegistrationDone(int duration) { + reset(); mProxy.onRegistrationDone(this, duration); } private void onRegistrationFailed(SipErrorCode errorCode, String message) { + reset(); mProxy.onRegistrationFailed(this, errorCode.toString(), message); } private void onRegistrationFailed(Throwable exception) { + reset(); exception = getRootCause(exception); onRegistrationFailed(getErrorCode(exception), exception.toString()); } + + private void onRegistrationFailed(Response response) { + reset(); + int statusCode = response.getStatusCode(); + onRegistrationFailed(getErrorCode(statusCode), + createErrorMessage(response)); + } } /** diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 7af5e95..6dd619c 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -24,9 +24,6 @@ // Log debug messages about InputDispatcherPolicy #define DEBUG_INPUT_DISPATCHER_POLICY 0 -// Log debug messages about input focus tracking -#define DEBUG_FOCUS 0 - #include "JNIHelp.h" #include "jni.h" #include <limits.h> @@ -44,81 +41,6 @@ namespace android { -// Window flags from WindowManager.LayoutParams -enum { - FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001, - FLAG_DIM_BEHIND = 0x00000002, - FLAG_BLUR_BEHIND = 0x00000004, - FLAG_NOT_FOCUSABLE = 0x00000008, - FLAG_NOT_TOUCHABLE = 0x00000010, - FLAG_NOT_TOUCH_MODAL = 0x00000020, - FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040, - FLAG_KEEP_SCREEN_ON = 0x00000080, - FLAG_LAYOUT_IN_SCREEN = 0x00000100, - FLAG_LAYOUT_NO_LIMITS = 0x00000200, - FLAG_FULLSCREEN = 0x00000400, - FLAG_FORCE_NOT_FULLSCREEN = 0x00000800, - FLAG_DITHER = 0x00001000, - FLAG_SECURE = 0x00002000, - FLAG_SCALED = 0x00004000, - FLAG_IGNORE_CHEEK_PRESSES = 0x00008000, - FLAG_LAYOUT_INSET_DECOR = 0x00010000, - FLAG_ALT_FOCUSABLE_IM = 0x00020000, - FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000, - FLAG_SHOW_WHEN_LOCKED = 0x00080000, - FLAG_SHOW_WALLPAPER = 0x00100000, - FLAG_TURN_SCREEN_ON = 0x00200000, - FLAG_DISMISS_KEYGUARD = 0x00400000, - FLAG_IMMERSIVE = 0x00800000, - FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000, - FLAG_COMPATIBLE_WINDOW = 0x20000000, - FLAG_SYSTEM_ERROR = 0x40000000, -}; - -// Window types from WindowManager.LayoutParams -enum { - FIRST_APPLICATION_WINDOW = 1, - TYPE_BASE_APPLICATION = 1, - TYPE_APPLICATION = 2, - TYPE_APPLICATION_STARTING = 3, - LAST_APPLICATION_WINDOW = 99, - FIRST_SUB_WINDOW = 1000, - TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW, - TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1, - TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2, - TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3, - TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4, - LAST_SUB_WINDOW = 1999, - FIRST_SYSTEM_WINDOW = 2000, - TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW, - TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1, - TYPE_PHONE = FIRST_SYSTEM_WINDOW+2, - TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3, - TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4, - TYPE_TOAST = FIRST_SYSTEM_WINDOW+5, - TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6, - TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7, - TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8, - TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9, - TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10, - TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11, - TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12, - TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13, - TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14, - LAST_SYSTEM_WINDOW = 2999, -}; - -// Delay between reporting long touch events to the power manager. -const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms - -// Default input dispatching timeout if there is no focused application or paused window -// from which to determine an appropriate dispatching timeout. -const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec - -// Minimum amount of time to provide to the input dispatcher for delivery of an event -// regardless of how long the application window was paused. -const nsecs_t MIN_INPUT_DISPATCHING_TIMEOUT = 1000 * 1000000LL; // 1 sec - // ---------------------------------------------------------------------------- static struct { @@ -134,7 +56,6 @@ static struct { jmethodID interceptKeyBeforeQueueing; jmethodID interceptKeyBeforeDispatching; jmethodID checkInjectEventsPermission; - jmethodID notifyAppSwitchComing; jmethodID filterTouchEvents; jmethodID filterJumpyTouchEvents; jmethodID getVirtualKeyDefinitions; @@ -235,7 +156,7 @@ public: inline sp<InputManager> getInputManager() const { return mInputManager; } - String8 dump(); + void dump(String8& dump); void setDisplaySize(int32_t displayId, int32_t width, int32_t height); void setDisplayOrientation(int32_t displayId, int32_t orientation); @@ -270,72 +191,33 @@ public: /* --- InputDispatcherPolicyInterface implementation --- */ virtual void notifyConfigurationChanged(nsecs_t when); + virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle); virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel); - virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel, - nsecs_t& outNewTimeout); + virtual nsecs_t notifyInputChannelANR(const sp<InputChannel>& inputChannel); virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel); virtual nsecs_t getKeyRepeatTimeout(); virtual nsecs_t getKeyRepeatDelay(); - virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); - virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); virtual int32_t getMaxEventsPerSecond(); + virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, + const KeyEvent* keyEvent, uint32_t policyFlags); + virtual void pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType); + virtual bool checkInjectEventsPermissionNonReentrant( + int32_t injectorPid, int32_t injectorUid); private: - struct InputWindow { - sp<InputChannel> inputChannel; - int32_t layoutParamsFlags; - int32_t layoutParamsType; - nsecs_t dispatchingTimeout; - int32_t frameLeft; - int32_t frameTop; - int32_t frameRight; - int32_t frameBottom; - int32_t visibleFrameLeft; - int32_t visibleFrameTop; - int32_t visibleFrameRight; - int32_t visibleFrameBottom; - int32_t touchableAreaLeft; - int32_t touchableAreaTop; - int32_t touchableAreaRight; - int32_t touchableAreaBottom; - bool visible; - bool hasFocus; - bool hasWallpaper; - bool paused; - int32_t ownerPid; - int32_t ownerUid; - - bool visibleFrameIntersects(const InputWindow* other) const; - bool touchableAreaContainsPoint(int32_t x, int32_t y) const; - }; - - struct InputApplication { - String8 name; - nsecs_t dispatchingTimeout; - jweak tokenObjWeak; - }; - - class ANRTimer { - enum Budget { - SYSTEM = 0, - APPLICATION = 1 - }; - - Budget mBudget; - nsecs_t mStartTime; - bool mFrozen; - InputWindow* mPausedWindow; + class ApplicationToken : public InputApplicationHandle { + jweak mTokenObjWeak; public: - ANRTimer(); + ApplicationToken(jweak tokenObjWeak) : + mTokenObjWeak(tokenObjWeak) { } - void dispatchFrozenBySystem(); - void dispatchPausedByApplication(InputWindow* pausedWindow); - bool waitForDispatchStateChangeLd(NativeInputManager* inputManager); + virtual ~ApplicationToken() { + JNIEnv* env = NativeInputManager::jniEnv(); + env->DeleteWeakGlobalRef(mTokenObjWeak); + } - nsecs_t getTimeSpentWaitingForApplication() const; + inline jweak getTokenObj() { return mTokenObjWeak; } }; sp<InputManager> mInputManager; @@ -364,80 +246,14 @@ private: jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel); - // Input target and focus tracking. (lock mDispatchLock) - Mutex mDispatchLock; - Condition mDispatchStateChanged; - - bool mDispatchEnabled; - bool mDispatchFrozen; - bool mWindowsReady; - Vector<InputWindow> mWindows; - Vector<InputWindow*> mWallpaperWindows; - Vector<sp<InputChannel> > mMonitoringChannels; - - // Focus tracking for keys, trackball, etc. - InputWindow* mFocusedWindow; - - // Focus tracking for touch. - bool mTouchDown; - InputWindow* mTouchedWindow; // primary target for current down - bool mTouchedWindowIsObscured; // true if other windows may obscure the target - Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets - struct OutsideTarget { - InputWindow* window; - bool obscured; - }; - Vector<OutsideTarget> mTempTouchedOutsideTargets; // temporary outside touch targets - Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets - - // Focused application. - InputApplication* mFocusedApplication; - InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication - - void dumpDeviceInfo(String8& dump); - void dumpDispatchStateLd(String8& dump); - void logDispatchStateLd(); - - bool notifyANR(jobject tokenObj, nsecs_t& outNewTimeout); - void releaseFocusedApplicationLd(JNIEnv* env); - - int32_t waitForFocusedWindowLd(uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, - Vector<InputTarget>& outTargets, InputWindow*& outFocusedWindow); - int32_t waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, - Vector<InputTarget>& outTargets, InputWindow*& outTouchedWindow); - bool isWindowObscuredLocked(const InputWindow* window); - - void releaseTouchedWindowLd(); - - int32_t waitForNonTouchEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); - int32_t waitForTouchEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); - - bool interceptKeyBeforeDispatching(const InputTarget& target, - const KeyEvent* keyEvent, uint32_t policyFlags); - - void pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType); - void pokeUserActivity(nsecs_t eventTime, int32_t eventType); - bool checkInjectionPermission(const InputWindow* window, - int32_t injectorPid, int32_t injectorUid); - static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow); - static void addTarget(const InputWindow* window, int32_t targetFlags, - nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets); - void registerMonitoringChannel(const sp<InputChannel>& inputChannel); - void unregisterMonitoringChannel(const sp<InputChannel>& inputChannel); - void addMonitoringTargetsLd(Vector<InputTarget>& outTargets); + static bool isPolicyKey(int32_t keyCode, bool isScreenOn); + static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } - - static bool isAppSwitchKey(int32_t keyCode); - static bool isPolicyKey(int32_t keyCode, bool isScreenOn); - static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); }; // ---------------------------------------------------------------------------- @@ -445,10 +261,7 @@ private: NativeInputManager::NativeInputManager(jobject callbacksObj) : mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mMaxEventsPerSecond(-1), - mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0), - mDispatchEnabled(true), mDispatchFrozen(false), mWindowsReady(true), - mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL), - mFocusedApplication(NULL) { + mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) { JNIEnv* env = jniEnv(); mCallbacksObj = env->NewGlobalRef(callbacksObj); @@ -461,27 +274,16 @@ NativeInputManager::~NativeInputManager() { JNIEnv* env = jniEnv(); env->DeleteGlobalRef(mCallbacksObj); - - releaseFocusedApplicationLd(env); } -String8 NativeInputManager::dump() { - String8 dump; - { // acquire lock - AutoMutex _l(mDisplayLock); - dump.append("Native Input Dispatcher State:\n"); - dumpDispatchStateLd(dump); - dump.append("\n"); - } // release lock - - dump.append("Input Devices:\n"); - dumpDeviceInfo(dump); +void NativeInputManager::dump(String8& dump) { + dump.append("Input Reader State:\n"); + mInputManager->getReader()->dump(dump); + dump.append("\n"); - return dump; -} - -bool NativeInputManager::isAppSwitchKey(int32_t keyCode) { - return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL; + dump.append("Input Dispatcher State:\n"); + mInputManager->getDispatcher()->dump(dump); + dump.append("\n"); } bool NativeInputManager::isPolicyKey(int32_t keyCode, bool isScreenOn) { @@ -503,6 +305,7 @@ bool NativeInputManager::isPolicyKey(int32_t keyCode, bool isScreenOn) { case AKEYCODE_MEDIA_PREVIOUS: case AKEYCODE_MEDIA_REWIND: case AKEYCODE_MEDIA_FAST_FORWARD: + // The policy always cares about these keys. return true; default: // We need to pass all keys to the policy in the following cases: @@ -565,12 +368,9 @@ status_t NativeInputManager::registerInputChannel(JNIEnv* env, mInputChannelObjWeakTable.add(inputChannel.get(), inputChannelObjWeak); } - status = mInputManager->registerInputChannel(inputChannel); + status = mInputManager->getDispatcher()->registerInputChannel(inputChannel, monitor); if (! status) { // Success. - if (monitor) { - registerMonitoringChannel(inputChannel); - } return OK; } @@ -604,9 +404,7 @@ status_t NativeInputManager::unregisterInputChannel(JNIEnv* env, env->DeleteWeakGlobalRef(inputChannelObjWeak); - unregisterMonitoringChannel(inputChannel); - - return mInputManager->unregisterInputChannel(inputChannel); + return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel); } jobject NativeInputManager::getInputChannelObjLocal(JNIEnv* env, @@ -710,20 +508,11 @@ int32_t NativeInputManager::interceptKey(nsecs_t when, } if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) { - pokeUserActivity(when, POWER_MANAGER_BUTTON_EVENT); + android_server_PowerManagerService_userActivity(when, POWER_MANAGER_BUTTON_EVENT); } if (wmActions & WM_ACTION_PASS_TO_USER) { actions |= InputReaderPolicyInterface::ACTION_DISPATCH; - - if (down && isAppSwitchKey(keyCode)) { - JNIEnv* env = jniEnv(); - - env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyAppSwitchComing); - checkAndClearExceptionFromCallback(env, "notifyAppSwitchComing"); - - actions |= InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING; - } } return actions; @@ -906,13 +695,42 @@ void NativeInputManager::notifyConfigurationChanged(nsecs_t when) { JNIEnv* env = jniEnv(); InputConfiguration config; - mInputManager->getInputConfiguration(& config); + mInputManager->getReader()->getInputConfiguration(& config); env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyConfigurationChanged, when, config.touchScreen, config.keyboard, config.navigation); checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged"); } +nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("notifyANR"); +#endif + + JNIEnv* env = jniEnv(); + + ApplicationToken* token = static_cast<ApplicationToken*>(inputApplicationHandle.get()); + jweak tokenObjWeak = token->getTokenObj(); + + jlong newTimeout; + jobject tokenObjLocal = env->NewLocalRef(tokenObjWeak); + if (tokenObjLocal) { + newTimeout = env->CallLongMethod(mCallbacksObj, + gCallbacksClassInfo.notifyANR, tokenObjLocal); + if (checkAndClearExceptionFromCallback(env, "notifyANR")) { + newTimeout = 0; // abort dispatch + } else { + assert(newTimeout >= 0); + } + + env->DeleteLocalRef(tokenObjLocal); + } else { + newTimeout = 0; // abort dispatch + } + + return newTimeout; +} + void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputChannel) { #if DEBUG_INPUT_DISPATCHER_POLICY LOGD("notifyInputChannelBroken - inputChannel='%s'", inputChannel->getName().string()); @@ -928,12 +746,9 @@ void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputC env->DeleteLocalRef(inputChannelObjLocal); } - - unregisterMonitoringChannel(inputChannel); } -bool NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel, - nsecs_t& outNewTimeout) { +nsecs_t NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel) { #if DEBUG_INPUT_DISPATCHER_POLICY LOGD("notifyInputChannelANR - inputChannel='%s'", inputChannel->getName().string()); @@ -947,20 +762,17 @@ bool NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChan newTimeout = env->CallLongMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelANR, inputChannelObjLocal); if (checkAndClearExceptionFromCallback(env, "notifyInputChannelANR")) { - newTimeout = -2; + newTimeout = 0; // abort dispatch + } else { + assert(newTimeout >= 0); } env->DeleteLocalRef(inputChannelObjLocal); } else { - newTimeout = -2; - } - - if (newTimeout == -2) { - return false; // abort + newTimeout = 0; // abort dispatch } - outNewTimeout = newTimeout; - return true; // resume + return newTimeout; } void NativeInputManager::notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) { @@ -981,27 +793,6 @@ void NativeInputManager::notifyInputChannelRecoveredFromANR(const sp<InputChanne } } -bool NativeInputManager::notifyANR(jobject tokenObj, nsecs_t& outNewTimeout) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("notifyANR"); -#endif - - JNIEnv* env = jniEnv(); - - jlong newTimeout = env->CallLongMethod(mCallbacksObj, - gCallbacksClassInfo.notifyANR, tokenObj); - if (checkAndClearExceptionFromCallback(env, "notifyANR")) { - newTimeout = -2; - } - - if (newTimeout == -2) { - return false; // abort - } - - outNewTimeout = newTimeout; - return true; // resume -} - nsecs_t NativeInputManager::getKeyRepeatTimeout() { if (! isScreenOn()) { // Disable key repeat when the screen is off. @@ -1032,89 +823,26 @@ int32_t NativeInputManager::getMaxEventsPerSecond() { } void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) { -#if DEBUG_FOCUS - LOGD("setInputWindows"); -#endif - { // acquire lock - AutoMutex _l(mDispatchLock); + Vector<InputWindow> windows; - sp<InputChannel> touchedWindowChannel; - if (mTouchedWindow) { - touchedWindowChannel = mTouchedWindow->inputChannel; - mTouchedWindow = NULL; + jsize length = env->GetArrayLength(windowObjArray); + for (jsize i = 0; i < length; i++) { + jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i); + if (! inputTargetObj) { + break; // found null element indicating end of used portion of the array } - size_t numTouchedWallpapers = mTouchedWallpaperWindows.size(); - if (numTouchedWallpapers != 0) { - for (size_t i = 0; i < numTouchedWallpapers; i++) { - mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel); - } - mTouchedWallpaperWindows.clear(); - } - - bool hadFocusedWindow = mFocusedWindow != NULL; - - mWindows.clear(); - mFocusedWindow = NULL; - mWallpaperWindows.clear(); - - if (windowObjArray) { - mWindowsReady = true; - - jsize length = env->GetArrayLength(windowObjArray); - for (jsize i = 0; i < length; i++) { - jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i); - if (! inputTargetObj) { - break; // found null element indicating end of used portion of the array - } - - mWindows.push(); - InputWindow& window = mWindows.editTop(); - bool valid = populateWindow(env, inputTargetObj, window); - if (! valid) { - mWindows.pop(); - } - - env->DeleteLocalRef(inputTargetObj); - } - size_t numWindows = mWindows.size(); - for (size_t i = 0; i < numWindows; i++) { - InputWindow* window = & mWindows.editItemAt(i); - if (window->hasFocus) { - mFocusedWindow = window; - } - - if (window->layoutParamsType == TYPE_WALLPAPER) { - mWallpaperWindows.push(window); - - for (size_t j = 0; j < numTouchedWallpapers; j++) { - if (window->inputChannel == mTempTouchedWallpaperChannels[i]) { - mTouchedWallpaperWindows.push(window); - } - } - } - - if (window->inputChannel == touchedWindowChannel) { - mTouchedWindow = window; - } - } - } else { - mWindowsReady = false; - } - - mTempTouchedWallpaperChannels.clear(); - - if ((hadFocusedWindow && ! mFocusedWindow) - || (mFocusedWindow && ! mFocusedWindow->visible)) { - preemptInputDispatch(); + windows.push(); + InputWindow& window = windows.editTop(); + bool valid = populateWindow(env, inputTargetObj, window); + if (! valid) { + windows.pop(); } - mDispatchStateChanged.broadcast(); + env->DeleteLocalRef(inputTargetObj); + } -#if DEBUG_FOCUS - logDispatchStateLd(); -#endif - } // release lock + mInputManager->getDispatcher()->setInputWindows(windows); } bool NativeInputManager::populateWindow(JNIEnv* env, jobject windowObj, @@ -1205,663 +933,60 @@ bool NativeInputManager::populateWindow(JNIEnv* env, jobject windowObj, } void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationObj) { -#if DEBUG_FOCUS - LOGD("setFocusedApplication"); -#endif - { // acquire lock - AutoMutex _l(mDispatchLock); - - releaseFocusedApplicationLd(env); - - if (applicationObj) { - jstring nameObj = jstring(env->GetObjectField(applicationObj, - gInputApplicationClassInfo.name)); - jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj, - gInputApplicationClassInfo.dispatchingTimeoutNanos); - jobject tokenObj = env->GetObjectField(applicationObj, - gInputApplicationClassInfo.token); - jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj); - if (! tokenObjWeak) { - LOGE("Could not create weak reference for application token."); - LOGE_EX(env); - env->ExceptionClear(); - } - env->DeleteLocalRef(tokenObj); - - mFocusedApplication = & mFocusedApplicationStorage; - - if (nameObj) { - const char* nameStr = env->GetStringUTFChars(nameObj, NULL); - mFocusedApplication->name.setTo(nameStr); - env->ReleaseStringUTFChars(nameObj, nameStr); - env->DeleteLocalRef(nameObj); - } else { - LOGE("InputApplication.name should not be null."); - mFocusedApplication->name.setTo("unknown"); - } - - mFocusedApplication->dispatchingTimeout = dispatchingTimeoutNanos; - mFocusedApplication->tokenObjWeak = tokenObjWeak; - } - - mDispatchStateChanged.broadcast(); - -#if DEBUG_FOCUS - logDispatchStateLd(); -#endif - } // release lock -} - -void NativeInputManager::releaseFocusedApplicationLd(JNIEnv* env) { - if (mFocusedApplication) { - env->DeleteWeakGlobalRef(mFocusedApplication->tokenObjWeak); - mFocusedApplication = NULL; - } -} - -void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { -#if DEBUG_FOCUS - LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen); -#endif - - { // acquire lock - AutoMutex _l(mDispatchLock); - - if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) { - mDispatchEnabled = enabled; - mDispatchFrozen = frozen; - - mDispatchStateChanged.broadcast(); - } - -#if DEBUG_FOCUS - logDispatchStateLd(); -#endif - } // release lock -} - -void NativeInputManager::preemptInputDispatch() { -#if DEBUG_FOCUS - LOGD("preemptInputDispatch"); -#endif - - mInputManager->preemptInputDispatch(); -} - -int32_t NativeInputManager::waitForFocusedWindowLd(uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets, - InputWindow*& outFocusedWindow) { - - int32_t injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; - bool firstIteration = true; - ANRTimer anrTimer; - for (;;) { - if (firstIteration) { - firstIteration = false; - } else { - if (! anrTimer.waitForDispatchStateChangeLd(this)) { - LOGW("Dropping event because the dispatcher timed out waiting to identify " - "the window that should receive it."); - injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; - break; - } + if (applicationObj) { + jstring nameObj = jstring(env->GetObjectField(applicationObj, + gInputApplicationClassInfo.name)); + jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj, + gInputApplicationClassInfo.dispatchingTimeoutNanos); + jobject tokenObj = env->GetObjectField(applicationObj, + gInputApplicationClassInfo.token); + jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj); + if (! tokenObjWeak) { + LOGE("Could not create weak reference for application token."); + LOGE_EX(env); + env->ExceptionClear(); } + env->DeleteLocalRef(tokenObj); - // If dispatch is not enabled then fail. - if (! mDispatchEnabled) { - LOGI("Dropping event because input dispatch is disabled."); - injectionResult = INPUT_EVENT_INJECTION_FAILED; - break; - } - - // If dispatch is frozen or we don't have valid window data yet then wait. - if (mDispatchFrozen || ! mWindowsReady) { -#if DEBUG_FOCUS - LOGD("Waiting because dispatch is frozen or windows are not ready."); -#endif - anrTimer.dispatchFrozenBySystem(); - continue; - } - - // If there is no currently focused window and no focused application - // then drop the event. - if (! mFocusedWindow) { - if (mFocusedApplication) { -#if DEBUG_FOCUS - LOGD("Waiting because there is no focused window but there is a " - "focused application that may yet introduce a new target: '%s'.", - mFocusedApplication->name.string()); -#endif - continue; - } - - LOGI("Dropping event because there is no focused window or focused application."); - injectionResult = INPUT_EVENT_INJECTION_FAILED; - break; - } - - // Check permissions. - if (! checkInjectionPermission(mFocusedWindow, injectorPid, injectorUid)) { - injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; - break; - } - - // If the currently focused window is paused then keep waiting. - if (mFocusedWindow->paused) { -#if DEBUG_FOCUS - LOGD("Waiting because focused window is paused."); -#endif - anrTimer.dispatchPausedByApplication(mFocusedWindow); - continue; - } - - // Success! - break; // done waiting, exit loop - } - - // Output targets. - if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { - addTarget(mFocusedWindow, InputTarget::FLAG_SYNC, - anrTimer.getTimeSpentWaitingForApplication(), outTargets); - - outFocusedWindow = mFocusedWindow; - } else { - outFocusedWindow = NULL; - } - -#if DEBUG_FOCUS - LOGD("waitForFocusedWindow finished: injectionResult=%d", - injectionResult); - logDispatchStateLd(); -#endif - return injectionResult; -} - -enum InjectionPermission { - INJECTION_PERMISSION_UNKNOWN, - INJECTION_PERMISSION_GRANTED, - INJECTION_PERMISSION_DENIED -}; - -int32_t NativeInputManager::waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets, - InputWindow*& outTouchedWindow) { - nsecs_t startTime = now(); - - // For security reasons, we defer updating the touch state until we are sure that - // event injection will be allowed. - // - // FIXME In the original code, screenWasOff could never be set to true. - // The reason is that the POLICY_FLAG_WOKE_HERE - // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw - // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was - // actually enqueued using the policyFlags that appeared in the final EV_SYN - // events upon which no preprocessing took place. So policyFlags was always 0. - // In the new native input dispatcher we're a bit more careful about event - // preprocessing so the touches we receive can actually have non-zero policyFlags. - // Unfortunately we obtain undesirable behavior. - // - // Here's what happens: - // - // When the device dims in anticipation of going to sleep, touches - // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause - // the device to brighten and reset the user activity timer. - // Touches on other windows (such as the launcher window) - // are dropped. Then after a moment, the device goes to sleep. Oops. - // - // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE - // instead of POLICY_FLAG_WOKE_HERE... - // - bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE; - - int32_t action = motionEvent->getAction(); - - bool firstIteration = true; - ANRTimer anrTimer; - int32_t injectionResult; - InjectionPermission injectionPermission; - for (;;) { - if (firstIteration) { - firstIteration = false; - } else { - if (! anrTimer.waitForDispatchStateChangeLd(this)) { - LOGW("Dropping event because the dispatcher timed out waiting to identify " - "the window that should receive it."); - injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; - injectionPermission = INJECTION_PERMISSION_UNKNOWN; - break; // timed out, exit wait loop - } - } - - // If dispatch is not enabled then fail. - if (! mDispatchEnabled) { - LOGI("Dropping event because input dispatch is disabled."); - injectionResult = INPUT_EVENT_INJECTION_FAILED; - injectionPermission = INJECTION_PERMISSION_UNKNOWN; - break; // failed, exit wait loop - } - - // If dispatch is frozen or we don't have valid window data yet then wait. - if (mDispatchFrozen || ! mWindowsReady) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Waiting because dispatch is frozen or windows are not ready."); -#endif - anrTimer.dispatchFrozenBySystem(); - continue; - } - - // Update the touch state as needed based on the properties of the touch event. - if (action == AMOTION_EVENT_ACTION_DOWN) { - /* Case 1: ACTION_DOWN */ - - InputWindow* newTouchedWindow = NULL; - mTempTouchedOutsideTargets.clear(); - - int32_t x = int32_t(motionEvent->getX(0)); - int32_t y = int32_t(motionEvent->getY(0)); - InputWindow* topErrorWindow = NULL; - bool obscured = false; - - // Traverse windows from front to back to find touched window and outside targets. - size_t numWindows = mWindows.size(); - for (size_t i = 0; i < numWindows; i++) { - InputWindow* window = & mWindows.editItemAt(i); - int32_t flags = window->layoutParamsFlags; - - if (flags & FLAG_SYSTEM_ERROR) { - if (! topErrorWindow) { - topErrorWindow = window; - } - } - - if (window->visible) { - if (! (flags & FLAG_NOT_TOUCHABLE)) { - bool isTouchModal = (flags & - (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL)) == 0; - if (isTouchModal || window->touchableAreaContainsPoint(x, y)) { - if (! screenWasOff || flags & FLAG_TOUCHABLE_WHEN_WAKING) { - newTouchedWindow = window; - obscured = isWindowObscuredLocked(window); - } - break; // found touched window, exit window loop - } - } - - if (flags & FLAG_WATCH_OUTSIDE_TOUCH) { - OutsideTarget outsideTarget; - outsideTarget.window = window; - outsideTarget.obscured = isWindowObscuredLocked(window); - mTempTouchedOutsideTargets.push(outsideTarget); - } - } - } - - // If there is an error window but it is not taking focus (typically because - // it is invisible) then wait for it. Any other focused window may in - // fact be in ANR state. - if (topErrorWindow && newTouchedWindow != topErrorWindow) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Waiting because system error window is pending."); -#endif - anrTimer.dispatchFrozenBySystem(); - continue; // wait some more - } - - // If we did not find a touched window then fail. - if (! newTouchedWindow) { - if (mFocusedApplication) { -#if DEBUG_FOCUS - LOGD("Waiting because there is no focused window but there is a " - "focused application that may yet introduce a new target: '%s'.", - mFocusedApplication->name.string()); -#endif - continue; - } - - LOGI("Dropping event because there is no touched window or focused application."); - injectionResult = INPUT_EVENT_INJECTION_FAILED; - injectionPermission = INJECTION_PERMISSION_UNKNOWN; - break; // failed, exit wait loop - } - - // Check permissions. - if (! checkInjectionPermission(newTouchedWindow, injectorPid, injectorUid)) { - injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; - injectionPermission = INJECTION_PERMISSION_DENIED; - break; // failed, exit wait loop - } - - // If the touched window is paused then keep waiting. - if (newTouchedWindow->paused) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Waiting because touched window is paused."); -#endif - anrTimer.dispatchPausedByApplication(newTouchedWindow); - continue; // wait some more - } - - // Success! Update the touch dispatch state for real. - releaseTouchedWindowLd(); - - mTouchedWindow = newTouchedWindow; - mTouchedWindowIsObscured = obscured; - - if (newTouchedWindow->hasWallpaper) { - mTouchedWallpaperWindows.appendVector(mWallpaperWindows); - } - - injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; - injectionPermission = INJECTION_PERMISSION_GRANTED; - break; // done - } else { - /* Case 2: Everything but ACTION_DOWN */ - - // Check permissions. - if (! checkInjectionPermission(mTouchedWindow, injectorPid, injectorUid)) { - injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; - injectionPermission = INJECTION_PERMISSION_DENIED; - break; // failed, exit wait loop - } - - // If the pointer is not currently down, then ignore the event. - if (! mTouchDown) { - LOGI("Dropping event because the pointer is not down."); - injectionResult = INPUT_EVENT_INJECTION_FAILED; - injectionPermission = INJECTION_PERMISSION_GRANTED; - break; // failed, exit wait loop - } - - // If there is no currently touched window then fail. - if (! mTouchedWindow) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Dropping event because there is no touched window to receive it."); -#endif - injectionResult = INPUT_EVENT_INJECTION_FAILED; - injectionPermission = INJECTION_PERMISSION_GRANTED; - break; // failed, exit wait loop - } - - // If the touched window is paused then keep waiting. - if (mTouchedWindow->paused) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("Waiting because touched window is paused."); -#endif - anrTimer.dispatchPausedByApplication(mTouchedWindow); - continue; // wait some more - } - - // Success! - injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; - injectionPermission = INJECTION_PERMISSION_GRANTED; - break; // done - } - } - - // Output targets. - if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { - size_t numWallpaperWindows = mTouchedWallpaperWindows.size(); - for (size_t i = 0; i < numWallpaperWindows; i++) { - addTarget(mTouchedWallpaperWindows[i], - InputTarget::FLAG_WINDOW_IS_OBSCURED, 0, outTargets); - } - - size_t numOutsideTargets = mTempTouchedOutsideTargets.size(); - for (size_t i = 0; i < numOutsideTargets; i++) { - const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i]; - int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE; - if (outsideTarget.obscured) { - outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; - } - addTarget(outsideTarget.window, outsideTargetFlags, 0, outTargets); - } - - int32_t targetFlags = InputTarget::FLAG_SYNC; - if (mTouchedWindowIsObscured) { - targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; - } - addTarget(mTouchedWindow, targetFlags, - anrTimer.getTimeSpentWaitingForApplication(), outTargets); - outTouchedWindow = mTouchedWindow; - } else { - outTouchedWindow = NULL; - } - mTempTouchedOutsideTargets.clear(); - - // Check injection permission once and for all. - if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) { - if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow, - injectorPid, injectorUid)) { - injectionPermission = INJECTION_PERMISSION_GRANTED; + String8 name; + if (nameObj) { + const char* nameStr = env->GetStringUTFChars(nameObj, NULL); + name.setTo(nameStr); + env->ReleaseStringUTFChars(nameObj, nameStr); + env->DeleteLocalRef(nameObj); } else { - injectionPermission = INJECTION_PERMISSION_DENIED; + LOGE("InputApplication.name should not be null."); + name.setTo("unknown"); } - } - - // Update final pieces of touch state if the injector had permission. - if (injectionPermission == INJECTION_PERMISSION_GRANTED) { - if (action == AMOTION_EVENT_ACTION_DOWN) { - if (mTouchDown) { - // This is weird. We got a down but we thought it was already down! - LOGW("Pointer down received while already down."); - } else { - mTouchDown = true; - } - if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { - // Since we failed to identify a target for this touch down, we may still - // be holding on to an earlier target from a previous touch down. Release it. - releaseTouchedWindowLd(); - } - } else if (action == AMOTION_EVENT_ACTION_UP) { - mTouchDown = false; - releaseTouchedWindowLd(); - } + InputApplication application; + application.name = name; + application.dispatchingTimeout = dispatchingTimeoutNanos; + application.handle = new ApplicationToken(tokenObjWeak); + mInputManager->getDispatcher()->setFocusedApplication(& application); } else { - LOGW("Not updating touch focus because injection was denied."); + mInputManager->getDispatcher()->setFocusedApplication(NULL); } - -#if DEBUG_FOCUS - LOGD("waitForTouchedWindow finished: injectionResult=%d", - injectionResult); - logDispatchStateLd(); -#endif - return injectionResult; } -void NativeInputManager::releaseTouchedWindowLd() { - mTouchedWindow = NULL; - mTouchedWindowIsObscured = false; - mTouchedWallpaperWindows.clear(); -} - -void NativeInputManager::addTarget(const InputWindow* window, int32_t targetFlags, - nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets) { - nsecs_t timeout = window->dispatchingTimeout - timeSpentWaitingForApplication; - if (timeout < MIN_INPUT_DISPATCHING_TIMEOUT) { - timeout = MIN_INPUT_DISPATCHING_TIMEOUT; - } - - outTargets.push(); - - InputTarget& target = outTargets.editTop(); - target.inputChannel = window->inputChannel; - target.flags = targetFlags; - target.timeout = timeout; - target.xOffset = - window->frameLeft; - target.yOffset = - window->frameTop; -} - -bool NativeInputManager::checkInjectionPermission(const InputWindow* window, - int32_t injectorPid, int32_t injectorUid) { - if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) { - JNIEnv* env = jniEnv(); - jboolean result = env->CallBooleanMethod(mCallbacksObj, - gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid); - checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission"); - - if (! result) { - if (window) { - LOGW("Permission denied: injecting event from pid %d uid %d to window " - "with input channel %s owned by uid %d", - injectorPid, injectorUid, window->inputChannel->getName().string(), - window->ownerUid); - } else { - LOGW("Permission denied: injecting event from pid %d uid %d", - injectorPid, injectorUid); - } - return false; - } - } - - return true; -} - -bool NativeInputManager::isWindowObscuredLocked(const InputWindow* window) { - size_t numWindows = mWindows.size(); - for (size_t i = 0; i < numWindows; i++) { - const InputWindow* other = & mWindows.itemAt(i); - if (other == window) { - break; - } - if (other->visible && window->visibleFrameIntersects(other)) { - return true; - } - } - return false; -} - -int32_t NativeInputManager::waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("waitForKeyEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", - policyFlags, injectorPid, injectorUid); -#endif - - int32_t windowType; - { // acquire lock - AutoMutex _l(mDispatchLock); - - InputWindow* focusedWindow; - int32_t injectionResult = waitForFocusedWindowLd(policyFlags, - injectorPid, injectorUid, outTargets, /*out*/ focusedWindow); - if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { - return injectionResult; - } - - windowType = focusedWindow->layoutParamsType; - } // release lock - - if (isPolicyKey(keyEvent->getKeyCode(), isScreenOn())) { - const InputTarget& target = outTargets.top(); - bool consumed = interceptKeyBeforeDispatching(target, keyEvent, policyFlags); - if (consumed) { - outTargets.clear(); - return INPUT_EVENT_INJECTION_SUCCEEDED; - } - - addMonitoringTargetsLd(outTargets); - } - - pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT); - return INPUT_EVENT_INJECTION_SUCCEEDED; -} - -int32_t NativeInputManager::waitForMotionEventTargets(MotionEvent* motionEvent, - uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, - Vector<InputTarget>& outTargets) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("waitForMotionEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", - policyFlags, injectorPid, injectorUid); -#endif - - int32_t source = motionEvent->getSource(); - if (source & AINPUT_SOURCE_CLASS_POINTER) { - return waitForTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid, - outTargets); - } else { - return waitForNonTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid, - outTargets); - } +void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { + mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen); } -int32_t NativeInputManager::waitForNonTouchEventTargets(MotionEvent* motionEvent, - uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, - Vector<InputTarget>& outTargets) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("waitForNonTouchEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", - policyFlags, injectorPid, injectorUid); -#endif - - int32_t windowType; - { // acquire lock - AutoMutex _l(mDispatchLock); - - InputWindow* focusedWindow; - int32_t injectionResult = waitForFocusedWindowLd(policyFlags, - injectorPid, injectorUid, outTargets, /*out*/ focusedWindow); - if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { - return injectionResult; - } - - windowType = focusedWindow->layoutParamsType; - - addMonitoringTargetsLd(outTargets); - } // release lock - - pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT); - return INPUT_EVENT_INJECTION_SUCCEEDED; +void NativeInputManager::preemptInputDispatch() { + mInputManager->getDispatcher()->preemptInputDispatch(); } -int32_t NativeInputManager::waitForTouchEventTargets(MotionEvent* motionEvent, - uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, - Vector<InputTarget>& outTargets) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("waitForTouchEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", - policyFlags, injectorPid, injectorUid); -#endif - - int32_t windowType; - { // acquire lock - AutoMutex _l(mDispatchLock); - - InputWindow* touchedWindow; - int32_t injectionResult = waitForTouchedWindowLd(motionEvent, policyFlags, - injectorPid, injectorUid, outTargets, /*out*/ touchedWindow); - if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { - return injectionResult; - } - - windowType = touchedWindow->layoutParamsType; - - addMonitoringTargetsLd(outTargets); - } // release lock - - int32_t eventType; - switch (motionEvent->getAction()) { - case AMOTION_EVENT_ACTION_DOWN: - eventType = POWER_MANAGER_TOUCH_EVENT; - break; - case AMOTION_EVENT_ACTION_UP: - eventType = POWER_MANAGER_TOUCH_UP_EVENT; - break; - default: - if (motionEvent->getEventTime() - motionEvent->getDownTime() - >= EVENT_IGNORE_DURATION) { - eventType = POWER_MANAGER_TOUCH_EVENT; - } else { - eventType = POWER_MANAGER_LONG_TOUCH_EVENT; - } - break; +bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel, + const KeyEvent* keyEvent, uint32_t policyFlags) { + bool isScreenOn = this->isScreenOn(); + if (! isPolicyKey(keyEvent->getKeyCode(), isScreenOn)) { + return false; } - pokeUserActivityIfNeeded(windowType, eventType); - return INPUT_EVENT_INJECTION_SUCCEEDED; -} -bool NativeInputManager::interceptKeyBeforeDispatching(const InputTarget& target, - const KeyEvent* keyEvent, uint32_t policyFlags) { JNIEnv* env = jniEnv(); - jobject inputChannelObj = getInputChannelObjLocal(env, target.inputChannel); + jobject inputChannelObj = getInputChannelObjLocal(env, inputChannel); if (inputChannelObj) { jboolean consumed = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.interceptKeyBeforeDispatching, @@ -1875,253 +1000,27 @@ bool NativeInputManager::interceptKeyBeforeDispatching(const InputTarget& target return consumed && ! error; } else { LOGW("Could not apply key dispatch policy because input channel '%s' is " - "no longer valid.", target.inputChannel->getName().string()); + "no longer valid.", inputChannel->getName().string()); return false; } } -void NativeInputManager::pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType) { - if (windowType != TYPE_KEYGUARD) { - nsecs_t eventTime = now(); - pokeUserActivity(eventTime, eventType); - } -} - -void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) { - android_server_PowerManagerService_userActivity(eventTime, eventType); -} - -void NativeInputManager::registerMonitoringChannel(const sp<InputChannel>& inputChannel) { - { // acquire lock - AutoMutex _l(mDispatchLock); - mMonitoringChannels.push(inputChannel); - } // release lock -} - -void NativeInputManager::unregisterMonitoringChannel(const sp<InputChannel>& inputChannel) { - { // acquire lock - AutoMutex _l(mDispatchLock); - - for (size_t i = 0; i < mMonitoringChannels.size(); i++) { - if (mMonitoringChannels[i] == inputChannel) { - mMonitoringChannels.removeAt(i); - break; - } - } - } // release lock -} - -void NativeInputManager::addMonitoringTargetsLd(Vector<InputTarget>& outTargets) { - for (size_t i = 0; i < mMonitoringChannels.size(); i++) { - outTargets.push(); - - InputTarget& target = outTargets.editTop(); - target.inputChannel = mMonitoringChannels[i]; - target.flags = 0; - target.timeout = -1; - target.xOffset = 0; - target.yOffset = 0; - } -} - -static void dumpMotionRange(String8& dump, - const char* name, const InputDeviceInfo::MotionRange* range) { - if (range) { - dump.appendFormat(" %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n", - name, range->min, range->max, range->flat, range->fuzz); - } -} - -#define DUMP_MOTION_RANGE(range) \ - dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range)); - -void NativeInputManager::dumpDeviceInfo(String8& dump) { - Vector<int32_t> deviceIds; - mInputManager->getInputDeviceIds(deviceIds); - - InputDeviceInfo deviceInfo; - for (size_t i = 0; i < deviceIds.size(); i++) { - int32_t deviceId = deviceIds[i]; - - status_t result = mInputManager->getInputDeviceInfo(deviceId, & deviceInfo); - if (result == NAME_NOT_FOUND) { - continue; - } else if (result != OK) { - dump.appendFormat(" ** Unexpected error %d getting information about input devices.\n", - result); - continue; - } - - dump.appendFormat(" Device %d: '%s'\n", - deviceInfo.getId(), deviceInfo.getName().string()); - dump.appendFormat(" sources = 0x%08x\n", - deviceInfo.getSources()); - dump.appendFormat(" keyboardType = %d\n", - deviceInfo.getKeyboardType()); - - dump.append(" motion ranges:\n"); - DUMP_MOTION_RANGE(X); - DUMP_MOTION_RANGE(Y); - DUMP_MOTION_RANGE(PRESSURE); - DUMP_MOTION_RANGE(SIZE); - DUMP_MOTION_RANGE(TOUCH_MAJOR); - DUMP_MOTION_RANGE(TOUCH_MINOR); - DUMP_MOTION_RANGE(TOOL_MAJOR); - DUMP_MOTION_RANGE(TOOL_MINOR); - DUMP_MOTION_RANGE(ORIENTATION); +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t windowType, int32_t eventType) { + if (windowType != InputWindow::TYPE_KEYGUARD) { + android_server_PowerManagerService_userActivity(eventTime, eventType); } } -#undef DUMP_MOTION_RANGE - -void NativeInputManager::logDispatchStateLd() { - String8 dump; - dumpDispatchStateLd(dump); - LOGD("%s", dump.string()); -} - -void NativeInputManager::dumpDispatchStateLd(String8& dump) { - dump.appendFormat(" dispatchEnabled: %d\n", mDispatchEnabled); - dump.appendFormat(" dispatchFrozen: %d\n", mDispatchFrozen); - dump.appendFormat(" windowsReady: %d\n", mWindowsReady); - - if (mFocusedApplication) { - dump.appendFormat(" focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n", - mFocusedApplication->name.string(), - mFocusedApplication->dispatchingTimeout / 1000000.0); - } else { - dump.append(" focusedApplication: <null>\n"); - } - dump.appendFormat(" focusedWindow: '%s'\n", - mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>"); - dump.appendFormat(" touchedWindow: '%s', touchDown=%d\n", - mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>", - mTouchDown); - for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) { - dump.appendFormat(" touchedWallpaperWindows[%d]: '%s'\n", - i, mTouchedWallpaperWindows[i]->inputChannel->getName().string()); - } - for (size_t i = 0; i < mWindows.size(); i++) { - dump.appendFormat(" windows[%d]: '%s', paused=%d, hasFocus=%d, hasWallpaper=%d, " - "visible=%d, flags=0x%08x, type=0x%08x, " - "frame=[%d,%d][%d,%d], " - "visibleFrame=[%d,%d][%d,%d], " - "touchableArea=[%d,%d][%d,%d], " - "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n", - i, mWindows[i].inputChannel->getName().string(), - mWindows[i].paused, mWindows[i].hasFocus, mWindows[i].hasWallpaper, - mWindows[i].visible, mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType, - mWindows[i].frameLeft, mWindows[i].frameTop, - mWindows[i].frameRight, mWindows[i].frameBottom, - mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop, - mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom, - mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop, - mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom, - mWindows[i].ownerPid, mWindows[i].ownerUid, - mWindows[i].dispatchingTimeout / 1000000.0); - } - - for (size_t i = 0; i < mMonitoringChannels.size(); i++) { - dump.appendFormat(" monitoringChannel[%d]: '%s'\n", - i, mMonitoringChannels[i]->getName().string()); - } -} - -// ---------------------------------------------------------------------------- - -bool NativeInputManager::InputWindow::visibleFrameIntersects(const InputWindow* other) const { - return visibleFrameRight > other->visibleFrameLeft - && visibleFrameLeft < other->visibleFrameRight - && visibleFrameBottom > other->visibleFrameTop - && visibleFrameTop < other->visibleFrameBottom; -} - -bool NativeInputManager::InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const { - return x >= touchableAreaLeft && x <= touchableAreaRight - && y >= touchableAreaTop && y <= touchableAreaBottom; -} - -// ---------------------------------------------------------------------------- - -NativeInputManager::ANRTimer::ANRTimer() : - mBudget(APPLICATION), mStartTime(now()), mFrozen(false), mPausedWindow(NULL) { -} - -void NativeInputManager::ANRTimer::dispatchFrozenBySystem() { - mFrozen = true; -} - -void NativeInputManager::ANRTimer::dispatchPausedByApplication(InputWindow* pausedWindow) { - mPausedWindow = pausedWindow; -} - -bool NativeInputManager::ANRTimer::waitForDispatchStateChangeLd(NativeInputManager* inputManager) { - nsecs_t currentTime = now(); - - Budget newBudget; - nsecs_t dispatchingTimeout; - sp<InputChannel> pausedChannel = NULL; - jobject tokenObj = NULL; - if (mFrozen) { - newBudget = SYSTEM; - dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; - mFrozen = false; - } else if (mPausedWindow) { - newBudget = APPLICATION; - dispatchingTimeout = mPausedWindow->dispatchingTimeout; - pausedChannel = mPausedWindow->inputChannel; - mPausedWindow = NULL; - } else if (inputManager->mFocusedApplication) { - newBudget = APPLICATION; - dispatchingTimeout = inputManager->mFocusedApplication->dispatchingTimeout; - tokenObj = jniEnv()->NewLocalRef(inputManager->mFocusedApplication->tokenObjWeak); - } else { - newBudget = APPLICATION; - dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; - } - - if (mBudget != newBudget) { - mBudget = newBudget; - mStartTime = currentTime; - } - - bool result = false; - nsecs_t timeoutRemaining = mStartTime + dispatchingTimeout - currentTime; - if (timeoutRemaining > 0 - && inputManager->mDispatchStateChanged.waitRelative(inputManager->mDispatchLock, - timeoutRemaining) == OK) { - result = true; - } else { - if (pausedChannel != NULL || tokenObj != NULL) { - bool resumed; - nsecs_t newTimeout = 0; - - inputManager->mDispatchLock.unlock(); // release lock - if (pausedChannel != NULL) { - resumed = inputManager->notifyInputChannelANR(pausedChannel, /*out*/ newTimeout); - } else { - resumed = inputManager->notifyANR(tokenObj, /*out*/ newTimeout); - } - inputManager->mDispatchLock.lock(); // re-acquire lock - - if (resumed) { - mStartTime = now() - dispatchingTimeout + newTimeout; - result = true; - } - } - } - - if (tokenObj) { - jniEnv()->DeleteLocalRef(tokenObj); - } +bool NativeInputManager::checkInjectEventsPermissionNonReentrant( + int32_t injectorPid, int32_t injectorUid) { + JNIEnv* env = jniEnv(); + jboolean result = env->CallBooleanMethod(mCallbacksObj, + gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid); + checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission"); return result; } -nsecs_t NativeInputManager::ANRTimer::getTimeSpentWaitingForApplication() const { - return mBudget == APPLICATION ? now() - mStartTime : 0; -} - // ---------------------------------------------------------------------------- static sp<NativeInputManager> gNativeInputManager; @@ -2184,7 +1083,7 @@ static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jcla return AKEY_STATE_UNKNOWN; } - return gNativeInputManager->getInputManager()->getScanCodeState( + return gNativeInputManager->getInputManager()->getReader()->getScanCodeState( deviceId, uint32_t(sourceMask), scanCode); } @@ -2194,7 +1093,7 @@ static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclas return AKEY_STATE_UNKNOWN; } - return gNativeInputManager->getInputManager()->getKeyCodeState( + return gNativeInputManager->getInputManager()->getReader()->getKeyCodeState( deviceId, uint32_t(sourceMask), keyCode); } @@ -2204,7 +1103,7 @@ static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass return AKEY_STATE_UNKNOWN; } - return gNativeInputManager->getInputManager()->getSwitchState( + return gNativeInputManager->getInputManager()->getReader()->getSwitchState( deviceId, uint32_t(sourceMask), sw); } @@ -2219,7 +1118,7 @@ static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass cl jsize numCodes = env->GetArrayLength(keyCodes); jboolean result; if (numCodes == env->GetArrayLength(keyCodes)) { - result = gNativeInputManager->getInputManager()->hasKeys( + result = gNativeInputManager->getInputManager()->getReader()->hasKeys( deviceId, uint32_t(sourceMask), numCodes, codes, flags); } else { result = JNI_FALSE; @@ -2306,14 +1205,14 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla KeyEvent keyEvent; android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent); - return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent, - injectorPid, injectorUid, syncMode, timeoutMillis); + return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( + & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis); } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) { MotionEvent motionEvent; android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent); - return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent, - injectorPid, injectorUid, syncMode, timeoutMillis); + return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( + & motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis); } else { jniThrowRuntimeException(env, "Invalid input event type."); return INPUT_EVENT_INJECTION_FAILED; @@ -2363,7 +1262,7 @@ static jobject android_server_InputManager_nativeGetInputDevice(JNIEnv* env, } InputDeviceInfo deviceInfo; - status_t status = gNativeInputManager->getInputManager()->getInputDeviceInfo( + status_t status = gNativeInputManager->getInputManager()->getReader()->getInputDeviceInfo( deviceId, & deviceInfo); if (status) { return NULL; @@ -2405,7 +1304,7 @@ static jintArray android_server_InputManager_nativeGetInputDeviceIds(JNIEnv* env } Vector<int> deviceIds; - gNativeInputManager->getInputManager()->getInputDeviceIds(deviceIds); + gNativeInputManager->getInputManager()->getReader()->getInputDeviceIds(deviceIds); jintArray deviceIdsObj = env->NewIntArray(deviceIds.size()); if (! deviceIdsObj) { @@ -2421,7 +1320,8 @@ static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) return NULL; } - String8 dump(gNativeInputManager->dump()); + String8 dump; + gNativeInputManager->dump(dump); return env->NewStringUTF(dump.string()); } @@ -2519,9 +1419,6 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz, "checkInjectEventsPermission", "(II)Z"); - GET_METHOD_ID(gCallbacksClassInfo.notifyAppSwitchComing, gCallbacksClassInfo.clazz, - "notifyAppSwitchComing", "()V"); - GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz, "filterTouchEvents", "()Z"); diff --git a/services/jni/com_android_server_PowerManagerService.h b/services/jni/com_android_server_PowerManagerService.h index 7c329b2..af10711 100644 --- a/services/jni/com_android_server_PowerManagerService.h +++ b/services/jni/com_android_server_PowerManagerService.h @@ -20,19 +20,9 @@ #include "JNIHelp.h" #include "jni.h" -namespace android { - -enum { - POWER_MANAGER_OTHER_EVENT = 0, - POWER_MANAGER_CHEEK_EVENT = 1, - POWER_MANAGER_TOUCH_EVENT = 2, // touch events are TOUCH for 300ms, and then either - // up events or LONG_TOUCH events. - POWER_MANAGER_LONG_TOUCH_EVENT = 3, - POWER_MANAGER_TOUCH_UP_EVENT = 4, - POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events. +#include <ui/PowerManager.h> - POWER_MANAGER_LAST_EVENT = POWER_MANAGER_BUTTON_EVENT, // Last valid event code. -}; +namespace android { extern bool android_server_PowerManagerService_isScreenOn(); extern bool android_server_PowerManagerService_isScreenBright(); diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java index caec7e1..4bf3282 100644 --- a/telephony/java/com/android/internal/telephony/CallManager.java +++ b/telephony/java/com/android/internal/telephony/CallManager.java @@ -74,6 +74,7 @@ public final class CallManager { private static final int EVENT_ECM_TIMER_RESET = 115; private static final int EVENT_SUBSCRIPTION_INFO_READY = 116; private static final int EVENT_SUPP_SERVICE_FAILED = 117; + private static final int EVENT_SERVICE_STATE_CHANGED = 118; // Singleton instance private static final CallManager INSTANCE = new CallManager(); @@ -109,9 +110,6 @@ public final class CallManager { protected final RegistrantList mDisconnectRegistrants = new RegistrantList(); - protected final RegistrantList mServiceStateRegistrants - = new RegistrantList(); - protected final RegistrantList mMmiRegistrants = new RegistrantList(); @@ -157,6 +155,9 @@ public final class CallManager { protected final RegistrantList mSuppServiceFailedRegistrants = new RegistrantList(); + protected final RegistrantList mServiceStateChangedRegistrants + = new RegistrantList(); + private CallManager() { mPhones = new ArrayList<Phone>(); mRingingCalls = new ArrayList<Call>(); @@ -351,6 +352,7 @@ public final class CallManager { phone.registerForEcmTimerReset(mHandler, EVENT_ECM_TIMER_RESET, null); phone.registerForSubscriptionInfoReady(mHandler, EVENT_SUBSCRIPTION_INFO_READY, null); phone.registerForSuppServiceFailed(mHandler, EVENT_SUPP_SERVICE_FAILED, null); + phone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, null); } private void unregisterForPhoneStates(Phone phone) { @@ -372,6 +374,7 @@ public final class CallManager { phone.unregisterForEcmTimerReset(mHandler); phone.unregisterForSubscriptionInfoReady(mHandler); phone.unregisterForSuppServiceFailed(mHandler); + phone.unregisterForServiceStateChanged(mHandler); } /** @@ -951,13 +954,17 @@ public final class CallManager { * Message.obj will contain an AsyncResult. * AsyncResult.result will be a ServiceState instance */ - public void registerForServiceStateChanged(Handler h, int what, Object obj){} + public void registerForServiceStateChanged(Handler h, int what, Object obj){ + mServiceStateChangedRegistrants.addUnique(h, what, obj); + } /** * Unregisters for ServiceStateChange notification. * Extraneous calls are tolerated silently */ - public void unregisterForServiceStateChanged(Handler h){} + public void unregisterForServiceStateChanged(Handler h){ + mServiceStateChangedRegistrants.remove(h); + } /** * Register for notifications when a supplementary service attempt fails. @@ -1399,6 +1406,8 @@ public final class CallManager { case EVENT_SUPP_SERVICE_FAILED: mSuppServiceFailedRegistrants.notifyRegistrants((AsyncResult) msg.obj); break; + case EVENT_SERVICE_STATE_CHANGED: + mServiceStateChangedRegistrants.notifyRegistrants((AsyncResult) msg.obj); } } }; diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java index e5456e4..c20c200 100644 --- a/telephony/java/com/android/internal/telephony/Connection.java +++ b/telephony/java/com/android/internal/telephony/Connection.java @@ -40,6 +40,7 @@ public abstract class Connection { MMI, /* not presently used; dial() returns null */ INVALID_NUMBER, /* invalid dial string */ INVALID_CREDENTIALS, /* invalid credentials */ + TIMED_OUT, /* client timed out */ LOST_SIGNAL, LIMIT_EXCEEDED, /* eg GSM ACM limit exceeded */ INCOMING_REJECTED, /* an incoming call that was rejected */ diff --git a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java index e7eda4f..7e407b2 100644 --- a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java +++ b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java @@ -26,7 +26,6 @@ import android.net.sip.SipManager; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import javax.sip.SipException; abstract class SipCallBase extends Call { private static final int MAX_CONNECTIONS_PER_CALL = 5; diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java index f30e11e..a720eac 100755 --- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java +++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java @@ -24,6 +24,7 @@ import android.net.rtp.AudioGroup; import android.net.rtp.AudioStream; import android.net.sip.SipAudioCall; import android.net.sip.SipErrorCode; +import android.net.sip.SipException; import android.net.sip.SipManager; import android.net.sip.SipProfile; import android.net.sip.SipSessionState; @@ -66,8 +67,6 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.List; -import javax.sip.SipException; - /** * {@hide} */ @@ -826,7 +825,8 @@ public class SipPhone extends SipPhoneBase { onError(Connection.DisconnectCause.INVALID_NUMBER); break; case TIME_OUT: - onError(Connection.DisconnectCause.CONGESTION); + case TRANSACTION_TERMINTED: + onError(Connection.DisconnectCause.TIMED_OUT); break; case INVALID_CREDENTIALS: onError(Connection.DisconnectCause.INVALID_CREDENTIALS); diff --git a/tests/DumpRenderTree2/AndroidManifest.xml b/tests/DumpRenderTree2/AndroidManifest.xml index b4dc190..5233677 100644 --- a/tests/DumpRenderTree2/AndroidManifest.xml +++ b/tests/DumpRenderTree2/AndroidManifest.xml @@ -40,6 +40,7 @@ limitations under the License. </activity> <activity android:name=".LayoutTestsExecutor" + android:theme="@style/WhiteBackground" android:label="Layout tests' executor" android:process=":executor"> </activity> diff --git a/tests/DumpRenderTree2/assets/run_layout_tests.py b/tests/DumpRenderTree2/assets/run_layout_tests.py index b13d8c9..d5bf8b3 100755 --- a/tests/DumpRenderTree2/assets/run_layout_tests.py +++ b/tests/DumpRenderTree2/assets/run_layout_tests.py @@ -3,34 +3,39 @@ """Run layout tests on the device. It runs the specified tests on the device, downloads the summaries to the temporary directory - and opens html details in the default browser. + and optionally shows the detailed results the host's default browser. Usage: - run_layout_tests.py PATH + run_layout_tests.py --show-results-in-browser test-relative-path """ -import sys +import logging +import optparse import os +import sys import subprocess -import logging -import webbrowser import tempfile +import webbrowser #TODO: These should not be hardcoded -RESULTS_ABSOLUTE_PATH = "/sdcard/android/LayoutTests-results/" +RESULTS_ABSOLUTE_PATH = "/sdcard/layout-test-results/" DETAILS_HTML = "details.html" SUMMARY_TXT = "summary.txt" -def main(): - if len(sys.argv) > 1: - path = sys.argv[1] +def main(options, args): + if args: + path = " ".join(args); else: - path = "" + path = ""; logging.basicConfig(level=logging.INFO, format='%(message)s') tmpdir = tempfile.gettempdir() + # Restart the server + cmd = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), "run_apache2.py") + " restart" + os.system(cmd); + # Run the tests in path cmd = "adb shell am instrument " cmd += "-e class com.android.dumprendertree2.scriptsupport.Starter#startLayoutTests " @@ -44,12 +49,14 @@ def main(): # Download the txt summary to tmp folder summary_txt_tmp_path = os.path.join(tmpdir, SUMMARY_TXT) - cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path + cmd = "rm -f " + summary_txt_tmp_path + ";" + cmd += "adb pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() # Download the html summary to tmp folder details_html_tmp_path = os.path.join(tmpdir, DETAILS_HTML) - cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path + cmd = "rm -f " + details_html_tmp_path + ";" + cmd += "adb pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() # Print summary to console @@ -59,7 +66,12 @@ def main(): logging.info("") # Open the browser with summary - webbrowser.open(details_html_tmp_path) + if options.show_results_in_browser != "false": + webbrowser.open(details_html_tmp_path) if __name__ == "__main__": - main(); + option_parser = optparse.OptionParser(usage="Usage: %prog [options] test-relative-path") + option_parser.add_option("", "--show-results-in-browser", default="true", + help="Show the results the host's default web browser, default=true") + options, args = option_parser.parse_args(); + main(options, args); diff --git a/tests/DumpRenderTree2/res/values/style.xml b/tests/DumpRenderTree2/res/values/style.xml new file mode 100644 index 0000000..35f3419 --- /dev/null +++ b/tests/DumpRenderTree2/res/values/style.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +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. +--> +<resources> + <style name="WhiteBackground"> + <item name="android:background">@android:color/white</item> + </style> +</resources>
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java index 4ab76e3..9bbf64a 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java @@ -56,12 +56,7 @@ public class FileFilter { private final Set<String> mFailList = new HashSet<String>(); private final Set<String> mSlowList = new HashSet<String>(); - private final String mRootDirPath; - - public FileFilter(String rootDirPath) { - /** It may or may not contain a trailing slash */ - this.mRootDirPath = rootDirPath; - + public FileFilter() { loadTestExpectations(); } @@ -287,37 +282,6 @@ public class FileFilter { } /** - * Return the path to the file relative to the tests root dir - * - * @param filePath - * @return - * the path relative to the tests root dir - */ - public String getRelativePath(String filePath) { - File rootDir = new File(mRootDirPath); - return filePath.replaceFirst(rootDir.getPath() + File.separator, ""); - } - - /** - * Return the path to the file relative to the tests root dir - * - * @param filePath - * @return - * the path relative to the tests root dir - */ - public String getRelativePath(File file) { - return getRelativePath(file.getAbsolutePath()); - } - - public File getAbsoluteFile(String relativePath) { - return new File(mRootDirPath, relativePath); - } - - public String getAboslutePath(String relativePath) { - return getAbsoluteFile(relativePath).getAbsolutePath(); - } - - /** * If the path contains extension (e.g .foo at the end of the file) then it changes * this (.foo) into newEnding (so it has to contain the dot if we want to preserve it). * @@ -336,4 +300,4 @@ public class FileFilter { return relativePath.substring(0, dotPos) + newEnding; } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java index 4202668..4438811 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java @@ -20,19 +20,35 @@ import android.util.Log; import com.android.dumprendertree2.forwarder.ForwarderManager; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.util.EntityUtils; + import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.MalformedURLException; +import java.net.SocketTimeoutException; import java.net.URL; -import java.net.URLConnection; import java.util.LinkedList; import java.util.List; @@ -45,6 +61,29 @@ public class FsUtils { private static final String SCRIPT_URL = ForwarderManager.getHostSchemePort(false) + "WebKitTools/DumpRenderTree/android/get_layout_tests_dir_contents.php"; + private static final int HTTP_TIMEOUT_MS = 5000; + + private static HttpClient sHttpClient; + + private static HttpClient getHttpClient() { + if (sHttpClient == null) { + HttpParams params = new BasicHttpParams(); + + SchemeRegistry schemeRegistry = new SchemeRegistry(); + schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), + ForwarderManager.HTTP_PORT)); + schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), + ForwarderManager.HTTPS_PORT)); + + ClientConnectionManager connectionManager = new ThreadSafeClientConnManager(params, + schemeRegistry); + sHttpClient = new DefaultHttpClient(connectionManager, params); + HttpConnectionParams.setSoTimeout(sHttpClient.getParams(), HTTP_TIMEOUT_MS); + HttpConnectionParams.setConnectionTimeout(sHttpClient.getParams(), HTTP_TIMEOUT_MS); + } + return sHttpClient; + } + public static void writeDataToStorage(File file, byte[] bytes, boolean append) { Log.d(LOG_TAG, "writeDataToStorage(): " + file.getAbsolutePath()); try { @@ -98,32 +137,34 @@ public class FsUtils { return null; } - byte[] bytes = null; - try { - InputStream inputStream = null; - ByteArrayOutputStream outputStream = null; - try { - URLConnection urlConnection = url.openConnection(); - inputStream = urlConnection.getInputStream(); - outputStream = new ByteArrayOutputStream(); - - byte[] buffer = new byte[4096]; - int length; - while ((length = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, length); + HttpGet httpRequest = new HttpGet(url.toString()); + ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() { + @Override + public byte[] handleResponse(HttpResponse response) throws IOException { + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + return null; } + HttpEntity entity = response.getEntity(); + return (entity == null ? null : EntityUtils.toByteArray(entity)); + } + }; - bytes = outputStream.toByteArray(); - } finally { - if (inputStream != null) { - inputStream.close(); - } - if (outputStream != null) { - outputStream.close(); + byte[] bytes = null; + try { + /** + * TODO: Not exactly sure why some requests hang indefinitely, but adding this + * timeout (in static getter for http client) in loop helps. + */ + boolean timedOut; + do { + timedOut = false; + try { + bytes = getHttpClient().execute(httpRequest, handler); + } catch (SocketTimeoutException e) { + timedOut = true; + Log.w(LOG_TAG, "Expected SocketTimeoutException: " + url, e); } - } - } catch (FileNotFoundException e) { - Log.w(LOG_TAG, "readDataFromUrl(): File not found: " + e.getMessage()); + } while (timedOut); } catch (IOException e) { Log.e(LOG_TAG, "url=" + url, e); } @@ -135,8 +176,6 @@ public class FsUtils { boolean mode) { String modeString = (mode ? "folders" : "files"); - List<String> results = new LinkedList<String>(); - URL url = null; try { url = new URL(SCRIPT_URL + @@ -146,34 +185,67 @@ public class FsUtils { } catch (MalformedURLException e) { Log.e(LOG_TAG, "path=" + dirRelativePath + " recurse=" + recurse + " mode=" + modeString, e); - return results; + return new LinkedList<String>(); } - try { - InputStream inputStream = null; - BufferedReader bufferedReader = null; - try { - URLConnection urlConnection = url.openConnection(); - inputStream = urlConnection.getInputStream(); - bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + HttpGet httpRequest = new HttpGet(url.toString()); + ResponseHandler<LinkedList<String>> handler = new ResponseHandler<LinkedList<String>>() { + @Override + public LinkedList<String> handleResponse(HttpResponse response) + throws IOException { + LinkedList<String> lines = new LinkedList<String>(); - String relativePath; - while ((relativePath = bufferedReader.readLine()) != null) { - results.add(relativePath); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + return lines; } - } finally { - if (inputStream != null) { - inputStream.close(); + HttpEntity entity = response.getEntity(); + if (entity == null) { + return lines; } - if (bufferedReader != null) { - bufferedReader.close(); + + BufferedReader reader = + new BufferedReader(new InputStreamReader(entity.getContent())); + String line; + try { + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } finally { + if (reader != null) { + reader.close(); + } } + + return lines; } + }; + + try { + return getHttpClient().execute(httpRequest, handler); } catch (IOException e) { - Log.e(LOG_TAG, "path=" + dirRelativePath + " recurse=" + recurse + " mode=" + - modeString, e); + Log.e(LOG_TAG, "url=" + url, e); } - return results; + return new LinkedList<String>(); + } + + public static void closeInputStream(InputStream inputStream) { + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + Log.e(LOG_TAG, "Couldn't close stream!", e); + } + } + + public static void closeOutputStream(OutputStream outputStream) { + try { + if (outputStream != null) { + outputStream.close(); + } + } catch (IOException e) { + Log.e(LOG_TAG, "Couldn't close stream!", e); + } } }
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java index 7d57eb7..ec8409a 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java @@ -35,12 +35,9 @@ public class LayoutTestController { mLayoutTestsExecutor = layoutTestsExecutor; } - public void waitUntilDone() { - mLayoutTestsExecutor.waitUntilDone(); - } - - public void notifyDone() { - mLayoutTestsExecutor.notifyDone(); + public void clearAllDatabases() { + Log.i(LOG_TAG, "clearAllDatabases() called"); + WebStorage.getInstance().deleteAllData(); } public void dumpAsText() { @@ -55,17 +52,25 @@ public class LayoutTestController { mLayoutTestsExecutor.dumpChildFramesAsText(); } - public void clearAllDatabases() { - Log.i(LOG_TAG, "clearAllDatabases() called"); - WebStorage.getInstance().deleteAllData(); + public void dumpDatabaseCallbacks() { + mLayoutTestsExecutor.dumpDatabaseCallbacks(); } - public void setCanOpenWindows() { - mLayoutTestsExecutor.setCanOpenWindows(); + public void notifyDone() { + mLayoutTestsExecutor.notifyDone(); } - public void dumpDatabaseCallbacks() { - mLayoutTestsExecutor.dumpDatabaseCallbacks(); + public void overridePreference(String key, boolean value) { + mLayoutTestsExecutor.overridePreference(key, value); + } + + public void setAppCacheMaximumSize(long size) { + Log.i(LOG_TAG, "setAppCacheMaximumSize() called with: " + size); + WebStorage.getInstance().setAppCacheMaximumSize(size); + } + + public void setCanOpenWindows() { + mLayoutTestsExecutor.setCanOpenWindows(); } public void setDatabaseQuota(long quota) { @@ -79,17 +84,6 @@ public class LayoutTestController { mLayoutTestsExecutor.setGeolocationPermission(allow); } - public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) { - Log.i(LOG_TAG, "setMockGeolocationPosition(): " + "latitude=" + latitude + - " longitude=" + longitude + " accuracy=" + accuracy); - MockGeolocation.getInstance().setPosition(latitude, longitude, accuracy); - } - - public void setMockGeolocationError(int code, String message) { - Log.i(LOG_TAG, "setMockGeolocationError(): " + "code=" + code + " message=" + message); - MockGeolocation.getInstance().setError(code, message); - } - public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { // Configuration is in WebKit, so stay on WebCore thread, but go via LayoutTestsExecutor @@ -100,4 +94,19 @@ public class LayoutTestController { mLayoutTestsExecutor.setMockDeviceOrientation( canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma); } -}
\ No newline at end of file + + public void setMockGeolocationError(int code, String message) { + Log.i(LOG_TAG, "setMockGeolocationError(): " + "code=" + code + " message=" + message); + MockGeolocation.getInstance().setError(code, message); + } + + public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) { + Log.i(LOG_TAG, "setMockGeolocationPosition(): " + "latitude=" + latitude + + " longitude=" + longitude + " accuracy=" + accuracy); + MockGeolocation.getInstance().setPosition(latitude, longitude, accuracy); + } + + public void waitUntilDone() { + mLayoutTestsExecutor.waitUntilDone(); + } +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java index 0bd2302..4c7124b 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java @@ -73,12 +73,6 @@ public class LayoutTestsExecutor extends Activity { } } - /** TODO: make it a setting */ - static final String TESTS_ROOT_DIR_PATH = - Environment.getExternalStorageDirectory() + - File.separator + "android" + - File.separator + "LayoutTests"; - private static final String LOG_TAG = "LayoutTestExecutor"; public static final String EXTRA_TESTS_LIST = "TestsList"; @@ -513,6 +507,11 @@ public class LayoutTestsExecutor extends Activity { private static final int MSG_SET_CAN_OPEN_WINDOWS = 4; private static final int MSG_DUMP_DATABASE_CALLBACKS = 5; private static final int MSG_SET_GEOLOCATION_PERMISSION = 6; + private static final int MSG_OVERRIDE_PREFERENCE = 7; + + /** String constants for use with layoutTestController.overridePreference() */ + private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED = + "WebKitOfflineWebApplicationCacheEnabled"; Handler mLayoutTestControllerHandler = new Handler() { @Override @@ -520,24 +519,12 @@ public class LayoutTestsExecutor extends Activity { assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name(); switch (msg.what) { - case MSG_WAIT_UNTIL_DONE: - mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST; - break; - - case MSG_NOTIFY_DONE: - if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) { - onTestFinished(); - } - break; - case MSG_DUMP_AS_TEXT: if (mCurrentResult == null) { mCurrentResult = new TextResult(mCurrentTestRelativePath); } - assert mCurrentResult instanceof TextResult : "mCurrentResult instanceof" + mCurrentResult.getClass().getName(); - break; case MSG_DUMP_CHILD_FRAMES_AS_TEXT: @@ -552,14 +539,36 @@ public class LayoutTestsExecutor extends Activity { ((TextResult)mCurrentResult).setDumpChildFramesAsText(true); break; - case MSG_SET_CAN_OPEN_WINDOWS: - mCanOpenWindows = true; - break; - case MSG_DUMP_DATABASE_CALLBACKS: mDumpDatabaseCallbacks = true; break; + case MSG_NOTIFY_DONE: + if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) { + onTestFinished(); + } + break; + + case MSG_OVERRIDE_PREFERENCE: + /** + * TODO: We should look up the correct WebView for the frame which + * called the layoutTestController method. Currently, we just use the + * WebView for the main frame. EventSender suffers from the same + * problem. + */ + if (msg.getData().getString("key").equals( + WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED)) { + mCurrentWebView.getSettings().setAppCacheEnabled(msg.getData().getBoolean( + "value")); + } else { + Log.w(LOG_TAG, "MSG_OVERRIDE_PREFERENCE: unsupported preference!"); + } + break; + + case MSG_SET_CAN_OPEN_WINDOWS: + mCanOpenWindows = true; + break; + case MSG_SET_GEOLOCATION_PERMISSION: mIsGeolocationPermissionSet = true; mGeolocationPermission = msg.arg1 == 1; @@ -576,6 +585,10 @@ public class LayoutTestsExecutor extends Activity { } break; + case MSG_WAIT_UNTIL_DONE: + mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST; + break; + default: assert false : "msg.what=" + msg.what; break; @@ -590,16 +603,6 @@ public class LayoutTestsExecutor extends Activity { mPendingGeolocationPermissionCallbacks = null; } - public void waitUntilDone() { - Log.i(LOG_TAG, mCurrentTestRelativePath + ": waitUntilDone() called"); - mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE); - } - - public void notifyDone() { - Log.i(LOG_TAG, mCurrentTestRelativePath + ": notifyDone() called"); - mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE); - } - public void dumpAsText(boolean enablePixelTest) { Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpAsText(" + enablePixelTest + ") called"); /** TODO: Implement */ @@ -614,16 +617,30 @@ public class LayoutTestsExecutor extends Activity { mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_CHILD_FRAMES_AS_TEXT); } - public void setCanOpenWindows() { - Log.i(LOG_TAG, mCurrentTestRelativePath + ": setCanOpenWindows() called"); - mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS); - } - public void dumpDatabaseCallbacks() { Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpDatabaseCallbacks() called"); mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_DATABASE_CALLBACKS); } + public void notifyDone() { + Log.i(LOG_TAG, mCurrentTestRelativePath + ": notifyDone() called"); + mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE); + } + + public void overridePreference(String key, boolean value) { + Log.i(LOG_TAG, mCurrentTestRelativePath + ": overridePreference(" + key + ", " + value + + ") called"); + Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_OVERRIDE_PREFERENCE); + msg.getData().putString("key", key); + msg.getData().putBoolean("value", value); + msg.sendToTarget(); + } + + public void setCanOpenWindows() { + Log.i(LOG_TAG, mCurrentTestRelativePath + ": setCanOpenWindows() called"); + mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS); + } + public void setGeolocationPermission(boolean allow) { Log.i(LOG_TAG, mCurrentTestRelativePath + ": setGeolocationPermission(" + allow + ") called"); @@ -640,4 +657,9 @@ public class LayoutTestsExecutor extends Activity { mCurrentWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma); } + + public void waitUntilDone() { + Log.i(LOG_TAG, mCurrentTestRelativePath + ": waitUntilDone() called"); + mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE); + } } diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java index d9da672..9caabdb 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java @@ -43,16 +43,8 @@ public class ManagerService extends Service { private static final int CRASH_TIMEOUT_MS = 20 * 1000; /** TODO: make it a setting */ - static final String TESTS_ROOT_DIR_PATH = - Environment.getExternalStorageDirectory() + - File.separator + "android" + - File.separator + "LayoutTests"; - - /** TODO: make it a setting */ static final String RESULTS_ROOT_DIR_PATH = - Environment.getExternalStorageDirectory() + - File.separator + "android" + - File.separator + "LayoutTests-results"; + Environment.getExternalStorageDirectory() + File.separator + "layout-test-results"; /** TODO: Make it a setting */ private static final List<String> EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES = @@ -141,7 +133,7 @@ public class ManagerService extends Service { public void onCreate() { super.onCreate(); - mFileFilter = new FileFilter(TESTS_ROOT_DIR_PATH); + mFileFilter = new FileFilter(); mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH); } @@ -283,4 +275,4 @@ public class ManagerService extends Service { return mLastExpectedResultPathFetched; } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java index 0efb78e..2cde296 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java @@ -201,9 +201,7 @@ public class Summarizer { private FileFilter mFileFilter; private String mResultsRootDirPath; - private String mTestsRelativePath; - private Date mDate; public Summarizer(FileFilter fileFilter, String resultsRootDirPath) { @@ -243,8 +241,9 @@ public class Summarizer { } public void summarize() { - createHtmlDetails(); - createTxtSummary(); + String webKitRevision = getWebKitRevision(); + createHtmlDetails(webKitRevision); + createTxtSummary(webKitRevision); } public void reset() { @@ -255,31 +254,28 @@ public class Summarizer { mDate = new Date(); } - private void createTxtSummary() { + private void createTxtSummary(String webKitRevision) { StringBuilder txt = new StringBuilder(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - txt.append(mTestsRelativePath + "\n"); + txt.append("Path: " + mTestsRelativePath + "\n"); txt.append("Date: " + dateFormat.format(mDate) + "\n"); txt.append("Build fingerprint: " + Build.FINGERPRINT + "\n"); txt.append("WebKit version: " + getWebKitVersionFromUserAgentString() + "\n"); - txt.append("WebKit revision: " + getWebKitRevision() + "\n"); + txt.append("WebKit revision: " + webKitRevision + "\n"); - txt.append("TOTAL: " + getTotalTestCount() + "\n"); - if (mCrashedTestsCount > 0) { - txt.append("CRASHED (total among all tests): " + mCrashedTestsCount + "\n"); - txt.append("-------------"); - } - txt.append("UNEXPECTED FAILURES: " + mUnexpectedFailures.size() + "\n"); - txt.append("UNEXPECTED PASSES: " + mUnexpectedPasses.size() + "\n"); - txt.append("EXPECTED FAILURES: " + mExpectedFailures.size() + "\n"); - txt.append("EXPECTED PASSES: " + mExpectedPasses.size() + "\n"); + txt.append("TOTAL: " + getTotalTestCount() + "\n"); + txt.append("CRASHED (among all tests): " + mCrashedTestsCount + "\n"); + txt.append("UNEXPECTED FAILURES: " + mUnexpectedFailures.size() + "\n"); + txt.append("UNEXPECTED PASSES: " + mUnexpectedPasses.size() + "\n"); + txt.append("EXPECTED FAILURES: " + mExpectedFailures.size() + "\n"); + txt.append("EXPECTED PASSES: " + mExpectedPasses.size() + "\n"); FsUtils.writeDataToStorage(new File(mResultsRootDirPath, TXT_SUMMARY_RELATIVE_PATH), txt.toString().getBytes(), false); } - private void createHtmlDetails() { + private void createHtmlDetails(String webKitRevision) { StringBuilder html = new StringBuilder(); html.append("<html><head>"); @@ -287,7 +283,7 @@ public class Summarizer { html.append(SCRIPT); html.append("</head><body>"); - createTopSummaryTable(html); + createTopSummaryTable(webKitRevision, html); createResultsListWithDiff(html, "Unexpected failures", mUnexpectedFailures); @@ -340,22 +336,22 @@ public class Summarizer { return "unknown"; } - private void createTopSummaryTable(StringBuilder html) { + private void createTopSummaryTable(String webKitRevision, StringBuilder html) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - html.append("<h1>" + mTestsRelativePath + "</h1>"); + html.append("<h1>" + "Layout tests' results for: " + + (mTestsRelativePath.equals("") ? "all tests" : mTestsRelativePath) + "</h1>"); html.append("<h3>" + "Date: " + dateFormat.format(new Date()) + "</h3>"); html.append("<h3>" + "Build fingerprint: " + Build.FINGERPRINT + "</h3>"); html.append("<h3>" + "WebKit version: " + getWebKitVersionFromUserAgentString() + "</h3>"); - String webkitRevision = getWebKitRevision(); html.append("<h3>" + "WebKit revision: "); - html.append("<a href=\"http://trac.webkit.org/browser/trunk?rev=" + webkitRevision + - "\" target=\"_blank\"><span class=\"path\">" + webkitRevision + "</span></a>"); + html.append("<a href=\"http://trac.webkit.org/browser/trunk?rev=" + webKitRevision + + "\" target=\"_blank\"><span class=\"path\">" + webKitRevision + "</span></a>"); html.append("</h3>"); html.append("<table class=\"summary\">"); createSummaryTableRow(html, "TOTAL", getTotalTestCount()); - createSummaryTableRow(html, "CRASHED", mCrashedTestsCount); + createSummaryTableRow(html, "CRASHED (among all tests)", mCrashedTestsCount); createSummaryTableRow(html, "UNEXPECTED FAILURES", mUnexpectedFailures.size()); createSummaryTableRow(html, "UNEXPECTED PASSES", mUnexpectedPasses.size()); createSummaryTableRow(html, "EXPECTED FAILURES", mExpectedFailures.size()); diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java index c714ec4..e0f1450 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java @@ -29,12 +29,6 @@ public class TestsListPreloaderThread extends Thread { private static final String LOG_TAG = "TestsListPreloaderThread"; - /** TODO: make it a setting */ - private static final String TESTS_ROOT_DIR_PATH = - Environment.getExternalStorageDirectory() + - File.separator + "android" + - File.separator + "LayoutTests"; - /** A list containing relative paths of tests to run */ private ArrayList<String> mTestsList = new ArrayList<String>(); @@ -55,7 +49,7 @@ public class TestsListPreloaderThread extends Thread { * @param doneMsg */ public TestsListPreloaderThread(String path, Message doneMsg) { - mFileFilter = new FileFilter(TESTS_ROOT_DIR_PATH); + mFileFilter = new FileFilter(); mRelativePath = path; mDoneMsg = doneMsg; } @@ -112,4 +106,4 @@ public class TestsListPreloaderThread extends Thread { } } } -}
\ No newline at end of file +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java index d165a1a..086ff59 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/AdbUtils.java @@ -36,52 +36,45 @@ public class AdbUtils { private static final int ADB_RESPONSE_SIZE = 4; /** - * Send an ADB command using existing socket connection + * Creates a new socket that can be configured to serve as a transparent proxy to a + * remote machine. This can be achieved by calling configureSocket() * - * The streams provided must be from a socket connected to adb already + * @return a socket that can be configured to link to remote machine + */ + public static Socket createSocket() { + Socket socket = null; + try { + socket = new Socket(ADB_HOST, ADB_PORT); + } catch (IOException e) { + Log.e(LOG_TAG, "Creation failed.", e); + } + return socket; + } + + /** + * Configures the connection to serve as a transparent proxy to a remote machine. + * The given streams must belong to a socket created by createSocket(). * - * @param is input stream of the socket connection - * @param os output stream of the socket - * @param cmd the adb command to send - * @return if adb gave a success response + * @param inputStream inputStream of the socket we want to configure + * @param outputStream outputStream of the socket we want to configure + * @param remoteAddress address of the remote machine (as you would type in a browser + * in a machine that the device is connected to via adb) + * @param remotePort port on which to connect + * @return if the configuration suceeded * @throws IOException */ - private static boolean sendAdbCmd(InputStream is, OutputStream os, String cmd) - throws IOException { - byte[] buf = new byte[ADB_RESPONSE_SIZE]; - + public static boolean configureConnection(InputStream inputStream, OutputStream outputStream, + String remoteAddress, int remotePort) throws IOException { + String cmd = "tcp:" + remotePort + ":" + remoteAddress; cmd = String.format("%04X", cmd.length()) + cmd; - os.write(cmd.getBytes()); - int read = is.read(buf); + + byte[] buf = new byte[ADB_RESPONSE_SIZE]; + outputStream.write(cmd.getBytes()); + int read = inputStream.read(buf); if (read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) { Log.w(LOG_TAG, "adb cmd faild."); return false; } return true; } - - /** - * Get a tcp socket connection to specified IP address and port proxied by adb - * - * The proxying is transparent, e.g. if a socket is returned, then it can be written to and - * read from as if it is directly connected to the target - * - * @param remoteAddress IP address of the host to connect to - * @param remotePort port of the host to connect to - * @return a valid Socket instance if successful, null otherwise - */ - public static Socket getSocketToRemoteMachine(String remoteAddress, int remotePort) { - try { - Socket socket = new Socket(ADB_HOST, ADB_PORT); - String cmd = "tcp:" + remotePort + ":" + remoteAddress; - if (!sendAdbCmd(socket.getInputStream(), socket.getOutputStream(), cmd)) { - socket.close(); - return null; - } - return socket; - } catch (IOException ioe) { - Log.w(LOG_TAG, "error creating adb socket", ioe); - return null; - } - } }
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java index 5e9f24e..4f01dae 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/ConnectionHandler.java @@ -18,6 +18,8 @@ package com.android.dumprendertree2.forwarder; import android.util.Log; +import com.android.dumprendertree2.FsUtils; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -38,36 +40,25 @@ public class ConnectionHandler { private class SocketPipeThread extends Thread { - private Socket mInSocket, mOutSocket; + private InputStream mInputStream; + private OutputStream mOutputStream; - public SocketPipeThread(Socket inSocket, Socket outSocket) { - mInSocket = inSocket; - mOutSocket = outSocket; + public SocketPipeThread(InputStream inputStream, OutputStream outputStream) { + mInputStream = inputStream; + mOutputStream = outputStream; + setName("SocketPipeThread: " + getName()); } @Override public void run() { - InputStream is; - OutputStream os; - try { - synchronized (this) { - is = mInSocket.getInputStream(); - os = mOutSocket.getOutputStream(); - } - } catch (IOException e) { - Log.w(LOG_TAG, this.toString(), e); - finish(); - return; - } - byte[] buffer = new byte[4096]; int length; while (true) { try { - if ((length = is.read(buffer)) <= 0) { + if ((length = mInputStream.read(buffer)) < 0) { break; } - os.write(buffer, 0, length); + mOutputStream.write(buffer, 0, length); } catch (IOException e) { /** This exception means one of the streams is closed */ Log.v(LOG_TAG, this.toString(), e); @@ -75,10 +66,6 @@ public class ConnectionHandler { } } - finish(); - } - - private void finish() { synchronized (mThreadsRunning) { mThreadsRunning--; if (mThreadsRunning == 0) { @@ -90,7 +77,7 @@ public class ConnectionHandler { @Override public String toString() { - return "SocketPipeThread:\n" + mInSocket + "\n=>\n" + mOutSocket; + return getName(); } } @@ -98,20 +85,51 @@ public class ConnectionHandler { private Socket mFromSocket, mToSocket; private SocketPipeThread mFromToPipe, mToFromPipe; + private InputStream mFromSocketInputStream, mToSocketInputStream; + private OutputStream mFromSocketOutputStream, mToSocketOutputStream; + + private int mPort; + private String mRemoteMachineIpAddress; private OnFinishedCallback mOnFinishedCallback; - public ConnectionHandler(Socket fromSocket, Socket toSocket) { + public ConnectionHandler(String remoteMachineIp, int port, Socket fromSocket, Socket toSocket) { + mRemoteMachineIpAddress = remoteMachineIp; + mPort = port; + mFromSocket = fromSocket; mToSocket = toSocket; - mFromToPipe = new SocketPipeThread(mFromSocket, mToSocket); - mToFromPipe = new SocketPipeThread(mToSocket, mFromSocket); + + try { + mFromSocketInputStream = mFromSocket.getInputStream(); + mToSocketInputStream = mToSocket.getInputStream(); + mFromSocketOutputStream = mFromSocket.getOutputStream(); + mToSocketOutputStream = mToSocket.getOutputStream(); + if (!AdbUtils.configureConnection(mToSocketInputStream, mToSocketOutputStream, + mRemoteMachineIpAddress, mPort)) { + throw new IOException("Configuring socket failed!"); + } + } catch (IOException e) { + Log.e(LOG_TAG, "Unable to start ConnectionHandler", e); + closeStreams(); + return; + } + + mFromToPipe = new SocketPipeThread(mFromSocketInputStream, mToSocketOutputStream); + mToFromPipe = new SocketPipeThread(mToSocketInputStream, mFromSocketOutputStream); } public void registerOnConnectionHandlerFinishedCallback(OnFinishedCallback callback) { mOnFinishedCallback = callback; } + private void closeStreams() { + FsUtils.closeInputStream(mFromSocketInputStream); + FsUtils.closeInputStream(mToSocketInputStream); + FsUtils.closeOutputStream(mFromSocketOutputStream); + FsUtils.closeOutputStream(mToSocketOutputStream); + } + public void start() { /** We have 2 threads running, one for each pipe, that we start here. */ mThreadsRunning = 2; diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java index 31cd8ea..b361a89 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/forwarder/Forwarder.java @@ -60,19 +60,18 @@ public class Forwarder extends Thread { @Override public void run() { while (true) { - /** These sockets will be closed when Forwarder.stop() is called */ Socket localSocket; Socket remoteSocket; try { localSocket = mServerSocket.accept(); - remoteSocket = AdbUtils.getSocketToRemoteMachine(mRemoteMachineIpAddress, - mPort); } catch (IOException e) { /** This most likely means that mServerSocket is already closed */ Log.w(LOG_TAG, "mPort=" + mPort, e); break; } + remoteSocket = AdbUtils.createSocket(); + if (remoteSocket == null) { try { localSocket.close(); @@ -86,7 +85,8 @@ public class Forwarder extends Thread { } final ConnectionHandler connectionHandler = - new ConnectionHandler(localSocket, remoteSocket); + new ConnectionHandler(mRemoteMachineIpAddress, mPort, localSocket, + remoteSocket); /** * We have to close the sockets after the ConnectionHandler finishes, so we @@ -98,9 +98,7 @@ public class Forwarder extends Thread { @Override public void onFinished() { synchronized (this) { - if (mConnectionHandlers.remove(connectionHandler)) { - Log.d(LOG_TAG, "removeConnectionHandler(): removed"); - } else { + if (!mConnectionHandlers.remove(connectionHandler)) { assert false : "removeConnectionHandler(): not in the collection"; } } diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java index b1862ef..35de88a 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java @@ -56,10 +56,6 @@ import java.util.List; public class DirListActivity extends ListActivity { private static final String LOG_TAG = "DirListActivity"; - private static final String ROOT_DIR_PATH = - Environment.getExternalStorageDirectory() + - File.separator + "android" + - File.separator + "LayoutTests"; /** TODO: This is just a guess - think of a better way to achieve it */ private static final int MEAN_TITLE_CHAR_SIZE = 13; @@ -82,13 +78,6 @@ public class DirListActivity extends ListActivity { private String mCurrentDirPath; /** - * TODO: This should not be a constant, but rather be configurable from somewhere. - */ - private String mRootDirPath = ROOT_DIR_PATH; - - private FileFilter mFileFilter; - - /** * A thread responsible for loading the contents of the directory from sd card * and sending them via Message to main thread that then loads them into * ListView @@ -196,7 +185,6 @@ public class DirListActivity extends ListActivity { ForwarderManager.getForwarderManager().start(); - mFileFilter = new FileFilter(ROOT_DIR_PATH); mListView = getListView(); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @@ -420,4 +408,4 @@ public class DirListActivity extends ListActivity { return subDirs.toArray(new ListItem[subDirs.size()]); } -}
\ No newline at end of file +} diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index ec1cbf1..5597415 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -15,7 +15,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.test.hwui"> + package="com.android.test.hwui"> <application android:label="HwUi" @@ -71,6 +71,15 @@ </activity> <activity + android:name="NewLayersActivity" + android:label="_NewLayers"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity android:name="XfermodeActivity" android:label="_Xfermodes" android:theme="@android:style/Theme.Translucent.NoTitleBar"> @@ -109,6 +118,16 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + + <activity + android:name="ThinPatchesActivity" + android:label="_9patchThin" + android:theme="@android:style/Theme.Translucent.NoTitleBar"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> <activity android:name="NinePatchesActivity" @@ -164,6 +183,15 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + + <activity + android:name="TransparentListActivity" + android:label="_TransparentList"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> <activity android:name="MoreShadersActivity" diff --git a/tests/HwAccelerationTest/res/drawable/btn_toggle_off.9.png b/tests/HwAccelerationTest/res/drawable/btn_toggle_off.9.png Binary files differnew file mode 100644 index 0000000..26ee1c2 --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable/btn_toggle_off.9.png diff --git a/tests/HwAccelerationTest/res/drawable/btn_toggle_on.9.png b/tests/HwAccelerationTest/res/drawable/btn_toggle_on.9.png Binary files differnew file mode 100644 index 0000000..53e95af --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable/btn_toggle_on.9.png diff --git a/tests/HwAccelerationTest/res/drawable/default_wallpaper.jpg b/tests/HwAccelerationTest/res/drawable/default_wallpaper.jpg Binary files differnew file mode 100644 index 0000000..5acad94 --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable/default_wallpaper.jpg diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedBlendActivity.java index 6c80a6d..5baa20c 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedBlendActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/AlphaLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java index 0217a05..1a68a93 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/AlphaLayersActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsActivity.java index cfa8d3c..4054353 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java index f6fd8fe..ef49c7f 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsAlphaActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsRectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsRectActivity.java index f8726c2..b192209 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsRectActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsRectActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersActivity.java index 49e1eaa..09d63d6 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/FramebufferBlendActivity.java index ef84b67..1556bae 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/FramebufferBlendActivity.java @@ -15,7 +15,7 @@ */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/LayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java index 437cd1c..b705117 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/LayersActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LayersActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java index c800d42..208dd88 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/LinesActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java index 94b936b..8fd4f6b 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ListActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MoreShadersActivity.java index f43eeba..182a474 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MoreShadersActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java new file mode 100644 index 0000000..d9a2893 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/NewLayersActivity.java @@ -0,0 +1,77 @@ +/* + * 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.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Bundle; +import android.view.View; + +@SuppressWarnings({"UnusedDeclaration"}) +public class NewLayersActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(new LayersView(this)); + } + + static class LayersView extends View { + private Paint mLayerPaint; + private final Paint mRectPaint; + + LayersView(Context c) { + super(c); + + mLayerPaint = new Paint(); + mLayerPaint.setAlpha(127); + mRectPaint = new Paint(); + mRectPaint.setAntiAlias(true); + mRectPaint.setTextSize(24.0f); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawRGB(128, 255, 128); + + canvas.save(); + + canvas.translate(140.0f, 100.0f); + drawStuff(canvas, Canvas.ALL_SAVE_FLAG); + + canvas.translate(0.0f, 200.0f); + drawStuff(canvas, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); + + canvas.restore(); + } + + private void drawStuff(Canvas canvas, int saveFlags) { + int count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint, saveFlags); + + mRectPaint.setColor(0x7fff0000); + canvas.drawRect(-20.0f, -20.0f, 220.0f, 120.0f, mRectPaint); + + mRectPaint.setColor(0xff000000); + canvas.drawText("This is a very long string to overlap between layers and framebuffer", + -100.0f, 50.0f, mRectPaint); + + canvas.restoreToCount(count); + } + } +} diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/NinePatchesActivity.java index 3268fbf..7410f79 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/NinePatchesActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.os.Bundle; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java index 39d9942..8d9bf37 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java index 2ba249a..5192bfe 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ResizeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ResizeActivity.java index e5771b8..04f9de1 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ResizeActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ResizeActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.os.Bundle; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/RotationActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RotationActivity.java index e629cb8..5c309b4 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/RotationActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RotationActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ShadersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java index 0cd1426..9c8e7ec 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ShadersActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/SimplePathsActivity.java index 071a118..c3e18a3 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/SimplePathsActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.os.Bundle; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/StackActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StackActivity.java index 5c8db6e..5655adf 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/StackActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StackActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.graphics.drawable.Drawable; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextActivity.java index abe9d5e..eb0df51 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextGammaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextGammaActivity.java index 185cfa4..773d390 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextGammaActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextGammaActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java new file mode 100644 index 0000000..d374c32 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ThinPatchesActivity.java @@ -0,0 +1,65 @@ +/* + * 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.test.hwui; + +import android.app.Activity; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; +import android.widget.FrameLayout; + +@SuppressWarnings({"UnusedDeclaration"}) +public class ThinPatchesActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + FrameLayout layout = new FrameLayout(this); + PatchView b = new PatchView(this); + b.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + layout.addView(b); + layout.setBackgroundColor(0xffffffff); + + setContentView(layout); + } + + private class PatchView extends View { + private Drawable mPatch; + + public PatchView(Activity activity) { + super(activity); + + final Resources resources = activity.getResources(); + mPatch = resources.getDrawable(R.drawable.btn_toggle_on); + } + + @Override + protected void onDraw(Canvas canvas) { + final int width = 100; + final int height = 60; + + final int left = (getWidth() - width) / 2; + final int top = (getHeight() - height) / 2; + + mPatch.setBounds(left, top, left + width, top + height); + mPatch.draw(canvas); + } + } +} diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/Transform3dActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Transform3dActivity.java index 6134cde..6df66e6 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/Transform3dActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Transform3dActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java new file mode 100644 index 0000000..f47b00f --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java @@ -0,0 +1,119 @@ +/* + * 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.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.ContextMenu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +@SuppressWarnings({"UnusedDeclaration"}) +public class TransparentListActivity extends Activity { + private static final String[] DATA_LIST = { + "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", + "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", + "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", + "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", + "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", + "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", + "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria", + "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde", + "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", + "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", + "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic", + "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic", + "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", + "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland", + "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia", + "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar", + "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau", + "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary", + "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica", + "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos", + "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", + "Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", + "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova", + "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", + "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", + "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas", + "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru", + "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", + "Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena", + "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon", + "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal", + "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", + "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea", + "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden", + "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas", + "The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", + "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda", + "Ukraine", "United Arab Emirates", "United Kingdom", + "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", + "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara", + "Yemen", "Yugoslavia", "Zambia", "Zimbabwe" + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().setBackgroundDrawable(getResources().getDrawable(R.drawable.default_wallpaper)); + setContentView(R.layout.list_activity); + + ListAdapter adapter = new SimpleListAdapter(this); + + ListView list = (ListView) findViewById(R.id.list); + list.setAdapter(adapter); + list.setCacheColorHint(0); + + registerForContextMenu(list); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + menu.setHeaderTitle("Context menu"); + menu.add("List item 1"); + menu.add("List item 2"); + menu.add("List item 3"); + } + + private static class SimpleListAdapter extends ArrayAdapter<String> { + public SimpleListAdapter(Context context) { + super(context, android.R.layout.simple_list_item_1, DATA_LIST); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView v = (TextView) super.getView(position, convertView, parent); + final Resources r = getContext().getResources(); + final DisplayMetrics metrics = r.getDisplayMetrics(); + v.setCompoundDrawablePadding((int) (6 * metrics.density + 0.5f)); + v.setCompoundDrawablesWithIntrinsicBounds(r.getDrawable(R.drawable.icon), + null, null, null); + return v; + } + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/XfermodeActivity.java index 8c81f02..411077f 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/XfermodeActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.test.hwui; +package com.android.test.hwui; import android.app.Activity; import android.content.Context; diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java index b665d2f..e31711e 100644 --- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java @@ -60,20 +60,19 @@ public class StatusBarTest extends TestActivity } private Test[] mTests = new Test[] { - new Test("Hide") { + new Test("Hide (FLAG_FULLSCREEN)") { public void run() { Window win = getWindow(); - WindowManager.LayoutParams winParams = win.getAttributes(); - winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; - win.setAttributes(winParams); + win.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + Log.d(TAG, "flags=" + Integer.toHexString(win.getAttributes().flags)); } }, - new Test("Show") { + new Test("Show (~FLAG_FULLSCREEN)") { public void run() { Window win = getWindow(); - WindowManager.LayoutParams winParams = win.getAttributes(); - winParams.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; - win.setAttributes(winParams); + win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN); + Log.d(TAG, "flags=" + Integer.toHexString(win.getAttributes().flags)); } }, new Test("Immersive: Enter") { diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 01728a1..822262e 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -835,7 +835,9 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) bool hasErrors = false; if (drawables != NULL) { - err = preProcessImages(bundle, assets, drawables); + if (bundle->getOutputAPKFile() != NULL) { + err = preProcessImages(bundle, assets, drawables); + } if (err == NO_ERROR) { err = makeFileResources(bundle, assets, &table, drawables, "drawable"); if (err != NO_ERROR) { diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java index 02f82b3..39083a5 100644 --- a/voip/java/android/net/sip/SipAudioCall.java +++ b/voip/java/android/net/sip/SipAudioCall.java @@ -20,8 +20,6 @@ import android.net.rtp.AudioGroup; import android.net.rtp.AudioStream; import android.os.Message; -import javax.sip.SipException; - /** * Interface for making audio calls over SIP. * @hide diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java index a4fa053..1ae548f 100644 --- a/voip/java/android/net/sip/SipAudioCallImpl.java +++ b/voip/java/android/net/sip/SipAudioCallImpl.java @@ -42,7 +42,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sdp.SdpException; -import javax.sip.SipException; /** * Class that handles an audio call over SIP. diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java index 2eb67e8..8624811 100644 --- a/voip/java/android/net/sip/SipErrorCode.java +++ b/voip/java/android/net/sip/SipErrorCode.java @@ -31,6 +31,9 @@ public enum SipErrorCode { /** When server responds with an error. */ SERVER_ERROR, + /** When transaction is terminated unexpectedly. */ + TRANSACTION_TERMINTED, + /** When some error occurs on the device, possibly due to a bug. */ CLIENT_ERROR, @@ -41,5 +44,8 @@ public enum SipErrorCode { INVALID_REMOTE_URI, /** When invalid credentials are provided. */ - INVALID_CREDENTIALS; + INVALID_CREDENTIALS, + + /** The client is in a transaction and cannot initiate a new one. */ + IN_PROGRESS; } diff --git a/voip/java/android/net/sip/SipException.java b/voip/java/android/net/sip/SipException.java new file mode 100644 index 0000000..d615342 --- /dev/null +++ b/voip/java/android/net/sip/SipException.java @@ -0,0 +1,37 @@ +/* + * 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 android.net.sip; + +/** + * @hide + */ +public class SipException extends Exception { + public SipException() { + } + + public SipException(String message) { + super(message); + } + + public SipException(String message, Throwable cause) { + // we want to eliminate the dependency on javax.sip.SipException + super(message, ((cause instanceof javax.sip.SipException) + && (cause.getCause() != null)) + ? cause.getCause() + : cause); + } +} diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java index beec8fe..ccae7f9 100644 --- a/voip/java/android/net/sip/SipManager.java +++ b/voip/java/android/net/sip/SipManager.java @@ -25,7 +25,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import java.text.ParseException; -import javax.sip.SipException; /** * The class provides API for various SIP related tasks. Specifically, the API @@ -501,15 +500,15 @@ public class SipManager { } @Override - public void onRegistrationFailed(ISipSession session, String className, + public void onRegistrationFailed(ISipSession session, String errorCode, String message) { - mListener.onRegistrationFailed(getUri(session), className, message); + mListener.onRegistrationFailed(getUri(session), errorCode, message); } @Override public void onRegistrationTimeout(ISipSession session) { mListener.onRegistrationFailed(getUri(session), - SipException.class.getName(), "registration timed out"); + SipErrorCode.TIME_OUT.toString(), "registration timed out"); } } } diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 1913fa0..e73bca0 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -21,6 +21,8 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.ScanResult; import android.net.DhcpInfo; +import android.os.WorkSource; + /** * Interface that allows controlling and querying Wi-Fi connectivity. * @@ -66,7 +68,9 @@ interface IWifiManager DhcpInfo getDhcpInfo(); - boolean acquireWifiLock(IBinder lock, int lockType, String tag); + boolean acquireWifiLock(IBinder lock, int lockType, String tag, in WorkSource ws); + + void updateWifiLockWorkSource(IBinder lock, in WorkSource ws); boolean releaseWifiLock(IBinder lock); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8c3ec5f..26ed878 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -23,6 +23,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.Handler; import android.os.RemoteException; +import android.os.WorkSource; import java.util.List; @@ -1050,6 +1051,7 @@ public class WifiManager { int mLockType; private boolean mRefCounted; private boolean mHeld; + private WorkSource mWorkSource; private WifiLock(int lockType, String tag) { mTag = tag; @@ -1075,7 +1077,7 @@ public class WifiManager { synchronized (mBinder) { if (mRefCounted ? (++mRefCount > 0) : (!mHeld)) { try { - mService.acquireWifiLock(mBinder, mLockType, mTag); + mService.acquireWifiLock(mBinder, mLockType, mTag, mWorkSource); synchronized (WifiManager.this) { if (mActiveLockCount >= MAX_ACTIVE_LOCKS) { mService.releaseWifiLock(mBinder); @@ -1147,6 +1149,32 @@ public class WifiManager { } } + public void setWorkSource(WorkSource ws) { + synchronized (mBinder) { + if (ws != null && ws.size() == 0) { + ws = null; + } + boolean changed = true; + if (ws == null) { + mWorkSource = null; + } else if (mWorkSource == null) { + changed = mWorkSource != null; + mWorkSource = new WorkSource(ws); + } else { + changed = mWorkSource.diff(ws); + if (changed) { + mWorkSource.set(ws); + } + } + if (changed && mHeld) { + try { + mService.updateWifiLockWorkSource(mBinder, mWorkSource); + } catch (RemoteException e) { + } + } + } + } + public String toString() { String s1, s2, s3; synchronized (mBinder) { |
