From da996f390e17e16f2dfa60e972e7ebc4f868f37e Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Fri, 13 Feb 2009 12:57:50 -0800 Subject: [PATCH] auto import from //branches/cupcake/...@131421 --- Android.mk | 2 +- api/current.xml | 1564 +++++++++++++-- core/java/android/app/LauncherActivity.java | 17 - core/java/android/app/SearchDialog.java | 8 +- core/java/android/content/Intent.java | 2 + core/java/android/gadget/GadgetHost.java | 24 +- core/java/android/gadget/GadgetHostView.java | 206 +- core/java/android/gadget/GadgetManager.java | 170 +- core/java/android/gadget/GadgetProvider.java | 53 +- .../{GadgetInfo.aidl => GadgetProviderInfo.aidl} | 2 +- .../{GadgetInfo.java => GadgetProviderInfo.java} | 58 +- core/java/android/gadget/package.html | 123 +- core/java/android/hardware/Camera.java | 17 +- .../inputmethodservice/InputMethodService.java | 85 +- .../android/inputmethodservice/KeyboardView.java | 4 + core/java/android/net/UrlQuerySanitizer.java | 126 +- core/java/android/pim/RecurrenceSet.java | 1 - .../android/preference/PreferenceGroupAdapter.java | 3 + core/java/android/provider/Settings.java | 22 +- .../android/server/BluetoothDeviceService.java | 61 +- core/java/android/server/BluetoothEventLoop.java | 83 +- .../text/method/ArrowKeyMovementMethod.java | 9 +- .../android/text/method/MetaKeyKeyListener.java | 8 +- .../text/method/PasswordTransformationMethod.java | 6 +- .../java/android/view/HapticFeedbackConstants.java | 45 + core/java/android/view/IWindowSession.aidl | 3 +- core/java/android/view/View.java | 105 +- core/java/android/view/ViewRoot.java | 15 +- core/java/android/view/WindowManager.java | 2 +- core/java/android/view/WindowManagerPolicy.java | 11 + .../view/inputmethod/BaseInputConnection.java | 30 +- .../android/view/inputmethod/InputConnection.java | 26 +- .../view/inputmethod/InputMethodManager.java | 33 +- core/java/android/webkit/CookieManager.java | 9 +- core/java/android/webkit/WebView.java | 126 +- core/java/android/webkit/WebViewCore.java | 6 +- core/java/android/webkit/WebViewDatabase.java | 47 +- core/java/android/widget/AbsListView.java | 4 + core/java/android/widget/CursorFilter.java | 5 +- core/java/android/widget/DatePicker.java | 27 +- core/java/android/widget/Filter.java | 6 + core/java/android/widget/Gallery.java | 6 + core/java/android/widget/ImageView.java | 1 + core/java/android/widget/RemoteViews.java | 69 + core/java/android/widget/TextView.java | 2 +- core/java/android/widget/ViewAnimator.java | 3 + core/java/android/widget/ViewFlipper.java | 4 + core/java/android/widget/ZoomButton.java | 2 + core/java/android/widget/ZoomRing.java | 271 ++- core/java/android/widget/ZoomRingController.java | 64 +- .../com/android/internal/gadget/IGadgetHost.aidl | 3 +- .../android/internal/gadget/IGadgetService.aidl | 6 +- .../internal/view/IInputConnectionWrapper.java | 24 +- .../com/android/internal/view/IInputContext.aidl | 6 +- .../android/internal/view/IInputMethodManager.aidl | 2 +- .../internal/view/InputConnectionWrapper.java | 13 +- .../com/android/internal/widget/NumberPicker.java | 62 +- core/jni/android_media_AudioRecord.cpp | 37 +- core/jni/android_media_AudioSystem.cpp | 16 +- core/jni/android_media_AudioTrack.cpp | 33 +- core/jni/android_server_BluetoothEventLoop.cpp | 4 + core/jni/android_util_EventLog.cpp | 2 +- core/res/res/drawable/presence_away.png | Bin 3500 -> 810 bytes core/res/res/drawable/presence_busy.png | Bin 3529 -> 811 bytes core/res/res/drawable/presence_invisible.png | Bin 1084 -> 693 bytes core/res/res/drawable/presence_offline.png | Bin 3577 -> 767 bytes core/res/res/drawable/presence_online.png | Bin 3528 -> 812 bytes core/res/res/drawable/zoom_ring_trail.xml | 37 + core/res/res/layout/date_picker.xml | 1 + core/res/res/layout/date_picker_dialog.xml | 14 +- core/res/res/layout/number_picker.xml | 30 +- core/res/res/layout/number_picker_edit.xml | 1 + core/res/res/layout/time_picker.xml | 2 + core/res/res/layout/time_picker_dialog.xml | 14 +- core/res/res/layout/time_picker_text.xml | 27 - core/res/res/values-cs-rCZ/strings.xml | 1112 ----------- core/res/res/values-cs/strings.xml | 1 - core/res/res/values-de/strings.xml | 1 - core/res/res/values-en-rGB/strings.xml | 762 +------ core/res/res/values-es/strings.xml | 1 - core/res/res/values-fr/strings.xml | 1 - core/res/res/values-it/strings.xml | 1 - core/res/res/values-ja/strings.xml | 16 +- core/res/res/values-ko/strings.xml | 1 - core/res/res/values-nb/strings.xml | 162 +- core/res/res/values-nl/strings.xml | 1 - core/res/res/values-pl/strings.xml | 1 - core/res/res/values-ru/strings.xml | 1 - core/res/res/values-zh-rCN/strings.xml | 1 - core/res/res/values-zh-rTW/strings.xml | 1 - core/res/res/values/attrs.xml | 14 + core/res/res/values/public.xml | 3 + core/res/res/values/strings.xml | 3 +- .../graphics/drawable/GradientDrawable.java | 133 +- .../android/graphics/drawable/RotateDrawable.java | 7 + .../android/graphics/drawable/ScaleDrawable.java | 7 + include/media/AudioSystem.h | 39 +- include/media/AudioTrack.h | 15 +- include/media/IAudioFlinger.h | 13 +- include/media/IAudioFlingerClient.h | 2 +- include/media/IMediaRecorder.h | 1 + include/media/PVMediaRecorder.h | 1 + include/media/mediarecorder.h | 1 + include/ui/ISurface.h | 12 +- include/utils/logger.h | 46 - libs/audioflinger/AudioFlinger.cpp | 2069 +++++++++++++------- libs/audioflinger/AudioFlinger.h | 612 +++--- libs/surfaceflinger/Android.mk | 2 - libs/surfaceflinger/Layer.cpp | 4 +- libs/surfaceflinger/LayerBase.cpp | 17 +- libs/surfaceflinger/LayerBase.h | 23 +- libs/surfaceflinger/LayerBuffer.cpp | 53 +- libs/surfaceflinger/LayerBuffer.h | 6 +- libs/surfaceflinger/LayerOrientationAnim.cpp | 2 +- libs/surfaceflinger/LayerScreenshot.cpp | 110 -- libs/surfaceflinger/LayerScreenshot.h | 57 - libs/surfaceflinger/RFBServer.cpp | 722 ------- libs/surfaceflinger/RFBServer.h | 316 --- libs/surfaceflinger/SurfaceFlinger.cpp | 141 +- libs/surfaceflinger/SurfaceFlinger.h | 4 +- libs/ui/ISurface.cpp | 2 +- .../internal/location/INetworkLocationManager.java | 9 + media/java/android/media/AudioManager.java | 15 +- media/java/android/media/AudioRecord.java | 59 +- media/java/android/media/AudioService.java | 4 +- media/java/android/media/AudioTrack.java | 8 +- media/java/android/media/MediaRecorder.java | 62 +- media/jni/android_media_MediaRecorder.cpp | 56 +- media/jni/soundpool/SoundPool.cpp | 9 +- media/libmedia/AudioRecord.cpp | 18 +- media/libmedia/AudioSystem.cpp | 103 +- media/libmedia/AudioTrack.cpp | 15 +- media/libmedia/IAudioFlinger.cpp | 47 +- media/libmedia/IAudioFlingerClient.cpp | 12 +- media/libmedia/IMediaRecorder.cpp | 29 +- media/libmedia/JetPlayer.cpp | 2 +- media/libmedia/ToneGenerator.cpp | 8 +- media/libmedia/mediaplayer.cpp | 2 +- media/libmedia/mediarecorder.cpp | 32 +- media/libmediaplayerservice/MediaPlayerService.cpp | 10 +- .../libmediaplayerservice/MediaRecorderClient.cpp | 11 + media/libmediaplayerservice/MediaRecorderClient.h | 1 + media/libmediaplayerservice/MidiFile.cpp | 2 +- media/libmediaplayerservice/VorbisPlayer.cpp | 2 +- preloaded-classes | 2 +- .../java/com/android/server/GadgetService.java | 137 +- .../android/server/InputMethodManagerService.java | 479 +++-- .../com/android/server/LocationManagerService.java | 30 +- .../com/android/server/PackageManagerService.java | 7 +- .../com/android/server/PowerManagerService.java | 8 +- services/java/com/android/server/WifiService.java | 17 +- .../com/android/server/WindowManagerService.java | 232 ++- .../android/server/am/ActivityManagerService.java | 76 +- .../com/android/server/am/UsageStatsService.java | 410 +++- tests/AndroidTests/AndroidManifest.xml | 1 + .../src/com/android/unit_tests/MenuTest.java | 15 +- .../src/com/android/unit_tests/TextUtilsTest.java | 2 +- tests/DumpRenderTree/compare_layout_results.py | 33 +- tests/DumpRenderTree/run_layout_tests.py | 108 +- .../android/dumprendertree/HTMLHostActivity.java | 19 +- tests/ImfTest/AndroidManifest.xml | 18 +- tests/ImfTest/res/values/config.xml | 21 + ...pAdjustmentBigEditTextNonScrollablePanScan.java | 35 - ...ppAdjustmentBigEditTextNonScrollableResize.java | 35 - .../BigEditTextActivityNonScrollablePanScan.java | 49 + .../BigEditTextActivityNonScrollableResize.java | 49 + ...a => BigEditTextActivityScrollablePanScan.java} | 31 +- ...va => BigEditTextActivityScrollableResize.java} | 31 +- .../samples/BottomEditTextActivityPanScan.java | 25 +- .../samples/BottomEditTextActivityResize.java | 25 +- .../android/imftest/samples/ButtonActivity.java | 12 +- ...TextDialog.java => EditTextActivityDialog.java} | 10 +- .../samples/EditTextActivityNoScrollPanScan.java | 38 - .../ManyEditTextActivityNoScrollPanScan.java | 43 +- .../samples/ManyEditTextActivityScrollPanScan.java | 51 +- .../samples/ManyEditTextActivityScrollResize.java | 24 +- .../samples/OneEditTextActivityNotSelected.java | 48 +- .../samples/OneEditTextActivitySelected.java | 40 +- tests/ImfTest/tests/Android.mk | 16 + tests/ImfTest/tests/AndroidManifest.xml | 34 + ...gEditTextActivityNonScrollablePanScanTests.java | 49 + ...igEditTextActivityNonScrollableResizeTests.java | 48 + .../BigEditTextActivityScrollablePanScanTests.java | 48 + .../BigEditTextActivityScrollableResizeTests.java | 49 + .../BottomEditTextActivityPanScanTests.java | 49 + .../samples/BottomEditTextActivityResizeTests.java | 49 + .../imftest/samples/ButtonActivityTest.java | 58 + .../android/imftest/samples/ImfBaseTestCase.java | 135 ++ .../samples/ManyEditTextActivityBaseTestCase.java | 40 + .../ManyEditTextActivityNoScrollPanScanTests.java | 36 + .../ManyEditTextActivityScrollPanScanTests.java | 37 + .../ManyEditTextActivityScrollResizeTests.java | 37 + .../OneEditTextActivityNotSelectedTests.java | 50 + .../samples/OneEditTextActivitySelectedTests.java | 53 + tests/gadgets/GadgetHostTest/AndroidManifest.xml | 14 +- .../gadgets/GadgetHostTest/res/xml/gadget_info.xml | 4 +- .../tests/gadgethost/GadgetHostActivity.java | 22 +- .../tests/gadgethost/TestGadgetProvider.java | 8 +- .../GadgetProviderTest/res/xml/gadget_info.xml | 2 +- .../tests/gadgetprovider/TestGadgetProvider.java | 6 +- .../src/com/android/layoutlib/bridge/Bridge.java | 6 + wifi/java/android/net/wifi/WifiStateTracker.java | 1 + 202 files changed, 7660 insertions(+), 6278 deletions(-) rename core/java/android/gadget/{GadgetInfo.aidl => GadgetProviderInfo.aidl} (95%) rename core/java/android/gadget/{GadgetInfo.java => GadgetProviderInfo.java} (56%) create mode 100644 core/java/android/view/HapticFeedbackConstants.java create mode 100644 core/res/res/drawable/zoom_ring_trail.xml delete mode 100644 core/res/res/layout/time_picker_text.xml delete mode 100644 core/res/res/values-cs-rCZ/strings.xml delete mode 100644 include/utils/logger.h delete mode 100644 libs/surfaceflinger/LayerScreenshot.cpp delete mode 100644 libs/surfaceflinger/LayerScreenshot.h delete mode 100644 libs/surfaceflinger/RFBServer.cpp delete mode 100644 libs/surfaceflinger/RFBServer.h create mode 100644 tests/ImfTest/res/values/config.xml delete mode 100644 tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollablePanScan.java delete mode 100644 tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollableResize.java create mode 100644 tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScan.java create mode 100644 tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResize.java rename tests/ImfTest/src/com/android/imftest/samples/{AppAdjustmentBigEditTextScrollablePanScan.java => BigEditTextActivityScrollablePanScan.java} (54%) rename tests/ImfTest/src/com/android/imftest/samples/{AppAdjustmentBigEditTextScrollableResize.java => BigEditTextActivityScrollableResize.java} (54%) rename tests/ImfTest/src/com/android/imftest/samples/{AppAdjustmentEditTextDialog.java => EditTextActivityDialog.java} (88%) delete mode 100644 tests/ImfTest/src/com/android/imftest/samples/EditTextActivityNoScrollPanScan.java create mode 100755 tests/ImfTest/tests/Android.mk create mode 100755 tests/ImfTest/tests/AndroidManifest.xml create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScanTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResizeTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollablePanScanTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollableResizeTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityPanScanTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityResizeTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/ButtonActivityTest.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityBaseTestCase.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScanTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollPanScanTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollResizeTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivityNotSelectedTests.java create mode 100755 tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivitySelectedTests.java diff --git a/Android.mk b/Android.mk index 764e88b39450..45f0f18cfe89 100644 --- a/Android.mk +++ b/Android.mk @@ -167,7 +167,7 @@ aidl_files := \ frameworks/base/core/java/android/content/Intent.aidl \ frameworks/base/core/java/android/content/SyncStats.aidl \ frameworks/base/core/java/android/content/res/Configuration.aidl \ - frameworks/base/core/java/android/gadget/GadgetInfo.aidl \ + frameworks/base/core/java/android/gadget/GadgetProviderInfo.aidl \ frameworks/base/core/java/android/net/Uri.aidl \ frameworks/base/core/java/android/os/Bundle.aidl \ frameworks/base/core/java/android/os/ParcelFileDescriptor.aidl \ diff --git a/api/current.xml b/api/current.xml index 55994fb6f1d0..2e8db511df36 100644 --- a/api/current.xml +++ b/api/current.xml @@ -3562,6 +3562,17 @@ visibility="public" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - + + + + - + + + + + + + + + + @@ -65005,6 +65892,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -124581,6 +125706,19 @@ + + + + + + - - + + + + - - - - - - - + + - - + + + - - - - + - - - - - - + + + + + + + + + + + + + + - - - - - - + + + + + + + + values = mOriginalValues; @@ -243,8 +228,6 @@ public abstract class LauncherActivity extends ListActivity { private int mIconWidth = -1; private int mIconHeight = -1; - private final Paint mPaint = new Paint(); - private final Rect mBounds = new Rect(); private final Rect mOldBounds = new Rect(); private Canvas mCanvas = new Canvas(); diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 64f1ba221474..5744ddca792b 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -835,9 +835,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // also guard against possible race conditions (late arrival after dismiss) if (mSearchable != null) { handled = doSuggestionsKey(v, keyCode, event); - if (!handled) { - handled = refocusingKeyListener(v, keyCode, event); - } } return handled; } @@ -1024,6 +1021,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @param jamQuery True means to set the query, false means to reset it to the user's choice */ private void jamSuggestionQuery(boolean jamQuery, AdapterView parent, int position) { + // quick check against race conditions + if (mSearchable == null) { + return; + } + mSuggestionsAdapter.setNonUserQuery(true); // disables any suggestions processing if (jamQuery) { CursorAdapter ca = getSuggestionsAdapter(parent); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 52aae0d3b437..c4d3f9d2ecee 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1112,6 +1112,8 @@ public class Intent implements Parcelable { *

My include the following extras: *

    *
  • {@link #EXTRA_UID} containing the integer uid assigned to the new package. + *
  • {@link #EXTRA_REPLACING} is set to true if this is following + * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package. *
*/ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) diff --git a/core/java/android/gadget/GadgetHost.java b/core/java/android/gadget/GadgetHost.java index 9176d1829853..31aed32a1e2b 100644 --- a/core/java/android/gadget/GadgetHost.java +++ b/core/java/android/gadget/GadgetHost.java @@ -38,6 +38,7 @@ import com.android.internal.gadget.IGadgetService; public class GadgetHost { static final int HANDLE_UPDATE = 1; + static final int HANDLE_PROVIDER_CHANGED = 2; static Object sServiceLock = new Object(); static IGadgetService sService; @@ -52,6 +53,13 @@ public class GadgetHost { msg.obj = views; msg.sendToTarget(); } + + public void providerChanged(int gadgetId, GadgetProviderInfo info) { + Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED); + msg.arg1 = gadgetId; + msg.obj = info; + msg.sendToTarget(); + } } Handler mHandler = new Handler() { @@ -61,6 +69,10 @@ public class GadgetHost { updateGadgetView(msg.arg1, (RemoteViews)msg.obj); break; } + case HANDLE_PROVIDER_CHANGED: { + onProviderChanged(msg.arg1, (GadgetProviderInfo)msg.obj); + break; + } } } }; @@ -183,7 +195,8 @@ public class GadgetHost { } } - public final GadgetHostView createView(Context context, int gadgetId, GadgetInfo gadget) { + public final GadgetHostView createView(Context context, int gadgetId, + GadgetProviderInfo gadget) { GadgetHostView view = onCreateView(context, gadgetId, gadget); view.setGadget(gadgetId, gadget); synchronized (mViews) { @@ -203,9 +216,16 @@ public class GadgetHost { * Called to create the GadgetHostView. Override to return a custom subclass if you * need it. {@more} */ - protected GadgetHostView onCreateView(Context context, int gadgetId, GadgetInfo gadget) { + protected GadgetHostView onCreateView(Context context, int gadgetId, + GadgetProviderInfo gadget) { return new GadgetHostView(context); } + + /** + * Called when the gadget provider for a gadget has been upgraded to a new apk. + */ + protected void onProviderChanged(int gadgetId, GadgetProviderInfo gadget) { + } void updateGadgetView(int gadgetId, RemoteViews views) { GadgetHostView v; diff --git a/core/java/android/gadget/GadgetHostView.java b/core/java/android/gadget/GadgetHostView.java index d92c1236f078..a985bd415559 100644 --- a/core/java/android/gadget/GadgetHostView.java +++ b/core/java/android/gadget/GadgetHostView.java @@ -18,7 +18,6 @@ package android.gadget; import android.content.Context; import android.content.pm.PackageManager; -import android.gadget.GadgetInfo; import android.graphics.Color; import android.util.Config; import android.util.Log; @@ -26,12 +25,18 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; import android.widget.FrameLayout; import android.widget.RemoteViews; import android.widget.TextView; import android.widget.ViewAnimator; -public class GadgetHostView extends ViewAnimator { +/** + * Provides the glue to show gadget views. This class offers automatic animation + * between updates, and will try recycling old views for each incoming + * {@link RemoteViews}. + */ +public class GadgetHostView extends ViewAnimator implements Animation.AnimationListener { static final String TAG = "GadgetHostView"; static final boolean LOGD = Config.LOGD || true; @@ -42,57 +47,93 @@ public class GadgetHostView extends ViewAnimator { return clazz.isAnnotationPresent(RemoteViews.RemoteView.class); } }; - + + Context mLocalContext; + int mGadgetId; - GadgetInfo mInfo; - View mActiveView; - View mStaleView; + GadgetProviderInfo mInfo; - protected int mDefaultGravity = Gravity.CENTER; - + View mActiveView = null; + View mStaleView = null; + + int mActiveLayoutId = -1; + int mStaleLayoutId = -1; + + /** + * Last set of {@link RemoteViews} applied to {@link #mActiveView} + */ + RemoteViews mActiveActions = null; + + /** + * Flag indicating that {@link #mActiveActions} has been applied to + * {@link #mStaleView}, meaning it's readyto recycle. + */ + boolean mStalePrepared = false; + + /** + * Create a host view. Uses default fade animations. + */ public GadgetHostView(Context context) { - super(context); + this(context, android.R.anim.fade_in, android.R.anim.fade_out); } - public void setGadget(int gadgetId, GadgetInfo info) { - if (LOGD) Log.d(TAG, "setGadget is incoming with info=" + info); + /** + * Create a host view. Uses specified animations when pushing + * {@link #updateGadget(RemoteViews)}. + * + * @param animationIn Resource ID of in animation to use + * @param animationOut Resource ID of out animation to use + */ + public GadgetHostView(Context context, int animationIn, int animationOut) { + super(context); + mLocalContext = context; + + // Prepare our default transition animations + setAnimateFirstView(true); + setInAnimation(context, animationIn); + setOutAnimation(context, animationOut); + + // Watch for animation events to prepare recycling + Animation inAnimation = getInAnimation(); + if (inAnimation != null) { + inAnimation.setAnimationListener(this); + } + } + + /** + * Set the gadget that will be displayed by this view. + */ + public void setGadget(int gadgetId, GadgetProviderInfo info) { if (mInfo != null) { // TODO: remove the old view, or whatever } mGadgetId = gadgetId; mInfo = info; - - View defaultView = getDefaultView(); - flipUpdate(defaultView); } - /** - * Trigger actual animation between current and new content in the - * {@link ViewAnimator}. - */ - protected void flipUpdate(View newContent) { - if (LOGD) Log.d(TAG, "pushing an update to surface"); - - // Take requested dimensions from parent, but apply default gravity. - ViewGroup.LayoutParams requested = newContent.getLayoutParams(); - if (requested == null) { - requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, - LayoutParams.FILL_PARENT); + public int getGadgetId() { + return mGadgetId; + } + + public GadgetProviderInfo getGadgetInfo() { + return mInfo; + } + + public void onAnimationEnd(Animation animation) { + // When our transition animation finishes, we should try bringing our + // newly-stale view up to the current view. + if (mActiveActions != null && + mStaleLayoutId == mActiveActions.getLayoutId()) { + if (LOGD) Log.d(TAG, "after animation, layoutId matched so we're recycling old view"); + mActiveActions.reapply(mLocalContext, mStaleView); + mStalePrepared = true; } - - FrameLayout.LayoutParams params = - new FrameLayout.LayoutParams(requested.width, requested.height); - params.gravity = mDefaultGravity; - newContent.setLayoutParams(params); - - // Add new content and animate to it - addView(newContent); - showNext(); - - // Dispose old stale view - removeView(mStaleView); - mStaleView = mActiveView; - mActiveView = newContent; + } + + public void onAnimationRepeat(Animation animation) { + } + + public void onAnimationStart(Animation animation) { } /** @@ -100,26 +141,42 @@ public class GadgetHostView extends ViewAnimator { * gadget provider. Will animate into these new views as needed. */ public void updateGadget(RemoteViews remoteViews) { - if (LOGD) Log.d(TAG, "updateGadget() with remoteViews = " + remoteViews); + if (LOGD) Log.d(TAG, "updateGadget called"); + boolean recycled = false; View newContent = null; Exception exception = null; - try { - if (remoteViews == null) { - // there is no remoteViews (yet), so use the initial layout - newContent = getDefaultView(); - } else { - // use the RemoteViews - // TODO: try applying RemoteViews to existing staleView if available - newContent = remoteViews.apply(mContext, this); + if (remoteViews == null) { + newContent = getDefaultView(); + } + + // If our stale view has been prepared to match active, and the new + // layout matches, try recycling it + if (newContent == null && mStalePrepared && + remoteViews.getLayoutId() == mStaleLayoutId) { + try { + remoteViews.reapply(mLocalContext, mStaleView); + newContent = mStaleView; + recycled = true; + if (LOGD) Log.d(TAG, "was able to recycled existing layout"); + } catch (RuntimeException e) { + exception = e; + } + } + + // Try normal RemoteView inflation + if (newContent == null) { + try { + newContent = remoteViews.apply(mLocalContext, this); + if (LOGD) Log.d(TAG, "had to inflate new layout"); + } catch (RuntimeException e) { + exception = e; } - } catch (RuntimeException e) { - exception = e; } if (exception != null && LOGD) { - Log.w(TAG, "Error inflating gadget " + mInfo, exception); + Log.w(TAG, "Error inflating gadget " + getGadgetInfo(), exception); } if (newContent == null) { @@ -128,8 +185,44 @@ public class GadgetHostView extends ViewAnimator { if (LOGD) Log.d(TAG, "updateGadget couldn't find any view, so inflating error"); newContent = getErrorView(); } - - flipUpdate(newContent); + + if (!recycled) { + prepareView(newContent); + addView(newContent); + } + + showNext(); + + if (!recycled) { + removeView(mStaleView); + } + + mStalePrepared = false; + mActiveActions = remoteViews; + + mStaleView = mActiveView; + mActiveView = newContent; + + mStaleLayoutId = mActiveLayoutId; + mActiveLayoutId = (remoteViews == null) ? -1 : remoteViews.getLayoutId(); + } + + /** + * Prepare the given view to be shown. This might include adjusting + * {@link FrameLayout.LayoutParams} before inserting. + */ + protected void prepareView(View view) { + // Take requested dimensions from parent, but apply default gravity. + ViewGroup.LayoutParams requested = view.getLayoutParams(); + if (requested == null) { + requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, + LayoutParams.FILL_PARENT); + } + + FrameLayout.LayoutParams params = + new FrameLayout.LayoutParams(requested.width, requested.height); + params.gravity = Gravity.CENTER; + view.setLayoutParams(params); } /** @@ -141,7 +234,7 @@ public class GadgetHostView extends ViewAnimator { try { if (mInfo != null) { - Context theirContext = mContext.createPackageContext( + Context theirContext = mLocalContext.createPackageContext( mInfo.provider.getPackageName(), 0 /* no flags */); LayoutInflater inflater = (LayoutInflater) theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -173,11 +266,10 @@ public class GadgetHostView extends ViewAnimator { * Inflate and return a view that represents an error state. */ protected View getErrorView() { - TextView tv = new TextView(mContext); + TextView tv = new TextView(mLocalContext); // TODO: move this error string and background color into resources tv.setText("Error inflating gadget"); tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); return tv; } } - diff --git a/core/java/android/gadget/GadgetManager.java b/core/java/android/gadget/GadgetManager.java index 20f40140cbdb..a9a2c8010596 100644 --- a/core/java/android/gadget/GadgetManager.java +++ b/core/java/android/gadget/GadgetManager.java @@ -38,68 +38,142 @@ public class GadgetManager { static final String TAG = "GadgetManager"; /** - * Send this when you want to pick a gadget to display. + * Send this from your gadget host activity when you want to pick a gadget to display. + * The gadget picker activity will be launched. + *

+ * You must supply the following extras: + * + * + * + * + * + *
{@link #EXTRA_GADGET_ID}A newly allocated gadgetId, which will be bound to the gadget provider + * once the user has selected one.
* *

* The system will respond with an onActivityResult call with the following extras in * the intent: - *

    - *
  • gadgetIds
  • - *
  • hostId
  • - *
- * TODO: Add constants for these. - * TODO: Where does this go? + * + * + * + * + * + *
{@link #EXTRA_GADGET_ID}The gadgetId that you supplied in the original intent.
+ *

+ * When you receive the result from the gadget pick activity, if the resultCode is + * {@link android.app.Activity#RESULT_OK}, a gadget has been selected. You should then + * check the GadgetProviderInfo for the returned gadget, and if it has one, launch its configuration + * activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete + * the gadgetId. + * + * @see #ACTION_GADGET_CONFIGURE + */ + public static final String ACTION_GADGET_PICK = "android.gadget.action.GADGET_PICK"; + + /** + * Sent when it is time to configure your gadget while it is being added to a host. + * This action is not sent as a broadcast to the gadget provider, but as a startActivity + * to the activity specified in the {@link GadgetProviderInfo GadgetProviderInfo meta-data}. + * + *

+ * The intent will contain the following extras: + * + * + * + * + * + *
{@link #EXTRA_GADGET_ID}The gadgetId to configure.
+ * + *

If you return {@link android.app.Activity#RESULT_OK} using + * {@link android.app.Activity#setResult Activity.setResult()}, the gadget will be added, + * and you will receive an {@link #ACTION_GADGET_UPDATE} broadcast for this gadget. + * If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add + * and not display this gadget, and you will receive a {@link #ACTION_GADGET_DELETED} broadcast. */ - public static final String GADGET_PICK_ACTION = "android.gadget.action.PICK_GADGET"; + public static final String ACTION_GADGET_CONFIGURE = "android.gadget.action.GADGET_CONFIGURE"; + /** + * An intent extra that contains one gadgetId. + *

+ * The value will be an int that can be retrieved like this: + * {@sample frameworks/base/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/GadgetHostActivity.java getExtra_EXTRA_GADGET_ID} + */ public static final String EXTRA_GADGET_ID = "gadgetId"; + + /** + * An intent extra that contains multiple gadgetIds. + *

+ * The value will be an int array that can be retrieved like this: + * {@sample frameworks/base/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/TestGadgetProvider.java getExtra_EXTRA_GADGET_IDS} + */ public static final String EXTRA_GADGET_IDS = "gadgetIds"; - public static final String EXTRA_HOST_ID = "hostId"; + + /** + * A sentiel value that the gadget manager will never return as a gadgetId. + */ + public static final int INVALID_GADGET_ID = 0; /** * Sent when it is time to update your gadget. * *

This may be sent in response to a new instance for this gadget provider having - * been instantiated, the requested {@link GadgetInfo#updatePeriodMillis update interval} + * been instantiated, the requested {@link GadgetProviderInfo#updatePeriodMillis update interval} * having lapsed, or the system booting. - */ - public static final String GADGET_UPDATE_ACTION = "android.gadget.action.GADGET_UPDATE"; - - /** - * Sent when it is time to configure your gadget. This action is not sent as a broadcast - * to the gadget provider, but as a startActivity to the activity specified in the - * {@link GadgetInfo GadgetInfo meta-data}. * - *

The {@link #EXTRA_GADGET_ID} extra contains the gadget ID. + *

+ * The intent will contain the following extras: + * + * + * + * + * + *
{@link #EXTRA_GADGET_IDS}The gadgetIds to update. This may be all of the gadgets created for this + * provider, or just a subset. The system tries to send updates for as few gadget + * instances as possible.
+ * + * @see GadgetProvider#onUpdate GadgetProvider.onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) */ - public static final String GADGET_CONFIGURE_ACTION = "android.gadget.action.GADGET_CONFIGURE"; + public static final String ACTION_GADGET_UPDATE = "android.gadget.action.GADGET_UPDATE"; /** - * Sent when the gadget is added to a host for the first time. This broadcast is sent at - * boot time if there is a gadget host installed with an instance for this provider. + * Sent when an instance of a gadget is deleted from its host. + * + * @see GadgetProvider#onDeleted GadgetProvider.onDeleted(Context context, int[] gadgetIds) */ - public static final String GADGET_ENABLED_ACTION = "android.gadget.action.GADGET_ENABLED"; + public static final String ACTION_GADGET_DELETED = "android.gadget.action.GADGET_DELETED"; /** - * Sent when an instances of a gadget is deleted from the host. + * Sent when an instance of a gadget is removed from the last host. + * + * @see GadgetProvider#onEnabled GadgetProvider.onEnabled(Context context) */ - public static final String GADGET_DELETED_ACTION = "android.gadget.action.GADGET_DELETED"; + public static final String ACTION_GADGET_DISABLED = "android.gadget.action.GADGET_DISABLED"; /** - * Sent when the gadget is removed from the last host. + * Sent when an instance of a gadget is added to a host for the first time. + * This broadcast is sent at boot time if there is a gadget host installed with + * an instance for this provider. + * + * @see GadgetProvider#onEnabled GadgetProvider.onEnabled(Context context) */ - public static final String GADGET_DISABLED_ACTION = "android.gadget.action.GADGET_DISABLED"; + public static final String ACTION_GADGET_ENABLED = "android.gadget.action.GADGET_ENABLED"; /** * Field for the manifest meta-data tag. + * + * @see GadgetProviderInfo */ - public static final String GADGET_PROVIDER_META_DATA = "android.gadget.provider"; + public static final String META_DATA_GADGET_PROVIDER = "android.gadget.provider"; static WeakHashMap> sManagerCache = new WeakHashMap(); static IGadgetService sService; Context mContext; + /** + * Get the GadgetManager instance to use for the supplied {@link android.content.Context + * Context} object. + */ public static GadgetManager getInstance(Context context) { synchronized (sManagerCache) { if (sService == null) { @@ -125,9 +199,11 @@ public class GadgetManager { } /** - * Call this with the new RemoteViews for your gadget whenever you need to. + * Set the RemoteViews to use for the specified gadgetIds. * *

+ * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast, + * and outside of the handler. * This method will only work when called from the uid that owns the gadget provider. * * @param gadgetIds The gadget instances for which to set the RemoteViews. @@ -143,9 +219,26 @@ public class GadgetManager { } /** - * Call this with the new RemoteViews for your gadget whenever you need to. + * Set the RemoteViews to use for the specified gadgetId. * *

+ * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast, + * and outside of the handler. + * This method will only work when called from the uid that owns the gadget provider. + * + * @param gadgetId The gadget instance for which to set the RemoteViews. + * @param views The RemoteViews object to show. + */ + public void updateGadget(int gadgetId, RemoteViews views) { + updateGadget(new int[] { gadgetId }, views); + } + + /** + * Set the RemoteViews to use for all gadget instances for the supplied gadget provider. + * + *

+ * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast, + * and outside of the handler. * This method will only work when called from the uid that owns the gadget provider. * * @param provider The {@link ComponentName} for the {@link @@ -165,7 +258,7 @@ public class GadgetManager { /** * Return a list of the gadget providers that are currently installed. */ - public List getInstalledProviders() { + public List getInstalledProviders() { try { return sService.getInstalledProviders(); } @@ -175,12 +268,12 @@ public class GadgetManager { } /** - * Get the available info about the gadget. If the gadgetId has not been bound yet, - * this method will return null. + * Get the available info about the gadget. * - * TODO: throws GadgetNotFoundException ??? if not valid + * @return A gadgetId. If the gadgetId has not been bound to a provider yet, or + * you don't have access to that gadgetId, null is returned. */ - public GadgetInfo getGadgetInfo(int gadgetId) { + public GadgetProviderInfo getGadgetInfo(int gadgetId) { try { return sService.getGadgetInfo(gadgetId); } @@ -190,7 +283,14 @@ public class GadgetManager { } /** - * Set the component for a given gadgetId. You need the GADGET_LIST permission. + * Set the component for a given gadgetId. + * + *

You need the GADGET_LIST permission. This method is to be used by the + * gadget picker. + * + * @param gadgetId The gadget instance for which to set the RemoteViews. + * @param provider The {@link android.content.BroadcastReceiver} that will be the gadget + * provider for this gadget. */ public void bindGadgetId(int gadgetId, ComponentName provider) { try { diff --git a/core/java/android/gadget/GadgetProvider.java b/core/java/android/gadget/GadgetProvider.java index 1ddfe3f0babf..7e10e7817a2b 100755 --- a/core/java/android/gadget/GadgetProvider.java +++ b/core/java/android/gadget/GadgetProvider.java @@ -55,7 +55,7 @@ public class GadgetProvider extends BroadcastReceiver { // Protect against rogue update broadcasts (not really a security issue, // just filter bad broacasts out so subclasses are less likely to crash). String action = intent.getAction(); - if (GadgetManager.GADGET_UPDATE_ACTION.equals(action)) { + if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS); @@ -64,7 +64,7 @@ public class GadgetProvider extends BroadcastReceiver { } } } - else if (GadgetManager.GADGET_DELETED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_DELETED.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS); @@ -73,102 +73,81 @@ public class GadgetProvider extends BroadcastReceiver { } } } - else if (GadgetManager.GADGET_ENABLED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_ENABLED.equals(action)) { this.onEnabled(context); } - else if (GadgetManager.GADGET_DISABLED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_DISABLED.equals(action)) { this.onDisabled(context); } } // END_INCLUDE(onReceive) /** - * Called in response to the {@link GadgetManager#GADGET_UPDATE_ACTION} broadcast when + * Called in response to the {@link GadgetManager#ACTION_GADGET_UPDATE} broadcast when * this gadget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews} * for a set of gadgets. Override this method to implement your own gadget functionality. * * {@more} - *

If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_UPDATE_ACTION intent action. - * For example: - * TODO: SAMPLE CODE GOES HERE - *

* * @param context The {@link android.content.Context Context} in which this receiver is * running. * @param gadgetManager A {@link GadgetManager} object you can call {@link - * GadgetManager#updateGadgets} on. + * GadgetManager#updateGadget} on. * @param gadgetIds The gadgetsIds for which an update is needed. Note that this * may be all of the gadget instances for this provider, or just * a subset of them. * - * @see GadgetManager#GADGET_UPDATE_ACTION + * @see GadgetManager#ACTION_GADGET_UPDATE */ public void onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) { } /** - * Called in response to the {@link GadgetManager#GADGET_DELETED_ACTION} broadcast when + * Called in response to the {@link GadgetManager#ACTION_GADGET_DELETED} broadcast when * one or more gadget instances have been deleted. Override this method to implement * your own gadget functionality. * * {@more} - *

If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_DELETED_ACTION intent action. - * For example: - * TODO: SAMPLE CODE GOES HERE - *

* * @param context The {@link android.content.Context Context} in which this receiver is * running. * @param gadgetIds The gadgetsIds that have been deleted from their host. * - * @see GadgetManager#GADGET_DELETED_ACTION + * @see GadgetManager#ACTION_GADGET_DELETED */ public void onDeleted(Context context, int[] gadgetIds) { } /** - * Called in response to the {@link GadgetManager#GADGET_ENABLED_ACTION} broadcast when + * Called in response to the {@link GadgetManager#ACTION_GADGET_ENABLED} broadcast when * the a gadget for this provider is instantiated. Override this method to implement your * own gadget functionality. * * {@more} * When the last gadget for this provider is deleted, - * {@link GadgetManager#GADGET_DISABLED_ACTION} is sent and {@link #onDisabled} - * is called. If after that, a gadget for this provider is created again, onEnabled() will - * be called again. + * {@link GadgetManager#ACTION_GADGET_DISABLED} is sent by the gadget manager, and + * {@link #onDisabled} is called. If after that, a gadget for this provider is created + * again, onEnabled() will be called again. * - *

If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_ENABLED_ACTION intent action. - * For example: - * TODO: SAMPLE CODE GOES HERE - *

- * * @param context The {@link android.content.Context Context} in which this receiver is * running. * - * @see GadgetManager#GADGET_ENABLED_ACTION + * @see GadgetManager#ACTION_GADGET_ENABLED */ public void onEnabled(Context context) { } /** - * Called in response to the {@link GadgetManager#GADGET_DISABLED_ACTION} broadcast, which + * Called in response to the {@link GadgetManager#ACTION_GADGET_DISABLED} broadcast, which * is sent when the last gadget instance for this provider is deleted. Override this method * to implement your own gadget functionality. * * {@more} - *

If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_DISABLED_ACTION intent action. - * For example: - * TODO: SAMPLE CODE GOES HERE - *

* * @param context The {@link android.content.Context Context} in which this receiver is * running. * - * @see GadgetManager#GADGET_DISABLED_ACTION + * @see GadgetManager#ACTION_GADGET_DISABLED */ public void onDisabled(Context context) { } diff --git a/core/java/android/gadget/GadgetInfo.aidl b/core/java/android/gadget/GadgetProviderInfo.aidl similarity index 95% rename from core/java/android/gadget/GadgetInfo.aidl rename to core/java/android/gadget/GadgetProviderInfo.aidl index 72315454b6dd..589f886776f4 100644 --- a/core/java/android/gadget/GadgetInfo.aidl +++ b/core/java/android/gadget/GadgetProviderInfo.aidl @@ -16,4 +16,4 @@ package android.gadget; -parcelable GadgetInfo; +parcelable GadgetProviderInfo; diff --git a/core/java/android/gadget/GadgetInfo.java b/core/java/android/gadget/GadgetProviderInfo.java similarity index 56% rename from core/java/android/gadget/GadgetInfo.java rename to core/java/android/gadget/GadgetProviderInfo.java index 5ac3da9cf2d1..95c043230ec3 100644 --- a/core/java/android/gadget/GadgetInfo.java +++ b/core/java/android/gadget/GadgetProviderInfo.java @@ -21,60 +21,88 @@ import android.os.Parcelable; import android.content.ComponentName; /** - * Describes the meta data for an installed gadget. + * Describes the meta data for an installed gadget provider. The fields in this class + * correspond to the fields in the <gadget-provider> xml tag. */ -public class GadgetInfo implements Parcelable { +public class GadgetProviderInfo implements Parcelable { /** * Identity of this gadget component. This component should be a {@link * android.content.BroadcastReceiver}, and it will be sent the Gadget intents * {@link android.gadget as described in the gadget package documentation}. + * + *

This field corresponds to the android:name attribute in + * the <receiver> element in the AndroidManifest.xml file. */ public ComponentName provider; /** * Minimum width of the gadget, in dp. + * + *

This field corresponds to the android:minWidth attribute in + * the gadget meta-data file. */ public int minWidth; /** * Minimum height of the gadget, in dp. + * + *

This field corresponds to the android:minHeight attribute in + * the gadget meta-data file. */ public int minHeight; /** * How often, in milliseconds, that this gadget wants to be updated. * The gadget manager may place a limit on how often a gadget is updated. + * + *

This field corresponds to the android:updatePeriodMillis attribute in + * the gadget meta-data file. */ public int updatePeriodMillis; /** * The resource id of the initial layout for this gadget. This should be * displayed until the RemoteViews for the gadget is available. + * + *

This field corresponds to the android:initialLayout attribute in + * the gadget meta-data file. */ public int initialLayout; /** * The activity to launch that will configure the gadget. + * + *

This class name of field corresponds to the android:configure attribute in + * the gadget meta-data file. The package name always corresponds to the package containing + * the gadget provider. */ public ComponentName configure; /** - * The label to display to the user. + * The label to display to the user in the gadget picker. If not supplied in the + * xml, the application label will be used. + * + *

This field corresponds to the android:label attribute in + * the <receiver> element in the AndroidManifest.xml file. */ public String label; /** - * The icon to display for this gadget in the picker list. + * The icon to display for this gadget in the gadget picker. If not supplied in the + * xml, the application icon will be used. + * + *

This field corresponds to the android:icon attribute in + * the <receiver> element in the AndroidManifest.xml file. */ public int icon; - public GadgetInfo() { + public GadgetProviderInfo() { } /** - * Unflatten the GadgetInfo from a parcel. + * Unflatten the GadgetProviderInfo from a parcel. */ - public GadgetInfo(Parcel in) { + public GadgetProviderInfo(Parcel in) { if (0 != in.readInt()) { this.provider = new ComponentName(in); } @@ -116,24 +144,24 @@ public class GadgetInfo implements Parcelable { } /** - * Parcelable.Creator that instantiates GadgetInfo objects + * Parcelable.Creator that instantiates GadgetProviderInfo objects */ - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { - public GadgetInfo createFromParcel(Parcel parcel) + public GadgetProviderInfo createFromParcel(Parcel parcel) { - return new GadgetInfo(parcel); + return new GadgetProviderInfo(parcel); } - public GadgetInfo[] newArray(int size) + public GadgetProviderInfo[] newArray(int size) { - return new GadgetInfo[size]; + return new GadgetProviderInfo[size]; } }; public String toString() { - return "GadgetInfo(provider=" + this.provider + ")"; + return "GadgetProviderInfo(provider=" + this.provider + ")"; } } diff --git a/core/java/android/gadget/package.html b/core/java/android/gadget/package.html index 4b8b9d9c3d6b..4c04396e8b7e 100644 --- a/core/java/android/gadget/package.html +++ b/core/java/android/gadget/package.html @@ -1,41 +1,126 @@ -{@hide}

Android allows applications to publish views to be embedded in other applications. These views are called gadgets, and are published by "gadget providers." The component that can -contain gadgets is called a "gadget host." See the links below for more information. +contain gadgets is called a "gadget host."

-

Gadget Providers

+

Gadget Providers

-

Gadget Hosts

- +

Gadget Hosts

+ + {@more} + +

Gadget Providers

-

Any application can publish gadgets. All an application needs to do to publish a gadget is +

+Any application can publish gadgets. All an application needs to do to publish a gadget is to have a {@link android.content.BroadcastReceiver} that receives the {@link -android.gadget.GadgetManager#GADGET_UPDATE_ACTION GadgetManager.GADGET_UPDATE_ACTION} intent, -and provide some meta-data about the gadget. +android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} intent, +and provide some meta-data about the gadget. Android provides the +{@link android.gadget.GadgetProvider} class, which extends BroadcastReceiver, as a convenience +class to aid in handling the broadcasts.

Declaring a gadget in the AndroidManifest

-

Adding the {@link android.gadget.GadgetInfo GadgetInfo} meta-data

+

+First, declare the {@link android.content.BroadcastReceiver} in your application's +AndroidManifest.xml file. + +{@sample frameworks/base/tests/gadgets/GadgetHostTest/AndroidManifest.xml GadgetProvider} + +

+The <receiver> element has the following attributes: +

    +
  • android:name - which specifies the + {@link android.content.BroadcastReceiver} or {@link android.gadget.GadgetProvider} + class.
  • +
  • android:label - which specifies the string resource that + will be shown by the gadget picker as the label.
  • +
  • android:icon - which specifies the drawable resource that + will be shown by the gadget picker as the icon.
  • +
+ +

+The <intent-filter> element tells the {@link android.content.pm.PackageManager} +that this {@link android.content.BroadcastReceiver} receives the {@link +android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} broadcast. +The gadget manager will send other broadcasts directly to your gadget provider as required. +It is only necessary to explicitly declare that you accept the {@link +android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} broadcast. + +

+The <meta-data> element tells the gadget manager which xml resource to +read to find the {@link android.gadget.GadgetProviderInfo} for your gadget provider. It has the following +attributes: +

    +
  • android:name="android.gadget.provider" - identifies this meta-data + as the {@link android.gadget.GadgetProviderInfo} descriptor.
  • +
  • android:resource - is the xml resource to use as that descriptor.
  • +
+ + +

Adding the {@link android.gadget.GadgetProviderInfo GadgetProviderInfo} meta-data

+ +

+For a gadget, the values in the {@link android.gadget.GadgetProviderInfo} structure are supplied +in an XML resource. In the example above, the xml resource is referenced with +android:resource="@xml/gadget_info". That XML file would go in your application's +directory at res/xml/gadget_info.xml. Here is a simple example. + +{@sample frameworks/base/tests/gadgets/GadgetHostTest/res/xml/gadget_info.xml GadgetProviderInfo} + +

+The attributes are as documented in the {@link android.gadget.GadgetProviderInfo GagetInfo} class. (86400000 milliseconds means once per day) +

Using the {@link android.gadget.GadgetProvider GadgetProvider} class

+

The GadgetProvider class is the easiest way to handle the gadget provider intent broadcasts. +See the src/com/example/android/apis/gadget/ExampleGadgetProvider.java +sample class in ApiDemos for an example. + +

Keep in mind that since the the GadgetProvider is a BroadcastReceiver, +your process is not guaranteed to keep running after the callback methods return. See +Application Fundamentals > +Broadcast Receiver Lifecycle for more information. + + +

Gadget Configuration UI

+

+Gadget hosts have the ability to start a configuration activity when a gadget is instantiated. +The activity should be declared as normal in AndroidManifest.xml, and it should be listed in +the GadgetProviderInfo XML file in the android:configure attribute. + +

The activity you specified will be launched with the {@link +android.gadget.GadgetManager#ACTION_GADGET_CONFIGURE} action. See the documentation for that +action for more info. + +

See the src/com/example/android/apis/gadget/ExampleGadgetConfigure.java +sample class in ApiDemos for an example. + + +

Gadget Broadcast Intents

-

{@link GadgetProvider} is just a convenience class. If you would like to receive the -gadget broadcasts directly, you can. By way of example, the implementation of -{@link GadgetProvider.onReceive} is quite simple:

+

{@link android.gadget.GadgetProvider} is just a convenience class. If you would like +to receive the gadget broadcasts directly, you can. The four intents you need to care about are: +

    +
  • {@link android.gadget.GadgetManager#ACTION_GADGET_UPDATE}
  • +
  • {@link android.gadget.GadgetManager#ACTION_GADGET_DELETED}
  • +
  • {@link android.gadget.GadgetManager#ACTION_GADGET_ENABLED}
  • +
  • {@link android.gadget.GadgetManager#ACTION_GADGET_DISABLED}
  • +
+ +

By way of example, the implementation of +{@link android.gadget.GadgetProvider#onReceive} is quite simple:

{@sample frameworks/base/core/java/android/gadget/GadgetProvider.java onReceive} diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index c09567c7f0a6..40a5b478e5f0 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -18,6 +18,7 @@ package android.hardware; import java.lang.ref.WeakReference; import java.util.HashMap; +import java.util.StringTokenizer; import java.io.IOException; import android.util.Log; @@ -494,11 +495,17 @@ public class Camera { */ public void unflatten(String flattened) { mMap.clear(); - String[] pairs = flattened.split(";"); - for (String p : pairs) { - String[] kv = p.split("="); - if (kv.length == 2) - mMap.put(kv[0], kv[1]); + + StringTokenizer tokenizer = new StringTokenizer(flattened, ";"); + while (tokenizer.hasMoreElements()) { + String kv = tokenizer.nextToken(); + int pos = kv.indexOf('='); + if (pos == -1) { + continue; + } + String k = kv.substring(0, pos); + String v = kv.substring(pos + 1); + mMap.put(k, v); } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index ea5f7414d63d..c8841201f11b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -206,6 +206,8 @@ public class InputMethodService extends AbstractInputMethodService { static final String TAG = "InputMethodService"; static final boolean DEBUG = false; + InputMethodManager mImm; + LayoutInflater mInflater; View mRootView; SoftInputWindow mWindow; @@ -293,6 +295,8 @@ public class InputMethodService extends AbstractInputMethodService { mInputConnection = binding.getConnection(); if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding + " ic=" + mInputConnection); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) ic.reportFullscreenMode(mIsFullscreen); initialize(); onBindInput(); } @@ -423,7 +427,7 @@ public class InputMethodService extends AbstractInputMethodService { * of the application behind. This value is relative to the top edge * of the input method window. */ - int contentTopInsets; + public int contentTopInsets; /** * This is the top part of the UI that is visibly covering the @@ -436,7 +440,7 @@ public class InputMethodService extends AbstractInputMethodService { * needed to make the focus visible. This value is relative to the top edge * of the input method window. */ - int visibleTopInsets; + public int visibleTopInsets; /** * Option for {@link #touchableInsets}: the entire window frame @@ -469,6 +473,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void onCreate() { super.onCreate(); + mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this); @@ -554,7 +559,6 @@ public class InputMethodService extends AbstractInputMethodService { boolean visible = mWindowVisible; boolean showingInput = mShowInputRequested; boolean showingForced = mShowInputForced; - boolean showingCandidates = mCandidatesVisibility == View.VISIBLE; initViews(); mInputViewStarted = false; mCandidatesViewStarted = false; @@ -577,9 +581,6 @@ public class InputMethodService extends AbstractInputMethodService { // Otherwise just put it back for its candidates. showWindow(false); } - if (showingCandidates) { - setCandidatesViewShown(true); - } } } @@ -670,6 +671,8 @@ public class InputMethodService extends AbstractInputMethodService { if (mIsFullscreen != isFullscreen || !mFullscreenApplied) { changed = true; mIsFullscreen = isFullscreen; + InputConnection ic = getCurrentInputConnection(); + if (ic != null) ic.reportFullscreenMode(isFullscreen); mFullscreenApplied = true; initialize(); Drawable bg = onCreateBackgroundDrawable(); @@ -860,12 +863,14 @@ public class InputMethodService extends AbstractInputMethodService { return isFullscreenMode() ? View.GONE : View.INVISIBLE; } - public void setStatusIcon(int iconResId) { + public void showStatusIcon(int iconResId) { mStatusIcon = iconResId; - InputConnection ic = getCurrentInputConnection(); - if (ic != null && mWindowVisible) { - ic.showStatusIcon(getPackageName(), iconResId); - } + mImm.showStatusIcon(mToken, getPackageName(), iconResId); + } + + public void hideStatusIcon() { + mStatusIcon = 0; + mImm.hideStatusIcon(mToken); } /** @@ -876,8 +881,7 @@ public class InputMethodService extends AbstractInputMethodService { * @param id Unique identifier of the new input method ot start. */ public void switchInputMethod(String id) { - ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)) - .setInputMethod(mToken, id); + mImm.setInputMethod(mToken, id); } public void setExtractView(View view) { @@ -1149,15 +1153,9 @@ public class InputMethodService extends AbstractInputMethodService { if (!wasVisible) { if (DEBUG) Log.v(TAG, "showWindow: showing!"); + onWindowShown(); mWindow.show(); } - - if (!wasVisible || !wasCreated) { - InputConnection ic = getCurrentInputConnection(); - if (ic != null) { - ic.showStatusIcon(getPackageName(), mStatusIcon); - } - } } public void hideWindow() { @@ -1173,14 +1171,26 @@ public class InputMethodService extends AbstractInputMethodService { if (mWindowVisible) { mWindow.hide(); mWindowVisible = false; - InputConnection ic = getCurrentInputConnection(); - if (ic != null) { - ic.hideStatusIcon(); - } + onWindowHidden(); } } /** + * Called when the input method window has been shown to the user, after + * previously not being visible. This is done after all of the UI setup + * for the window has occurred (creating its views etc). + */ + public void onWindowShown() { + } + + /** + * Called when the input method window has been hidden from the user, + * after previously being visible. + */ + public void onWindowHidden() { + } + + /** * Called when a new client has bound to the input method. This * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)} * and {@link #onFinishInput()} calls as the user navigates through its @@ -1341,8 +1351,7 @@ public class InputMethodService extends AbstractInputMethodService { * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set. */ public void dismissSoftInput(int flags) { - ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)) - .hideSoftInputFromInputMethod(mToken, flags); + mImm.hideSoftInputFromInputMethod(mToken, flags); } /** @@ -1447,17 +1456,19 @@ public class InputMethodService extends AbstractInputMethodService { return true; } } else { - KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); - if (movement.onKeyDown(eet, - (Spannable)eet.getText(), keyCode, down)) { - KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); - movement.onKeyUp(eet, - (Spannable)eet.getText(), keyCode, up); - while (--count > 0) { - movement.onKeyDown(eet, - (Spannable)eet.getText(), keyCode, down); + if (!movement.onKeyOther(eet, (Spannable)eet.getText(), event)) { + KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); + if (movement.onKeyDown(eet, + (Spannable)eet.getText(), keyCode, down)) { + KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); movement.onKeyUp(eet, (Spannable)eet.getText(), keyCode, up); + while (--count > 0) { + movement.onKeyDown(eet, + (Spannable)eet.getText(), keyCode, down); + movement.onKeyUp(eet, + (Spannable)eet.getText(), keyCode, up); + } } } } @@ -1593,5 +1604,9 @@ public class InputMethodService extends AbstractInputMethodService { p.println(" mExtractedToken=" + mExtractedToken); p.println(" mIsInputViewShown=" + mIsInputViewShown + " mStatusIcon=" + mStatusIcon); + p.println("Last computed insets:"); + p.println(" contentTopInsets=" + mTmpInsets.contentTopInsets + + " visibleTopInsets=" + mTmpInsets.visibleTopInsets + + " touchableInsets=" + mTmpInsets.touchableInsets); } } diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index b2c74f24bf00..b8bd10dc44de 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -1084,6 +1084,10 @@ public class KeyboardView extends View implements View.OnClickListener { if (mPreviewPopup.isShowing()) { mPreviewPopup.dismiss(); } + mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); + mHandler.removeMessages(MSG_SHOW_PREVIEW); + dismissPopupKeyboard(); } diff --git a/core/java/android/net/UrlQuerySanitizer.java b/core/java/android/net/UrlQuerySanitizer.java index 70e50b7c2635..a6efcdd3da51 100644 --- a/core/java/android/net/UrlQuerySanitizer.java +++ b/core/java/android/net/UrlQuerySanitizer.java @@ -23,7 +23,7 @@ import java.util.Set; import java.util.StringTokenizer; /** - * + * * Sanitizes the Query portion of a URL. Simple example: * * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer(); @@ -32,7 +32,7 @@ import java.util.StringTokenizer; * String name = sanitizer.getValue("name")); * // name now contains "Joe_User" * - * + * * Register ValueSanitizers to customize the way individual * parameters are sanitized: * @@ -46,7 +46,7 @@ import java.util.StringTokenizer; * unregistered parameter sanitizer does not allow any special characters, * and ' ' is a special character.) * - * + * * There are several ways to create ValueSanitizers. In order of increasing * sophistication: *
    @@ -56,7 +56,7 @@ import java.util.StringTokenizer; *
  1. Subclass UrlQuerySanitizer.ValueSanitizer to define your own value * sanitizer. *
- * + * */ public class UrlQuerySanitizer { @@ -84,7 +84,7 @@ public class UrlQuerySanitizer { */ public String mValue; } - + final private HashMap mSanitizers = new HashMap(); final private HashMap mEntries = @@ -95,9 +95,9 @@ public class UrlQuerySanitizer { private boolean mPreferFirstRepeatedParameter; private ValueSanitizer mUnregisteredParameterValueSanitizer = getAllIllegal(); - + /** - * A functor used to sanitize a single query value. + * A functor used to sanitize a single query value. * */ public static interface ValueSanitizer { @@ -108,7 +108,7 @@ public class UrlQuerySanitizer { */ public String sanitize(String value); } - + /** * Sanitize values based on which characters they contain. Illegal * characters are replaced with either space or '_', depending upon @@ -117,7 +117,7 @@ public class UrlQuerySanitizer { public static class IllegalCharacterValueSanitizer implements ValueSanitizer { private int mFlags; - + /** * Allow space (' ') characters. */ @@ -165,21 +165,21 @@ public class UrlQuerySanitizer { * such as "javascript:" or "vbscript:" */ public final static int SCRIPT_URL_OK = 1 << 10; - + /** * Mask with all fields set to OK */ public final static int ALL_OK = 0x7ff; - + /** * Mask with both regular space and other whitespace OK */ public final static int ALL_WHITESPACE_OK = SPACE_OK | OTHER_WHITESPACE_OK; - + // Common flag combinations: - + /** *
    *
  • Deny all special characters. @@ -262,18 +262,18 @@ public class UrlQuerySanitizer { */ public final static int ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL = ALL_OK & ~(NUL_OK | LT_OK | GT_OK); - + /** * Script URL definitions */ - + private final static String JAVASCRIPT_PREFIX = "javascript:"; - + private final static String VBSCRIPT_PREFIX = "vbscript:"; - + private final static int MIN_SCRIPT_PREFIX_LENGTH = Math.min( JAVASCRIPT_PREFIX.length(), VBSCRIPT_PREFIX.length()); - + /** * Construct a sanitizer. The parameters set the behavior of the * sanitizer. @@ -312,7 +312,7 @@ public class UrlQuerySanitizer { } } } - + // If whitespace isn't OK, get rid of whitespace at beginning // and end of value. if ( (mFlags & ALL_WHITESPACE_OK) == 0) { @@ -337,7 +337,7 @@ public class UrlQuerySanitizer { } return stringBuilder.toString(); } - + /** * Trim whitespace from the beginning and end of a string. *

    @@ -361,7 +361,7 @@ public class UrlQuerySanitizer { } return value.substring(start, end + 1); } - + /** * Check if c is whitespace. * @param c character to test @@ -380,7 +380,7 @@ public class UrlQuerySanitizer { return false; } } - + /** * Check whether an individual character is legal. Uses the * flag bit-set passed into the constructor. @@ -400,11 +400,11 @@ public class UrlQuerySanitizer { case '%' : return (mFlags & PCT_OK) != 0; case '\0': return (mFlags & NUL_OK) != 0; default : return (c >= 32 && c < 127) || - (c >= 128 && c <= 255 && ((mFlags & NON_7_BIT_ASCII_OK) != 0)); - } + ((c >= 128) && ((mFlags & NON_7_BIT_ASCII_OK) != 0)); + } } } - + /** * Get the current value sanitizer used when processing * unregistered parameter values. @@ -412,14 +412,14 @@ public class UrlQuerySanitizer { * Note: The default unregistered parameter value sanitizer is * one that doesn't allow any special characters, similar to what * is returned by calling createAllIllegal. - * + * * @return the current ValueSanitizer used to sanitize unregistered * parameter values. */ public ValueSanitizer getUnregisteredParameterValueSanitizer() { return mUnregisteredParameterValueSanitizer; } - + /** * Set the value sanitizer used when processing unregistered * parameter values. @@ -430,46 +430,46 @@ public class UrlQuerySanitizer { ValueSanitizer sanitizer) { mUnregisteredParameterValueSanitizer = sanitizer; } - - + + // Private fields for singleton sanitizers: - + private static final ValueSanitizer sAllIllegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_ILLEGAL); - + private static final ValueSanitizer sAllButNulLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_BUT_NUL_LEGAL); - + private static final ValueSanitizer sAllButWhitespaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_BUT_WHITESPACE_LEGAL); - + private static final ValueSanitizer sURLLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.URL_LEGAL); - + private static final ValueSanitizer sUrlAndSpaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.URL_AND_SPACE_LEGAL); - + private static final ValueSanitizer sAmpLegal = new IllegalCharacterValueSanitizer( - IllegalCharacterValueSanitizer.AMP_LEGAL); - + IllegalCharacterValueSanitizer.AMP_LEGAL); + private static final ValueSanitizer sAmpAndSpaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.AMP_AND_SPACE_LEGAL); - + private static final ValueSanitizer sSpaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.SPACE_LEGAL); - + private static final ValueSanitizer sAllButNulAndAngleBracketsLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL); - + /** * Return a value sanitizer that does not allow any special characters, * and also does not allow script URLs. @@ -478,7 +478,7 @@ public class UrlQuerySanitizer { public static final ValueSanitizer getAllIllegal() { return sAllIllegal; } - + /** * Return a value sanitizer that allows everything except Nul ('\0') * characters. Script URLs are allowed. @@ -547,7 +547,7 @@ public class UrlQuerySanitizer { public static final ValueSanitizer getAllButNulAndAngleBracketsLegal() { return sAllButNulAndAngleBracketsLegal; } - + /** * Constructs a UrlQuerySanitizer. *

    @@ -560,7 +560,7 @@ public class UrlQuerySanitizer { */ public UrlQuerySanitizer() { } - + /** * Constructs a UrlQuerySanitizer and parse a URL. * This constructor is provided for convenience when the @@ -585,7 +585,7 @@ public class UrlQuerySanitizer { setAllowUnregisteredParamaters(true); parseUrl(url); } - + /** * Parse the query parameters out of an encoded URL. * Works by extracting the query portion from the URL and then @@ -604,7 +604,7 @@ public class UrlQuerySanitizer { } parseQuery(query); } - + /** * Parse a query. A query string is any number of parameter-value clauses * separated by any non-zero number of ampersands. A parameter-value clause @@ -631,7 +631,7 @@ public class UrlQuerySanitizer { } } } - + /** * Get a set of all of the parameters found in the sanitized query. *

    @@ -641,7 +641,7 @@ public class UrlQuerySanitizer { public Set getParameterSet() { return mEntries.keySet(); } - + /** * An array list of all of the parameter value pairs in the sanitized * query, in the order they appeared in the query. May contain duplicate @@ -691,7 +691,7 @@ public class UrlQuerySanitizer { } mSanitizers.put(parameter, valueSanitizer); } - + /** * Register a value sanitizer for an array of parameters. * @param parameters An array of unencoded parameter names. @@ -705,7 +705,7 @@ public class UrlQuerySanitizer { mSanitizers.put(parameters[i], valueSanitizer); } } - + /** * Set whether or not unregistered parameters are allowed. If they * are not allowed, then they will be dropped when a query is sanitized. @@ -718,7 +718,7 @@ public class UrlQuerySanitizer { boolean allowUnregisteredParamaters) { mAllowUnregisteredParamaters = allowUnregisteredParamaters; } - + /** * Get whether or not unregistered parameters are allowed. If not * allowed, they will be dropped when a query is parsed. @@ -728,10 +728,10 @@ public class UrlQuerySanitizer { public boolean getAllowUnregisteredParamaters() { return mAllowUnregisteredParamaters; } - + /** * Set whether or not the first occurrence of a repeated parameter is - * preferred. True means the first repeated parameter is preferred. + * preferred. True means the first repeated parameter is preferred. * False means that the last repeated parameter is preferred. *

    * The preferred parameter is the one that is returned when getParameter @@ -746,7 +746,7 @@ public class UrlQuerySanitizer { boolean preferFirstRepeatedParameter) { mPreferFirstRepeatedParameter = preferFirstRepeatedParameter; } - + /** * Get whether or not the first occurrence of a repeated parameter is * preferred. @@ -757,10 +757,10 @@ public class UrlQuerySanitizer { public boolean getPreferFirstRepeatedParameter() { return mPreferFirstRepeatedParameter; } - + /** * Parse an escaped parameter-value pair. The default implementation - * unescapes both the parameter and the value, then looks up the + * unescapes both the parameter and the value, then looks up the * effective value sanitizer for the parameter and uses it to sanitize * the value. If all goes well then addSanitizedValue is called with * the unescaped parameter and the sanitized unescaped value. @@ -779,7 +779,7 @@ public class UrlQuerySanitizer { String sanitizedValue = valueSanitizer.sanitize(unescapedValue); addSanitizedEntry(unescapedParameter, sanitizedValue); } - + /** * Record a sanitized parameter-value pair. Override if you want to * do additional filtering or validation. @@ -796,7 +796,7 @@ public class UrlQuerySanitizer { } mEntries.put(parameter, value); } - + /** * Get the value sanitizer for a parameter. Returns null if there * is no value sanitizer registered for the parameter. @@ -807,7 +807,7 @@ public class UrlQuerySanitizer { public ValueSanitizer getValueSanitizer(String parameter) { return mSanitizers.get(parameter); } - + /** * Get the effective value sanitizer for a parameter. Like getValueSanitizer, * except if there is no value sanitizer registered for a parameter, and @@ -823,7 +823,7 @@ public class UrlQuerySanitizer { } return sanitizer; } - + /** * Unescape an escaped string. *

      @@ -867,7 +867,7 @@ public class UrlQuerySanitizer { } return stringBuilder.toString(); } - + /** * Test if a character is a hexidecimal digit. Both upper case and lower * case hex digits are allowed. @@ -877,7 +877,7 @@ public class UrlQuerySanitizer { protected boolean isHexDigit(char c) { return decodeHexDigit(c) >= 0; } - + /** * Convert a character that represents a hexidecimal digit into an integer. * If the character is not a hexidecimal digit, then -1 is returned. @@ -885,7 +885,7 @@ public class UrlQuerySanitizer { * @param c the hexidecimal digit. * @return the integer value of the hexidecimal digit. */ - + protected int decodeHexDigit(char c) { if (c >= '0' && c <= '9') { return c - '0'; @@ -900,7 +900,7 @@ public class UrlQuerySanitizer { return -1; } } - + /** * Clear the existing entries. Called to get ready to parse a new * query string. diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java index c6615da03eef..1a287c814dca 100644 --- a/core/java/android/pim/RecurrenceSet.java +++ b/core/java/android/pim/RecurrenceSet.java @@ -140,7 +140,6 @@ public class RecurrenceSet { recurrence = recurrence.substring(tzidx + 1); } Time time = new Time(tz); - boolean rdateNotInUtc = !tz.equals(Time.TIMEZONE_UTC); String[] rawDates = recurrence.split(","); int n = rawDates.length; long[] dates = new long[n]; diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index 05c295224374..02ab1da35fda 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -88,6 +88,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) { mPreferenceGroup = preferenceGroup; + // If this group gets or loses any children, let us know + mPreferenceGroup.setOnPreferenceChangeInternalListener(this); + mPreferenceList = new ArrayList(); mPreferenceClassNames = new ArrayList(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 054da1de5f48..c6a7b407101c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -879,15 +879,6 @@ public final class Settings { public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios"; /** - * The interval in milliseconds after which Wi-Fi is considered idle. - * When idle, it is possible for the device to be switched from Wi-Fi to - * the mobile data network. - * - * @hide pending API Council approval - */ - public static final String WIFI_IDLE_MS = "wifi_idle_ms"; - - /** * The policy for deciding when Wi-Fi should go to sleep (which will in * turn switch to using the mobile data as an Internet connection). *

      @@ -1288,6 +1279,12 @@ public final class Settings { */ public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled"; + /** + * Whether the haptic feedback (long presses, ...) are enabled. The value is + * boolean (1 or 0). + */ + public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; + // Settings moved to Settings.Secure /** @@ -2732,6 +2729,13 @@ public final class Settings { "gprs_register_check_period_ms"; /** + * The interval in milliseconds after which Wi-Fi is considered idle. + * When idle, it is possible for the device to be switched from Wi-Fi to + * the mobile data network. + */ + public static final String WIFI_IDLE_MS = "wifi_idle_ms"; + + /** * Screen timeout in milliseconds corresponding to the * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest * possible screen timeout behavior.) diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index d1497619a92e..7c15045dbfb4 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -25,8 +25,8 @@ package android.server; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; // just for dump() import android.bluetooth.BluetoothError; +import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothIntent; import android.bluetooth.IBluetoothDevice; import android.bluetooth.IBluetoothDeviceCallback; @@ -35,23 +35,20 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.os.RemoteException; -import android.provider.Settings; -import android.util.Log; import android.os.Binder; import android.os.Handler; import android.os.Message; +import android.os.RemoteException; import android.os.SystemService; +import android.provider.Settings; +import android.util.Log; -import java.io.IOException; import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.FileWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class BluetoothDeviceService extends IBluetoothDevice.Stub { @@ -119,7 +116,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public synchronized boolean disable() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - + if (mEnableThread != null && mEnableThread.isAlive()) { return false; } @@ -229,9 +226,9 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { long origCallerIdentityToken = Binder.clearCallingIdentity(); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, bluetoothOn ? 1 : 0); - Binder.restoreCallingIdentity(origCallerIdentityToken); + Binder.restoreCallingIdentity(origCallerIdentityToken); } - + private native int enableNative(); private native int disableNative(); @@ -247,6 +244,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public class BondState { private final HashMap mState = new HashMap(); private final HashMap mPinAttempt = new HashMap(); + private final ArrayList mAutoPairingFailures = new ArrayList(); public synchronized void loadBondState() { if (!mIsEnabled) { @@ -281,8 +279,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { intent.putExtra(BluetoothIntent.BOND_PREVIOUS_STATE, oldState); if (state == BluetoothDevice.BOND_NOT_BONDED) { if (reason <= 0) { - Log.w(TAG, "setBondState() called to unbond device with invalid reason code " + - "Setting reason = BOND_RESULT_REMOVED"); + Log.w(TAG, "setBondState() called to unbond device, but reason code is " + + "invalid. Overriding reason code with BOND_RESULT_REMOVED"); reason = BluetoothDevice.UNBOND_REASON_REMOVED; } intent.putExtra(BluetoothIntent.REASON, reason); @@ -290,11 +288,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } else { mState.put(address, state); } - if (state == BluetoothDevice.BOND_BONDING) { - mPinAttempt.put(address, Integer.valueOf(0)); - } else { - mPinAttempt.remove(address); - } + mContext.sendBroadcast(intent, BLUETOOTH_PERM); } @@ -316,6 +310,24 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { return result.toArray(new String[result.size()]); } + public synchronized void addAutoPairingFailure(String address) { + if (!mAutoPairingFailures.contains(address)) { + mAutoPairingFailures.add(address); + } + } + + public synchronized boolean isAutoPairingAttemptsInProgress(String address) { + return getAttempt(address) != 0; + } + + public synchronized void clearPinAttempts(String address) { + mPinAttempt.remove(address); + } + + public synchronized boolean hasAutoPairingFailed(String address) { + return mAutoPairingFailures.contains(address); + } + public synchronized int getAttempt(String address) { Integer attempt = mPinAttempt.get(address); if (attempt == null) { @@ -326,10 +338,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public synchronized void attempt(String address) { Integer attempt = mPinAttempt.get(address); + int newAttempt; if (attempt == null) { - return; + newAttempt = 1; + } else { + newAttempt = attempt.intValue() + 1; } - mPinAttempt.put(address, new Integer(attempt.intValue() + 1)); + mPinAttempt.put(address, new Integer(newAttempt)); } } @@ -508,7 +523,11 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { return false; } address = address.toUpperCase(); - if (mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) { + + // Check for bond state only if we are not performing auto + // pairing exponential back-off attempts. + if (!mBondState.isAutoPairingAttemptsInProgress(address) && + mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) { return false; } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 0f60fae34620..b5e409027ce9 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -24,6 +24,8 @@ import android.bluetooth.BluetoothIntent; import android.bluetooth.IBluetoothDeviceCallback; import android.content.Context; import android.content.Intent; +import android.os.Handler; +import android.os.Message; import android.os.RemoteException; import android.util.Log; @@ -48,9 +50,33 @@ class BluetoothEventLoop { private BluetoothDeviceService mBluetoothService; private Context mContext; + private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; + + // The time (in millisecs) to delay the pairing attempt after the first + // auto pairing attempt fails. We use an exponential delay with + // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and + // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. + private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; + private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; + private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: + String address = (String)msg.obj; + if (address != null) { + mBluetoothService.createBond(address); + return; + } + break; + } + } + }; + static { classInitNative(); } private static native void classInitNative(); @@ -149,16 +175,6 @@ class BluetoothEventLoop { mContext.sendBroadcast(intent, BLUETOOTH_PERM); } - private void onPairingRequest() { - Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - } - - private void onPairingCancel() { - Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - } - private void onRemoteDeviceFound(String address, int deviceClass, short rssi) { Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION); intent.putExtra(BluetoothIntent.ADDRESS, address); @@ -214,12 +230,55 @@ class BluetoothEventLoop { address = address.toUpperCase(); if (result == BluetoothError.SUCCESS) { mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); + if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { + mBluetoothService.getBondState().clearPinAttempts(address); + } + } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && + mBluetoothService.getBondState().getAttempt(address) == 1) { + mBluetoothService.getBondState().addAutoPairingFailure(address); + pairingAttempt(address, result); + } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && + mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { + pairingAttempt(address, result); } else { mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED, result); + if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { + mBluetoothService.getBondState().clearPinAttempts(address); + } } } + private void pairingAttempt(String address, int result) { + // This happens when our initial guess of "0000" as the pass key + // fails. Try to create the bond again and display the pin dialog + // to the user. Use back-off while posting the delayed + // message. The initial value is + // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is + // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is + // reached, display an error to the user. + int attempt = mBluetoothService.getBondState().getAttempt(address); + if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > + MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { + mBluetoothService.getBondState().clearPinAttempts(address); + mBluetoothService.getBondState().setBondState(address, + BluetoothDevice.BOND_NOT_BONDED, result); + return; + } + + Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); + message.obj = address; + boolean postResult = mHandler.sendMessageDelayed(message, + attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); + if (!postResult) { + mBluetoothService.getBondState().clearPinAttempts(address); + mBluetoothService.getBondState().setBondState(address, + BluetoothDevice.BOND_NOT_BONDED, result); + return; + } + mBluetoothService.getBondState().attempt(address); + } + private void onBondingCreated(String address) { mBluetoothService.getBondState().setBondState(address.toUpperCase(), BluetoothDevice.BOND_BONDED); @@ -253,12 +312,12 @@ class BluetoothEventLoop { case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: - if (mBluetoothService.getBondState().getAttempt(address) < 1) { + if (!mBluetoothService.getBondState().hasAutoPairingFailed(address)) { mBluetoothService.getBondState().attempt(address); mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); return; } - } + } } Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); intent.putExtra(BluetoothIntent.ADDRESS, address); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index a559b9d6f43d..6df0b35cb497 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -16,6 +16,7 @@ package android.text.method; +import android.util.Log; import android.view.KeyEvent; import android.text.*; import android.widget.TextView; @@ -185,15 +186,9 @@ implements MovementMethod if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) { int repeat = event.getRepeatCount(); - boolean first = true; boolean handled = false; while ((--repeat) > 0) { - if (first && executeDown(view, text, code)) { - handled = true; - MetaKeyKeyListener.adjustMetaAfterKeypress(text); - MetaKeyKeyListener.resetLockedMeta(text); - } - first = false; + handled |= executeDown(view, text, code); } return handled; } diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java index d89fbec557ae..39ad97689a5d 100644 --- a/core/java/android/text/method/MetaKeyKeyListener.java +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -287,10 +287,10 @@ public abstract class MetaKeyKeyListener { } public static void clearMetaKeyState(Editable content, int states) { - if ((states&META_SHIFT_ON) != 0) resetLock(content, CAP); - if ((states&META_ALT_ON) != 0) resetLock(content, ALT); - if ((states&META_SYM_ON) != 0) resetLock(content, SYM); - if ((states&META_SELECTING) != 0) resetLock(content, SELECTING); + if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP); + if ((states&META_ALT_ON) != 0) content.removeSpan(ALT); + if ((states&META_SYM_ON) != 0) content.removeSpan(SYM); + if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING); } /** diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java index 85adabd72abb..fad4f64ffe1e 100644 --- a/core/java/android/text/method/PasswordTransformationMethod.java +++ b/core/java/android/text/method/PasswordTransformationMethod.java @@ -105,8 +105,10 @@ implements TransformationMethod, TextWatcher sp.removeSpan(old[i]); } - sp.setSpan(new Visible(sp, this), start, start + count, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (count == 1) { + sp.setSpan(new Visible(sp, this), start, start + count, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } } } diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java new file mode 100644 index 000000000000..cc3563c40f8d --- /dev/null +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -0,0 +1,45 @@ +/* + * 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.view; + +/** + * Constants to be used to perform haptic feedback effects via + * {@link View#performHapticFeedback(int)} + */ +public class HapticFeedbackConstants { + + private HapticFeedbackConstants() {} + + public static final int LONG_PRESS = 0; + + /** @hide pending API council */ + public static final int ZOOM_RING_TICK = 1; + + /** + * Flag for {@link View#performHapticFeedback(int, int) + * View.performHapticFeedback(int, int)}: Ignore the setting in the + * view for whether to perform haptic feedback, do it always. + */ + public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001; + + /** + * Flag for {@link View#performHapticFeedback(int, int) + * View.performHapticFeedback(int, int)}: Ignore the global setting + * for whether to perform haptic feedback, do it always. + */ + public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002; +} diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 7276f173cf3f..115685669406 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -106,5 +106,6 @@ interface IWindowSession { void setInTouchMode(boolean showFocus); boolean getInTouchMode(); + + boolean performHapticFeedback(IWindow window, int effectId, boolean always); } - diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a51b5646c157..1d5e7cd49ca5 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -836,6 +836,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback { public static final int SOUND_EFFECTS_ENABLED = 0x08000000; /** + * View flag indicating whether this view should have haptic feedback + * enabled for events such as long presses. + */ + public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000; + + /** * Use with {@link #focusSearch}. Move focus to the previous selectable * item. */ @@ -1637,6 +1643,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { public View(Context context) { mContext = context; mResources = context != null ? context.getResources() : null; + mViewFlags = SOUND_EFFECTS_ENABLED|HAPTIC_FEEDBACK_ENABLED; ++sInstanceCount; } @@ -1703,9 +1710,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback { int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; - viewFlagValues |= SOUND_EFFECTS_ENABLED; - viewFlagMasks |= SOUND_EFFECTS_ENABLED; - final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); @@ -1801,6 +1805,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback { viewFlagValues &= ~SOUND_EFFECTS_ENABLED; viewFlagMasks |= SOUND_EFFECTS_ENABLED; } + case com.android.internal.R.styleable.View_hapticFeedbackEnabled: + if (!a.getBoolean(attr, true)) { + viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED; + viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED; + } case R.styleable.View_scrollbars: final int scrollbars = a.getInt(attr, SCROLLBARS_NONE); if (scrollbars != SCROLLBARS_NONE) { @@ -2182,6 +2191,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (!handled) { handled = showContextMenu(); } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } return handled; } @@ -2742,7 +2754,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * Set whether this view should have sound effects enabled for events such as * clicking and touching. * - * You may wish to disable sound effects for a view if you already play sounds, + *

      You may wish to disable sound effects for a view if you already play sounds, * for instance, a dial key that plays dtmf tones. * * @param soundEffectsEnabled whether sound effects are enabled for this view. @@ -2768,6 +2780,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } /** + * Set whether this view should have haptic feedback for events such as + * long presses. + * + *

      You may wish to disable haptic feedback if your view already controls + * its own haptic feedback. + * + * @param hapticFeedbackEnabled whether haptic feedback enabled for this view. + * @see #isHapticFeedbackEnabled() + * @see #performHapticFeedback(int) + * @attr ref android.R.styleable#View_hapticFeedbackEnabled + */ + public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) { + setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED); + } + + /** + * @return whether this view should have haptic feedback enabled for events + * long presses. + * + * @see #setHapticFeedbackEnabled(boolean) + * @see #performHapticFeedback(int) + * @attr ref android.R.styleable#View_hapticFeedbackEnabled + */ + @ViewDebug.ExportedProperty + public boolean isHapticFeedbackEnabled() { + return HAPTIC_FEEDBACK_ENABLED == (mViewFlags & HAPTIC_FEEDBACK_ENABLED); + } + + /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. @@ -7312,20 +7353,57 @@ public class View implements Drawable.Callback, KeyEvent.Callback { /** * Play a sound effect for this view. * - * The framework will play sound effects for some built in actions, such as + *

      The framework will play sound effects for some built in actions, such as * clicking, but you may wish to play these effects in your widget, * for instance, for internal navigation. * - * The sound effect will only be played if sound effects are enabled by the user, and + *

      The sound effect will only be played if sound effects are enabled by the user, and * {@link #isSoundEffectsEnabled()} is true. * * @param soundConstant One of the constants defined in {@link SoundEffectConstants} */ - protected void playSoundEffect(int soundConstant) { - if (mAttachInfo == null || mAttachInfo.mSoundEffectPlayer == null || !isSoundEffectsEnabled()) { + public void playSoundEffect(int soundConstant) { + if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) { return; } - mAttachInfo.mSoundEffectPlayer.playSoundEffect(soundConstant); + mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant); + } + + /** + * Provide haptic feedback to the user for this view. + * + *

      The framework will provide haptic feedback for some built in actions, + * such as long presses, but you may wish to provide feedback for your + * own widget. + * + *

      The feedback will only be performed if + * {@link #isHapticFeedbackEnabled()} is true. + * + * @param feedbackConstant One of the constants defined in + * {@link HapticFeedbackConstants} + */ + public boolean performHapticFeedback(int feedbackConstant) { + return performHapticFeedback(feedbackConstant, 0); + } + + /** + * Like {@link #performHapticFeedback(int)}, with additional options. + * + * @param feedbackConstant One of the constants defined in + * {@link HapticFeedbackConstants} + * @param flags Additional flags as per {@link HapticFeedbackConstants}. + */ + public boolean performHapticFeedback(int feedbackConstant, int flags) { + if (mAttachInfo == null) { + return false; + } + if ((flags&HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0 + && !isHapticFeedbackEnabled()) { + return false; + } + return mAttachInfo.mRootCallbacks.performHapticFeedback( + feedbackConstant, + (flags&HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0); } /** @@ -7704,8 +7782,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { */ static class AttachInfo { - interface SoundEffectPlayer { + interface Callbacks { void playSoundEffect(int effectId); + boolean performHapticFeedback(int effectId, boolean always); } /** @@ -7775,7 +7854,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { final IBinder mWindowToken; - final SoundEffectPlayer mSoundEffectPlayer; + final Callbacks mRootCallbacks; /** * The top view of the hierarchy. @@ -7922,12 +8001,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * @param handler the events handler the view must use */ AttachInfo(IWindowSession session, IWindow window, - Handler handler, SoundEffectPlayer effectPlayer) { + Handler handler, Callbacks effectPlayer) { mSession = session; mWindow = window; mWindowToken = window.asBinder(); mHandler = handler; - mSoundEffectPlayer = effectPlayer; + mRootCallbacks = effectPlayer; } } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 4e46397bae38..ccfa6bf97fcf 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -61,7 +61,7 @@ import static javax.microedition.khronos.opengles.GL10.*; */ @SuppressWarnings({"EmptyCatchBlock"}) public final class ViewRoot extends Handler implements ViewParent, - View.AttachInfo.SoundEffectPlayer { + View.AttachInfo.Callbacks { private static final String TAG = "ViewRoot"; private static final boolean DBG = false; @SuppressWarnings({"ConstantConditionalExpression"}) @@ -1637,7 +1637,7 @@ public final class ViewRoot extends Handler implements ViewParent, dispatchDetachedFromWindow(); break; case DISPATCH_KEY_FROM_IME: - if (true) Log.v( + if (LOCAL_LOGV) Log.v( "ViewRoot", "Dispatching key " + msg.obj + " from IME to " + mView); deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false); @@ -2238,6 +2238,17 @@ public final class ViewRoot extends Handler implements ViewParent, /** * {@inheritDoc} */ + public boolean performHapticFeedback(int effectId, boolean always) { + try { + return sWindowSession.performHapticFeedback(mWindow, effectId, always); + } catch (RemoteException e) { + return false; + } + } + + /** + * {@inheritDoc} + */ public View focusSearch(View focused, int direction) { checkThread(); if (!(mView instanceof ViewGroup)) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d08a6fa67558..406af3e3d384 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -925,7 +925,7 @@ public interface WindowManager extends ViewManager { sb.append(Integer.toHexString(windowAnimations)); } if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { - sb.append("or="); + sb.append(" or="); sb.append(screenOrientation); } sb.append('}'); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 542b35fc6b8d..051f823fdfde 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -771,4 +771,15 @@ public interface WindowManagerPolicy { public boolean isCheekPressedAgainstScreen(MotionEvent ev); public void setCurrentOrientation(int newOrientation); + + /** + * Call from application to perform haptic feedback on its window. + */ + public boolean performHapticFeedback(WindowState win, int effectId, boolean always); + + /** + * Called when we have stopped keeping the screen on because a window + * requesting this is no longer visible. + */ + public void screenOnStopped(); } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 56c6c924fbf3..9509b15317eb 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -371,6 +371,14 @@ public class BaseInputConnection implements InputConnection { if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); final Editable content = getEditable(); if (content == null) return false; + int len = content.length(); + if (start > len || end > len) { + // If the given selection is out of bounds, just ignore it. + // Most likely the text was changed out from under the IME, + // the the IME is going to have to update all of its state + // anyway. + return true; + } Selection.setSelection(content, start, end); return true; } @@ -396,20 +404,10 @@ public class BaseInputConnection implements InputConnection { } /** - * Provides standard implementation for hiding the status icon associated - * with the current input method. + * Updates InputMethodManager with the current fullscreen mode. */ - public boolean hideStatusIcon() { - mIMM.updateStatusIcon(0, null); - return true; - } - - /** - * Provides standard implementation for showing the status icon associated - * with the current input method. - */ - public boolean showStatusIcon(String packageName, int resId) { - mIMM.updateStatusIcon(resId, packageName); + public boolean reportFullscreenMode(boolean enabled) { + mIMM.setFullscreenMode(enabled); return true; } @@ -420,7 +418,11 @@ public class BaseInputConnection implements InputConnection { Editable content = getEditable(); if (content != null) { - if (content.length() == 1) { + final int N = content.length(); + if (N == 0) { + return; + } + if (N == 1) { // If it's 1 character, we have a chance of being // able to generate normal key events... if (mKeyCharacterMap == null) { diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 8c30d3fd4128..13173f656397 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -267,6 +267,13 @@ public interface InputConnection { public boolean clearMetaKeyStates(int states); /** + * Called by the IME to tell the client when it switches between fullscreen + * and normal modes. This will normally be called for you by the standard + * implementation of {@link android.inputmethodservice.InputMethodService}. + */ + public boolean reportFullscreenMode(boolean enabled); + + /** * API to send private commands from an input method to its connected * editor. This can be used to provide domain-specific features that are * only known between certain input methods and their clients. Note that @@ -284,23 +291,4 @@ public interface InputConnection { * valid. */ public boolean performPrivateCommand(String action, Bundle data); - - /** - * Show an icon in the status bar. - * - * @param packageName The package holding the icon resource to be shown. - * @param resId The resource id of the icon to show. - * - * @return Returns true on success, false if the input connection is no longer - * valid. - */ - public boolean showStatusIcon(String packageName, int resId); - - /** - * Hide the icon shown in the status bar. - * - * @return Returns true on success, false if the input connection is no longer - * valid. - */ - public boolean hideStatusIcon(); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 99d5aa511df9..fe1416680620 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -214,6 +214,11 @@ public final class InputMethodManager { */ boolean mActive = false; + /** + * As reported by IME through InputConnection. + */ + boolean mFullscreenMode; + // ----------------------------------------------------------- /** @@ -374,6 +379,7 @@ public final class InputMethodManager { public void setActive(boolean active) { mActive = active; + mFullscreenMode = false; } }; @@ -443,14 +449,36 @@ public final class InputMethodManager { } } - public void updateStatusIcon(int iconId, String iconPackage) { + public void showStatusIcon(IBinder imeToken, String packageName, int iconId) { + try { + mService.updateStatusIcon(imeToken, packageName, iconId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void hideStatusIcon(IBinder imeToken) { try { - mService.updateStatusIcon(iconId, iconPackage); + mService.updateStatusIcon(imeToken, null, 0); } catch (RemoteException e) { throw new RuntimeException(e); } } + /** @hide */ + public void setFullscreenMode(boolean enabled) { + mFullscreenMode = true; + } + + /** + * Allows you to discover whether the attached input method is running + * in fullscreen mode. Return true if it is fullscreen, entirely covering + * your UI, else returns false. + */ + public boolean isFullscreenMode() { + return mFullscreenMode; + } + /** * Return true if the given view is the currently active view for the * input method. @@ -503,7 +531,6 @@ public final class InputMethodManager { void finishInputLocked() { if (mServedView != null) { if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); - updateStatusIcon(0, null); if (mCurrentTextBoxAttribute != null) { try { diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 5a37f040d5ff..07c1a5d1c025 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -171,6 +171,10 @@ public final class CookieManager { boolean pathMatch(String urlPath) { if (urlPath.startsWith(path)) { int len = path.length(); + if (len == 0) { + Log.w(LOGTAG, "Empty cookie path"); + return false; + } int urlLen = urlPath.length(); if (path.charAt(len-1) != PATH_DELIM && urlLen > len) { // make sure /wee doesn't match /we @@ -864,7 +868,10 @@ public final class CookieManager { "illegal format for max-age: " + value); } } else if (name.equals(PATH)) { - cookie.path = value; + // only allow non-empty path value + if (value.length() > 0) { + cookie.path = value; + } } else if (name.equals(DOMAIN)) { int lastPeriod = value.lastIndexOf(PERIOD); if (lastPeriod == 0) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 330670042468..bdbf38a70369 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -260,19 +260,8 @@ public class WebView extends AbsoluteLayout // Whether we are in the drag tap mode, which exists starting at the second // tap's down, through its move, and includes its up. These events should be // given to the method on the zoom controller. - private boolean mInZoomTapDragMode; - - // The event time of the previous touch up. - private long mPreviousUpTime; - - private Runnable mRemoveReleaseSingleTap = new Runnable() { - public void run() { - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - } - }; - + private boolean mInZoomTapDragMode = false; + // Whether to prevent drag during touch. The initial value depends on // mForwardTouchEvents. If WebCore wants touch events, we assume it will // take control of touch events unless it says no for touch down event. @@ -517,6 +506,11 @@ public class WebView extends AbsoluteLayout private ZoomRingController mZoomRingController; + // These keep track of the center point of the zoom ring. They are used to + // determine the point around which we should zoom. + private float mZoomCenterX; + private float mZoomCenterY; + private ZoomRingController.OnZoomListener mZoomListener = new ZoomRingController.OnZoomListener() { @@ -554,12 +548,9 @@ public class WebView extends AbsoluteLayout deltaZoomLevel == 0) { return false; } - - int deltaX = centerX - getViewWidth() / 2; - int deltaY = centerY - getViewHeight() / 2; + mZoomCenterX = (float) centerX; + mZoomCenterY = (float) centerY; - pinScrollBy(deltaX, deltaY, false, 0); - while (deltaZoomLevel != 0) { if (deltaZoomLevel > 0) { if (!zoomIn()) return false; @@ -569,15 +560,16 @@ public class WebView extends AbsoluteLayout deltaZoomLevel++; } } - - pinScrollBy(-deltaX, -deltaY, false, 0); - + return true; } public void onSimpleZoom(boolean zoomIn) { - if (zoomIn) zoomIn(); - else zoomOut(); + if (zoomIn) { + zoomIn(); + } else { + zoomOut(); + } } }; @@ -1586,8 +1578,8 @@ public class WebView extends AbsoluteLayout int oldX = mScrollX; int oldY = mScrollY; float ratio = scale * mInvActualScale; // old inverse - float sx = ratio * oldX + (ratio - 1) * getViewWidth() * 0.5f; - float sy = ratio * oldY + (ratio - 1) * getViewHeight() * 0.5f; + float sx = ratio * oldX + (ratio - 1) * mZoomCenterX; + float sy = ratio * oldY + (ratio - 1) * mZoomCenterY; // now update our new scale and inverse if (scale != mActualScale && !mPreviewZoomOnly) { @@ -2264,8 +2256,8 @@ public class WebView extends AbsoluteLayout zoomScale = mZoomScale; } float scale = (mActualScale - zoomScale) * mInvActualScale; - float tx = scale * ((getLeft() + getRight()) * 0.5f + mScrollX); - float ty = scale * ((getTop() + getBottom()) * 0.5f + mScrollY); + float tx = scale * (mZoomCenterX + mScrollX); + float ty = scale * (mZoomCenterY + mScrollY); // this block pins the translate to "legal" bounds. This makes the // animation a bit non-obvious, but it means we won't pop when the @@ -3025,8 +3017,8 @@ public class WebView extends AbsoluteLayout (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0); break; case KeyEvent.KEYCODE_9: - debugDump(); - break; + nativeInstrumentReport(); + return true; } } @@ -3161,6 +3153,7 @@ public class WebView extends AbsoluteLayout * @hide */ public void emulateShiftHeld() { + mExtendSelection = false; mShiftIsPressed = true; } @@ -3176,6 +3169,7 @@ public class WebView extends AbsoluteLayout mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection); copiedSomething = true; } + mExtendSelection = false; } mShiftIsPressed = false; if (mTouchMode == TOUCH_SELECT_MODE) { @@ -3218,6 +3212,11 @@ public class WebView extends AbsoluteLayout } } + /** + * @deprecated WebView should not have implemented + * ViewTreeObserver.OnGlobalFocusChangeListener. This method + * does nothing now. + */ @Deprecated public void onGlobalFocusChanged(View oldFocus, View newFocus) { } @@ -3281,7 +3280,11 @@ public class WebView extends AbsoluteLayout @Override protected void onSizeChanged(int w, int h, int ow, int oh) { super.onSizeChanged(w, h, ow, oh); - + // Center zooming to the center of the screen. This is appropriate for + // this case of zooming, and it also sets us up properly if we remove + // the new zoom ring controller + mZoomCenterX = getViewWidth() * .5f; + mZoomCenterY = getViewHeight() * .5f; // we always force, in case our height changed, in which case we still // want to send the notification over to webkit setNewZoomScale(mActualScale, true); @@ -3342,25 +3345,12 @@ public class WebView extends AbsoluteLayout + mTouchMode); } - if (mZoomRingController.isVisible()) { - if (mInZoomTapDragMode) { - mZoomRingController.handleDoubleTapEvent(ev); - if (ev.getAction() == MotionEvent.ACTION_UP) { - // Just released the second tap, no longer in tap-drag mode - mInZoomTapDragMode = false; - } - return true; - } else { - // TODO: properly do this. - /* - * When the zoom widget is showing, the user can tap outside of - * it to dismiss it. Furthermore, he can drag outside of it to - * pan the browser. However, we do not want a tap on a link to - * open the link. - */ - post(mRemoveReleaseSingleTap); - // Continue through to normal processing + if (mZoomRingController.isVisible() && mInZoomTapDragMode) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + // Just released the second tap, no longer in tap-drag mode + mInZoomTapDragMode = false; } + return mZoomRingController.handleDoubleTapEvent(ev); } int action = ev.getAction(); @@ -3418,21 +3408,19 @@ public class WebView extends AbsoluteLayout , viewToContent(mSelectY), false); mTouchSelection = mExtendSelection = true; } else if (!ZoomRingController.useOldZoom(mContext) && - eventTime - mPreviousUpTime < DOUBLE_TAP_TIMEOUT && - getSettings().supportZoom() && - mMinZoomScale < mMaxZoomScale) { + mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { // Found doubletap, invoke the zoom controller - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); mZoomRingController.setVisible(true); mInZoomTapDragMode = true; - mZoomRingController.handleDoubleTapEvent(ev); + return mZoomRingController.handleDoubleTapEvent(ev); } else { mTouchMode = TOUCH_INIT_MODE; mPreventDrag = mForwardTouchEvents; } - if (mTouchMode == TOUCH_INIT_MODE) { + // don't trigger the link if zoom ring is visible + if (mTouchMode == TOUCH_INIT_MODE + && !mZoomRingController.isVisible()) { mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); } @@ -3485,9 +3473,6 @@ public class WebView extends AbsoluteLayout mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); } - // Prevent double-tap from being invoked - mPreviousUpTime = 0; - // if it starts nearly horizontal or vertical, enforce it int ax = Math.abs(deltaX); int ay = Math.abs(deltaY); @@ -3597,6 +3582,10 @@ public class WebView extends AbsoluteLayout case MotionEvent.ACTION_UP: { switch (mTouchMode) { case TOUCH_INIT_MODE: // tap + if (mZoomRingController.isVisible()) { + // don't trigger the link if zoom ring is visible + break; + } mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); if (getSettings().supportZoom() && (mMinZoomScale < mMaxZoomScale)) { @@ -3611,7 +3600,7 @@ public class WebView extends AbsoluteLayout break; case TOUCH_SELECT_MODE: commitCopy(); - mTouchSelection = mExtendSelection = false; + mTouchSelection = false; break; case SCROLL_ZOOM_ANIMATION_IN: case SCROLL_ZOOM_ANIMATION_OUT: @@ -3679,7 +3668,6 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } - mPreviousUpTime = eventTime; break; } case MotionEvent.ACTION_CANCEL: { @@ -4110,6 +4098,14 @@ public class WebView extends AbsoluteLayout } /** + * @hide pending API council? Assuming we make ZoomRingController itself + * public, which I think we will. + */ + public ZoomRingController getZoomRingController() { + return mZoomRingController; + } + + /** * Perform zoom in in the webview * @return TRUE if zoom in succeeds. FALSE if no zoom changes. */ @@ -4193,16 +4189,15 @@ public class WebView extends AbsoluteLayout return; } switchOutDrawHistory(); - // FIXME: we don't know if the current (x,y) is on a focus node or - // not -- so playing the sound effect here is premature - if (nativeUpdateFocusNode()) { - playSoundEffect(SoundEffectConstants.CLICK); - } // mLastTouchX and mLastTouchY are the point in the current viewport int contentX = viewToContent((int) mLastTouchX + mScrollX); int contentY = viewToContent((int) mLastTouchY + mScrollY); int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop(); nativeMotionUp(contentX, contentY, contentSize, true); + if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField + && !mFocusNode.mIsTextArea) { + playSoundEffect(SoundEffectConstants.CLICK); + } } @Override @@ -5013,6 +5008,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeUpdateFocusNode(); private native Rect nativeGetFocusRingBounds(); private native Rect nativeGetNavBounds(); + private native void nativeInstrumentReport(); private native void nativeMarkNodeInvalid(int node); private native void nativeMotionUp(int x, int y, int slop, boolean isClick); // returns false if it handled the key diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 8f788872bfe1..b979032f55de 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -330,8 +330,7 @@ final class WebViewCore { String currentText, int keyCode, int keyValue, boolean down, boolean cap, boolean fn, boolean sym); - private native void nativeSaveDocumentState(int frame, int node, int x, - int y); + private native void nativeSaveDocumentState(int frame); private native void nativeSetFinalFocus(int framePtr, int nodePtr, int x, int y, boolean block); @@ -777,8 +776,7 @@ final class WebViewCore { case SAVE_DOCUMENT_STATE: { FocusData fDat = (FocusData) msg.obj; - nativeSaveDocumentState(fDat.mFrame, fDat.mNode, - fDat.mX, fDat.mY); + nativeSaveDocumentState(fDat.mFrame); break; } diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 96f36983343a..1004e30ef5e6 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -531,33 +531,34 @@ public class WebViewDatabase { * @param url The url * @return CacheResult The CacheManager.CacheResult */ - @SuppressWarnings("deprecation") CacheResult getCache(String url) { if (url == null || mCacheDatabase == null) { return null; } - CacheResult ret = null; - final String s = "SELECT filepath, lastmodify, etag, expires, mimetype, encoding, httpstatus, location, contentlength FROM cache WHERE url = "; - StringBuilder sb = new StringBuilder(256); - sb.append(s); - DatabaseUtils.appendEscapedSQLString(sb, url); - Cursor cursor = mCacheDatabase.rawQuery(sb.toString(), null); + Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, " + + "mimetype, encoding, httpstatus, location, contentlength " + + "FROM cache WHERE url = ?", + new String[] { url }); - if (cursor.moveToFirst()) { - ret = new CacheResult(); - ret.localPath = cursor.getString(0); - ret.lastModified = cursor.getString(1); - ret.etag = cursor.getString(2); - ret.expires = cursor.getLong(3); - ret.mimeType = cursor.getString(4); - ret.encoding = cursor.getString(5); - ret.httpStatusCode = cursor.getInt(6); - ret.location = cursor.getString(7); - ret.contentLength = cursor.getLong(8); + try { + if (cursor.moveToFirst()) { + CacheResult ret = new CacheResult(); + ret.localPath = cursor.getString(0); + ret.lastModified = cursor.getString(1); + ret.etag = cursor.getString(2); + ret.expires = cursor.getLong(3); + ret.mimeType = cursor.getString(4); + ret.encoding = cursor.getString(5); + ret.httpStatusCode = cursor.getInt(6); + ret.location = cursor.getString(7); + ret.contentLength = cursor.getLong(8); + return ret; + } + } finally { + if (cursor != null) cursor.close(); } - cursor.close(); - return ret; + return null; } /** @@ -565,16 +566,12 @@ public class WebViewDatabase { * * @param url The url */ - @SuppressWarnings("deprecation") void removeCache(String url) { if (url == null || mCacheDatabase == null) { return; } - StringBuilder sb = new StringBuilder(256); - sb.append("DELETE FROM cache WHERE url = "); - DatabaseUtils.appendEscapedSQLString(sb, url); - mCacheDatabase.execSQL(sb.toString()); + mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url }); } /** diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 378d2183a7db..c012e25cf0c5 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -31,6 +31,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -1622,6 +1623,9 @@ public abstract class AbsListView extends AdapterView implements Te mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); handled = super.showContextMenuForChild(AbsListView.this); } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } return handled; } diff --git a/core/java/android/widget/CursorFilter.java b/core/java/android/widget/CursorFilter.java index afd5b10e0dd3..dbded69364f3 100644 --- a/core/java/android/widget/CursorFilter.java +++ b/core/java/android/widget/CursorFilter.java @@ -60,11 +60,10 @@ class CursorFilter extends Filter { } @Override - protected void publishResults(CharSequence constraint, - FilterResults results) { + protected void publishResults(CharSequence constraint, FilterResults results) { Cursor oldCursor = mClient.getCursor(); - if (results.values != oldCursor) { + if (results.values != null && results.values != oldCursor) { mClient.changeCursor((Cursor) results.values); } } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 67010b2dcece..54f27072b4f9 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -47,11 +47,8 @@ public class DatePicker extends FrameLayout { /* UI Components */ private final NumberPicker mDayPicker; private final NumberPicker mMonthPicker; - private final NumberPicker mYearPicker; - - private final int mStartYear; - private final int mEndYear; - + private final NumberPicker mYearPicker; + /** * How we notify users the date has changed. */ @@ -87,12 +84,9 @@ public class DatePicker extends FrameLayout { public DatePicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.date_picker, - this, // we are the parent - true); - + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.date_picker, this, true); + mDayPicker = (NumberPicker) findViewById(R.id.day); mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); mDayPicker.setSpeed(100); @@ -134,20 +128,17 @@ public class DatePicker extends FrameLayout { }); // attributes - TypedArray a = context - .obtainStyledAttributes(attrs, R.styleable.DatePicker); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker); - mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); - mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); + int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); + int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); mYearPicker.setRange(mStartYear, mEndYear); a.recycle(); // initialize to current date Calendar cal = Calendar.getInstance(); - init(cal.get(Calendar.YEAR), - cal.get(Calendar.MONTH), - cal.get(Calendar.DAY_OF_MONTH), null); + init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null); // re-order the number pickers to match the current date format reorderPickers(); diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java index 7f1601e581c8..a2316cf6c7bf 100644 --- a/core/java/android/widget/Filter.java +++ b/core/java/android/widget/Filter.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.util.Log; /** *

      A filter constrains data with a filtering pattern.

      @@ -36,6 +37,8 @@ import android.os.Message; * @see android.widget.Filterable */ public abstract class Filter { + private static final String LOG_TAG = "Filter"; + private static final String THREAD_NAME = "Filter"; private static final int FILTER_TOKEN = 0xD0D0F00D; private static final int FINISH_TOKEN = 0xDEADBEEF; @@ -221,6 +224,9 @@ public abstract class Filter { RequestArguments args = (RequestArguments) msg.obj; try { args.results = performFiltering(args.constraint); + } catch (Exception e) { + args.results = new FilterResults(); + Log.w(LOG_TAG, "An exception occured during performFiltering()!", e); } finally { message = mResultHandler.obtainMessage(what); message.obj = args; diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index ffabb0227060..e7b303ada4d0 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -27,6 +27,7 @@ import android.util.Config; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -994,6 +995,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList return; } + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); long id = getItemIdAtPosition(mDownTouchPosition); dispatchLongPress(mDownTouchView, mDownTouchPosition, id); } @@ -1086,6 +1088,10 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList handled = super.showContextMenuForChild(this); } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + return handled; } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index b5d4e2d5a6d4..4ae322e1fb01 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -48,6 +48,7 @@ import android.widget.RemoteViews.RemoteView; * @attr ref android.R.styleable#ImageView_maxHeight * @attr ref android.R.styleable#ImageView_tint * @attr ref android.R.styleable#ImageView_scaleType + * @attr ref android.R.styleable#ImageView_cropToPadding */ @RemoteView public class ImageView extends View { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a1023bdc5d7d..25afee83a548 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -35,6 +35,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater.Filter; import android.view.View.OnClickListener; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -548,6 +550,54 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()} + * or {@link android.widget.ViewFlipper#stopFlipping()} along with + * {@link android.widget.ViewFlipper#setFlipInterval(int)}. + */ + private class SetFlipping extends Action { + public SetFlipping(int id, boolean flipping, int milliseconds) { + this.viewId = id; + this.flipping = flipping; + this.milliseconds = milliseconds; + } + + public SetFlipping(Parcel parcel) { + viewId = parcel.readInt(); + flipping = parcel.readInt() != 0; + milliseconds = parcel.readInt(); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + dest.writeInt(flipping ? 1 : 0); + dest.writeInt(milliseconds); + } + + @Override + public void apply(View root) { + final View target = root.findViewById(viewId); + if (target instanceof ViewFlipper) { + final ViewFlipper flipper = (ViewFlipper) target; + if (milliseconds != -1) { + flipper.setFlipInterval(milliseconds); + } + if (flipping) { + flipper.startFlipping(); + } else { + flipper.stopFlipping(); + } + } + } + + int viewId; + boolean flipping; + int milliseconds; + + public final static int TAG = 10; + } + + /** * Create a new RemoteViews object that will display the views contained * in the specified layout file. * @@ -603,6 +653,9 @@ public class RemoteViews implements Parcelable, Filter { case SetTextColor.TAG: mActions.add(new SetTextColor(parcel)); break; + case SetFlipping.TAG: + mActions.add(new SetFlipping(parcel)); + break; default: throw new ActionException("Tag " + tag + "not found"); } @@ -769,6 +822,22 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()} + * or {@link android.widget.ViewFlipper#stopFlipping()} along with + * {@link android.widget.ViewFlipper#setFlipInterval(int)}. + * + * @param viewId The id of the view to apply changes to + * @param flipping True means we should + * {@link android.widget.ViewFlipper#startFlipping()}, otherwise + * {@link android.widget.ViewFlipper#stopFlipping()}. + * @param milliseconds How long to wait before flipping to the next view, or + * -1 to leave unchanged. + */ + public void setFlipping(int viewId, boolean flipping, int milliseconds) { + addAction(new SetFlipping(viewId, flipping, milliseconds)); + } + + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. * diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d21c01722d96..2ae5d4ee5364 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -3844,7 +3844,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean doDown = true; if (otherEvent != null) { try { - boolean handled = mMovement.onKeyOther(this, (Editable) mText, + boolean handled = mMovement.onKeyOther(this, (Spannable) mText, otherEvent); doDown = false; if (handled) { diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index 8c652e5ead34..fa8935e3bc55 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -28,6 +28,9 @@ import android.view.animation.AnimationUtils; /** * Base class for a {@link FrameLayout} container that will perform animations * when switching between its views. + * + * @attr ref android.R.styleable#ViewAnimator_inAnimation + * @attr ref android.R.styleable#ViewAnimator_outAnimation */ public class ViewAnimator extends FrameLayout { diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index a3c15d945b3b..e20bfdf0f52f 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -22,12 +22,16 @@ import android.content.res.TypedArray; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; +import android.widget.RemoteViews.RemoteView; /** * Simple {@link ViewAnimator} that will animate between two or more views * that have been added to it. Only one child is shown at a time. If * requested, can automatically flip between each child at a regular interval. + * + * @attr ref android.R.styleable#ViewFlipper_flipInterval */ +@RemoteView public class ViewFlipper extends ViewAnimator { private int mFlipInterval = 3000; private boolean mKeepFlipping = false; diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java index df3f307a3cbc..0df919d02a5f 100644 --- a/core/java/android/widget/ZoomButton.java +++ b/core/java/android/widget/ZoomButton.java @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.GestureDetector; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -57,6 +58,7 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener() { @Override public void onLongPress(MotionEvent e) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); onLongClick(ZoomButton.this); } }); diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java index 20d605617075..be3b1fbd527f 100644 --- a/core/java/android/widget/ZoomRing.java +++ b/core/java/android/widget/ZoomRing.java @@ -6,10 +6,9 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.drawable.Drawable; -import android.os.Handler; +import android.graphics.drawable.RotateDrawable; import android.util.AttributeSet; -import android.util.Log; -import android.view.KeyEvent; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -18,17 +17,20 @@ import android.view.ViewConfiguration; * @hide */ public class ZoomRing extends View { - + // TODO: move to ViewConfiguration? - private static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getJumpTapTimeout(); + static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getJumpTapTimeout(); // TODO: get from theme private static final int DISABLED_ALPHA = 160; - + private static final String TAG = "ZoomRing"; + // TODO: Temporary until the trail is done + private static final boolean DRAW_TRAIL = false; + // TODO: xml - private static final int THUMB_DISTANCE = 63; - + private static final int THUMB_DISTANCE = 63; + /** To avoid floating point calculations, we multiply radians by this value. */ public static final int RADIAN_INT_MULTIPLIER = 100000000; /** PI using our multiplier. */ @@ -36,68 +38,81 @@ public class ZoomRing extends View { /** PI/2 using our multiplier. */ private static final int HALF_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED / 2; + private int mZeroAngle = HALF_PI_INT_MULTIPLIED * 3; + private static final int THUMB_GRAB_SLOP = PI_INT_MULTIPLIED / 4; - + /** The cached X of our center. */ private int mCenterX; - /** The cached Y of our center. */ + /** The cached Y of our center. */ private int mCenterY; /** The angle of the thumb (in int radians) */ private int mThumbAngle; private boolean mIsThumbAngleValid; - private int mThumbCenterX; - private int mThumbCenterY; private int mThumbHalfWidth; private int mThumbHalfHeight; - - private int mCallbackThreshold = Integer.MAX_VALUE; - - /** The accumulated amount of drag for the thumb (in int radians). */ - private int mAcculumalatedThumbDrag = 0; - + /** The inner radius of the track. */ private int mBoundInnerRadiusSquared = 0; /** The outer radius of the track. */ private int mBoundOuterRadiusSquared = Integer.MAX_VALUE; - + private int mPreviousWidgetDragX; private int mPreviousWidgetDragY; - + private boolean mDrawThumb = true; private Drawable mThumbDrawable; - + private static final int MODE_IDLE = 0; private static final int MODE_DRAG_THUMB = 1; + /** + * User has his finger down, but we are waiting for him to pass the touch + * slop before going into the #MODE_MOVE_ZOOM_RING. This is a good time to + * show the movable hint. + */ + private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4; private static final int MODE_MOVE_ZOOM_RING = 2; private static final int MODE_TAP_DRAG = 3; private int mMode; - private long mPreviousTapTime; - - private Handler mHandler = new Handler(); - + private long mPreviousDownTime; + private int mPreviousDownX; + private int mPreviousDownY; + private Disabler mDisabler = new Disabler(); - + private OnZoomRingCallback mCallback; - + private int mPreviousCallbackAngle; + private int mCallbackThreshold = Integer.MAX_VALUE; + private boolean mResetThumbAutomatically = true; private int mThumbDragStartAngle; - + private final int mTouchSlop; + private Drawable mTrail; + private double mAcculumalatedTrailAngle; + public ZoomRing(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - // TODO get drawable from style instead + + ViewConfiguration viewConfiguration = ViewConfiguration.get(context); + mTouchSlop = viewConfiguration.getScaledTouchSlop(); + + // TODO get drawables from style instead Resources res = context.getResources(); mThumbDrawable = res.getDrawable(R.drawable.zoom_ring_thumb); - + if (DRAW_TRAIL) { + mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate(); + } + // TODO: add padding to drawable setBackgroundResource(R.drawable.zoom_ring_track); // TODO get from style setBounds(30, Integer.MAX_VALUE); - + mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2; mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2; - + mCallbackThreshold = PI_INT_MULTIPLIED / 6; } @@ -108,7 +123,7 @@ public class ZoomRing extends View { public ZoomRing(Context context) { this(context, null); } - + public void setCallback(OnZoomRingCallback callback) { mCallback = callback; } @@ -132,26 +147,49 @@ public class ZoomRing extends View { mBoundOuterRadiusSquared = Integer.MAX_VALUE; } } - + public void setThumbAngle(int angle) { mThumbAngle = angle; - mThumbCenterX = (int) (Math.cos(1f * angle / RADIAN_INT_MULTIPLIER) * THUMB_DISTANCE) - + mCenterX; - mThumbCenterY = (int) (Math.sin(1f * angle / RADIAN_INT_MULTIPLIER) * THUMB_DISTANCE) - * -1 + mCenterY; + int unoffsetAngle = angle + mZeroAngle; + int thumbCenterX = (int) (Math.cos(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * + THUMB_DISTANCE) + mCenterX; + int thumbCenterY = (int) (Math.sin(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * + THUMB_DISTANCE) * -1 + mCenterY; + + mThumbDrawable.setBounds(thumbCenterX - mThumbHalfWidth, + thumbCenterY - mThumbHalfHeight, + thumbCenterX + mThumbHalfWidth, + thumbCenterY + mThumbHalfHeight); + + if (DRAW_TRAIL) { + double degrees; + degrees = Math.min(359.0, Math.abs(mAcculumalatedTrailAngle)); + int level = (int) (10000.0 * degrees / 360.0); + + mTrail.setLevel((int) (10000.0 * + (-Math.toDegrees(angle / (double) RADIAN_INT_MULTIPLIER) - + degrees + 90) / 360.0)); + ((RotateDrawable) mTrail).getDrawable().setLevel(level); + } + invalidate(); } - + + public void resetThumbAngle(int angle) { + mPreviousCallbackAngle = angle; + setThumbAngle(angle); + } + public void resetThumbAngle() { if (mResetThumbAutomatically) { - setThumbAngle(HALF_PI_INT_MULTIPLIED); + resetThumbAngle(0); } } - + public void setResetThumbAutomatically(boolean resetThumbAutomatically) { mResetThumbAutomatically = resetThumbAutomatically; } - + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), @@ -162,7 +200,7 @@ public class ZoomRing extends View { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - + // Cache the center point mCenterX = (right - left) / 2; mCenterY = (bottom - top) / 2; @@ -172,8 +210,12 @@ public class ZoomRing extends View { if (mThumbAngle == Integer.MIN_VALUE) { resetThumbAngle(); } + + if (DRAW_TRAIL) { + mTrail.setBounds(0, 0, right - left, bottom - top); + } } - + @Override public boolean onTouchEvent(MotionEvent event) { return handleTouch(event.getAction(), event.getEventTime(), @@ -184,61 +226,66 @@ public class ZoomRing extends View { private void resetState() { mMode = MODE_IDLE; mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE; - mAcculumalatedThumbDrag = 0; + mAcculumalatedTrailAngle = 0.0; mIsThumbAngleValid = false; } - + public void setTapDragMode(boolean tapDragMode, int x, int y) { resetState(); mMode = tapDragMode ? MODE_TAP_DRAG : MODE_IDLE; mIsThumbAngleValid = false; - + if (tapDragMode && mCallback != null) { onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY)); } } - + public boolean handleTouch(int action, long time, int x, int y, int rawX, int rawY) { switch (action) { - + case MotionEvent.ACTION_DOWN: - if (mPreviousTapTime + DOUBLE_TAP_DISMISS_TIMEOUT >= time) { + if (mPreviousDownTime + DOUBLE_TAP_DISMISS_TIMEOUT >= time) { if (mCallback != null) { mCallback.onZoomRingDismissed(); } } else { - mPreviousTapTime = time; + mPreviousDownTime = time; + mPreviousDownX = x; + mPreviousDownY = y; } resetState(); return true; - + case MotionEvent.ACTION_MOVE: // Fall through to code below switch break; - + case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (mCallback != null) { - if (mMode == MODE_MOVE_ZOOM_RING) { - mCallback.onZoomRingMovingStopped(); + if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + mCallback.onZoomRingSetMovableHintVisible(false); + if (mMode == MODE_MOVE_ZOOM_RING) { + mCallback.onZoomRingMovingStopped(); + } } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) { onThumbDragStopped(getAngle(x - mCenterX, y - mCenterY)); } } mDisabler.setEnabling(true); return true; - + default: return false; } - + // local{X,Y} will be where the center of the widget is (0,0) int localX = x - mCenterX; int localY = y - mCenterY; boolean isTouchingThumb = true; boolean isInBounds = true; int touchAngle = getAngle(localX, localY); - + int radiusSquared = localX * localX + localY * localY; if (radiusSquared < mBoundInnerRadiusSquared || radiusSquared > mBoundOuterRadiusSquared) { @@ -246,7 +293,7 @@ public class ZoomRing extends View { isTouchingThumb = false; isInBounds = false; } - + int deltaThumbAndTouch = getDelta(touchAngle, mThumbAngle); int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ? deltaThumbAndTouch : -deltaThumbAndTouch; @@ -255,19 +302,35 @@ public class ZoomRing extends View { // Didn't grab close enough to the thumb isTouchingThumb = false; } - + if (mMode == MODE_IDLE) { - mMode = isTouchingThumb ? MODE_DRAG_THUMB : MODE_MOVE_ZOOM_RING; - + if (isTouchingThumb) { + mMode = MODE_DRAG_THUMB; + } else { + mMode = MODE_WAITING_FOR_MOVE_ZOOM_RING; + } + if (mCallback != null) { if (mMode == MODE_DRAG_THUMB) { onThumbDragStarted(touchAngle); - } else if (mMode == MODE_MOVE_ZOOM_RING) { + } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + mCallback.onZoomRingSetMovableHintVisible(true); + } + } + + } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + if (Math.abs(x - mPreviousDownX) > mTouchSlop || + Math.abs(y - mPreviousDownY) > mTouchSlop) { + /* Make sure the user has moved the slop amount before going into that mode. */ + mMode = MODE_MOVE_ZOOM_RING; + + if (mCallback != null) { mCallback.onZoomRingMovingStarted(); } } } - + + // Purposefully not an "else if" if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) { if (isInBounds) { onThumbDragged(touchAngle, mIsThumbAngleValid ? deltaThumbAndTouch : 0); @@ -277,13 +340,13 @@ public class ZoomRing extends View { } else if (mMode == MODE_MOVE_ZOOM_RING) { onZoomRingMoved(rawX, rawY); } - + return true; } - + private int getDelta(int angle1, int angle2) { int delta = angle1 - angle2; - + // Assume this is a result of crossing over the discontinuous 0 -> 2pi if (delta > PI_INT_MULTIPLIED || delta < -PI_INT_MULTIPLIED) { // Bring both the radians and previous angle onto a continuous range @@ -295,7 +358,7 @@ public class ZoomRing extends View { delta -= PI_INT_MULTIPLIED * 2; } } - + return delta; } @@ -303,46 +366,69 @@ public class ZoomRing extends View { mThumbDragStartAngle = startAngle; mCallback.onZoomRingThumbDraggingStarted(startAngle); } - + private void onThumbDragged(int touchAngle, int deltaAngle) { - mAcculumalatedThumbDrag += deltaAngle; - if (mAcculumalatedThumbDrag > mCallbackThreshold - || mAcculumalatedThumbDrag < -mCallbackThreshold) { + mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER); + int totalDeltaAngle = getDelta(touchAngle, mPreviousCallbackAngle); + if (totalDeltaAngle > mCallbackThreshold + || totalDeltaAngle < -mCallbackThreshold) { if (mCallback != null) { boolean canStillZoom = mCallback.onZoomRingThumbDragged( - mAcculumalatedThumbDrag / mCallbackThreshold, - mAcculumalatedThumbDrag, mThumbDragStartAngle, touchAngle); + totalDeltaAngle / mCallbackThreshold, + mThumbDragStartAngle, touchAngle); mDisabler.setEnabling(canStillZoom); + + if (canStillZoom) { + // TODO: we're trying the haptics to see how it goes with + // users, so we're ignoring the settings (for now) + performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK, + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING | + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } } - mAcculumalatedThumbDrag = 0; + + // Get the closest tick and lock on there + mPreviousCallbackAngle = getClosestTickAngle(touchAngle); } - + setThumbAngle(touchAngle); mIsThumbAngleValid = true; } - + + private int getClosestTickAngle(int angle) { + int smallerAngleDistance = angle % mCallbackThreshold; + int smallerAngle = angle - smallerAngleDistance; + if (smallerAngleDistance < mCallbackThreshold / 2) { + // Closer to the smaller angle + return smallerAngle; + } else { + // Closer to the bigger angle (premodding) + return (smallerAngle + mCallbackThreshold) % (PI_INT_MULTIPLIED * 2); + } + } + private void onThumbDragStopped(int stopAngle) { mCallback.onZoomRingThumbDraggingStopped(stopAngle); } - + private void onZoomRingMoved(int x, int y) { if (mPreviousWidgetDragX != Integer.MIN_VALUE) { int deltaX = x - mPreviousWidgetDragX; int deltaY = y - mPreviousWidgetDragY; - + if (mCallback != null) { mCallback.onZoomRingMoved(deltaX, deltaY); } } - + mPreviousWidgetDragX = x; mPreviousWidgetDragY = y; } - + @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); - + if (!hasWindowFocus && mCallback != null) { mCallback.onZoomRingDismissed(); } @@ -353,22 +439,25 @@ public class ZoomRing extends View { // Convert from [-pi,pi] to {0,2pi] if (radians < 0) { - return -radians; + radians = -radians; } else if (radians > 0) { - return 2 * PI_INT_MULTIPLIED - radians; + radians = 2 * PI_INT_MULTIPLIED - radians; } else { - return 0; + radians = 0; } + + radians = radians - mZeroAngle; + return radians >= 0 ? radians : radians + 2 * PI_INT_MULTIPLIED; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - + if (mDrawThumb) { - mThumbDrawable.setBounds(mThumbCenterX - mThumbHalfWidth, mThumbCenterY - - mThumbHalfHeight, mThumbCenterX + mThumbHalfWidth, mThumbCenterY - + mThumbHalfHeight); + if (DRAW_TRAIL) { + mTrail.draw(canvas); + } mThumbDrawable.draw(canvas); } } @@ -409,12 +498,14 @@ public class ZoomRing extends View { } public interface OnZoomRingCallback { + void onZoomRingSetMovableHintVisible(boolean visible); + void onZoomRingMovingStarted(); boolean onZoomRingMoved(int deltaX, int deltaY); void onZoomRingMovingStopped(); void onZoomRingThumbDraggingStarted(int startAngle); - boolean onZoomRingThumbDragged(int numLevels, int dragAmount, int startAngle, int curAngle); + boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle); void onZoomRingThumbDraggingStopped(int endAngle); void onZoomRingDismissed(); diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java index 2ca03741b7b5..eb287670c7a1 100644 --- a/core/java/android/widget/ZoomRingController.java +++ b/core/java/android/widget/ZoomRingController.java @@ -17,14 +17,17 @@ package android.widget; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.os.Vibrator; import android.provider.Settings; import android.util.Log; import android.view.Gravity; @@ -42,6 +45,7 @@ import android.view.animation.DecelerateInterpolator; /** * TODO: Docs + * * @hide */ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, @@ -222,7 +226,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, public ZoomRingController(Context context, View ownerView) { mContext = context; mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - + mOwnerView = ownerView; mZoomRing = new ZoomRing(context); @@ -437,7 +441,15 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, case MotionEvent.ACTION_UP: mTouchMode = TOUCH_MODE_IDLE; + + /* + * This is a power-user feature that only shows the + * zoom while the user is performing the tap-drag. + * That means once it is released, the zoom ring + * should disappear. + */ mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY()); + dismissZoomRingDelayed(0); break; } break; @@ -560,10 +572,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY); } + public void onZoomRingSetMovableHintVisible(boolean visible) { + setPanningArrowsVisible(visible); + } + public void onZoomRingMovingStarted() { mHandler.removeMessages(MSG_DISMISS_ZOOM_RING); mScroller.abortAnimation(); - setPanningArrowsVisible(true); } private void setPanningArrowsVisible(boolean visible) { @@ -641,8 +656,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, } } - public boolean onZoomRingThumbDragged(int numLevels, int dragAmount, int startAngle, - int curAngle) { + public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) { if (mCallback != null) { int deltaZoomLevel = -numLevels; int globalZoomCenterX = mContainerLayoutParams.x + mZoomRing.getLeft() + @@ -650,7 +664,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() + mZoomRingHeight / 2; - return mCallback.onDragZoom(deltaZoomLevel, globalZoomCenterX - mOwnerViewBounds.left, + return mCallback.onDragZoom(deltaZoomLevel, + globalZoomCenterX - mOwnerViewBounds.left, globalZoomCenterY - mOwnerViewBounds.top, (float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER, (float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER); @@ -719,6 +734,45 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, ensureZoomRingIsCentered(); } + /** + * Shows a "tutorial" (some text) to the user teaching her the new zoom + * invocation method. + *

      + * It checks the global system setting to ensure this has not been seen + * before. Furthermore, if the application does not have privilege to write + * to the system settings, it will store this bit locally in a shared + * preference. + * + * @hide This should only be used by our main apps--browser, maps, and + * gallery + */ + public static void showZoomTutorialOnce(Context context) { + ContentResolver cr = context.getContentResolver(); + if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) { + return; + } + + SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); + if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) { + return; + } + + try { + Settings.System.putInt(cr, SETTING_NAME_SHOWN_TOAST, 1); + } catch (SecurityException e) { + /* + * The app does not have permission to clear this global flag, make + * sure the user does not see the message when he comes back to this + * same app at least. + */ + sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit(); + } + + Toast.makeText(context, + com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short, + Toast.LENGTH_LONG).show(); + } + private class Panner implements Runnable { private static final int RUN_DELAY = 15; private static final float STOP_SLOWDOWN = 0.8f; diff --git a/core/java/com/android/internal/gadget/IGadgetHost.aidl b/core/java/com/android/internal/gadget/IGadgetHost.aidl index a5b865496db7..e7b5a1e22e60 100644 --- a/core/java/com/android/internal/gadget/IGadgetHost.aidl +++ b/core/java/com/android/internal/gadget/IGadgetHost.aidl @@ -17,11 +17,12 @@ package com.android.internal.gadget; import android.content.ComponentName; -import android.gadget.GadgetInfo; +import android.gadget.GadgetProviderInfo; import android.widget.RemoteViews; /** {@hide} */ oneway interface IGadgetHost { void updateGadget(int gadgetId, in RemoteViews views); + void providerChanged(int gadgetId, in GadgetProviderInfo info); } diff --git a/core/java/com/android/internal/gadget/IGadgetService.aidl b/core/java/com/android/internal/gadget/IGadgetService.aidl index 1b3946fce3a8..a22f3f399ec4 100644 --- a/core/java/com/android/internal/gadget/IGadgetService.aidl +++ b/core/java/com/android/internal/gadget/IGadgetService.aidl @@ -17,7 +17,7 @@ package com.android.internal.gadget; import android.content.ComponentName; -import android.gadget.GadgetInfo; +import android.gadget.GadgetProviderInfo; import com.android.internal.gadget.IGadgetHost; import android.widget.RemoteViews; @@ -41,8 +41,8 @@ interface IGadgetService { // void updateGadgetIds(in int[] gadgetIds, in RemoteViews views); void updateGadgetProvider(in ComponentName provider, in RemoteViews views); - List getInstalledProviders(); - GadgetInfo getGadgetInfo(int gadgetId); + List getInstalledProviders(); + GadgetProviderInfo getGadgetInfo(int gadgetId); void bindGadgetId(int gadgetId, in ComponentName provider); } diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index b0b00b2d4c52..ac72a20e6483 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -32,8 +32,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_DELETE_SURROUNDING_TEXT = 80; private static final int DO_BEGIN_BATCH_EDIT = 90; private static final int DO_END_BATCH_EDIT = 95; - private static final int DO_HIDE_STATUS_ICON = 100; - private static final int DO_SHOW_STATUS_ICON = 110; + private static final int DO_REPORT_FULLSCREEN_MODE = 100; private static final int DO_PERFORM_PRIVATE_COMMAND = 120; private static final int DO_CLEAR_META_KEY_STATES = 130; @@ -133,12 +132,8 @@ public class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessage(DO_END_BATCH_EDIT)); } - public void hideStatusIcon() { - dispatchMessage(obtainMessage(DO_HIDE_STATUS_ICON)); - } - - public void showStatusIcon(String packageName, int resId) { - dispatchMessage(obtainMessageIO(DO_SHOW_STATUS_ICON, resId, packageName)); + public void reportFullscreenMode(boolean enabled) { + dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0)); } public void performPrivateCommand(String action, Bundle data) { @@ -323,22 +318,13 @@ public class IInputConnectionWrapper extends IInputContext.Stub { ic.endBatchEdit(); return; } - case DO_HIDE_STATUS_ICON: { - InputConnection ic = mInputConnection.get(); - if (ic == null || !isActive()) { - Log.w(TAG, "hideStatusIcon on inactive InputConnection"); - return; - } - ic.hideStatusIcon(); - return; - } - case DO_SHOW_STATUS_ICON: { + case DO_REPORT_FULLSCREEN_MODE: { InputConnection ic = mInputConnection.get(); if (ic == null || !isActive()) { Log.w(TAG, "showStatusIcon on inactive InputConnection"); return; } - ic.showStatusIcon((String)msg.obj, msg.arg1); + ic.reportFullscreenMode(msg.arg1 != 1); return; } case DO_PERFORM_PRIVATE_COMMAND: { diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 7cc8ada6044e..02b604438a35 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -56,13 +56,11 @@ import com.android.internal.view.IInputContextCallback; void endBatchEdit(); + void reportFullscreenMode(boolean enabled); + void sendKeyEvent(in KeyEvent event); void clearMetaKeyStates(int states); void performPrivateCommand(String action, in Bundle data); - - void showStatusIcon(String packageName, int resId); - - void hideStatusIcon(); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 2f5cd14ea7ac..1b1c7f78a7af 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -47,7 +47,7 @@ interface IInputMethodManager { void showInputMethodPickerFromClient(in IInputMethodClient client); void setInputMethod(in IBinder token, String id); void hideMySoftInput(in IBinder token, int flags); - void updateStatusIcon(int iconId, String iconPackage); + void updateStatusIcon(in IBinder token, String packageName, int iconId); boolean setInputMethodEnabled(String id, boolean enabled); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index af4ad25fc6f2..32d9f3dd2287 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -322,18 +322,9 @@ public class InputConnectionWrapper implements InputConnection { } } - public boolean hideStatusIcon() { + public boolean reportFullscreenMode(boolean enabled) { try { - mIInputContext.showStatusIcon(null, 0); - return true; - } catch (RemoteException e) { - return false; - } - } - - public boolean showStatusIcon(String packageName, int resId) { - try { - mIInputContext.showStatusIcon(packageName, resId); + mIInputContext.reportFullscreenMode(enabled); return true; } catch (RemoteException e) { return false; diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java index 20ea6a6d2879..1647c207ff00 100644 --- a/core/java/com/android/internal/widget/NumberPicker.java +++ b/core/java/com/android/internal/widget/NumberPicker.java @@ -28,12 +28,8 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnLongClickListener; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.widget.EditText; -import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.ViewSwitcher; +import android.widget.LinearLayout; import com.android.internal.R; @@ -71,25 +67,18 @@ public class NumberPicker extends LinearLayout implements OnClickListener, private final Runnable mRunnable = new Runnable() { public void run() { if (mIncrement) { - changeCurrent(mCurrent + 1, mSlideUpInAnimation, mSlideUpOutAnimation); + changeCurrent(mCurrent + 1); mHandler.postDelayed(this, mSpeed); } else if (mDecrement) { - changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation); + changeCurrent(mCurrent - 1); mHandler.postDelayed(this, mSpeed); } } }; - - private final LayoutInflater mInflater; + private final TextView mText; - private final InputFilter mInputFilter; private final InputFilter mNumberInputFilter; - - private final Animation mSlideUpOutAnimation; - private final Animation mSlideUpInAnimation; - private final Animation mSlideDownOutAnimation; - private final Animation mSlideDownInAnimation; - + private String[] mDisplayedValues; private int mStart; private int mEnd; @@ -110,14 +99,14 @@ public class NumberPicker extends LinearLayout implements OnClickListener, this(context, attrs, 0); } - public NumberPicker(Context context, AttributeSet attrs, - int defStyle) { + @SuppressWarnings({"UnusedDeclaration"}) + public NumberPicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); setOrientation(VERTICAL); - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mInflater.inflate(R.layout.number_picker, this, true); + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.number_picker, this, true); mHandler = new Handler(); - mInputFilter = new NumberPickerInputFilter(); + InputFilter inputFilter = new NumberPickerInputFilter(); mNumberInputFilter = new NumberRangeKeyListener(); mIncrementButton = (NumberPickerButton) findViewById(R.id.increment); mIncrementButton.setOnClickListener(this); @@ -130,30 +119,9 @@ public class NumberPicker extends LinearLayout implements OnClickListener, mText = (TextView) findViewById(R.id.timepicker_input); mText.setOnFocusChangeListener(this); - mText.setFilters(new InputFilter[] { mInputFilter }); + mText.setFilters(new InputFilter[] {inputFilter}); mText.setRawInputType(InputType.TYPE_CLASS_NUMBER); - - mSlideUpOutAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, -100); - mSlideUpOutAnimation.setDuration(200); - mSlideUpInAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, 100, - Animation.RELATIVE_TO_SELF, 0); - mSlideUpInAnimation.setDuration(200); - mSlideDownOutAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 100); - mSlideDownOutAnimation.setDuration(200); - mSlideDownInAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, -100, - Animation.RELATIVE_TO_SELF, 0); - mSlideDownInAnimation.setDuration(200); - + if (!isEnabled()) { setEnabled(false); } @@ -228,9 +196,9 @@ public class NumberPicker extends LinearLayout implements OnClickListener, // now perform the increment/decrement if (R.id.increment == v.getId()) { - changeCurrent(mCurrent + 1, mSlideUpInAnimation, mSlideUpOutAnimation); + changeCurrent(mCurrent + 1); } else if (R.id.decrement == v.getId()) { - changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation); + changeCurrent(mCurrent - 1); } } @@ -240,7 +208,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, : String.valueOf(value); } - private void changeCurrent(int current, Animation in, Animation out) { + private void changeCurrent(int current) { // Wrap around the values if we go past the start or end if (current > mEnd) { diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 307c6fdd89c9..288433af6a0d 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -267,7 +267,7 @@ static void android_media_AudioRecord_finalize(JNIEnv *env, jobject thiz) { (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj); if (lpRecorder) { - //LOGV("About to delete lpRecorder: %x\n", (int)lpRecorder); + LOGV("About to delete lpRecorder: %x\n", (int)lpRecorder); lpRecorder->stop(); delete lpRecorder; } @@ -449,6 +449,39 @@ static jint android_media_AudioRecord_get_pos_update_period(JNIEnv *env, jobjec // ---------------------------------------------------------------------------- +// returns the minimum required size for the successful creation of an AudioRecord instance. +// returns 0 if the parameter combination is not supported. +// return -1 if there was an error querying the buffer size. +static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env, jobject thiz, + jint sampleRateInHertz, jint nbChannels, jint audioFormat) { + + size_t inputBuffSize = 0; + LOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)", sampleRateInHertz, nbChannels, audioFormat); + + status_t result = AudioSystem::getInputBufferSize( + sampleRateInHertz, + (audioFormat == javaAudioRecordFields.PCM16 ? + AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT), + nbChannels, &inputBuffSize); + switch(result) { + case(NO_ERROR): + if(inputBuffSize == 0) { + LOGV("Recording parameters are not supported: %dHz, %d channel(s), (java) format %d", + sampleRateInHertz, nbChannels, audioFormat); + return 0; + } else { + // the minimum buffer size is twice the hardware input buffer size + return 2*inputBuffSize; + } + break; + case(PERMISSION_DENIED): + default: + return -1; + } +} + + +// ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { // name, signature, funcPtr @@ -470,6 +503,8 @@ static JNINativeMethod gMethods[] = { "(I)I", (void *)android_media_AudioRecord_set_pos_update_period}, {"native_get_pos_update_period", "()I", (void *)android_media_AudioRecord_get_pos_update_period}, + {"native_get_min_buff_size", + "(III)I", (void *)android_media_AudioRecord_get_min_buff_size}, }; // field names found in android/media/AudioRecord.java diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 6bd365519567..692610ea42cc 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -53,13 +53,8 @@ static int android_media_AudioSystem_setVolume(JNIEnv *env, jobject clazz, jint type, jint volume) { LOGV("setVolume(%d)", int(volume)); - if (int(type) == AudioTrack::VOICE_CALL) { - return check_AudioSystem_Command(AudioSystem::setStreamVolume(type, float(volume) / 100.0)); - } else if (int(type) == AudioTrack::BLUETOOTH_SCO) { - return check_AudioSystem_Command(AudioSystem::setStreamVolume(type, float(1.0))); - } else { - return check_AudioSystem_Command(AudioSystem::setStreamVolume(type, AudioSystem::linearToLog(volume))); - } + + return check_AudioSystem_Command(AudioSystem::setStreamVolume(type, AudioSystem::linearToLog(volume))); } static int @@ -68,12 +63,7 @@ android_media_AudioSystem_getVolume(JNIEnv *env, jobject clazz, jint type) float v; int v_int = -1; if (AudioSystem::getStreamVolume(int(type), &v) == NO_ERROR) { - // voice call volume is converted to log scale in the hardware - if (int(type) == AudioTrack::VOICE_CALL) { - v_int = lrint(v * 100.0); - } else { - v_int = AudioSystem::logToLinear(v); - } + v_int = AudioSystem::logToLinear(v); } return v_int; } diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index bbecc1b6a0cf..6ca821d2fca5 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -72,6 +72,7 @@ class AudioTrackJniStorage { sp mMemHeap; sp mMemBase; audiotrack_callback_cookie mCallbackData; + int mStreamType; AudioTrackJniStorage() { } @@ -168,11 +169,11 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th int afSampleRate; int afFrameCount; - if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) { + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { LOGE("Error creating AudioTrack: Could not get AudioSystem frame count."); return AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM; } - if (AudioSystem::getOutputSamplingRate(&afSampleRate) != NO_ERROR) { + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { LOGE("Error creating AudioTrack: Could not get AudioSystem sampling rate."); return AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM; } @@ -183,21 +184,21 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th } // check the stream type - AudioTrack::stream_type atStreamType; + AudioSystem::stream_type atStreamType; if (streamType == javaAudioTrackFields.STREAM_VOICE_CALL) { - atStreamType = AudioTrack::VOICE_CALL; + atStreamType = AudioSystem::VOICE_CALL; } else if (streamType == javaAudioTrackFields.STREAM_SYSTEM) { - atStreamType = AudioTrack::SYSTEM; + atStreamType = AudioSystem::SYSTEM; } else if (streamType == javaAudioTrackFields.STREAM_RING) { - atStreamType = AudioTrack::RING; + atStreamType = AudioSystem::RING; } else if (streamType == javaAudioTrackFields.STREAM_MUSIC) { - atStreamType = AudioTrack::MUSIC; + atStreamType = AudioSystem::MUSIC; } else if (streamType == javaAudioTrackFields.STREAM_ALARM) { - atStreamType = AudioTrack::ALARM; + atStreamType = AudioSystem::ALARM; } else if (streamType == javaAudioTrackFields.STREAM_NOTIFICATION) { - atStreamType = AudioTrack::NOTIFICATION; + atStreamType = AudioSystem::NOTIFICATION; } else if (streamType == javaAudioTrackFields.STREAM_BLUETOOTH_SCO) { - atStreamType = AudioTrack::BLUETOOTH_SCO; + atStreamType = AudioSystem::BLUETOOTH_SCO; } else { LOGE("Error creating AudioTrack: unknown stream type."); return AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE; @@ -238,6 +239,8 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th // we use a weak reference so the AudioTrack object can be garbage collected. lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); + lpJniStorage->mStreamType = atStreamType; + // create the native AudioTrack object AudioTrack* lpTrack = new AudioTrack(); if (lpTrack == NULL) { @@ -656,8 +659,14 @@ static jint android_media_AudioTrack_reload(JNIEnv *env, jobject thiz) { // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_get_output_sample_rate(JNIEnv *env, jobject thiz) { - int afSamplingRate; - if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) { + int afSamplingRate; + AudioTrackJniStorage* lpJniStorage = (AudioTrackJniStorage *)env->GetIntField( + thiz, javaAudioTrackFields.jniData); + if (lpJniStorage == NULL) { + return DEFAULT_OUTPUT_SAMPLE_RATE; + } + + if (AudioSystem::getOutputSamplingRate(&afSamplingRate, lpJniStorage->mStreamType) != NO_ERROR) { return DEFAULT_OUTPUT_SAMPLE_RATE; } else { return afSamplingRate; diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index 75a0fbee23eb..e5ae2ea19bea 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -751,6 +751,10 @@ void onCreateBondingResult(DBusMessage *msg, void *user) { // Other device is not responding at all LOGV("... error = %s (%s)\n", err.name, err.message); result = BOND_RESULT_REMOTE_DEVICE_DOWN; + } else if (!strcmp(err.name, BLUEZ_DBUS_BASE_IFC ".Error.AlreadyExists")) { + // already bonded + LOGV("... error = %s (%s)\n", err.name, err.message); + result = BOND_RESULT_SUCCESS; } else { LOGE("%s: D-Bus error: %s (%s)\n", __FUNCTION__, err.name, err.message); result = BOND_RESULT_ERROR; diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp index d0cac183ab67..5e5103a2fabe 100644 --- a/core/jni/android_util_EventLog.cpp +++ b/core/jni/android_util_EventLog.cpp @@ -19,7 +19,7 @@ #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "jni.h" -#include "utils/logger.h" +#include "cutils/logger.h" #define END_DELIMITER '\n' #define INT_BUFFER_SIZE (sizeof(jbyte)+sizeof(jint)+sizeof(END_DELIMITER)) diff --git a/core/res/res/drawable/presence_away.png b/core/res/res/drawable/presence_away.png index a539ec7787190c1e5aa5627bf388478de8c7fbfe..f8120df43bfe56831686901c2aeed0d15f693f4e 100644 GIT binary patch delta 787 zcmV+u1MK{)8>$A7B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0008a zNklkw74j_4#}QoNG3lTc&BQ*X#AQrKP3Z?;7Fai6$l{($Q#iI*~|>$K&yE zBoYA`Iv5#1*L55m98@$-ds8l#7nYZoi+>nRO--fA;8kB=-&iV@Ld5t4d|ySQyzSur z=xrc53SD`CQh%w0-QC?4GJLVPxLCYYS~8iO&SWxUy{ROW!Zg%I0jAkN=Fw*d-&-9* zz}SZ0pFudY07X&81VPXUnqgezpV`^j;r{;qaSEuLPtj5Nf{0Io(gq~B!F$dz#vJCd zhP9qiPfyQCI-L#&wGU7ZHxZ<2)wT_#MYXpr2ypkDV}H!y5SKNq^^Cf^yR%(gU9j`- zAR2&i1nA!kRDY0>RU})$ll?$A z40J@?P-rIG4vz+tdR48#;j)Icp3%w4$r>dpvFu|suHAutN-d}Y6^d%z4c>E(G3GFr zHLSff+4+2ao+kR4&@`|&{}9kNs0VI&yy(xJ6MrFb7ris@Vfz%G@YjQbgXaUuYkz-# zf$qm*XU0E{WJ0}>!$z%0B1FJsZO4P R{R{vA002ovPDHLkV1jNxeck{7 delta 3498 zcmV;b4OQ~02CN&9B!3BTNLh0L01FcU01FcV0GgZ_000V4X+uL$P-t&-Z*ypGa3D!T zLm+T+Z)Rz1WdHzp+MQEpR8#2|J@?-9LQ9B%luK_?6$l_wLW_VDktQl32@pz%A)(n7 zQNa;KMFbnjpojyGj)066Q7jCK3fKqaA)=0hqlk*i`{8?|Yk$_f_vX$1wbwr9tn;0- z&j-K=43f59&ghTmgWD0l;*TI7}*0BAb^tj|`8MF3bZ02F3R#5n-i zEdVe{S7t~6u(trf&JYW-00;~KFj0twDF6g}0AR=?BX|IWnE(_<@>e|ZE3OddDgXd@ znX){&BsoQaTL>+22Uk}v9w^R97b_GtVFF>AKrX_0nSU8Ffiw@`^UMGMppg|3;Dhu1 zc+L*4&dxTDwhmt{>c0m6B4T3W{^ifBa6kY6;dFk{{wy!E8h|?nfNlPwCGG@hUJIag z_lst-4?wj5py}FI^KkfnJUm6Akh$5}<>chpO2k52Vaiv1{%68pz*qfj`F=e7_x0eu z;v|7GU4MZ`1o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcqjPo+3 zB8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S1Au6Q z;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO0Dk~Ppn)o|K^yeJ7%adB9Ki+L!3+Fg zHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_vKpix|QD}yfa1JiQRk#j4a1Z)n2%fLC6RbVIkUx0b+_+BaR3cnT7Zv!AJxWizFb)h!jyGOOZ85F;a?DAXP{m@;!0_ zIe&*-M!JzZ$N(~e{D!NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWw%BIv?Wdily+ylO`+*KY$4Vz$Cr4+G&IO(4Q`uA9rwXSQO+7mGt}d!;r5mBU zM0dY#r|y`ZzFvTyOmC;&dA;ZQ9DOhSRQ+xGr}ak+SO&8UBnI0I&KNw!HF0k|9WTe* z@liuv!$3o&VU=N*;e?U7(SJOn)kcj*4~%KXT;n9;ZN_cJqb3F>Atp;r>P_yNQcbz0 zDW*G2J50yT%*~?B)|oY%Ju%lZ=bPu7*PGwBU|M)uEVih&xMfMQu79>|wtZn|Vi#w( z#jeBdlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!h;8Eq#KMS9gFl*neeosSBfoHYnBQIkwkyowPu(zdm zs`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMeBmZRodjHV?r+_5^X9J0W zL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0?0=B0A@}E)&XLY(4uw#D z=+@8&Vdi0r!+s1Wg@=V#hChyQh*%oYF_$%W(cD9G-$eREmPFp0XE9GXuPsV7Dn6<% zYCPIEx-_~!#x7=A%+*+(SV?S4962s3t~PFLzTf=q^M~S{;tS(@7nm=|U2u7!&cgJC zrxvL$5-d8FKz~e#PB@hCK@cja7K|nG6L%$!3VFgE!e=5c(KgYD*h5?@9!~N|DouKl z?2)`Rc_hU%r7Y#SgeR$xyi5&D-J3d|7MgY-Z8AMNy)lE5k&tmhsv%92wrA>R=4N)w ztYw9={>5&Kw=W)*2gz%*kgNq+Eef_mrsz~!DAy_nvVUh~S7yJ>iOM;atDY;(?aZ^v z+mJV$@1Ote62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~p zu715HdQEGAUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$ z+<4_1hktL%znR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX4c}I@?e+FW+b@^R zDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&_B8C(+grT%{XWUQ z+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?SIDu(gXbmBM!FLxzyDi(mhmCkJc;e zM-ImyzW$x>cP$Mz4ONYt#^NJzM0w=t_X*$k9t}F$c8q(h;Rn+nb{%IOFKR-X@|s4Q zQ=0o*Vq3aT%s$c9>fU<%N829{oHRUHc}nwC$!Xf@g42^{^3RN&m7RTlF8SPG+oHC6 z=YM0)-)awU@466l;nGF_i|0GMJI-A4xODQe+vO8ixL2C5I$v$-bm~0*lhaSfyPUh4 zuDM)mx$b(swR>jw=^LIm&fWCAdGQwi*43UlJ>9+YdT;l|_x0Zv-F|W>{m#p~*>@-I zt-MdXU-UrjLD@syht)q@{@mE_+<$7ocYmPs(cDM(28Dyq{*m>M4?_iynUBkc4TkHU zI6gT!;y-fz>HMcd&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M z!p0uH$#^p{Ui4P`?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&Gk-1H z0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}0008lNkli`+j>G#fnm@r5v^BI&L!~gPf7%av&l-g#E4UtoJ6J+|G=FE@8C3UwZImcvH)FnBwd4`vgBv`Xlc3 z_hGs*oBy7>k9Qt_@A$L+N`Kf4SA$s>{d2oik%3GaqHK&5AiMDE(FSqw1ZkR88z;L? zy(l`7{2a)*3zTPtJ&xoLp8VQi+v3 zeu4HhrfwD;?dhiLxiCJ2T&@hZs;JuP1Zo`NDi`hPrsH07!I``ja(`g0&ueiWcPfm4 zQn=S#Y6?aG;)Q?TUR6}Gl(8y{7osNa$cZx!0%U=i1?1%bzFpwoAXk`~1q8^l?4TxT z+!F!0`8lXM!vBP-PjW<%RC=`}N%rG3-ox>cJC9I#Df_?d&J%Ea;xyi~W4m9w_tCvm zYwfm(4AtBca`zdiI)9w;dQ)}O-Dfa&3lSMwYq!@wUO&C~w1b}pYi7=DXr<@L6QtKg zUcP{FRL&zn&D}!X+(0}HX)=tCZ1={yH+BFhisHihgZ{F$b~k&K65aq%qNng+ze?f&^2#U_-5Ksr0q4kLNj8C?M#f_kZRzx95D{_wKpxyHB!h z8~UOvR)_2wZCl9Zy-W3J2mGd4EX7l}lMD4;%k2G4R5K2t?aS%J4K zi{LfqxW*X9GKaa<9fiZ;sIKc?C%5u(6K)rsjcmhV9)CcRf3Anh4dk)y$4c8wDo*X{y@We#(zLLQF?x{pI ztV2QN5P!!U=2nI1^w(uszID)c3ChwYHUeFutv|IUQQjgfaRj&e*4VL@Wx@MkfX=c) zNexcazy%-Ns1?C$&T)+~jAagUt3tV4Zh;bo;6OjhHzLTOfGnL=4&>&;;5FyC#u&yj zhq>6 SI}P~&0000f59&ghTmgWD0l;*TI7}*0BAb^tj|`8MF3bZ02F3R#5n-i zEdVe{S7t~6u(trf&JYW-00;~KFj0twDF6g}0AR=?BX|IWnE(_<@>e|ZE3OddDgXd@ znX){&BsoQaTL>+22Uk}v9w^R97b_GtVFF>AKrX_0nSU8Ffiw@`^UMGMppg|3;Dhu1 zc+L*4&dxTDwhmt{>c0m6B4T3W{^ifBa6kY6;dFk{{wy!E8h|?nfNlPwCGG@hUJIag z_lst-4?wj5py}FI^KkfnJUm6Akh$5}<>chpO2k52Vaiv1{%68pz*qfj`F=e7_x0eu z;v|7GU4MZ`1o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcqjPo+3 zB8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S1Au6Q z;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO0Dk~Ppn)o|K^yeJ7%adB9Ki+L!3+Fg zHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_vKpix|QD}yfa1JiQRk#j4a1Z)n2%fLC6RbVIkUx0b+_+BaR3cnT7Zv!AJxWizFb)h!jyGOOZ85F;a?DAXP{m@;!0_ zIe&*-M!JzZ$N(~e{D!NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWw%BIv?Wdily+ylO`+*KY$4Vz$Cr4+G&IO(4Q`uA9rwXSQO+7mGt}d!;r5mBU zM0dY#r|y`ZzFvTyOmC;&dA;ZQ9DOhSRQ+xGr}ak+SO&8UBnI0I&KNw!HF0k|9WTe* z@liuv!$3o&VU=N*;e?U7(SJOn)kcj*4~%KXT;n9;ZN_cJqb3F>Atp;r>P_yNQcbz0 zDW*G2J50yT%*~?B)|oY%Ju%lZ=bPu7*PGwBU|M)uEVih&xMfMQu79>|wtZn|Vi#w( z#jeBdlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!h;8Eq#KMS9gFl*neeosSBfoHYnBQIkwkyowPu(zdm zs`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMeBmZRodjHV?r+_5^X9J0W zL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0?0=B0A@}E)&XLY(4uw#D z=+@8&Vdi0r!+s1Wg@=V#hChyQh*%oYF_$%W(cD9G-$eREmPFp0XE9GXuPsV7Dn6<% zYCPIEx-_~!#x7=A%+*+(SV?S4962s3t~PFLzTf=q^M~S{;tS(@7nm=|U2u7!&cgJC zrxvL$5-d8FKz~e#PB@hCK@cja7K|nG6L%$!3VFgE!e=5c(KgYD*h5?@9!~N|DouKl z?2)`Rc_hU%r7Y#SgeR$xyi5&D-J3d|7MgY-Z8AMNy)lE5k&tmhsv%92wrA>R=4N)w ztYw9={>5&Kw=W)*2gz%*kgNq+Eef_mrsz~!DAy_nvVUh~S7yJ>iOM;atDY;(?aZ^v z+mJV$@1Ote62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~p zu715HdQEGAUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$ z+<4_1hktL%znR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX4c}I@?e+FW+b@^R zDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&_B8C(+grT%{XWUQ z+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?SIDu(gXbmBM!FLxzyDi(mhmCkJc;e zM-ImyzW$x>cP$Mz4ONYt#^NJzM0w=t_X*$k9t}F$c8q(h;Rn+nb{%IOFKR-X@|s4Q zQ=0o*Vq3aT%s$c9>fU<%N829{oHRUHc}nwC$!Xf@g42^{^3RN&m7RTlF8SPG+oHC6 z=YM0)-)awU@466l;nGF_i|0GMJI-A4xODQe+vO8ixL2C5I$v$-bm~0*lhaSfyPUh4 zuDM)mx$b(swR>jw=^LIm&fWCAdGQwi*43UlJ>9+YdT;l|_x0Zv-F|W>{m#p~*>@-I zt-MdXU-UrjLD@syht)q@{@mE_+<$7ocYmPs(cDM(28Dyq{*m>M4?_iynUBkc4TkHU zI6gT!;y-fz>HMcd&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M z!p0uH$#^p{Ui4P`?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&Gk-1H z0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}0008?NklGvryxv6gLW84aG&sq67p< zHxi0SR>iuAU>1!5A-V}AtEqQFhjEt@pCJk*rbUT4O#4f~!#! zy&nc4K`uaY0uIzE)>^ucx|rY0mbLcQ`WNfF&loK&FI|qJXr=OEr5Xi7>3=AO*do>d z0(l>uEE2y~CANn6e%v+>%<9_BwauZ?((=-LWwP>l>6KEoq!4}m16V~oPzQK`8aD6g->sTeFDzm5{G25=a5A4o5xJd zPQmkqLXtXvdvoLau}ce=-hZ8{PJQz7g#v};ufRG)9j=!k3ffCR!DUbsydO{T-gy&! z&Q+5?^e3-aEY_naBHY-6huw2Gs3-t3V1J>aV3lxVkJ5*8n1|iE7v{rS5Cq8Gle4+N zK+ceBhG+9~5EEPrh>Bs&3vxl(&-rj_KoweFA{nVXm7U8o-GHBC{C}KJkPFJA{)7)+ zww|I+!0FkI{UZvHo^AKQpYZVnIs!9VF2YfaPJfh{(bHqubF&-tRgMN7(;4li>?;Zv zXPz+hlY@IMX6Vljm4S3p4%ZH`)NcDlB^MGzt1tdOFl z@V_V z+b8$U>g*fSn*j9t{b3xo4(E+AchWTN594uv!283TG3L(U!9d}BRy*?dk8Q8HH(r`7 zE%%tO`GOHyfasyJNL2uIRHAEyq1No74iT8002ovPDHLkV1oa< Bt1JKj diff --git a/core/res/res/drawable/presence_invisible.png b/core/res/res/drawable/presence_invisible.png index fb86cf128342148d45bb0c74b8b015cddedd1152..21399a4f3618f0e6549016cd7b53e75767029de6 100644 GIT binary patch delta 632 zcmV-;0*C#)2(<-}NPhxXNkl({MOk4hDm7)9Li3SS)sa6Ut_@5(|9s`~69;*Gs$Ij>hA$cw%X_TC&+} z;@Ru`l0Ki0n$0F1k4NIt$Y?Z*IR5wheNw4Z6buGQ zuh%CP3dMj!pMMZn;^*n9@wbSXb2JnP!bcY_G z7f4^D(GWLUWB|h+KEy8l0tZ}K$mw)OQGdJL79>ttlyf?r-kE4B$AAGA9B}0xS*_N4 zq&=I>$Ye4J5=y{DTtu4eX1qWPNh zJmFu%;c@WKfyC-|yDxm=E9NAZ%TXec5JQN0fng6HV!!|kj{Kq`ql;B46{*wdJTq~@ z{W#w;#DD?zntSszQmfTsJZy1(HKIJtVovzF>wIHx{34gR(UrgY9{tPuF2Dd~Po<+F S1x;%J00007%Q6vyAp&VJeJkKH)2-6pYWRs|d4HctGt zyE8k(n>C5bu0}hS=l$OQ{omUqN-4Phdg}0#xzHs0i>`IAV}E7uaG5(dq(h=&wb~B* zKhsXI5c7q~gTtOnGPJ@ep zqMD$$Bn=SEL8s$4?$V@tM(WZ)T_KQ!nkhxv@~rdMp+R#4-el36fN; z)WJXr(r{@Ol>?IZ@|MHQaVeDX{;7ArX8wf#;El75hkt{jZ(JJ=A>kZ2`bDrLL#qJ{ zHjK1&lm`TrS^J@e3e{1c?BJiU7;fB}=V9$Kq2fOZ}u2 z1Ro_LA$LlpF}icQaTg25N_*W>Nwr!{;4vynbQACp8l%0Dx}$hggQjZI1YCg9Dh&pH z;M2f&_kS|KP@MA!7`hxGFr!wJmND8(R6vnP#Asd=o|6gshPR2MK?$K=&Yop%)&+x^ z*ka6^2#{h6q>V*~&=?}L!*m4FigX8=0TdINDxsbe5M~p5)%3jEh--rwZX!q@hS8Ae zg&+;32-5qsJbV~fvyGWdGczDo|5v)c`S*96_kWeo9F@cpIFC^rb}o(~76M{a8tdOe zq|+~vZeSLicd$$w7#cF{_r9USp7?@*BPMo?fsxAsXDscCPzc3Rc`|~;I2J-^f6#a= zi@7dT{Cr5QY}n^=iEi||XF7w8553}8w=p>rF-F0%3=mj9mkB~J7(ofD;c=Au!%Pz@ zlYcw8H6BF0Vdv9!zw-$_^!THL=AVDP9Y@inT5X!#*|-U8+XALbKwl&(A_#I2>^fP< zWil`~RhOmlN{@tMrTNF&smC5V*x7#jm%m+pq*|*UW7MwkB=#=+be499F$}t0u+ii~ zp`e^%KxXD6#yxe+i{`Nf|xO)I0x&C{!{QVF6s+IELf>+$HsIrHOQ(F=k z#8HxkgyH~VWk$O%qNV1Q);sfiZe4P5zW?_<+PW6MTJ-9*+JS5?bAOH%Zo&54pq&pP w`Yr5*KjQ1M_{+JK_wHU;7~oKB$Nek70DvL_;T%_u*Z=?k07*qoM6N<$f&$$B9smFU diff --git a/core/res/res/drawable/presence_offline.png b/core/res/res/drawable/presence_offline.png index da54fe7fccf409e86b29ec072f8710d7aeac85c7..3941b8205251753b2a12c04a64c71407ad5d6b56 100644 GIT binary patch delta 743 zcmV@sQ}xOJ=G z#zo{#A`B|aa%HJhmRc6h`Oe^DVqzGExo~{<-Sd6lz305|s(`}y}j~pLa|s(VS#Vc)6-$E*GsRjuheKX^@pyb~W@cucgHBFP$m8+Q($W$=KR*-qNCI=LAqNbw;DD-9RVuC7XB&(6*$l}brqjy2?f0Tvu^H6gAy%?nS9i;Hs9 zN~J=DLV=RWq`V_A#~N~Y2NoP~KREmN_;{Ng{RK{Dx5}==!^2WW-;gNV>2%^2^d0`) z=kslTaQ5}}^$t7P&A{;1))pNd9Z8^H7-M5&6krO4$nW<{NxabkI4ty204M54CX-Rh z;(>dgeoaRPn{_6keU&aps Z1^_KHl~Z@X8HWG>002ovPDHLkV1jC2Y!(0j delta 3575 zcmVf59&ghTmgWD0l;*TI7}*0BAb^tj|`8MF3bZ02F3R#5n-i zEdVe{S7t~6u(trf&JYW-00;~KFj0twDF6g}0AR=?BX|IWnE(_<@>e|ZE3OddDgXd@ znX){&BsoQaTL>+22Uk}v9w^R97b_GtVFF>AKrX_0nSU8Ffiw@`^UMGMppg|3;Dhu1 zc+L*4&dxTDwhmt{>c0m6B4T3W{^ifBa6kY6;dFk{{wy!E8h|?nfNlPwCGG@hUJIag z_lst-4?wj5py}FI^KkfnJUm6Akh$5}<>chpO2k52Vaiv1{%68pz*qfj`F=e7_x0eu z;v|7GU4MZ`1o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcqjPo+3 zB8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S1Au6Q z;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO0Dk~Ppn)o|K^yeJ7%adB9Ki+L!3+Fg zHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_vKpix|QD}yfa1JiQRk#j4a1Z)n2%fLC6RbVIkUx0b+_+BaR3cnT7Zv!AJxWizFb)h!jyGOOZ85F;a?DAXP{m@;!0_ zIe&*-M!JzZ$N(~e{D!NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWw%BIv?Wdily+ylO`+*KY$4Vz$Cr4+G&IO(4Q`uA9rwXSQO+7mGt}d!;r5mBU zM0dY#r|y`ZzFvTyOmC;&dA;ZQ9DOhSRQ+xGr}ak+SO&8UBnI0I&KNw!HF0k|9WTe* z@liuv!$3o&VU=N*;e?U7(SJOn)kcj*4~%KXT;n9;ZN_cJqb3F>Atp;r>P_yNQcbz0 zDW*G2J50yT%*~?B)|oY%Ju%lZ=bPu7*PGwBU|M)uEVih&xMfMQu79>|wtZn|Vi#w( z#jeBdlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!h;8Eq#KMS9gFl*neeosSBfoHYnBQIkwkyowPu(zdm zs`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMeBmZRodjHV?r+_5^X9J0W zL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0?0=B0A@}E)&XLY(4uw#D z=+@8&Vdi0r!+s1Wg@=V#hChyQh*%oYF_$%W(cD9G-$eREmPFp0XE9GXuPsV7Dn6<% zYCPIEx-_~!#x7=A%+*+(SV?S4962s3t~PFLzTf=q^M~S{;tS(@7nm=|U2u7!&cgJC zrxvL$5-d8FKz~e#PB@hCK@cja7K|nG6L%$!3VFgE!e=5c(KgYD*h5?@9!~N|DouKl z?2)`Rc_hU%r7Y#SgeR$xyi5&D-J3d|7MgY-Z8AMNy)lE5k&tmhsv%92wrA>R=4N)w ztYw9={>5&Kw=W)*2gz%*kgNq+Eef_mrsz~!DAy_nvVUh~S7yJ>iOM;atDY;(?aZ^v z+mJV$@1Ote62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~p zu715HdQEGAUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$ z+<4_1hktL%znR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX4c}I@?e+FW+b@^R zDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&_B8C(+grT%{XWUQ z+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?SIDu(gXbmBM!FLxzyDi(mhmCkJc;e zM-ImyzW$x>cP$Mz4ONYt#^NJzM0w=t_X*$k9t}F$c8q(h;Rn+nb{%IOFKR-X@|s4Q zQ=0o*Vq3aT%s$c9>fU<%N829{oHRUHc}nwC$!Xf@g42^{^3RN&m7RTlF8SPG+oHC6 z=YM0)-)awU@466l;nGF_i|0GMJI-A4xODQe+vO8ixL2C5I$v$-bm~0*lhaSfyPUh4 zuDM)mx$b(swR>jw=^LIm&fWCAdGQwi*43UlJ>9+YdT;l|_x0Zv-F|W>{m#p~*>@-I zt-MdXU-UrjLD@syht)q@{@mE_+<$7ocYmPs(cDM(28Dyq{*m>M4?_iynUBkc4TkHU zI6gT!;y-fz>HMcd&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M z!p0uH$#^p{Ui4P`?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&Gk-1H z0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}0009dNkla``hhR$0v}EeZ=5xo6f{LwSN*%{U)4R*@AtVc zE6-QPQ7kH@=2fER(j=XZM5yRVB?lt1hp@ZxYUA(waU=JER=!Ee!D1&R~tWXy!j8%>WkH9#+aA+R6e7PMyUY+0u`u4L4N`WIPY-p z+=)N;YwJI*Zw`T0U#u?Vllf2fk)6>-Ls@4qlmw^-8ps+M-g{1arzg(2@78}#Nb1RxTzlYbTwAzns&;W5S_r7VXKwnp3S_Pj9$5rH@;L4j!+thM#N z0&6X1!hjn1M37Q2lLl+-e4?9rF3U39xf}B0&KxRHn5G$WqnjFk;c;hfNL`SCZCi9( z&m~zSn@$^Pol*6H-|lf2a?ov!LgC+gx~E+fYUm@^LOPYA8frR8AAhE`wfd-2)U8)2 z1gX#N#$g}|1qyVk8C9s>-HZ&KD^-cAQ^xq^pX(6fx*n` zhnnK5KoIiJa!lLcE*$&aeNs}Wz)aLlIXylFKu>C<5@L)8qf*Kq0``%G1Y#8lA>ewB z{lk5zYTN$dKCb5oA%7672npoVb%s*P-e^%2yP*u*N+~*>4pIs3w>p!%GVV&b0}~NC zoen4nW!Uyte)qxao!2+V$H#^KM1RrBS}oHuxIY|~R8&<(S(P6EZy`w%o__Nb(=?Q2 zdE(C8_e)DlzYia6_t)+-Gi6?8jci7wzP)%~5QS0zK(r=>9>v!j~4F7j(q&Fz1@2|ZmnHR8|i$O xWOFE{2lLaDQVwDn_uRSLeKP;#Umx2)0|1tH+-*Z};lcm_002ovPDHLkV1f^=wch{$ diff --git a/core/res/res/drawable/presence_online.png b/core/res/res/drawable/presence_online.png index 879a762955d18520d1abb221e5f03ec44398515e..22d5683e2f5970a3f53b9d8970713863b6b83d2d 100644 GIT binary patch delta 789 zcmV+w1M2+98>|M9B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0008c zNklSl#Ib-gAD~`Fgz&Ydh_B**hM^8wq3-X1|lNt zAQLa!sNiuPy?9$a=t1VdgR;SPkYQW#PhB_Ht!K2joy%sk`9nq%6B7y<-0$t}9ZRKB(83?F?SBtl z*miKq?n0M!8h>)F2gPC$o12@9WO!$Oe%>(?TaJlDVsc<$U@X;>#5+8K7W@o48vK60 zgKJh1-}M#9IEgd;w;;>%m>>uxf~LHRrl+Sz2L}hAkx}G@o`EXo5e|nD2nOKyxkgsg zg3&NgtJM(F6kL|FSYKbSuC1+Iotc@*ITa<7$+V&f^!b5ySsa&v$GR#Do# zG3GFrHLUe|)X~w=-`3WKulfhb5*>s3Z#KCKKKwxwZ9blh(Zk`ghP9p%_k$a2nLICU zaF1=@VSl3*&Ro{8mX~uh>i&k0dU0IrLqi8OmB@x0))r+ZXNC;<9tpK%spk8%F9K7coW6WVL zYgqf+WUsES&eBBRHf#;|USGi%{Vnyt?Uge}7JpX`oRH4p?)m2k2r^Ij!=a&}DX$`4 zg+gJD?$4quhVkIiD_lSI1TlLUdgYiCyyqNa%waBTyo<^ewIY|xDfxVUmM-UhyNv$I53Dfd_ZqkmaH1sDLhN2LT+ T(hi3B00000NkvXXu0mjf6)<=j delta 3526 zcmV;%4LS0x2FM$bB!3BTNLh0L01FcU01FcV0GgZ_000V4X+uL$P-t&-Z*ypGa3D!T zLm+T+Z)Rz1WdHzp+MQEpR8#2|J@?-9LQ9B%luK_?6$l_wLW_VDktQl32@pz%A)(n7 zQNa;KMFbnjpojyGj)066Q7jCK3fKqaA)=0hqlk*i`{8?|Yk$_f_vX$1wbwr9tn;0- z&j-K=43f59&ghTmgWD0l;*TI7}*0BAb^tj|`8MF3bZ02F3R#5n-i zEdVe{S7t~6u(trf&JYW-00;~KFj0twDF6g}0AR=?BX|IWnE(_<@>e|ZE3OddDgXd@ znX){&BsoQaTL>+22Uk}v9w^R97b_GtVFF>AKrX_0nSU8Ffiw@`^UMGMppg|3;Dhu1 zc+L*4&dxTDwhmt{>c0m6B4T3W{^ifBa6kY6;dFk{{wy!E8h|?nfNlPwCGG@hUJIag z_lst-4?wj5py}FI^KkfnJUm6Akh$5}<>chpO2k52Vaiv1{%68pz*qfj`F=e7_x0eu z;v|7GU4MZ`1o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcqjPo+3 zB8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S1Au6Q z;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO0Dk~Ppn)o|K^yeJ7%adB9Ki+L!3+Fg zHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_vKpix|QD}yfa1JiQRk#j4a1Z)n2%fLC6RbVIkUx0b+_+BaR3cnT7Zv!AJxWizFb)h!jyGOOZ85F;a?DAXP{m@;!0_ zIe&*-M!JzZ$N(~e{D!NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWw%BIv?Wdily+ylO`+*KY$4Vz$Cr4+G&IO(4Q`uA9rwXSQO+7mGt}d!;r5mBU zM0dY#r|y`ZzFvTyOmC;&dA;ZQ9DOhSRQ+xGr}ak+SO&8UBnI0I&KNw!HF0k|9WTe* z@liuv!$3o&VU=N*;e?U7(SJOn)kcj*4~%KXT;n9;ZN_cJqb3F>Atp;r>P_yNQcbz0 zDW*G2J50yT%*~?B)|oY%Ju%lZ=bPu7*PGwBU|M)uEVih&xMfMQu79>|wtZn|Vi#w( z#jeBdlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!h;8Eq#KMS9gFl*neeosSBfoHYnBQIkwkyowPu(zdm zs`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMeBmZRodjHV?r+_5^X9J0W zL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0?0=B0A@}E)&XLY(4uw#D z=+@8&Vdi0r!+s1Wg@=V#hChyQh*%oYF_$%W(cD9G-$eREmPFp0XE9GXuPsV7Dn6<% zYCPIEx-_~!#x7=A%+*+(SV?S4962s3t~PFLzTf=q^M~S{;tS(@7nm=|U2u7!&cgJC zrxvL$5-d8FKz~e#PB@hCK@cja7K|nG6L%$!3VFgE!e=5c(KgYD*h5?@9!~N|DouKl z?2)`Rc_hU%r7Y#SgeR$xyi5&D-J3d|7MgY-Z8AMNy)lE5k&tmhsv%92wrA>R=4N)w ztYw9={>5&Kw=W)*2gz%*kgNq+Eef_mrsz~!DAy_nvVUh~S7yJ>iOM;atDY;(?aZ^v z+mJV$@1Ote62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~p zu715HdQEGAUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$ z+<4_1hktL%znR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX4c}I@?e+FW+b@^R zDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&_B8C(+grT%{XWUQ z+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?SIDu(gXbmBM!FLxzyDi(mhmCkJc;e zM-ImyzW$x>cP$Mz4ONYt#^NJzM0w=t_X*$k9t}F$c8q(h;Rn+nb{%IOFKR-X@|s4Q zQ=0o*Vq3aT%s$c9>fU<%N829{oHRUHc}nwC$!Xf@g42^{^3RN&m7RTlF8SPG+oHC6 z=YM0)-)awU@466l;nGF_i|0GMJI-A4xODQe+vO8ixL2C5I$v$-bm~0*lhaSfyPUh4 zuDM)mx$b(swR>jw=^LIm&fWCAdGQwi*43UlJ>9+YdT;l|_x0Zv-F|W>{m#p~*>@-I zt-MdXU-UrjLD@syht)q@{@mE_+<$7ocYmPs(cDM(28Dyq{*m>M4?_iynUBkc4TkHU zI6gT!;y-fz>HMcd&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M z!p0uH$#^p{Ui4P`?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&Gk-1H z0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}0008>NklVte{qu9a%2lue70(%2jEY&|&?@W|C@hqucvGU-*UwcBZyZYeL~^V-9+{)PXwuh$-; zh)A<^%UH90>Cxjy|9>1F9@^+i^pUP!or$Y$`hV@``)Ty7M2; zZvapf#aQ+a_7{vXoB7MU$n(6&^P(t5`qa-~=7lk4bNAQ%g+IyIsW(5io!(A0uE&dh z=y#N-T1u%#Nwf^a$zE>LUUFi#uXV1S``G>+00>8`SaS~DF#rGn07*qoM6N<$f~$V7 AQ~&?~ diff --git a/core/res/res/drawable/zoom_ring_trail.xml b/core/res/res/drawable/zoom_ring_trail.xml new file mode 100644 index 000000000000..08931ac5aafc --- /dev/null +++ b/core/res/res/drawable/zoom_ring_trail.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker.xml index a398bd006bcc..0760cc0b7123 100644 --- a/core/res/res/layout/date_picker.xml +++ b/core/res/res/layout/date_picker.xml @@ -24,6 +24,7 @@ diff --git a/core/res/res/layout/date_picker_dialog.xml b/core/res/res/layout/date_picker_dialog.xml index 879f3398f1bf..949c8a3082c7 100644 --- a/core/res/res/layout/date_picker_dialog.xml +++ b/core/res/res/layout/date_picker_dialog.xml @@ -17,11 +17,9 @@ */ --> - - - + diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml index 422733a002c5..bbdb31cb44e6 100644 --- a/core/res/res/layout/number_picker.xml +++ b/core/res/res/layout/number_picker.xml @@ -22,23 +22,21 @@ - - - + android:background="@drawable/timepicker_up_btn" /> + + + - + android:background="@drawable/timepicker_down_btn" /> + diff --git a/core/res/res/layout/number_picker_edit.xml b/core/res/res/layout/number_picker_edit.xml index 46f4845ad3e7..f3af6e9b211e 100644 --- a/core/res/res/layout/number_picker_edit.xml +++ b/core/res/res/layout/number_picker_edit.xml @@ -23,6 +23,7 @@ android:gravity="center_horizontal" android:singleLine="true" style="?android:attr/textAppearanceLargeInverse" + android:textColor="@android:color/primary_text_light" android:textSize="30sp" android:background="@drawable/timepicker_input" /> diff --git a/core/res/res/layout/time_picker.xml b/core/res/res/layout/time_picker.xml index bdfe4900e663..c601e0e83077 100644 --- a/core/res/res/layout/time_picker.xml +++ b/core/res/res/layout/time_picker.xml @@ -21,6 +21,7 @@ @@ -55,5 +56,6 @@ android:paddingLeft="20dip" android:paddingRight="20dip" style="?android:attr/textAppearanceLargeInverse" + android:textColor="@android:color/primary_text_light_nodisable" /> diff --git a/core/res/res/layout/time_picker_dialog.xml b/core/res/res/layout/time_picker_dialog.xml index 6dc1bf62d150..d5a6b5eb9bc1 100644 --- a/core/res/res/layout/time_picker_dialog.xml +++ b/core/res/res/layout/time_picker_dialog.xml @@ -17,11 +17,9 @@ */ --> - - - + diff --git a/core/res/res/layout/time_picker_text.xml b/core/res/res/layout/time_picker_text.xml deleted file mode 100644 index bad980b64324..000000000000 --- a/core/res/res/layout/time_picker_text.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/core/res/res/values-cs-rCZ/strings.xml b/core/res/res/values-cs-rCZ/strings.xml deleted file mode 100644 index e1eb3f475b6c..000000000000 --- a/core/res/res/values-cs-rCZ/strings.xml +++ /dev/null @@ -1,1112 +0,0 @@ - - - - - - - - - - - - - - - "<bez názvu>" - "…" - "(žádné telefonní číslo)" - "(neznámý)" - "Hlasová schránka" - "Msisdn1" - "Chyba sítě nebo neplatný kód MMI." - "Služba povolena" - "Služba povolena pro:" - "Služba zakázána" - "Registrace úspěšná" - "Odstranění úspěšné" - "Nesprávné heslo." - "MMI dokončeno" - - - - - - - - - - - - - - - - - - - - - - - - - - - "Výchozí nastavení omezení ID - omezení. Další hovor: omezení" - "Výchozí nastavení omezení ID - omezení. Další hovor: bez omezení" - "Výchozí nastavení omezení ID - bez omezení. Další hovor: omezení" - "Výchozí nastavení omezení ID - bez omezení. Další hovor: bez omezení" - "Služba není poskytována." - "Omezení ID v trvalém režimu." - "Hlasový záznam" - "Data" - "FAX" - "SMS" - "Asynchronní" - "Synchronizace" - "Pakety" - "PAD" - "{0}: Nepřesměrováno" - "{0}: {1}" - "{0}: {1} po {2} sekundách" - "{0}: Nepřesměrováno ({1})" - "{0}: Nepřesměrováno ({1} po {2} sekundách)" - "OK" - "Neznámá chyba" - "Neznámý hostitel" - "Nepodporované schéma ověření. Ověření se nezdařilo." - "Ověřování se nezdařilo" - "Ověření serverem proxy se nezdařilo" - "Připojení k serveru se nezdařilo" - "Čtení nebo zápis na server se nezdařil" - "Časový limit připojení k serveru vypršel" - "Příliš mnoho přesměrování serverů" - "Nepodporovaný protokol" - "Navázání spojení typu SSL handshake se nezdařilo" - "Nepodařilo se analyzovat URL" - "File error" - "File not found" - - - "Synchronizace" - "Synchronizace" - - - - - - - "Možnosti napájení" - "Tichý režim" - "Zapnout rádio" - "Vypnout rádio" - - - "Vypnuto" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Příjem zpráv SMS" - "Umožňuje aplikacím přijímat a zpracovávat zprávy SMS. Škodlivé aplikace mohou sledovat vaše zprávy nebo je odstraňovat, aniž by se zobrazily." - "Příjem zpráv MMS" - "Umožňuje aplikacím přijímat a zpracovávat zprávy MMS. Škodlivé aplikace mohou sledovat vaše zprávy nebo je odstraňovat, aniž by se zobrazily." - - - - - - - - - - - - - "Příjem zpráv WAP" - "Umožňuje aplikacím přijímat a zpracovávat zprávy WAP. Škodlivé aplikace mohou sledovat vaše zprávy nebo je odstraňovat, aniž by se zobrazily." - "Získat informace o úkolech" - "Umožňuje aplikacím načítat informace o aktuálně a naposledy spuštěných úkolech. Umožňuje škodlivým aplikacím zjišťovat soukromé informace o jiných aplikacích." - - - - - - - - - - - - - - - - - - - - - - - - - "Výpis stavu systému" - "Umožňuje aplikacím načítat vnitřní stav systému. Škodlivé aplikace mohou načítat široký rozsah soukromých a důvěrných informací, jež by obvykle neměly nikdy vyžadovat." - "Přidat systémovou službu" - "Umožňuje aplikacím vydávat vlastní systémové služby nižší úrovně. Škodlivé aplikace mohou napadnout systém a vykrást nebo poškodit jeho data." - "Nastavení sledování činností" - "Umožňuje aplikacím sledovat a řídit spouštění činností systému. Škodlivé aplikace mohou zcela zničit systém. Toto oprávnění je nutné pouze pro vývoj, nikdy pro normální používání zařízení." - "Sada vysílání odebrána" - "Umožňuje aplikacím vysílat oznámení o odebrání sady aplikací. Škodlivé aplikace toho mohou využít k likvidaci jiné spuštěné aplikace." - - - - - - - - - - - - - - - - - "Instalace aktualizace systému" - "Umožňuje aplikacím přijímat oznámení o aktualizacích systému čekajících na dokončení a spouštět jejich instalaci. Škodlivé aplikace toho mohou využít k poškození systému neautorizovanými aktualizacemi nebo obecně k zásahům do aktualizačního procesu." - - - - - "Okno vnitřního systému" - "Umožňuje vytváření oken určených k použití uživatelským rozhraním vnitřního systému . Není určeno k použití normálními aplikacemi." - "Okno systémových výstrah" - "Umožňuje aplikacím zobrazovat okna systémových výstrah. Škodlivé aplikace mohou ovládnout celou obrazovku zařízení." - - - - - - - - - - - - - - - - - - - - - "Signálové trvalé procesy" - "Umožňuje aplikacím vyžadovat, aby se přiváděný signál odesílal do všech trvalých procesů." - - - - - "Odstranit sady" - "Umožňuje aplikacím odstranit sady systému Android. Škodlivé aplikace toho mohou využít k odstranění důležitých aplikací." - - - - - - - - - - - - - "Instalovat sady" - "Umožňuje aplikacím instalovat nové nebo aktualizované sady systému Android. Škodlivé aplikace toho mohou využít k přidání nových aplikací s libovolně silnými oprávněními." - - - - - - - - - - - - - "Povolit nebo zakázat součásti aplikací" - "Umožňuje změnu aplikace bez ohledu na to, zda je součást další aplikace povolená nebo zakázaná. Škodlivá aplikace toho může využít k zakázání důležitých funkcí zařízení. Je třeba nakládat s oprávněními opatrně, protože se mohou součásti aplikace dostat do stavu nepoužitelnosti, nekonzistence nebo nestability." - "Nastavení upřednostňovaných aplikací" - "Umožňuje aplikacím upravovat oblíbené aplikace. Škodlivé aplikace tak mohou bez upozornění měnit spouštěné aplikace a klamně využívat stávající aplikace ke shromažďování vašich soukromých dat." - "Nastavení systému pro zápis" - "Umožňuje aplikacím upravovat data nastavení systému. Škodlivé aplikace mohou narušit systémovou konfiguraci." - - - - - - - - - "Spustit při spouštění" - "Umožňuje aplikacím spouštět se po dokončení spuštění systému. Tím se může prodlužovat doba spouštění zařízení a aplikace může svým stálým spouštěním zpomalovat celé zařízení." - "Vysílat lepivý obsah (sticky)" - "Umožňuje aplikacím odesílat tzv. lepivé (sticky) vysílání, které zůstává i po ukončení vysílání. Škodlivé aplikace mohou zpomalit zařízení nebo narušit jeho stabilitu vynucením využívání příliš velké části paměti." - "Čtení dat o kontaktech" - "Umožňuje aplikacím číst všechna data o kontaktech (adresy) uložená v zařízení. Škodlivé aplikace toho mohou využívat k odesílání vašich dat jiným osobám." - "Zápis dat o kontaktech" - "Umožňuje aplikacím upravovat data o kontaktech (adresy) uložená v zařízení. Škodlivé aplikace toho mohou využívat k vymazání nebo úpravě dat o kontaktech." - - - - - - - - - - - - - - - - - - - - - - - - - "Používat službu GPS" - "Technologii GPS v zařízení lze používat, pokud je k dispozici. Toto oprávnění vyžaduje oprávnění ACCESS_LOCATION. Škodlivé aplikace toho mohou využívat k určení vaší polohy a mohou spotřebovávat zbytečně energii baterie." - "Používat službu Cell ID" - "Identifikátory pro technologii využívající polohu vysílačů mobilních sítí (je-li k dispozici) se používají k určení přibližné polohy zařízení. Toto oprávnění vyžaduje oprávnění ACCESS_LOCATION. Škodlivé aplikace toho mohou využívat k určení vaší přibližné polohy." - "Používat službu SurfaceFlinger" - "Umožňuje aplikacím používat funkce nižší úrovně SurfaceFlinger." - "Čtení vyrovnávací paměti rámce" - "Umožňuje aplikacím používat čtení obsahu vyrovnávací paměti rámce." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Volat telefonní čísla" - "Umožňuje aplikacím volat telefonní čísla bez vašeho zásahu. Škodlivé aplikace mohou přinést na váš telefonní účet neočekávané hovory." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Výchozí" - "Zaměstnání" - "Primární" - "Vlastní…" - - - "Poštovní" - "Výchozí" - "Zaměstnání" - "Vlastní…" - - - - - - - - - - - - - - - - - - - - - "Telefon odemknete stisknutím tlačítka nabídky a poté 0." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Výrobní test skončil chybou" - "Akce FACTORY_TEST je podporována pouze pro sady instalované v adresáři /system/app." - "Nebyla nalezena žádná sada, která zajišťuje akci FACTORY_TEST." - "Restartovat" - - - - - - - - - - - - - - - "Další" - "Menu+" - - - - - - - "PŘEJÍT" - "Dnes" - "Včera" - "Zítra" - - - - - - - - - - - - - - - - - - - - - - - - - - - "den" - "dnů" - "hodinu" - "hodin" - "minutu" - "minut" - "sekund" - "sekund" - "týden" - "týdnů" - - - - - "Neděle" - "Pondělí" - "Úterý" - "Středa" - "Čtvrtek" - "Pátek" - "Sobota" - "Každý den v týdnu (Po–Pá)" - "Denně" - "Týdně (%s)" - "Měsíčně" - "Ročně" - - - - - - - "dop." - "odp." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 37632f8e4491..e0c0a64ee7f5 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 3015957a1039..d9c417434a30 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -818,6 +818,5 @@ - diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 4cb3ee1c004c..d9cf3d5122ac 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1,765 +1,5 @@ - "B" - "KB" - "MB" - "GB" - "TB" - "PB" - "<untitled>" - "…" - "(No phone number)" - "(Unknown)" - "Voicemail" - "MSISDN1" - "Connection problem or invalid MMI code." - "Service was enabled." - "Service was enabled for:" - "Service has been disabled." - "Registration was successful." - "Erasure was successful." - "Incorrect password." - "MMI complete." - "The old PIN you typed is not correct." - "The PUK you typed is not correct." - "The PINs you entered do not match." - "Type a PIN that is 4 to 8 numbers." - - - "Type PUK2 to unblock SIM card." - "Incoming Caller ID" - "Outgoing Caller ID" - "Call forwarding" - "Call waiting" - "Call barring" - "Password change" - "PIN change" - "Caller ID defaults to restricted. Next call: Restricted" - "Caller ID defaults to restricted. Next call: Not restricted" - "Caller ID defaults to not restricted. Next call: Restricted" - "Caller ID defaults to not restricted. Next call: Not restricted" - "Service not provisioned." - "The caller ID setting cannot be changed." - "Voice" - "Data" - "FAX" - "SMS" - "Async" - "Sync" - "Packet" - "PAD" - "{0}: Not forwarded" - "{0}: {1}" - "{0}: {1} after {2} seconds" - "{0}: Not forwarded" - "{0}: Not forwarded" - "OK" - "The Web page contains an error." - "The URL could not be found." - "The site authentication scheme is not supported." - "Authentication was unsuccessful." - "Authentication via the proxy server was unsuccessful." - "The connection to the server was unsuccessful." - "The server failed to communicate. Try again later." - "The connection to the server timed out." - "The page contains too many server redirects." - "The protocol is not supported." - "A secure connection could not be established." - "The page could not be opened because the URL is invalid." - "The file could not be accessed." - "The requested file was not found." - "Too many requests are being processed. Try again later." - "Sync" - "Sync" - "Too many %s deletes." - "Phone storage is full! Delete some files to free space." - "Me" - "Phone options" - "Silent mode" - "Turn on wireless" - "Turn off wireless" - "Screen lock" - "Power off" - "Shutting down…" - "Your phone will shut down." - "No recent applications." - "Phone options" - "Screen lock" - "Power off" - "Silent mode" - "Sound is OFF" - "Sound is ON" - "Safe mode" - "Cost you money" - "Allow applications to do things that can cost you money." - "Your messages" - "Read and write your SMS, e-mail, and other messages." - "Your personal information" - "Direct access to your contacts and calendar stored on the phone." - "Your location" - "Monitor your physical location" - "Network communication" - "Allow applications to access various network features." - "Your Google accounts" - "Access the available Google accounts." - "Hardware controls" - "Direct access to hardware on the handset." - "Phone calls" - "Monitor, record, and process phone calls." - "System tools" - "Lower-level access and control of the system." - "Development tools" - "Features only needed for application developers." - "disable or modify status bar" - "Allows application to disable the status bar or add and remove system icons." - "expand/collapse status bar" - "Allows application to expand or collapse the status bar." - "intercept outgoing calls" - "Allows application to process outgoing calls and change the number to be dialed. Malicious applications may monitor, redirect, or prevent outgoing calls." - "receive SMS" - "Allows application to receive and process SMS messages. Malicious applications may monitor your messages or delete them without showing them to you." - "receive MMS" - "Allows application to receive and process MMS messages. Malicious applications may monitor your messages or delete them without showing them to you." - "send SMS messages" - "Allows application to send SMS messages. Malicious applications may cost you money by sending messages without your confirmation." - "read SMS or MMS" - "Allows application to read SMS messages stored on your phone or SIM card. Malicious applications may read your confidential messages." - "edit SMS or MMS" - "Allows application to write to SMS messages stored on your phone or SIM card. Malicious applications may delete your messages." - "receive WAP" - "Allows application to receive and process WAP messages. Malicious applications may monitor your messages or delete them without showing them to you." - "retrieve running applications" - "Allows application to retrieve information about currently and recently running tasks. May allow malicious applications to discover private information about other applications." - "reorder running applications" - "Allows an application to move tasks to the foreground and background. Malicious applications can force themselves to the front without your control." - "enable application debugging" - "Allows an application to turn on debugging for another application. Malicious applications can use this to kill other applications." - "change your UI settings" - "Allows an application to change the current configuration, such as the locale or overall font size." - "restart other applications" - "Allows an application to forcibly restart other applications." - "keep from being stopped" - - - "force application to close" - "Allows an application to force any activity that is in the foreground to close and go back. Should never be needed for normal applications." - "retrieve system internal state" - "Allows application to retrieve internal state of the system. Malicious applications may retrieve a wide variety of private and secure information that they should never normally need." - "publish low-level services" - "Allows application to publish its own low-level system services. Malicious applications may hijack the system, and steal or corrupt any data on it." - "monitor and control all application launching" - "Allows an application to monitor and control how the system launches activities. Malicious applications may completely compromise the system. This permission is only needed for development, never for normal phone usage." - "send package removed broadcast" - "Allows an application to broadcast a notification that an application package has been removed. Malicious applications may use this to kill any other running application." - - - - - - - - - "limit number of running processes" - "Allows an application to control the maximum number of processes that will run. Never needed for normal applications." - "make all background applications close" - "Allows an application to control whether activities are always finished as soon as they go to the background. Never needed for normal applications." - "automatically install system updates" - "Allows an application to receive notifications about pending system updates and trigger their installation. Malicious applications may use this to corrupt the system with unauthorized updates, or generally interfere with the update process." - "modify battery statistics" - "Allows the modification of collected battery statistics. Not for use by normal applications." - "display unauthorized windows" - "Allows the creation of windows that are intended to be used by the internal system user interface. Not for use by normal applications." - "display system-level alerts" - "Allows an application to show system alert windows. Malicious applications can take over the entire screen of the phone." - "modify global animation speed" - "Allows an application to change the global animation speed (faster or slower animations) at any time." - "manage application tokens" - "Allows applications to create and manage their own tokens, bypassing their normal Z-ordering. Should never be needed for normal applications." - "press keys and control buttons" - "Allows an application to deliver its own input events (key presses, etc.) to other applications. Malicious applications can use this to take over the phone." - "record what you type and actions you take" - "Allows applications to watch the keys you press even when interacting with another application (such as entering a password). Should never be needed for normal applications." - "change screen orientation" - "Allows an application to change the rotation of the screen at any time. Should never be needed for normal applications." - "send Linux signals to applications" - "Allows application to request that the supplied signal be sent to all persistent processes." - "make application always run" - - - "delete applications" - "Allows an application to delete Android packages. Malicious applications can use this to delete important applications." - "delete other applications data" - "Allows an application to clear user data." - "delete other applications cache" - "Allows an application to delete cache files." - "measure application storage space" - "Allows an application to retrieve its code, data, and cache sizes" - "directly install applications" - "Allows an application to install new or updated Android packages. Malicious applications can use this to add new applications with arbitrarily powerful permissions." - "delete all application cache data" - "Allows an application to free phone storage by deleting files in application cache directory. Access is very restricted usually to system process." - "read system log files" - - - - - - - "enable or disable application components" - "Allows an application to change whether a component of another application is enabled or not. Malicious applications can use this to disable important phone capabilities. Care must be used with permission, as it is possible to get application components into an unusable, inconsistant, or unstable state." - "set preferred applications" - "Allows an application to modify your preferred applications. This can allow malicious applications to silently change the applications that are run, spoofing your existing applications to collect private data from you." - "modify global system settings" - "Allows an application to modify the systems settings data. Malicious applications can corrupt your systems configuration." - - - - - - - - - "automatically start at boot" - "Allows an application to have itself started as soon as the system has finished booting. This can make it take longer to start the phone and allow the application to slow down the overall phone by always running." - "send sticky broadcast" - "Allows an application to send sticky broadcasts, which remain after the broadcast ends. Malicious applications can make the phone slow or unstable by causing it to use too much memory." - "read contact data" - "Allows an application to read all of the contact (address) data stored on your phone. Malicious applications can use this to send your data to other people." - "write contact data" - "Allows an application to modify the contact (address) data stored on your phone. Malicious applications can use this to erase or modify your contact data." - "write owner data" - "Allows an application to modify the phone owner data stored on your phone. Malicious applications can use this to erase or modify owner data." - "read owner data" - "Allows an application read the phone owner data stored on your phone. Malicious applications can use this to read phone owner data." - "read calendar data" - "Allows an application to read all of the calendar events stored on your phone. Malicious applications can use this to send your calendar events to other people." - "write calendar data" - "Allows an application to modify the calendar events stored on your phone. Malicious applications can use this to erase or modify your calendar data." - "mock location sources for testing" - "Create mock location sources for testing. Malicious applications can use this to override the location and/or status returned by real location sources such as GPS or Network providers." - - - - - "fine (GPS) location" - "Access fine location sources such as the Global Positioning System on the phone, where available. Malicious applications can use this to determine where you are, and may consume additional battery power." - "coarse (network-based) location" - "Access coarse location sources such as the cellular network database to determine an approximate phone location, where available. Malicious applications can use this to determine approximately where you are." - "access SurfaceFlinger" - "Allows application to use SurfaceFlinger low-level features." - "read frame buffer" - "Allows application to use read the content of the frame buffer." - "change your audio settings" - "Allows application to modify global audio settings such as volume and routing." - "record audio" - "Allows application to access the audio record path." - "take pictures" - "Allows application to take pictures with the camera. This allows the application at any time to collect images the camera is seeing." - "permanently disable phone" - "Allows the application to disable the entire phone permanently. This is very dangerous." - - - - - "mount and unmount filesystems" - "Allows the application to mount and unmount filesystems for removable storage." - "control vibrator" - "Allows the application to control the vibrator." - "control flashlight" - "Allows the application to control the flashlight." - "test hardware" - "Allows the application to control various peripherals for the purpose of hardware testing." - "directly call phone numbers" - "Allows the application to call phone numbers without your intervention. Malicious applications may cause unexpected calls on your phone bill. Note that this does not allow the application to call emergency numbers." - - - - - - - - - - - - - "modify phone state" - "Allows the application to control the phone features of the device. An application with this permission can switch networks, turn the phone radio on and off and the like without ever notifying you." - "read phone state" - "Allows the application to access the phone features of the device. An application with this permission can determine the phone number of this phone, whether a call is active, the number that call is connected to and the like." - "prevent phone from sleeping" - "Allows an application to prevent the phone from going to sleep." - "power phone on or off" - "Allows the application to turn the phone on or off." - "run in factory test mode" - "Run as a low-level manufacturer test, allowing complete access to the phone hardware. Only available when a phone is running in manufacturer test mode." - "set wallpaper" - "Allows the application to set the system wallpaper." - "set wallpaper size hints" - "Allows the application to set the system wallpaper size hints." - "reset system to factory defaults" - "Allows an application to completely reset the system to its factory settings, erasing all data, configuration, and installed applications." - "set time zone" - "Allows an application to change the phones time zone." - "discover known accounts" - "Allows an application to get the list of accounts known by the phone." - "view network state" - "Allows an application to view the state of all networks." - "full Internet access" - "Allows an application to create network sockets." - - - - - "change network connectivity" - "Allows an application to change the state network connectivity." - "view Wi-Fi state" - "Allows an application to view the information about the state of Wi-Fi." - "change Wi-Fi state" - "Allows an application to connect to and disconnect from Wi-Fi access points, and to make changes to configured Wi-Fi networks." - "bluetooth administration" - "Allows an application to configure the local Bluetooth phone, and to discover and pair with remote devices." - "create Bluetooth connections" - "Allows an application to view configuration of the local Bluetooth phone, and to make and accept connections with paired devices." - "disable keylock" - "Allows an application to disable the keylock and any associated password security. A legitimate example of this is the phone disabling the keylock when receiving an incoming phone call, then re-enabling the keylock when the call is finished." - "read sync settings" - "Allows an application to read the sync settings, such as whether sync is enabled for Contacts." - "write sync settings" - "Allows an application to modify the sync settings, such as whether sync is enabled for Contacts." - "read sync statistics" - "Allows an application to reafocusd the sync stats; e.g., the history of syncs that have occurred." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Enter PIN code:" - "Incorrect PIN code!" - "To unlock, press Menu then 0." - "Emergency number" - "(No service)" - "Screen locked" - "Press Menu to unlock or place emergency call." - "Press Menu to unlock." - "Draw pattern to unlock:" - "Emergency call" - "Correct!" - "Sorry, try again:" - "Charging (%d%%)" - "Connect your charger." - "No SIM card." - "No SIM card in phone." - "Please insert a SIM card." - - - "SIM card is PUK-locked." - "Please contact Customer Care." - "SIM card is locked." - "Unlocking SIM card…" - "You have incorrectly drawn your unlock pattern %d times. "\n\n"Please try again in %d seconds." - "You have incorrectly drawn your unlock pattern %d times. After %d more unsuccessful attempts, you will be asked to unlock your phone using your Google sign-in."\n\n" Please try again in %d seconds." - "Try again in %d seconds." - "Forgot pattern?" - "Too many pattern attempts!" - "To unlock,"\n"sign in with your Google account:" - "Username (email)" - "Password" - "Sign in" - "Invalid username or password." - - - - - - - - - - - "Clear notifications" - "No notifications" - "Ongoing" - "Notifications" - "%d" - "Charging…" - "Please connect charger" - "The battery is getting low:" - "less than %d%% remaining." - "Factory test failed" - "The FACTORY_TEST action is only supported for packages installed in /system/app." - "No package was found that provides the FACTORY_TEST action." - "Reboot" - "Confirm" - "Do you want the browser to remember this password?" - "Not now" - "Remember" - "Never" - "You do not have permission to open this page." - "Text copied to clipboard." - "More" - "Menu+" - - - - - - - "Search" - "Today" - "Yesterday" - "Tomorrow" - "1 month ago" - - - - - - - - - - - - - - - - - - - "on %s" - "at %s" - "in %s" - "day" - "days" - "hour" - "hours" - "min" - "mins" - "sec" - "secs" - "week" - "weeks" - "year" - "years" - "Sunday" - "Monday" - "Tuesday" - "Wednesday" - "Thursday" - "Friday" - "Saturday" - "Every weekday (Mon–Fri)" - "Daily" - "Weekly on %s" - "Monthly" - "Yearly" - "Cannot play video" - "Sorry, this video cannot be played." - "OK" - "AM" - "PM" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "noon" - "Noon" - "midnight" - "Midnight" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Sunday" - "Monday" - "Tuesday" - "Wednesday" - "Thursday" - "Friday" - "Saturday" - "Sun" - "Mon" - "Tue" - "Wed" - "Thu" - "Fri" - "Sat" - "Su" - "Mo" - "Tu" - "We" - "Th" - "Fr" - "Sa" - "Su" - "M" - "Tu" - "W" - "Th" - "F" - "Sa" - "S" - "M" - "T" - "W" - "T" - "F" - "S" - "January" - "February" - "March" - "April" - "May" - "June" - "July" - "August" - "September" - "October" - "November" - "December" - "Jan" - "Feb" - "Mar" - "Apr" - "May" - "Jun" - "Jul" - "Aug" - "Sep" - "Oct" - "Nov" - "Dec" - "J" - "F" - "M" - "A" - "M" - "J" - "J" - "A" - "S" - "O" - "N" - "D" - - - - - "Select all" - "Cut" - "Cut all" - "Copy" - "Copy all" - "Paste" - "Copy URL" - - - - - "Low on space" - "Your phone is running low on internal storage space." - "OK" - "Cancel" - "OK" - "Cancel" - "ON" - "OFF" - "Complete action using" - "Use by default for this action." - "Clear default in Home Settings > Applications > Manage applications." - "Select an action" - "No applications can perform this action." - "Sorry!" - "The application %1$s (process %2$s) has stopped unexpectedly. Please try again." - "The process %1$s has stopped unexpectedly. Please try again." - "Application unresponsive" - "Activity %1$s (in application %2$s) is not responding." - "Activity %1$s (in process %2$s) is not responding." - "Application %1$s (in process %2$s) is not responding." - "Process %1$s is not responding." - "Force close" - "Wait" - "Debug" - "Select an action for text" - "Ringer volume" - "Music/video volume" - "In-call volume" - "Alarm volume" - "Volume" - "Default ringtone" - "Default ringtone (%1$s)" - "Silent" - "Select a ringtone" - "Unknown ringtone" - - - - - "Select character to insert" - - - "Sending SMS messages" - "A large number of SMS messages are being sent. Select \"OK\" to continue, or \"Cancel\" to stop sending." - "OK" - "Cancel" - "Set" - "Default" - "No permissions required" - - - - - - - - - - - - - - - - - - - - - - - - - - - - + B diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index f8881b6f4dbc..dc1744552739 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 47f1bf49dad5..95e30528a109 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 710c49340ce4..2cf7b43b2cf3 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 0eba7f55144d..24e3cb6e7cad 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -558,12 +558,12 @@ "%2$s、%1$s" - "yyyy\'年\'MMMMd\'日\'" - "yyyy\'年\'MMMMd\'日\'" - "yyyy\'年\'MMMd\'日\'" - "yyyy\'年\'MMMd\'日\'" - "h:mm a" - "H:mm" + "yyyy'\'\'年\'\''MMMMd'\'\'日\'\''" + "yyyy'\'\'年\'\''MMMMd'\'\'日\'\''" + "yyyy'\'\'年\'\''MMMd'\'\'日\'\''" + "yyyy'\'\'年\'\''MMMd'\'\'日\'\''" + "h':'mm' 'a" + "H':'mm" "正午" "正午" "午前0時" @@ -573,7 +573,8 @@ "%Y年%B%-d日" - "%Y年 %B" + + "%H:%M:%S" "%Y年%B%-d日%H:%M:%S" "%2$s%3$s日~%7$s%8$s日" @@ -816,6 +817,5 @@ - diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 4272684f8bb6..140f32f16527 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 9f4955773541..466f2b175619 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -262,10 +262,8 @@ "Lar applikasjonen tvinge telefonen til å starte på nytt." "montere og avmontere filsystemer" "Lar applikasjonen montere og avmontere filsystemer for uttagbar lagring." - - - - + "formatere ekstern lagringsplass" + "Lar applikasjonen formatere ekstern lagringsplass." "kontrollere vibratoren" "Lar applikasjonen kontrollere vibratoren." "kontrollere lommelykten" @@ -280,10 +278,8 @@ "Lar applikasjonen slå av/på varsling om plasseringsendringer fra radioen. Ikke ment for vanlige applikasjoner." "få tilgang til egenskaper for innsjekking" "Gir lese- og skrivetilgang til egenskaper lastet opp av innsjekkingstjenesten. Ikke ment for vanlige applikasjoner." - - - - + "velg gadgeter" + "Lar applikasjonen fortelle systemet hvilke gadgeter som kan brukes av hvilke applikasjoner. Med denne rettigheten kan applikasjoner andre applikasjoner tilgang til personlig data. Ikke ment for vanlige applikasjoner." "endre telefontilstand" "Lar applikasjonen kontrollere telefonfunksjonaliteten i enheten. En applikasjon med denne rettigheten kan endre nettverk, slå telefonens radio av eller på og lignende uten noensinne å varsle brukeren." "lese telefontilstand" @@ -312,10 +308,8 @@ "Lar applikasjonen to endre APN-innstillinger slik som mellomtjener eller port for hvilket som helst aksesspunkt." "endre nettverkskonnektivitet" "Lar applikasjonen endre tilstanden til nettverkskonnektivitet." - - - - + "endre innstilling for bakgrunnsdata" + "Lar applikasjonen endre innstillingen for bakgrunnsdata." "se tilstand for trådløse nettverk" "Lar applikasjonen få se informasjon om tilstanden til de trådløse nettene." "endre tilstand for trådløse nettverk" @@ -336,14 +330,10 @@ "Lar applikasjonen hente detaljer om hvilke nyhetskilder som synkroniseres." "endre abonnement på nyhetskilder" "Lar applikasjonen redigere hvilke nyhetskilder som synkroniseres. Dette kan gi en ondsinnet applikasjon tilgang til å endre hvilke nyhetskilder som synkroniseres." - - - - - - - - + "lese brukerdefinert ordliste" + "Lar applikasjonen lese private ord, navn og uttrykk som brukeren har lagret i den brukerdefinerte ordlisten." + "skrive til brukerdefinert ordliste" + "Lar applikasjonen skrive nye ord til den brukerdefinerte ordlisten." "Hjemme" "Mobil" @@ -440,12 +430,9 @@ "The FACTORY_TEST action is only supported for packages installed in /system/app." "No package was found that provides the FACTORY_TEST action." "Reboot" - - - - - - + "Siden \\\'%s\\\' sier:" + "JavaScript" + "Naviger bort fra denne siden?"\n\n"%s"\n\n"Velg OK for å fortsette, eller Avbryt for å forbli på denne siden." "Bekreft" "Ønsker du at nettleseren skal huske dette passordet?" "Ikke nå" @@ -569,17 +556,15 @@ "%1$s, %2$s %3$s" "%2$s %3$s" "%1$s, %3$s" - - - - + "%1$s, %2$s" + "%1$s, %2$s" "%1$s, %2$s" - "MMMM' 'd'., 'yyyy" - "d'. 'MMMM' 'yyyy" - "MMM' 'd', 'yyyy" - "d'. 'MMM' 'yyyy" - "h':'mm' 'a" - "H':'mm" + "MMMM'\\\'\'\\\'\' \\\'\'\\\'\''d'\\\'\'\\\'\'., \\\'\'\\\'\''yyyy" + "d'\\\'\'\\\'\'. \\\'\'\\\'\''MMMM'\\\'\'\\\'\' \\\'\'\\\'\''yyyy" + "MMM'\\\'\'\\\'\' \\\'\'\\\'\''d'\\\'\'\\\'\', \\\'\'\\\'\''yyyy" + "d'\\\'\'\\\'\'. \\\'\'\\\'\''MMM'\\\'\'\\\'\' \\\'\'\\\'\''yyyy" + "h'\\\'\'\\\'\':\\\'\'\\\'\''mm'\\\'\'\\\'\' \\\'\'\\\'\''a" + "H'\\\'\'\\\'\':\\\'\'\\\'\''mm" "middag" "Middag" "midnatt" @@ -596,18 +581,14 @@ "%2$s %3$s – %7$s %8$s" "%1$s %3$s. %2$s – %6$s %8$s. %7$s" "%3$s. %2$s – %8$s. %7$s %9$s" - - + "%1$s %2$s %3$s – %6$s %7$s %8$s, %9$s" "%3$s. %2$s %5$s – %8$s. %7$s %10$s" "%1$s %3$s. %2$s %5$s – %6$s %8$s. %7$s %10$s" - - - - + "%3$s. %2$s %4$s %5$s – %8$s. %7$s %9$s %10$s" + "%1$s %3$s. %2$s %4$s %5$s – %6$s %8$s. %7$s %9$s %10$s" "%3$s.%2$s. – %8$s.%7$s." "%1$s %3$s.%2$s. – %6$s %8$s.%7$s." - - + "%3$s.%2$s.%4$s – %8$s.%7$s.%9$s" "%1$s %3$s.%2$s.%4$s – %6$s %8$s.%7$s.%9$s" "%3$s.%2$s. %5$s – %8$s.%7$s. %10$s" "%1$s %3$s.%2$s. %5$s – %6$s %8$s.%7$s. %10$s" @@ -711,8 +692,7 @@ "Lim inn" "Kopier URL" "Inndatametode" - - + "Legg \\\"%s\\\" til ordlisten" "Rediger tekst" "Lite plass" "Det begynner å bli lite lagringsplass på telefonen." @@ -720,8 +700,7 @@ "Avbryt" "OK" "Avbryt" - - + "Merk" "På" "Av" "Complete action using" @@ -745,8 +724,7 @@ "Medievolum" "Spiller over Bluetooth" "Samtalevolum" - - + "Bluetooth-samtalevolum" "Alarmvolum" "Varslingsvolum" "Volum" @@ -766,7 +744,7 @@ "Sett inn tegn" "Ukjent applikasjon" "Sending SMS messages" - "A large number of SMS messages are being sent. Select \\\"OK\\\" to continue, or \\\"Cancel\\\" to stop sending." + "A large number of SMS messages are being sent. Select \\\\\\\"OK\\\\\\\" to continue, or \\\\\\\"Cancel\\\\\\\" to stop sending." "OK" "Avbryt" "Lagre" @@ -776,67 +754,39 @@ "Vis alle" "Laster inn…" "USB koblet til" - "Du har koblet telefonen til en datamaskin via USB. Velg \\\"Monter\\\" dersom du ønsker å kopiere filer mellom datmaskinen og minnekortet i telefonen." + "Du har koblet telefonen til en datamaskin via USB. Velg \\\\\\\"Monter\\\\\\\" dersom du ønsker å kopiere filer mellom datmaskinen og minnekortet i telefonen." "Monter" "Ikke monter" "Det oppsto et problem med å bruke minnekortet ditt for USB-lagring." "USB tilkoblet" "Velg om du ønsker å kopiere filer til/fra en datamaskin." - - - - - - - - - - - - - - - - - - - - + "Slå av USB-lagring" + "Velg for å slå av USB-lagring." + "Slå av USB-lagring" + "Før du slår av USB-lagring, sjekk at du har avmontert enheten i USB-verten. Velg «slå av» for å slå av USB-lagring." + "Slå av" + "Avbryt" + "Det oppsto et problem under avslutningen av USB-lagring. Sjekk at USB-verten har avmontert og prøv igjen." + "Formatere minnekort" + "Er du sikker på at du ønsker å formatere minnekortet? Alle data på kortet vil gå tapt." + "Format" "Velg inndatametode" "ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ" - - "TAG_FONT""kandidater""u&gt;CLOSE_FONT" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "TAG_FONT""kandidater""CLOSE_FONT" + "Forbereder minnekort" + "Sjekker for feil" + "Tomt minnekort" + "Minnekortet er tomt eller bruker et ustøttet filsystem." + "Skadet minnekort" + "Minnekortet er skadet. Det kan være du må formatere kortet." + "Minnekortet ble tatt ut uventet" + "Avmonter minnekortet før det tas ut, for å unngå datatap." + "Trygt å ta ut minnekort" + "Minnekortet kan nå trygt tas ut." + "Minnekortet ble tatt ut" + "Minnekortet ble tatt ut. Sett inn et nytt minnekort for å øke lagringsplassen." + "Fant ingen tilsvarende aktiviteter" + "oppdater statistikk over komponentbruk" + "Tillater endring av innsamlet data om bruk av komponenter. Ikke ment for vanlige applikasjoner." diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 7ce4c66c95ff..a2810a1f1527 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 03c4f8b25a7b..12f16163e210 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index d25ab8da5783..76a358dddc21 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 447fccdaba67..13d4e9c69697 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index e97c142be81d..419e8c2eb4b8 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -817,6 +817,5 @@ - diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 3f21303b6b39..593d1ff2a8a2 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1056,6 +1056,10 @@ enabled for events such as clicking and touching. --> + + + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 103158511bdb..9175f31abca5 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1001,6 +1001,9 @@ + + + - Congratulations on downloading the Android software update. This update includes a number of great new features for you to enjoy. One major improvement we've made is to how you zoom. Now, when you want to zoom, just double tap on the screen. This will bring up a zoom widget. Drag the widget's handle clockwise to zoom in, and counter-clockwise to zoom out. - + Double tap to zoom diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 82cb795deccb..3db45f0daa08 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -47,7 +47,9 @@ import java.io.IOException; * @attr ref android.R.styleable#GradientDrawable_visible * @attr ref android.R.styleable#GradientDrawable_shape * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio + * @attr ref android.R.styleable#GradientDrawable_innerRadius * @attr ref android.R.styleable#GradientDrawable_thicknessRatio + * @attr ref android.R.styleable#GradientDrawable_thickness * @attr ref android.R.styleable#GradientDrawable_useLevel * @attr ref android.R.styleable#GradientDrawableSize_width * @attr ref android.R.styleable#GradientDrawableSize_height @@ -121,6 +123,8 @@ public class GradientDrawable extends Drawable { private Paint mLayerPaint; // internal, used if we use saveLayer() private boolean mRectIsDirty; // internal state private boolean mMutated; + private Path mRingPath; + private boolean mPathIsDirty; /** * Controls how the gradient is oriented relative to the drawable's bounds @@ -213,6 +217,7 @@ public class GradientDrawable extends Drawable { } public void setShape(int shape) { + mRingPath = null; mGradientState.setShape(shape); } @@ -248,14 +253,12 @@ public class GradientDrawable extends Drawable { // remember the alpha values, in case we temporarily overwrite them // when we modulate them with mAlpha final int prevFillAlpha = mFillPaint.getAlpha(); - final int prevStrokeAlpha = mStrokePaint != null ? - mStrokePaint.getAlpha() : 0; + final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0; // compute the modulate alpha values final int currFillAlpha = modulateAlpha(prevFillAlpha); final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha); - final boolean haveStroke = currStrokeAlpha > 0 && - mStrokePaint.getStrokeWidth() > 0; + final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint.getStrokeWidth() > 0; final boolean haveFill = currFillAlpha > 0; final GradientState st = mGradientState; /* we need a layer iff we're drawing both a fill and stroke, and the @@ -264,7 +267,7 @@ public class GradientDrawable extends Drawable { of the fill (if any) without worrying about blending artifacts. */ final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && - currStrokeAlpha < 255; + currStrokeAlpha < 255; /* Drawing with a layer is slower than direct drawing, but it allows us to apply paint effects like alpha and colorfilter to @@ -336,10 +339,10 @@ public class GradientDrawable extends Drawable { break; } case RING: - Path ring = buildRing(st); - canvas.drawPath(ring, mFillPaint); + Path path = buildRing(st); + canvas.drawPath(path, mFillPaint); if (haveStroke) { - canvas.drawPath(ring, mStrokePaint); + canvas.drawPath(path, mStrokePaint); } break; } @@ -355,6 +358,9 @@ public class GradientDrawable extends Drawable { } private Path buildRing(GradientState st) { + if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath; + mPathIsDirty = false; + float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f; RectF bounds = new RectF(mRect); @@ -362,9 +368,11 @@ public class GradientDrawable extends Drawable { float x = bounds.width() / 2.0f; float y = bounds.height() / 2.0f; - float thickness = bounds.width() / st.mThickness; + float thickness = st.mThickness != -1 ? + st.mThickness : bounds.width() / st.mThicknessRatio; // inner radius - float radius = bounds.width() / st.mInnerRadius; + float radius = st.mInnerRadius != -1 ? + st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio; RectF innerBounds = new RectF(bounds); innerBounds.inset(x - radius, y - radius); @@ -372,27 +380,33 @@ public class GradientDrawable extends Drawable { bounds = new RectF(innerBounds); bounds.inset(-thickness, -thickness); - Path path = new Path(); + if (mRingPath == null) { + mRingPath = new Path(); + } else { + mRingPath.reset(); + } + + final Path ringPath = mRingPath; // arcTo treats the sweep angle mod 360, so check for that, since we // think 360 means draw the entire oval if (sweep < 360 && sweep > -360) { - path.setFillType(Path.FillType.EVEN_ODD); + ringPath.setFillType(Path.FillType.EVEN_ODD); // inner top - path.moveTo(x + radius, y); + ringPath.moveTo(x + radius, y); // outer top - path.lineTo(x + radius + thickness, y); + ringPath.lineTo(x + radius + thickness, y); // outer arc - path.arcTo(bounds, 0.0f, sweep, false); + ringPath.arcTo(bounds, 0.0f, sweep, false); // inner arc - path.arcTo(innerBounds, sweep, -sweep, false); - path.close(); + ringPath.arcTo(innerBounds, sweep, -sweep, false); + ringPath.close(); } else { // add the entire ovals - path.addOval(bounds, Path.Direction.CW); - path.addOval(innerBounds, Path.Direction.CCW); + ringPath.addOval(bounds, Path.Direction.CW); + ringPath.addOval(innerBounds, Path.Direction.CCW); } - return path; + return ringPath; } public void setColor(int argb) { @@ -430,6 +444,8 @@ public class GradientDrawable extends Drawable { @Override protected void onBoundsChange(Rect r) { super.onBoundsChange(r); + mRingPath = null; + mPathIsDirty = true; mRectIsDirty = true; } @@ -437,6 +453,7 @@ public class GradientDrawable extends Drawable { protected boolean onLevelChange(int level) { super.onLevelChange(level); mRectIsDirty = true; + mPathIsDirty = true; invalidateSelf(); return true; } @@ -462,8 +479,9 @@ public class GradientDrawable extends Drawable { mRect.set(bounds.left + inset, bounds.top + inset, bounds.right - inset, bounds.bottom - inset); - - if (st.mColors != null) { + + final int[] colors = st.mColors; + if (colors != null) { RectF r = mRect; float x0, x1, y0, y1; @@ -505,8 +523,7 @@ public class GradientDrawable extends Drawable { } mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, - st.mColors, st.mPositions, - Shader.TileMode.CLAMP)); + colors, st.mPositions, Shader.TileMode.CLAMP)); } else if (st.mGradient == RADIAL_GRADIENT) { x0 = r.left + (r.right - r.left) * st.mCenterX; y0 = r.top + (r.bottom - r.top) * st.mCenterY; @@ -514,30 +531,38 @@ public class GradientDrawable extends Drawable { final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f; mFillPaint.setShader(new RadialGradient(x0, y0, - level * st.mGradientRadius, st.mColors, null, + level * st.mGradientRadius, colors, null, Shader.TileMode.CLAMP)); } else if (st.mGradient == SWEEP_GRADIENT) { x0 = r.left + (r.right - r.left) * st.mCenterX; y0 = r.top + (r.bottom - r.top) * st.mCenterY; - float[] positions = null; - int[] colors = st.mColors; + int[] tempColors = colors; + float[] tempPositions = null; if (st.mUseLevel) { - final int length = st.mColors.length; - colors = new int[length + 1]; - System.arraycopy(st.mColors, 0, colors, 0, length); - colors[length] = st.mColors[length - 1]; + tempColors = st.mTempColors; + final int length = colors.length; + if (tempColors == null || tempColors.length != length + 1) { + tempColors = st.mTempColors = new int[length + 1]; + } + System.arraycopy(colors, 0, tempColors, 0, length); + tempColors[length] = colors[length - 1]; + tempPositions = st.mTempPositions; final float fraction = 1.0f / (float) (length - 1); - positions = new float[length + 1]; + if (tempPositions == null || tempPositions.length != length + 1) { + tempPositions = st.mTempPositions = new float[length + 1]; + } + final float level = (float) getLevel() / 10000.0f; for (int i = 0; i < length; i++) { - positions[i] = i * fraction * level; + tempPositions[i] = i * fraction * level; } - positions[length] = 1.0f; + tempPositions[length] = 1.0f; + } - mFillPaint.setShader(new SweepGradient(x0, y0, colors, positions)); + mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); } } } @@ -561,10 +586,18 @@ public class GradientDrawable extends Drawable { com.android.internal.R.styleable.GradientDrawable_shape, RECTANGLE); if (shapeType == RING) { - st.mInnerRadius = a.getFloat( - com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f); - st.mThickness = a.getFloat( - com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f); + st.mInnerRadius = a.getDimensionPixelSize( + com.android.internal.R.styleable.GradientDrawable_innerRadius, -1); + if (st.mInnerRadius == -1) { + st.mInnerRadiusRatio = a.getFloat( + com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f); + } + st.mThickness = a.getDimensionPixelSize( + com.android.internal.R.styleable.GradientDrawable_thickness, -1); + if (st.mThickness == -1) { + st.mThicknessRatio = a.getFloat( + com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f); + } st.mUseLevelForShape = a.getBoolean( com.android.internal.R.styleable.GradientDrawable_useLevel, true); } @@ -808,6 +841,8 @@ public class GradientDrawable extends Drawable { public int mGradient = LINEAR_GRADIENT; public Orientation mOrientation; public int[] mColors; + public int[] mTempColors; // no need to copy + public float[] mTempPositions; // no need to copy public float[] mPositions; public boolean mHasSolidColor; public int mSolidColor; @@ -820,8 +855,10 @@ public class GradientDrawable extends Drawable { public Rect mPadding; public int mWidth = -1; public int mHeight = -1; - public float mInnerRadius; - public float mThickness; + public float mInnerRadiusRatio; + public float mThicknessRatio; + public int mInnerRadius; + public int mThickness; private float mCenterX = 0.5f; private float mCenterY = 0.5f; private float mGradientRadius = 0.5f; @@ -844,17 +881,25 @@ public class GradientDrawable extends Drawable { mGradient = state.mGradient; mOrientation = state.mOrientation; mColors = state.mColors.clone(); - mPositions = state.mPositions.clone(); + if (state.mPositions != null) { + mPositions = state.mPositions.clone(); + } mHasSolidColor = state.mHasSolidColor; mStrokeWidth = state.mStrokeWidth; mStrokeColor = state.mStrokeColor; mStrokeDashWidth = state.mStrokeDashWidth; mStrokeDashGap = state.mStrokeDashGap; mRadius = state.mRadius; - mRadiusArray = state.mRadiusArray.clone(); - mPadding = new Rect(state.mPadding); + if (state.mRadiusArray != null) { + mRadiusArray = state.mRadiusArray.clone(); + } + if (state.mPadding != null) { + mPadding = new Rect(state.mPadding); + } mWidth = state.mWidth; mHeight = state.mHeight; + mInnerRadiusRatio = state.mInnerRadiusRatio; + mThicknessRatio = state.mThicknessRatio; mInnerRadius = state.mInnerRadius; mThickness = state.mThickness; mCenterX = state.mCenterX; diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index e4b821a905c0..cb16cb711b42 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -88,6 +88,13 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { canvas.restoreToCount(saveCount); } + /** + * Returns the drawable rotated by this RotateDrawable. + */ + public Drawable getDrawable() { + return mState.mDrawable; + } + @Override public int getChangingConfigurations() { return super.getChangingConfigurations() diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index b3322c9634bb..7125ab1e689e 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -63,6 +63,13 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } } + /** + * Returns the drawable scaled by this ScaleDrawable. + */ + public Drawable getDrawable() { + return mScaleState.mDrawable; + } + private static float getPercent(TypedArray a, int name) { String s = a.getString(name); if (s != null) { diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h index 6bd54ba6611e..7437f6526660 100644 --- a/include/media/AudioSystem.h +++ b/include/media/AudioSystem.h @@ -29,8 +29,27 @@ class AudioSystem { public: + enum stream_type { + DEFAULT =-1, + VOICE_CALL = 0, + SYSTEM = 1, + RING = 2, + MUSIC = 3, + ALARM = 4, + NOTIFICATION = 5, + BLUETOOTH_SCO = 6, + NUM_STREAM_TYPES + }; + + enum audio_output_type { + AUDIO_OUTPUT_DEFAULT =-1, + AUDIO_OUTPUT_HARDWARE = 0, + AUDIO_OUTPUT_A2DP = 1, + NUM_AUDIO_OUTPUT_TYPES + }; + enum audio_format { - DEFAULT = 0, + FORMAT_DEFAULT = 0, PCM_16_BIT, PCM_8_BIT, INVALID_FORMAT @@ -96,9 +115,11 @@ public: static float linearToLog(int volume); static int logToLinear(float volume); - static status_t getOutputSamplingRate(int* samplingRate); - static status_t getOutputFrameCount(int* frameCount); - static status_t getOutputLatency(uint32_t* latency); + static status_t getOutputSamplingRate(int* samplingRate, int stream = DEFAULT); + static status_t getOutputFrameCount(int* frameCount, int stream = DEFAULT); + static status_t getOutputLatency(uint32_t* latency, int stream = DEFAULT); + + static bool routedToA2dpOutput(int streamType); static status_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount, size_t* buffSize); @@ -117,9 +138,10 @@ private: virtual void binderDied(const wp& who); // IAudioFlingerClient - virtual void audioOutputChanged(uint32_t frameCount, uint32_t samplingRate, uint32_t latency); + virtual void a2dpEnabledChanged(bool enabled); }; + static int getOutput(int streamType); static sp gAudioFlingerClient; @@ -128,9 +150,10 @@ private: static Mutex gLock; static sp gAudioFlinger; static audio_error_callback gAudioErrorCallback; - static int gOutSamplingRate; - static int gOutFrameCount; - static uint32_t gOutLatency; + static int gOutSamplingRate[NUM_AUDIO_OUTPUT_TYPES]; + static int gOutFrameCount[NUM_AUDIO_OUTPUT_TYPES]; + static uint32_t gOutLatency[NUM_AUDIO_OUTPUT_TYPES]; + static bool gA2dpEnabled; static size_t gInBuffSize; // previous parameters for recording buffer size queries diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h index 5b2bab98bb27..659f5f8aa335 100644 --- a/include/media/AudioTrack.h +++ b/include/media/AudioTrack.h @@ -42,19 +42,6 @@ class audio_track_cblk_t; class AudioTrack { public: - - enum stream_type { - DEFAULT =-1, - VOICE_CALL = 0, - SYSTEM = 1, - RING = 2, - MUSIC = 3, - ALARM = 4, - NOTIFICATION = 5, - BLUETOOTH_SCO = 6, - NUM_STREAM_TYPES - }; - enum channel_index { MONO = 0, LEFT = 0, @@ -128,7 +115,7 @@ public: * Parameters: * * streamType: Select the type of audio stream this track is attached to - * (e.g. AudioTrack::MUSIC). + * (e.g. AudioSystem::MUSIC). * sampleRate: Track sampling rate in Hz. * format: PCM sample format (e.g AudioSystem::PCM_16_BIT for signed * 16 bits per sample). diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h index df601d7ca03f..6f13fe0b52c2 100644 --- a/include/media/IAudioFlinger.h +++ b/include/media/IAudioFlinger.h @@ -65,11 +65,11 @@ public: /* query the audio hardware state. This state never changes, * and therefore can be cached. */ - virtual uint32_t sampleRate() const = 0; - virtual int channelCount() const = 0; - virtual int format() const = 0; - virtual size_t frameCount() const = 0; - virtual uint32_t latency() const = 0; + virtual uint32_t sampleRate(int output) const = 0; + virtual int channelCount(int output) const = 0; + virtual int format(int output) const = 0; + virtual size_t frameCount(int output) const = 0; + virtual uint32_t latency(int output) const = 0; /* set/get the audio hardware state. This will probably be used by * the preference panel, mostly. @@ -117,6 +117,9 @@ public: // force AudioFlinger thread out of standby virtual void wakeUp() = 0; + + // is A2DP output enabled + virtual bool isA2dpEnabled() const = 0; }; diff --git a/include/media/IAudioFlingerClient.h b/include/media/IAudioFlingerClient.h index 10c3e0fd9f38..c3deb0b4e8f2 100644 --- a/include/media/IAudioFlingerClient.h +++ b/include/media/IAudioFlingerClient.h @@ -32,7 +32,7 @@ public: DECLARE_META_INTERFACE(AudioFlingerClient); // Notifies a change of audio output from/to hardware to/from A2DP. - virtual void audioOutputChanged(uint32_t frameCount, uint32_t samplingRate, uint32_t latency) = 0; + virtual void a2dpEnabledChanged(bool enabled) = 0; }; diff --git a/include/media/IMediaRecorder.h b/include/media/IMediaRecorder.h index 49e45d138ffd..0dff84e87ce1 100644 --- a/include/media/IMediaRecorder.h +++ b/include/media/IMediaRecorder.h @@ -38,6 +38,7 @@ public: virtual status_t setVideoEncoder(int ve) = 0; virtual status_t setAudioEncoder(int ae) = 0; virtual status_t setOutputFile(const char* path) = 0; + virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0; virtual status_t setVideoSize(int width, int height) = 0; virtual status_t setVideoFrameRate(int frames_per_second) = 0; virtual status_t prepare() = 0; diff --git a/include/media/PVMediaRecorder.h b/include/media/PVMediaRecorder.h index 5fee0d6a7ead..f795d040e950 100644 --- a/include/media/PVMediaRecorder.h +++ b/include/media/PVMediaRecorder.h @@ -43,6 +43,7 @@ public: status_t setCamera(const sp& camera); status_t setPreviewSurface(const sp& surface); status_t setOutputFile(const char *path); + status_t setOutputFile(int fd, int64_t offset, int64_t length); status_t prepare(); status_t start(); status_t stop(); diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h index a901d32ee3c9..436e8f1d0cf2 100644 --- a/include/media/mediarecorder.h +++ b/include/media/mediarecorder.h @@ -102,6 +102,7 @@ public: status_t setVideoEncoder(int ve); status_t setAudioEncoder(int ae); status_t setOutputFile(const char* path); + status_t setOutputFile(int fd, int64_t offset, int64_t length); status_t setVideoSize(int width, int height); status_t setVideoFrameRate(int frames_per_second); status_t prepare(); diff --git a/include/ui/ISurface.h b/include/ui/ISurface.h index 1c8043dbf048..87b320f431b8 100644 --- a/include/ui/ISurface.h +++ b/include/ui/ISurface.h @@ -25,6 +25,8 @@ #include #include +#include + namespace android { typedef int32_t SurfaceID; @@ -49,16 +51,8 @@ public: class BufferHeap { public: enum { - /* flip source image horizontally */ - FLIP_H = 0x01, - /* flip source image vertically */ - FLIP_V = 0x02, /* rotate source image 90 degrees */ - ROT_90 = 0x04, - /* rotate source image 180 degrees */ - ROT_180 = 0x03, - /* rotate source image 270 degrees */ - ROT_270 = 0x07, + ROT_90 = HAL_TRANSFORM_ROT_90, }; BufferHeap(); diff --git a/include/utils/logger.h b/include/utils/logger.h deleted file mode 100644 index 3a08019a8866..000000000000 --- a/include/utils/logger.h +++ /dev/null @@ -1,46 +0,0 @@ -/* utils/logger.h -** -** Copyright 2007, The Android Open Source Project -** -** This file is dual licensed. It may be redistributed and/or modified -** under the terms of the Apache 2.0 License OR version 2 of the GNU -** General Public License. -*/ - -#ifndef _UTILS_LOGGER_H -#define _UTILS_LOGGER_H - -#include - -struct logger_entry { - uint16_t len; /* length of the payload */ - uint16_t __pad; /* no matter what, we get 2 bytes of padding */ - int32_t pid; /* generating process's pid */ - int32_t tid; /* generating process's tid */ - int32_t sec; /* seconds since Epoch */ - int32_t nsec; /* nanoseconds */ - char msg[0]; /* the entry's payload */ -}; - -#define LOGGER_LOG_MAIN "log/main" -#define LOGGER_LOG_RADIO "log/radio" -#define LOGGER_LOG_EVENTS "log/events" - -#define LOGGER_ENTRY_MAX_LEN (4*1024) -#define LOGGER_ENTRY_MAX_PAYLOAD \ - (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry)) - -#ifdef HAVE_IOCTL - -#include - -#define __LOGGERIO 0xAE - -#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */ -#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */ -#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */ -#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */ - -#endif // HAVE_IOCTL - -#endif /* _UTILS_LOGGER_H */ diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp index 017a298c6927..d347f14caeee 100644 --- a/libs/audioflinger/AudioFlinger.cpp +++ b/libs/audioflinger/AudioFlinger.cpp @@ -47,6 +47,15 @@ #include "A2dpAudioInterface.h" #endif +// ---------------------------------------------------------------------------- +// the sim build doesn't have gettid + +#ifndef HAVE_GETTID +# define gettid getpid +#endif + +// ---------------------------------------------------------------------------- + namespace android { //static const nsecs_t kStandbyTimeInNsecs = seconds(3); @@ -59,6 +68,13 @@ static const float MAX_GAIN = 4096.0f; static const int8_t kMaxTrackRetries = 50; static const int8_t kMaxTrackStartupRetries = 50; +static const int kStartSleepTime = 30000; +static const int kStopSleepTime = 30000; + +// Maximum number of pending buffers allocated by OutputTrack::write() +static const uint8_t kMaxOutputTrackBuffers = 5; + + #define AUDIOFLINGER_SECURITY_ENABLED 1 // ---------------------------------------------------------------------------- @@ -98,13 +114,10 @@ static bool settingsAllowed() { // ---------------------------------------------------------------------------- AudioFlinger::AudioFlinger() - : BnAudioFlinger(), Thread(false), - mMasterVolume(0), mMasterMute(true), mHardwareAudioMixer(0), mA2dpAudioMixer(0), - mAudioMixer(0), mAudioHardware(0), mA2dpAudioInterface(0), mHardwareOutput(0), - mA2dpOutput(0), mOutput(0), mRequestedOutput(0), mAudioRecordThread(0), - mSampleRate(0), mFrameCount(0), mChannelCount(0), mFormat(0), mMixBuffer(0), - mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mStandby(false), - mInWrite(false), mA2dpDisableCount(0), mA2dpSuppressed(false) + : BnAudioFlinger(), + mAudioHardware(0), mA2dpAudioInterface(0), + mA2dpEnabled(false), mA2dpEnabledReq(false), + mForcedSpeakerCount(0), mForcedRoute(0), mRouteRestoreTime(0), mMusicMuteSaved(false) { mHardwareStatus = AUDIO_HW_IDLE; mAudioHardware = AudioHardwareInterface::create(); @@ -113,42 +126,43 @@ AudioFlinger::AudioFlinger() // open 16-bit output stream for s/w mixer mHardwareStatus = AUDIO_HW_OUTPUT_OPEN; status_t status; - mHardwareOutput = mAudioHardware->openOutputStream(AudioSystem::PCM_16_BIT, 0, 0, &status); + AudioStreamOut *hwOutput = mAudioHardware->openOutputStream(AudioSystem::PCM_16_BIT, 0, 0, &status); mHardwareStatus = AUDIO_HW_IDLE; - if (mHardwareOutput) { - mHardwareAudioMixer = new AudioMixer(getOutputFrameCount(mHardwareOutput), mHardwareOutput->sampleRate()); - mRequestedOutput = mHardwareOutput; - doSetOutput(mHardwareOutput); - - // FIXME - this should come from settings - setMasterVolume(1.0f); - setRouting(AudioSystem::MODE_NORMAL, AudioSystem::ROUTE_SPEAKER, AudioSystem::ROUTE_ALL); - setRouting(AudioSystem::MODE_RINGTONE, AudioSystem::ROUTE_SPEAKER, AudioSystem::ROUTE_ALL); - setRouting(AudioSystem::MODE_IN_CALL, AudioSystem::ROUTE_EARPIECE, AudioSystem::ROUTE_ALL); - setMode(AudioSystem::MODE_NORMAL); - mMasterMute = false; + if (hwOutput) { + mHardwareMixerThread = new MixerThread(this, hwOutput, AudioSystem::AUDIO_OUTPUT_HARDWARE); } else { - LOGE("Failed to initialize output stream, status: %d", status); + LOGE("Failed to initialize hardware output stream, status: %d", status); } #ifdef WITH_A2DP // Create A2DP interface mA2dpAudioInterface = new A2dpAudioInterface(); - mA2dpOutput = mA2dpAudioInterface->openOutputStream(AudioSystem::PCM_16_BIT, 0, 0, &status); - mA2dpAudioMixer = new AudioMixer(getOutputFrameCount(mA2dpOutput), mA2dpOutput->sampleRate()); - - // create a buffer big enough for both hardware and A2DP audio output. - size_t hwFrameCount = getOutputFrameCount(mHardwareOutput); - size_t a2dpFrameCount = getOutputFrameCount(mA2dpOutput); - size_t frameCount = (hwFrameCount > a2dpFrameCount ? hwFrameCount : a2dpFrameCount); -#else - size_t frameCount = getOutputFrameCount(mHardwareOutput); + AudioStreamOut *a2dpOutput = mA2dpAudioInterface->openOutputStream(AudioSystem::PCM_16_BIT, 0, 0, &status); + if (a2dpOutput) { + mA2dpMixerThread = new MixerThread(this, a2dpOutput, AudioSystem::AUDIO_OUTPUT_A2DP); + if (hwOutput) { + uint32_t frameCount = ((a2dpOutput->bufferSize()/a2dpOutput->frameSize()) * hwOutput->sampleRate()) / a2dpOutput->sampleRate(); + MixerThread::OutputTrack *a2dpOutTrack = new MixerThread::OutputTrack(mA2dpMixerThread, + hwOutput->sampleRate(), + AudioSystem::PCM_16_BIT, + hwOutput->channelCount(), + frameCount); + mHardwareMixerThread->setOuputTrack(a2dpOutTrack); + } + } else { + LOGE("Failed to initialize A2DP output stream, status: %d", status); + } #endif - // FIXME - Current mixer implementation only supports stereo output: Always - // Allocate a stereo buffer even if HW output is mono. - mMixBuffer = new int16_t[frameCount * 2]; - memset(mMixBuffer, 0, frameCount * 2 * sizeof(int16_t)); - + + // FIXME - this should come from settings + setRouting(AudioSystem::MODE_NORMAL, AudioSystem::ROUTE_SPEAKER, AudioSystem::ROUTE_ALL); + setRouting(AudioSystem::MODE_RINGTONE, AudioSystem::ROUTE_SPEAKER, AudioSystem::ROUTE_ALL); + setRouting(AudioSystem::MODE_IN_CALL, AudioSystem::ROUTE_EARPIECE, AudioSystem::ROUTE_ALL); + setMode(AudioSystem::MODE_NORMAL); + + setMasterVolume(1.0f); + setMasterMute(false); + // Start record thread mAudioRecordThread = new AudioRecordThread(mAudioHardware); if (mAudioRecordThread != 0) { @@ -162,7 +176,7 @@ AudioFlinger::AudioFlinger() property_get("ro.audio.silent", value, "0"); if (atoi(value)) { LOGD("Silence is golden"); - mMasterMute = true; + setMasterMute(true); } } @@ -172,63 +186,35 @@ AudioFlinger::~AudioFlinger() mAudioRecordThread->exit(); mAudioRecordThread.clear(); } + mHardwareMixerThread.clear(); delete mAudioHardware; // deleting mA2dpAudioInterface also deletes mA2dpOutput; +#ifdef WITH_A2DP + mA2dpMixerThread.clear(); delete mA2dpAudioInterface; - delete [] mMixBuffer; - delete mHardwareAudioMixer; - delete mA2dpAudioMixer; -} - -void AudioFlinger::setOutput(AudioStreamOut* output) -{ - mRequestedOutput = output; - mWaitWorkCV.broadcast(); -} - -void AudioFlinger::doSetOutput(AudioStreamOut* output) -{ - mSampleRate = output->sampleRate(); - mChannelCount = output->channelCount(); - - // FIXME - Current mixer implementation only supports stereo output - if (mChannelCount == 1) { - LOGE("Invalid audio hardware channel count"); - } - mFormat = output->format(); - mFrameCount = getOutputFrameCount(output); - mAudioMixer = (output == mA2dpOutput ? mA2dpAudioMixer : mHardwareAudioMixer); - mOutput = output; - notifyOutputChange_l(); +#endif } -size_t AudioFlinger::getOutputFrameCount(AudioStreamOut* output) -{ - return output->bufferSize() / output->channelCount() / sizeof(int16_t); -} #ifdef WITH_A2DP -bool AudioFlinger::streamDisablesA2dp(int streamType) +void AudioFlinger::setA2dpEnabled(bool enable) { - return (streamType == AudioTrack::SYSTEM || - streamType == AudioTrack::RING || - streamType == AudioTrack::ALARM || - streamType == AudioTrack::VOICE_CALL || - streamType == AudioTrack::BLUETOOTH_SCO || - streamType == AudioTrack::NOTIFICATION); + LOGV_IF(enable, "set output to A2DP\n"); + LOGV_IF(!enable, "set output to hardware audio\n"); + + mA2dpEnabledReq = enable; + mA2dpMixerThread->wakeUp(); } +#endif // WITH_A2DP -void AudioFlinger::setA2dpEnabled(bool enable) +bool AudioFlinger::streamForcedToSpeaker(int streamType) { - if (enable) { - LOGD("set output to A2DP\n"); - setOutput(mA2dpOutput); - } else { - LOGD("set output to hardware audio\n"); - setOutput(mHardwareOutput); - } + // NOTE that streams listed here must not be routed to A2DP by default: + // AudioSystem::routedToA2dpOutput(streamType) == false + return (streamType == AudioSystem::RING || + streamType == AudioSystem::ALARM || + streamType == AudioSystem::NOTIFICATION); } -#endif // WITH_A2DP status_t AudioFlinger::dumpClients(int fd, const Vector& args) { @@ -251,40 +237,6 @@ status_t AudioFlinger::dumpClients(int fd, const Vector& args) return NO_ERROR; } -status_t AudioFlinger::dumpTracks(int fd, const Vector& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - - result.append("Tracks:\n"); - result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); - for (size_t i = 0; i < mTracks.size(); ++i) { - wp wTrack = mTracks[i]; - if (wTrack != 0) { - sp track = wTrack.promote(); - if (track != 0) { - track->dump(buffer, SIZE); - result.append(buffer); - } - } - } - - result.append("Active Tracks:\n"); - result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); - for (size_t i = 0; i < mActiveTracks.size(); ++i) { - wp wTrack = mTracks[i]; - if (wTrack != 0) { - sp track = wTrack.promote(); - if (track != 0) { - track->dump(buffer, SIZE); - result.append(buffer); - } - } - } - write(fd, result.string(), result.size()); - return NO_ERROR; -} status_t AudioFlinger::dumpInternals(int fd, const Vector& args) { @@ -292,18 +244,6 @@ status_t AudioFlinger::dumpInternals(int fd, const Vector& args) char buffer[SIZE]; String8 result; - snprintf(buffer, SIZE, "AudioMixer tracks: %08x\n", audioMixer()->trackNames()); - result.append(buffer); - snprintf(buffer, SIZE, "last write occurred (msecs): %llu\n", ns2ms(systemTime() - mLastWriteTime)); - result.append(buffer); - snprintf(buffer, SIZE, "total writes: %d\n", mNumWrites); - result.append(buffer); - snprintf(buffer, SIZE, "delayed writes: %d\n", mNumDelayedWrites); - result.append(buffer); - snprintf(buffer, SIZE, "blocked in write: %d\n", mInWrite); - result.append(buffer); - snprintf(buffer, SIZE, "standby: %d\n", mStandby); - result.append(buffer); snprintf(buffer, SIZE, "Hardware status: %d\n", mHardwareStatus); result.append(buffer); write(fd, result.string(), result.size()); @@ -332,8 +272,12 @@ status_t AudioFlinger::dump(int fd, const Vector& args) AutoMutex lock(&mLock); dumpClients(fd, args); - dumpTracks(fd, args); dumpInternals(fd, args); + mHardwareMixerThread->dump(fd, args); +#ifdef WITH_A2DP + mA2dpMixerThread->dump(fd, args); +#endif + if (mAudioHardware) { mAudioHardware->dumpState(fd, args); } @@ -341,336 +285,135 @@ status_t AudioFlinger::dump(int fd, const Vector& args) return NO_ERROR; } -// Thread virtuals -bool AudioFlinger::threadLoop() +// IAudioFlinger interface + + +sp AudioFlinger::createTrack( + pid_t pid, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + const sp& sharedBuffer, + status_t *status) { - unsigned long sleepTime = kBufferRecoveryInUsecs; - int16_t* curBuf = mMixBuffer; - Vector< sp > tracksToRemove; - size_t enabledTracks = 0; - nsecs_t standbyTime = systemTime(); - nsecs_t outputSwitchStandbyTime = 0; + sp track; + sp trackHandle; + sp client; + wp wclient; + status_t lStatus; - do { - enabledTracks = 0; - { // scope for the mLock - - Mutex::Autolock _l(mLock); - const SortedVector< wp >& activeTracks = mActiveTracks; + if (streamType >= AudioSystem::NUM_STREAM_TYPES) { + LOGE("invalid stream type"); + lStatus = BAD_VALUE; + goto Exit; + } - // put audio hardware into standby after short delay - if UNLIKELY(!activeTracks.size() && systemTime() > standbyTime) { - // wait until we have something to do... - LOGV("Audio hardware entering standby\n"); - mHardwareStatus = AUDIO_HW_STANDBY; - if (!mStandby) { - mOutput->standby(); - mStandby = true; - } - if (outputSwitchStandbyTime) { - AudioStreamOut *output = (mOutput == mHardwareOutput) ? mA2dpOutput : mHardwareOutput; - output->standby(); - outputSwitchStandbyTime = 0; - } - mHardwareStatus = AUDIO_HW_IDLE; - // we're about to wait, flush the binder command buffer - IPCThreadState::self()->flushCommands(); - mWaitWorkCV.wait(mLock); - LOGV("Audio hardware exiting standby\n"); - standbyTime = systemTime() + kStandbyTimeInNsecs; - continue; - } + { + Mutex::Autolock _l(mLock); - // check for change in output - if (mRequestedOutput != mOutput) { + wclient = mClients.valueFor(pid); - // put current output into standby mode - if (mOutput) { - outputSwitchStandbyTime = systemTime() + milliseconds(mOutput->latency()); - } + if (wclient != NULL) { + client = wclient.promote(); + } else { + client = new Client(this, pid); + mClients.add(pid, client); + } +#ifdef WITH_A2DP + if (isA2dpEnabled() && AudioSystem::routedToA2dpOutput(streamType)) { + track = mA2dpMixerThread->createTrack(client, streamType, sampleRate, format, + channelCount, frameCount, sharedBuffer, &lStatus); + } else +#endif + { + track = mHardwareMixerThread->createTrack(client, streamType, sampleRate, format, + channelCount, frameCount, sharedBuffer, &lStatus); + } + if (track != NULL) { + trackHandle = new TrackHandle(track); + lStatus = NO_ERROR; + } + } - // change output - doSetOutput(mRequestedOutput); - } - if (outputSwitchStandbyTime && systemTime() > outputSwitchStandbyTime) { - AudioStreamOut *output = (mOutput == mHardwareOutput) ? mA2dpOutput : mHardwareOutput; - output->standby(); - outputSwitchStandbyTime = 0; - } +Exit: + if(status) { + *status = lStatus; + } + return trackHandle; +} - // find out which tracks need to be processed - size_t count = activeTracks.size(); - for (size_t i=0 ; i t = activeTracks[i].promote(); - if (t == 0) continue; +uint32_t AudioFlinger::sampleRate(int output) const +{ +#ifdef WITH_A2DP + if (output == AudioSystem::AUDIO_OUTPUT_A2DP) { + return mA2dpMixerThread->sampleRate(); + } +#endif + return mHardwareMixerThread->sampleRate(); +} - Track* const track = t.get(); - audio_track_cblk_t* cblk = track->cblk(); +int AudioFlinger::channelCount(int output) const +{ +#ifdef WITH_A2DP + if (output == AudioSystem::AUDIO_OUTPUT_A2DP) { + return mA2dpMixerThread->channelCount(); + } +#endif + return mHardwareMixerThread->channelCount(); +} - // The first time a track is added we wait - // for all its buffers to be filled before processing it - mAudioMixer->setActiveTrack(track->name()); - if (cblk->framesReady() && (track->isReady() || track->isStopped()) && - !track->isPaused()) - { - //LOGD("u=%08x, s=%08x [OK]", u, s); +int AudioFlinger::format(int output) const +{ +#ifdef WITH_A2DP + if (output == AudioSystem::AUDIO_OUTPUT_A2DP) { + return mA2dpMixerThread->format(); + } +#endif + return mHardwareMixerThread->format(); +} - // compute volume for this track - int16_t left, right; - if (track->isMuted() || mMasterMute || track->isPausing()) { - left = right = 0; - if (track->isPausing()) { - LOGV("paused(%d)", track->name()); - track->setPaused(); - } - } else { - float typeVolume = mStreamTypes[track->type()].volume; - float v = mMasterVolume * typeVolume; - float v_clamped = v * cblk->volume[0]; - if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; - left = int16_t(v_clamped); - v_clamped = v * cblk->volume[1]; - if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; - right = int16_t(v_clamped); - } +size_t AudioFlinger::frameCount(int output) const +{ +#ifdef WITH_A2DP + if (output == AudioSystem::AUDIO_OUTPUT_A2DP) { + return mA2dpMixerThread->frameCount(); + } +#endif + return mHardwareMixerThread->frameCount(); +} - // XXX: these things DON'T need to be done each time - mAudioMixer->setBufferProvider(track); - mAudioMixer->enable(AudioMixer::MIXING); +uint32_t AudioFlinger::latency(int output) const +{ +#ifdef WITH_A2DP + if (output == AudioSystem::AUDIO_OUTPUT_A2DP) { + return mA2dpMixerThread->latency(); + } +#endif + return mHardwareMixerThread->latency(); +} - int param; - if ( track->mFillingUpStatus == Track::FS_FILLED) { - // no ramp for the first volume setting - track->mFillingUpStatus = Track::FS_ACTIVE; - if (track->mState == TrackBase::RESUMING) { - track->mState = TrackBase::ACTIVE; - param = AudioMixer::RAMP_VOLUME; - } else { - param = AudioMixer::VOLUME; - } - } else { - param = AudioMixer::RAMP_VOLUME; - } - mAudioMixer->setParameter(param, AudioMixer::VOLUME0, left); - mAudioMixer->setParameter(param, AudioMixer::VOLUME1, right); - mAudioMixer->setParameter( - AudioMixer::TRACK, - AudioMixer::FORMAT, track->format()); - mAudioMixer->setParameter( - AudioMixer::TRACK, - AudioMixer::CHANNEL_COUNT, track->channelCount()); - mAudioMixer->setParameter( - AudioMixer::RESAMPLE, - AudioMixer::SAMPLE_RATE, - int(cblk->sampleRate)); - - // reset retry count - track->mRetryCount = kMaxTrackRetries; - enabledTracks++; - } else { - //LOGD("u=%08x, s=%08x [NOT READY]", u, s); - if (track->isStopped()) { - track->reset(); - } - if (track->isTerminated() || track->isStopped() || track->isPaused()) { - // We have consumed all the buffers of this track. - // Remove it from the list of active tracks. - LOGV("remove(%d) from active list", track->name()); - tracksToRemove.add(track); - } else { - // No buffers for this track. Give it a few chances to - // fill a buffer, then remove it from active list. - if (--(track->mRetryCount) <= 0) { - LOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name()); - tracksToRemove.add(track); - } - } - // LOGV("disable(%d)", track->name()); - mAudioMixer->disable(AudioMixer::MIXING); - } - } - - // remove all the tracks that need to be... - count = tracksToRemove.size(); - if (UNLIKELY(count)) { - for (size_t i=0 ; i& track = tracksToRemove[i]; - removeActiveTrack(track); - if (track->isTerminated()) { - mTracks.remove(track); - deleteTrackName(track->mName); - } - } - } - } - if (LIKELY(enabledTracks)) { - // mix buffers... - mAudioMixer->process(curBuf); - - // output audio to hardware - mLastWriteTime = systemTime(); - mInWrite = true; - size_t mixBufferSize = mFrameCount*mChannelCount*sizeof(int16_t); - mOutput->write(curBuf, mixBufferSize); - mNumWrites++; - mInWrite = false; - mStandby = false; - nsecs_t temp = systemTime(); - standbyTime = temp + kStandbyTimeInNsecs; - nsecs_t delta = temp - mLastWriteTime; - nsecs_t maxPeriod = seconds(mFrameCount) / mSampleRate * 2; - if (delta > maxPeriod) { - LOGW("write blocked for %llu msecs", ns2ms(delta)); - mNumDelayedWrites++; - } - sleepTime = kBufferRecoveryInUsecs; - } else { - // There was nothing to mix this round, which means all - // active tracks were late. Sleep a little bit to give - // them another chance. If we're too late, the audio - // hardware will zero-fill for us. -// LOGV("no buffers - usleep(%lu)", sleepTime); - usleep(sleepTime); - if (sleepTime < kMaxBufferRecoveryInUsecs) { - sleepTime += kBufferRecoveryInUsecs; - } - } - - // finally let go of all our tracks, without the lock held - // since we can't guarantee the destructors won't acquire that - // same lock. - tracksToRemove.clear(); - } while (true); - - return false; -} - -status_t AudioFlinger::readyToRun() -{ - if (mSampleRate == 0) { - LOGE("No working audio driver found."); - return NO_INIT; - } - LOGI("AudioFlinger's main thread ready to run."); - return NO_ERROR; -} - -void AudioFlinger::onFirstRef() -{ - run("AudioFlinger", ANDROID_PRIORITY_URGENT_AUDIO); -} - -// IAudioFlinger interface -sp AudioFlinger::createTrack( - pid_t pid, - int streamType, - uint32_t sampleRate, - int format, - int channelCount, - int frameCount, - uint32_t flags, - const sp& sharedBuffer, - status_t *status) -{ - sp track; - sp trackHandle; - sp client; - wp wclient; - status_t lStatus; - - if (streamType >= AudioTrack::NUM_STREAM_TYPES) { - LOGE("invalid stream type"); - lStatus = BAD_VALUE; - goto Exit; - } - - // Resampler implementation limits input sampling rate to 2 x output sampling rate. - if (sampleRate > MAX_SAMPLE_RATE || sampleRate > mSampleRate*2) { - LOGE("Sample rate out of range: %d", sampleRate); - lStatus = BAD_VALUE; - goto Exit; - } - - { - Mutex::Autolock _l(mLock); - - if (mSampleRate == 0) { - LOGE("Audio driver not initialized."); - lStatus = NO_INIT; - goto Exit; - } - - wclient = mClients.valueFor(pid); - - if (wclient != NULL) { - client = wclient.promote(); - } else { - client = new Client(this, pid); - mClients.add(pid, client); - } - - track = new Track(this, client, streamType, sampleRate, format, - channelCount, frameCount, sharedBuffer); - mTracks.add(track); - trackHandle = new TrackHandle(track); - - lStatus = NO_ERROR; - } - -Exit: - if(status) { - *status = lStatus; - } - return trackHandle; -} - -uint32_t AudioFlinger::sampleRate() const -{ - return mSampleRate; -} - -int AudioFlinger::channelCount() const -{ - return mChannelCount; -} - -int AudioFlinger::format() const -{ - return mFormat; -} - -size_t AudioFlinger::frameCount() const -{ - return mFrameCount; -} - -uint32_t AudioFlinger::latency() const -{ - if (mOutput) { - return mOutput->latency(); - } - else { - return 0; - } -} - -status_t AudioFlinger::setMasterVolume(float value) -{ - // check calling permissions - if (!settingsAllowed()) { - return PERMISSION_DENIED; - } +status_t AudioFlinger::setMasterVolume(float value) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } // when hw supports master volume, don't scale in sw mixer AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; if (mAudioHardware->setMasterVolume(value) == NO_ERROR) { - mMasterVolume = 1.0f; - } - else { - mMasterVolume = value; + value = 1.0f; } mHardwareStatus = AUDIO_HW_IDLE; + mHardwareMixerThread->setMasterVolume(value); +#ifdef WITH_A2DP + mA2dpMixerThread->setMasterVolume(value); +#endif + return NO_ERROR; } @@ -688,20 +431,17 @@ status_t AudioFlinger::setRouting(int mode, uint32_t routes, uint32_t mask) } #ifdef WITH_A2DP - LOGD("setRouting %d %d %d\n", mode, routes, mask); + LOGD("setRouting %d %d %d, tid %d, calling tid %d\n", mode, routes, mask, gettid(), IPCThreadState::self()->getCallingPid()); if (mode == AudioSystem::MODE_NORMAL && (mask & AudioSystem::ROUTE_BLUETOOTH_A2DP)) { AutoMutex lock(&mLock); bool enableA2dp = false; if (routes & AudioSystem::ROUTE_BLUETOOTH_A2DP) { - if (mA2dpDisableCount > 0) - mA2dpSuppressed = true; - else - enableA2dp = true; + enableA2dp = true; } setA2dpEnabled(enableA2dp); - LOGD("setOutput done\n"); + LOGV("setOutput done\n"); } #endif @@ -714,6 +454,12 @@ status_t AudioFlinger::setRouting(int mode, uint32_t routes, uint32_t mask) err = mAudioHardware->getRouting(mode, &r); if (err == NO_ERROR) { r = (r & ~mask) | (routes & mask); + if (mode == AudioSystem::MODE_NORMAL || + (mode == AudioSystem::MODE_CURRENT && getMode() == AudioSystem::MODE_NORMAL)) { + mSavedRoute = r; + r |= mForcedRoute; + LOGV("setRouting mSavedRoute %08x mForcedRoute %08x\n", mSavedRoute, mForcedRoute); + } mHardwareStatus = AUDIO_HW_SET_ROUTING; err = mAudioHardware->setRouting(mode, r); } @@ -726,9 +472,14 @@ uint32_t AudioFlinger::getRouting(int mode) const { uint32_t routes = 0; if ((mode >= AudioSystem::MODE_CURRENT) && (mode < AudioSystem::NUM_MODES)) { - mHardwareStatus = AUDIO_HW_GET_ROUTING; - mAudioHardware->getRouting(mode, &routes); - mHardwareStatus = AUDIO_HW_IDLE; + if (mode == AudioSystem::MODE_NORMAL || + (mode == AudioSystem::MODE_CURRENT && getMode() == AudioSystem::MODE_NORMAL)) { + routes = mSavedRoute; + } else { + mHardwareStatus = AUDIO_HW_GET_ROUTING; + mAudioHardware->getRouting(mode, &routes); + mHardwareStatus = AUDIO_HW_IDLE; + } } else { LOGW("Illegal value: getRouting(%d)", mode); } @@ -791,19 +542,21 @@ status_t AudioFlinger::setMasterMute(bool muted) if (!settingsAllowed()) { return PERMISSION_DENIED; } - - mMasterMute = muted; + mHardwareMixerThread->setMasterMute(muted); +#ifdef WITH_A2DP + mA2dpMixerThread->setMasterMute(muted); +#endif return NO_ERROR; } float AudioFlinger::masterVolume() const { - return mMasterVolume; + return mHardwareMixerThread->masterVolume(); } bool AudioFlinger::masterMute() const { - return mMasterMute; + return mHardwareMixerThread->masterMute(); } status_t AudioFlinger::setStreamVolume(int stream, float value) @@ -813,14 +566,25 @@ status_t AudioFlinger::setStreamVolume(int stream, float value) return PERMISSION_DENIED; } - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) { + if (uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { return BAD_VALUE; } - mStreamTypes[stream].volume = value; + mHardwareMixerThread->setStreamVolume(stream, value); +#ifdef WITH_A2DP + mA2dpMixerThread->setStreamVolume(stream, value); +#endif + status_t ret = NO_ERROR; - if (stream == AudioTrack::VOICE_CALL || - stream == AudioTrack::BLUETOOTH_SCO) { + if (stream == AudioSystem::VOICE_CALL || + stream == AudioSystem::BLUETOOTH_SCO) { + + if (stream == AudioSystem::VOICE_CALL) { + value = (float)AudioSystem::logToLinear(value)/100.0f; + } else { // (type == AudioSystem::BLUETOOTH_SCO) + value = 1.0f; + } + AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_SET_VOICE_VOLUME; ret = mAudioHardware->setVoiceVolume(value); @@ -837,59 +601,58 @@ status_t AudioFlinger::setStreamMute(int stream, bool muted) return PERMISSION_DENIED; } - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) { + if (uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { return BAD_VALUE; } + #ifdef WITH_A2DP - if (stream == AudioTrack::MUSIC) + mA2dpMixerThread->setStreamMute(stream, muted); +#endif + if (stream == AudioSystem::MUSIC) { - AutoMutex lock(&mLock); - if (mA2dpDisableCount > 0) + AutoMutex lock(&mHardwareLock); + if (mForcedRoute != 0) mMusicMuteSaved = muted; else - mStreamTypes[stream].mute = muted; + mHardwareMixerThread->setStreamMute(stream, muted); } else { - mStreamTypes[stream].mute = muted; + mHardwareMixerThread->setStreamMute(stream, muted); } -#else - mStreamTypes[stream].mute = muted; -#endif + + + return NO_ERROR; } float AudioFlinger::streamVolume(int stream) const { - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) { + if (uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { return 0.0f; } - return mStreamTypes[stream].volume; + return mHardwareMixerThread->streamVolume(stream); } bool AudioFlinger::streamMute(int stream) const { - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) { + if (uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { return true; } -#ifdef WITH_A2DP - if (stream == AudioTrack::MUSIC && mA2dpDisableCount > 0) + + if (stream == AudioSystem::MUSIC && mForcedRoute != 0) { return mMusicMuteSaved; } -#endif - return mStreamTypes[stream].mute; + return mHardwareMixerThread->streamMute(stream); } bool AudioFlinger::isMusicActive() const { - size_t count = mActiveTracks.size(); - for (size_t i = 0 ; i < count ; ++i) { - sp t = mActiveTracks[i].promote(); - if (t == 0) continue; - Track* const track = t.get(); - if (t->mStreamType == AudioTrack::MUSIC) - return true; - } - return false; + #ifdef WITH_A2DP + if (isA2dpEnabled()) { + return mA2dpMixerThread->isMusicActive(); + } + #endif + return mHardwareMixerThread->isMusicActive(); } status_t AudioFlinger::setParameter(const char* key, const char* value) @@ -897,6 +660,8 @@ status_t AudioFlinger::setParameter(const char* key, const char* value) status_t result, result2; AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_SET_PARAMETER; + + LOGV("setParameter() key %s, value %s, tid %d, calling tid %d", key, value, gettid(), IPCThreadState::self()->getCallingPid()); result = mAudioHardware->setParameter(key, value); if (mA2dpAudioInterface) { result2 = mA2dpAudioInterface->setParameter(key, value); @@ -907,9 +672,15 @@ status_t AudioFlinger::setParameter(const char* key, const char* value) return result; } +size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) +{ + return mAudioHardware->getInputBufferSize(sampleRate, format, channelCount); +} void AudioFlinger::registerClient(const sp& client) { + + LOGV("registerClient() %p, tid %d, calling tid %d", client.get(), gettid(), IPCThreadState::self()->getCallingPid()); Mutex::Autolock _l(mLock); sp binder = client->asBinder(); @@ -917,21 +688,13 @@ void AudioFlinger::registerClient(const sp& client) LOGV("Adding notification client %p", binder.get()); binder->linkToDeath(this); mNotificationClients.add(binder); + client->a2dpEnabledChanged(isA2dpEnabled()); } } - -size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) -{ - return mAudioHardware->getInputBufferSize(sampleRate, format, channelCount); -} - -void AudioFlinger::wakeUp() -{ - mWaitWorkCV.broadcast(); -} - void AudioFlinger::binderDied(const wp& who) { + + LOGV("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), IPCThreadState::self()->getCallingPid()); Mutex::Autolock _l(mLock); IBinder *binder = who.unsafe_get(); @@ -945,33 +708,684 @@ void AudioFlinger::binderDied(const wp& who) { } } -// must be called with mLock held -void AudioFlinger::notifyOutputChange_l() +void AudioFlinger::handleOutputSwitch() { - size_t size = mNotificationClients.size(); - uint32_t latency = mOutput->latency(); - for (size_t i = 0; i < size; i++) { - sp binder = mNotificationClients.itemAt(i).promote(); - if (binder != NULL) { - LOGV("Notifying output change to client %p", binder.get()); - sp client = interface_cast (binder); - client->audioOutputChanged(mFrameCount, mSampleRate, latency); + if (mA2dpEnabled != mA2dpEnabledReq) + { + Mutex::Autolock _l(mLock); + + if (mA2dpEnabled != mA2dpEnabledReq) + { + mA2dpEnabled = mA2dpEnabledReq; + SortedVector < sp > tracks; + SortedVector < wp > activeTracks; + + // We hold mA2dpMixerThread mLock already + Mutex::Autolock _l(mHardwareMixerThread->mLock); + + // Transfer tracks playing on MUSIC stream from one mixer to the other + if (mA2dpEnabled) { + mHardwareMixerThread->getTracks(tracks, activeTracks); + mA2dpMixerThread->putTracks(tracks, activeTracks); + } else { + mA2dpMixerThread->getTracks(tracks, activeTracks); + mHardwareMixerThread->putTracks(tracks, activeTracks); + } + + // Notify AudioSystem of the A2DP activation/deactivation + size_t size = mNotificationClients.size(); + for (size_t i = 0; i < size; i++) { + sp binder = mNotificationClients.itemAt(i).promote(); + if (binder != NULL) { + LOGV("Notifying output change to client %p", binder.get()); + sp client = interface_cast (binder); + client->a2dpEnabledChanged(mA2dpEnabled); + } + } + + mHardwareMixerThread->wakeUp(); } } } void AudioFlinger::removeClient(pid_t pid) { + LOGV("removeClient() pid %d, tid %d, calling tid %d", pid, gettid(), IPCThreadState::self()->getCallingPid()); Mutex::Autolock _l(mLock); mClients.removeItem(pid); } -status_t AudioFlinger::addTrack(const sp& track) +void AudioFlinger::wakeUp() { - Mutex::Autolock _l(mLock); + mHardwareMixerThread->wakeUp(); +#ifdef WITH_A2DP + mA2dpMixerThread->wakeUp(); +#endif // WITH_A2DP +} - // here the track could be either new, or restarted - // in both cases "unstop" the track +bool AudioFlinger::isA2dpEnabled() const +{ + return mA2dpEnabledReq; +} + +void AudioFlinger::handleForcedSpeakerRoute(int command) +{ + switch(command) { + case ACTIVE_TRACK_ADDED: + { + AutoMutex lock(mHardwareLock); + if (mForcedSpeakerCount++ == 0) { + mRouteRestoreTime = 0; + mMusicMuteSaved = mHardwareMixerThread->streamMute(AudioSystem::MUSIC); + if (mForcedRoute == 0 && !(mSavedRoute & AudioSystem::ROUTE_SPEAKER)) { + LOGV("Route forced to Speaker ON %08x", mSavedRoute | AudioSystem::ROUTE_SPEAKER); + mHardwareMixerThread->setStreamMute(AudioSystem::MUSIC, true); + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + mAudioHardware->setMasterVolume(0); + usleep(mHardwareMixerThread->latency()*1000); + mHardwareStatus = AUDIO_HW_SET_ROUTING; + mAudioHardware->setRouting(AudioSystem::MODE_NORMAL, mSavedRoute | AudioSystem::ROUTE_SPEAKER); + mHardwareStatus = AUDIO_HW_IDLE; + // delay track start so that audio hardware has time to siwtch routes + usleep(kStartSleepTime); + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + mAudioHardware->setMasterVolume(mHardwareMixerThread->masterVolume()); + mHardwareStatus = AUDIO_HW_IDLE; + } + mForcedRoute = AudioSystem::ROUTE_SPEAKER; + } + LOGV("mForcedSpeakerCount incremented to %d", mForcedSpeakerCount); + } + break; + case ACTIVE_TRACK_REMOVED: + { + AutoMutex lock(mHardwareLock); + if (mForcedSpeakerCount > 0){ + if (--mForcedSpeakerCount == 0) { + mRouteRestoreTime = systemTime() + milliseconds(kStopSleepTime/1000); + } + LOGV("mForcedSpeakerCount decremented to %d", mForcedSpeakerCount); + } else { + LOGE("mForcedSpeakerCount is already zero"); + } + } + break; + case CHECK_ROUTE_RESTORE_TIME: + case FORCE_ROUTE_RESTORE: + if (mRouteRestoreTime) { + AutoMutex lock(mHardwareLock); + if (mRouteRestoreTime && + (systemTime() > mRouteRestoreTime || command == FORCE_ROUTE_RESTORE)) { + mHardwareMixerThread->setStreamMute(AudioSystem::MUSIC, mMusicMuteSaved); + mForcedRoute = 0; + if (!(mSavedRoute & AudioSystem::ROUTE_SPEAKER)) { + mHardwareStatus = AUDIO_HW_SET_ROUTING; + mAudioHardware->setRouting(AudioSystem::MODE_NORMAL, mSavedRoute); + mHardwareStatus = AUDIO_HW_IDLE; + LOGV("Route forced to Speaker OFF %08x", mSavedRoute); + } + mRouteRestoreTime = 0; + } + } + break; + } +} + + +// ---------------------------------------------------------------------------- + +AudioFlinger::MixerThread::MixerThread(const sp& audioFlinger, AudioStreamOut* output, int outputType) + : Thread(false), + mAudioFlinger(audioFlinger), mAudioMixer(0), mOutput(output), mOutputType(outputType), + mSampleRate(0), mFrameCount(0), mChannelCount(0), mFormat(0), mMixBuffer(0), + mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mStandby(false), + mInWrite(false) +{ + mSampleRate = output->sampleRate(); + mChannelCount = output->channelCount(); + + // FIXME - Current mixer implementation only supports stereo output + if (mChannelCount == 1) { + LOGE("Invalid audio hardware channel count"); + } + + mFormat = output->format(); + mFrameCount = output->bufferSize() / output->channelCount() / sizeof(int16_t); + mAudioMixer = new AudioMixer(mFrameCount, output->sampleRate()); + + // FIXME - Current mixer implementation only supports stereo output: Always + // Allocate a stereo buffer even if HW output is mono. + mMixBuffer = new int16_t[mFrameCount * 2]; + memset(mMixBuffer, 0, mFrameCount * 2 * sizeof(int16_t)); +} + +AudioFlinger::MixerThread::~MixerThread() +{ + delete [] mMixBuffer; + delete mAudioMixer; +} + +status_t AudioFlinger::MixerThread::dump(int fd, const Vector& args) +{ + dumpInternals(fd, args); + dumpTracks(fd, args); + return NO_ERROR; +} + +status_t AudioFlinger::MixerThread::dumpTracks(int fd, const Vector& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "Output %d mixer thread tracks\n", mOutputType); + result.append(buffer); + result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); + for (size_t i = 0; i < mTracks.size(); ++i) { + wp wTrack = mTracks[i]; + if (wTrack != 0) { + sp track = wTrack.promote(); + if (track != 0) { + track->dump(buffer, SIZE); + result.append(buffer); + } + } + } + + snprintf(buffer, SIZE, "Output %d mixer thread active tracks\n", mOutputType); + result.append(buffer); + result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); + for (size_t i = 0; i < mActiveTracks.size(); ++i) { + wp wTrack = mTracks[i]; + if (wTrack != 0) { + sp track = wTrack.promote(); + if (track != 0) { + track->dump(buffer, SIZE); + result.append(buffer); + } + } + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioFlinger::MixerThread::dumpInternals(int fd, const Vector& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "Output %d mixer thread internals\n", mOutputType); + result.append(buffer); + snprintf(buffer, SIZE, "AudioMixer tracks: %08x\n", mAudioMixer->trackNames()); + result.append(buffer); + snprintf(buffer, SIZE, "last write occurred (msecs): %llu\n", ns2ms(systemTime() - mLastWriteTime)); + result.append(buffer); + snprintf(buffer, SIZE, "total writes: %d\n", mNumWrites); + result.append(buffer); + snprintf(buffer, SIZE, "delayed writes: %d\n", mNumDelayedWrites); + result.append(buffer); + snprintf(buffer, SIZE, "blocked in write: %d\n", mInWrite); + result.append(buffer); + snprintf(buffer, SIZE, "standby: %d\n", mStandby); + result.append(buffer); + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +// Thread virtuals +bool AudioFlinger::MixerThread::threadLoop() +{ + unsigned long sleepTime = kBufferRecoveryInUsecs; + int16_t* curBuf = mMixBuffer; + Vector< sp > tracksToRemove; + size_t enabledTracks = 0; + nsecs_t standbyTime = systemTime(); + size_t mixBufferSize = mFrameCount*mChannelCount*sizeof(int16_t); + nsecs_t maxPeriod = seconds(mFrameCount) / mSampleRate * 2; + +#ifdef WITH_A2DP + bool outputTrackActive = false; +#endif + + do { + enabledTracks = 0; + { // scope for the mLock + + Mutex::Autolock _l(mLock); + +#ifdef WITH_A2DP + if (mOutputType == AudioSystem::AUDIO_OUTPUT_A2DP) { + mAudioFlinger->handleOutputSwitch(); + } + if (mOutputTrack != NULL && !mAudioFlinger->isA2dpEnabled()) { + if (outputTrackActive) { + mOutputTrack->stop(); + outputTrackActive = false; + } + } +#endif + + const SortedVector< wp >& activeTracks = mActiveTracks; + + // put audio hardware into standby after short delay + if UNLIKELY(!activeTracks.size() && systemTime() > standbyTime) { + // wait until we have something to do... + LOGV("Audio hardware entering standby, output %d\n", mOutputType); +// mAudioFlinger->mHardwareStatus = AUDIO_HW_STANDBY; + if (!mStandby) { + mOutput->standby(); + mStandby = true; + } + +#ifdef WITH_A2DP + if (outputTrackActive) { + mOutputTrack->stop(); + outputTrackActive = false; + } +#endif + if (mOutputType == AudioSystem::AUDIO_OUTPUT_HARDWARE) { + mAudioFlinger->handleForcedSpeakerRoute(FORCE_ROUTE_RESTORE); + } +// mHardwareStatus = AUDIO_HW_IDLE; + // we're about to wait, flush the binder command buffer + IPCThreadState::self()->flushCommands(); + mWaitWorkCV.wait(mLock); + LOGV("Audio hardware exiting standby, output %d\n", mOutputType); + standbyTime = systemTime() + kStandbyTimeInNsecs; + continue; + } + + // Forced route to speaker is handled by hardware mixer thread + if (mOutputType == AudioSystem::AUDIO_OUTPUT_HARDWARE) { + mAudioFlinger->handleForcedSpeakerRoute(CHECK_ROUTE_RESTORE_TIME); + } + + // find out which tracks need to be processed + size_t count = activeTracks.size(); + for (size_t i=0 ; i t = activeTracks[i].promote(); + if (t == 0) continue; + + Track* const track = t.get(); + audio_track_cblk_t* cblk = track->cblk(); + + // The first time a track is added we wait + // for all its buffers to be filled before processing it + mAudioMixer->setActiveTrack(track->name()); + if (cblk->framesReady() && (track->isReady() || track->isStopped()) && + !track->isPaused()) + { + //LOGV("track %d u=%08x, s=%08x [OK]", track->name(), cblk->user, cblk->server); + + // compute volume for this track + int16_t left, right; + if (track->isMuted() || mMasterMute || track->isPausing()) { + left = right = 0; + if (track->isPausing()) { + LOGV("paused(%d)", track->name()); + track->setPaused(); + } + } else { + float typeVolume = mStreamTypes[track->type()].volume; + float v = mMasterVolume * typeVolume; + float v_clamped = v * cblk->volume[0]; + if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; + left = int16_t(v_clamped); + v_clamped = v * cblk->volume[1]; + if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; + right = int16_t(v_clamped); + } + + // XXX: these things DON'T need to be done each time + mAudioMixer->setBufferProvider(track); + mAudioMixer->enable(AudioMixer::MIXING); + + int param; + if ( track->mFillingUpStatus == Track::FS_FILLED) { + // no ramp for the first volume setting + track->mFillingUpStatus = Track::FS_ACTIVE; + if (track->mState == TrackBase::RESUMING) { + track->mState = TrackBase::ACTIVE; + param = AudioMixer::RAMP_VOLUME; + } else { + param = AudioMixer::VOLUME; + } + } else { + param = AudioMixer::RAMP_VOLUME; + } + mAudioMixer->setParameter(param, AudioMixer::VOLUME0, left); + mAudioMixer->setParameter(param, AudioMixer::VOLUME1, right); + mAudioMixer->setParameter( + AudioMixer::TRACK, + AudioMixer::FORMAT, track->format()); + mAudioMixer->setParameter( + AudioMixer::TRACK, + AudioMixer::CHANNEL_COUNT, track->channelCount()); + mAudioMixer->setParameter( + AudioMixer::RESAMPLE, + AudioMixer::SAMPLE_RATE, + int(cblk->sampleRate)); + + // reset retry count + track->mRetryCount = kMaxTrackRetries; + enabledTracks++; + } else { + //LOGV("track %d u=%08x, s=%08x [NOT READY]", track->name(), cblk->user, cblk->server); + if (track->isStopped()) { + track->reset(); + } + if (track->isTerminated() || track->isStopped() || track->isPaused()) { + // We have consumed all the buffers of this track. + // Remove it from the list of active tracks. + LOGV("remove(%d) from active list", track->name()); + tracksToRemove.add(track); + } else { + // No buffers for this track. Give it a few chances to + // fill a buffer, then remove it from active list. + if (--(track->mRetryCount) <= 0) { + LOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name()); + tracksToRemove.add(track); + } + } + // LOGV("disable(%d)", track->name()); + mAudioMixer->disable(AudioMixer::MIXING); + } + } + + // remove all the tracks that need to be... + count = tracksToRemove.size(); + if (UNLIKELY(count)) { + for (size_t i=0 ; i& track = tracksToRemove[i]; + removeActiveTrack(track); + if (track->isTerminated()) { + mTracks.remove(track); + deleteTrackName(track->mName); + } + } + } + } + + if (LIKELY(enabledTracks)) { + // mix buffers... + mAudioMixer->process(curBuf); + +#ifdef WITH_A2DP + if (mOutputTrack != NULL && mAudioFlinger->isA2dpEnabled()) { + if (!outputTrackActive) { + LOGV("starting output track in mixer for output %d", mOutputType); + mOutputTrack->start(); + outputTrackActive = true; + } + mOutputTrack->write(curBuf, mFrameCount); + } +#endif + + // output audio to hardware + mLastWriteTime = systemTime(); + mInWrite = true; + mOutput->write(curBuf, mixBufferSize); + mNumWrites++; + mInWrite = false; + mStandby = false; + nsecs_t temp = systemTime(); + standbyTime = temp + kStandbyTimeInNsecs; + nsecs_t delta = temp - mLastWriteTime; + if (delta > maxPeriod) { + LOGW("write blocked for %llu msecs", ns2ms(delta)); + mNumDelayedWrites++; + } + sleepTime = kBufferRecoveryInUsecs; + } else { +#ifdef WITH_A2DP + if (mOutputTrack != NULL && mAudioFlinger->isA2dpEnabled()) { + if (outputTrackActive) { + mOutputTrack->write(curBuf, 0); + if (mOutputTrack->bufferQueueEmpty()) { + mOutputTrack->stop(); + outputTrackActive = false; + } else { + standbyTime = systemTime() + kStandbyTimeInNsecs; + } + } + } +#endif + // There was nothing to mix this round, which means all + // active tracks were late. Sleep a little bit to give + // them another chance. If we're too late, the audio + // hardware will zero-fill for us. +// LOGV("no buffers - usleep(%lu)", sleepTime); + usleep(sleepTime); + if (sleepTime < kMaxBufferRecoveryInUsecs) { + sleepTime += kBufferRecoveryInUsecs; + } + } + + // finally let go of all our tracks, without the lock held + // since we can't guarantee the destructors won't acquire that + // same lock. + tracksToRemove.clear(); + } while (true); + + return false; +} + +status_t AudioFlinger::MixerThread::readyToRun() +{ + if (mSampleRate == 0) { + LOGE("No working audio driver found."); + return NO_INIT; + } + LOGI("AudioFlinger's thread ready to run for output %d", mOutputType); + return NO_ERROR; +} + +void AudioFlinger::MixerThread::onFirstRef() +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "Mixer Thread for output %d", mOutputType); + + run(buffer, ANDROID_PRIORITY_URGENT_AUDIO); +} + + +sp AudioFlinger::MixerThread::createTrack( + const sp& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + const sp& sharedBuffer, + status_t *status) +{ + sp track; + status_t lStatus; + + // Resampler implementation limits input sampling rate to 2 x output sampling rate. + if (sampleRate > MAX_SAMPLE_RATE || sampleRate > mSampleRate*2) { + LOGE("Sample rate out of range: %d mSampleRate %d", sampleRate, mSampleRate); + lStatus = BAD_VALUE; + goto Exit; + } + + { + Mutex::Autolock _l(mLock); + + if (mSampleRate == 0) { + LOGE("Audio driver not initialized."); + lStatus = NO_INIT; + goto Exit; + } + + track = new Track(this, client, streamType, sampleRate, format, + channelCount, frameCount, sharedBuffer); + mTracks.add(track); + lStatus = NO_ERROR; + } + +Exit: + if(status) { + *status = lStatus; + } + return track; +} + +void AudioFlinger::MixerThread::getTracks( + SortedVector < sp >& tracks, + SortedVector < wp >& activeTracks) +{ + size_t size = mTracks.size(); + LOGV ("MixerThread::getTracks() for output %d, mTracks.size %d, mActiveTracks.size %d", mOutputType, mTracks.size(), mActiveTracks.size()); + for (size_t i = 0; i < size; i++) { + sp t = mTracks[i]; + if (AudioSystem::routedToA2dpOutput(t->mStreamType)) { + tracks.add(t); + int j = mActiveTracks.indexOf(t); + if (j >= 0) { + t = mActiveTracks[j].promote(); + if (t != NULL) { + activeTracks.add(t); + } + } + } + } + + size = activeTracks.size(); + for (size_t i = 0; i < size; i++) { + removeActiveTrack(activeTracks[i]); + } + + size = tracks.size(); + for (size_t i = 0; i < size; i++) { + sp t = tracks[i]; + mTracks.remove(t); + deleteTrackName(t->name()); + } +} + +void AudioFlinger::MixerThread::putTracks( + SortedVector < sp >& tracks, + SortedVector < wp >& activeTracks) +{ + + LOGV ("MixerThread::putTracks() for output %d, tracks.size %d, activeTracks.size %d", mOutputType, tracks.size(), activeTracks.size()); + + size_t size = tracks.size(); + for (size_t i = 0; i < size ; i++) { + sp t = tracks[i]; + int name = getTrackName(); + + if (name < 0) return; + + t->mName = name; + t->mMixerThread = this; + mTracks.add(t); + + int j = activeTracks.indexOf(t); + if (j >= 0) { + addActiveTrack(t); + } + } +} + +uint32_t AudioFlinger::MixerThread::sampleRate() const +{ + return mSampleRate; +} + +int AudioFlinger::MixerThread::channelCount() const +{ + return mChannelCount; +} + +int AudioFlinger::MixerThread::format() const +{ + return mFormat; +} + +size_t AudioFlinger::MixerThread::frameCount() const +{ + return mFrameCount; +} + +uint32_t AudioFlinger::MixerThread::latency() const +{ + if (mOutput) { + return mOutput->latency(); + } + else { + return 0; + } +} + +status_t AudioFlinger::MixerThread::setMasterVolume(float value) +{ + mMasterVolume = value; + return NO_ERROR; +} + +status_t AudioFlinger::MixerThread::setMasterMute(bool muted) +{ + mMasterMute = muted; + return NO_ERROR; +} + +float AudioFlinger::MixerThread::masterVolume() const +{ + return mMasterVolume; +} + +bool AudioFlinger::MixerThread::masterMute() const +{ + return mMasterMute; +} + +status_t AudioFlinger::MixerThread::setStreamVolume(int stream, float value) +{ + mStreamTypes[stream].volume = value; + return NO_ERROR; +} + +status_t AudioFlinger::MixerThread::setStreamMute(int stream, bool muted) +{ + mStreamTypes[stream].mute = muted; + return NO_ERROR; +} + +float AudioFlinger::MixerThread::streamVolume(int stream) const +{ + return mStreamTypes[stream].volume; +} + +bool AudioFlinger::MixerThread::streamMute(int stream) const +{ + return mStreamTypes[stream].mute; +} + +bool AudioFlinger::MixerThread::isMusicActive() const +{ + size_t count = mActiveTracks.size(); + for (size_t i = 0 ; i < count ; ++i) { + sp t = mActiveTracks[i].promote(); + if (t == 0) continue; + Track* const track = t.get(); + if (t->mStreamType == AudioSystem::MUSIC) + return true; + } + return false; +} + +status_t AudioFlinger::MixerThread::addTrack(const sp& track) +{ + status_t status = ALREADY_EXISTS; + Mutex::Autolock _l(mLock); + + // here the track could be either new, or restarted + // in both cases "unstop" the track if (track->isPaused()) { track->mState = TrackBase::RESUMING; LOGV("PAUSED => RESUMING (%d)", track->name()); @@ -981,9 +1395,6 @@ status_t AudioFlinger::addTrack(const sp& track) } // set retry count for buffer fill track->mRetryCount = kMaxTrackStartupRetries; - LOGV("mWaitWorkCV.broadcast"); - mWaitWorkCV.broadcast(); - if (mActiveTracks.indexOf(track) < 0) { // the track is newly added, make sure it fills up all its // buffers before playing. This is to ensure the client will @@ -991,12 +1402,16 @@ status_t AudioFlinger::addTrack(const sp& track) track->mFillingUpStatus = Track::FS_FILLING; track->mResetDone = false; addActiveTrack(track); - return NO_ERROR; + status = NO_ERROR; } - return ALREADY_EXISTS; + + LOGV("mWaitWorkCV.broadcast"); + mWaitWorkCV.broadcast(); + + return status; } -void AudioFlinger::removeTrack(wp track, int name) +void AudioFlinger::MixerThread::removeTrack(wp track, int name) { Mutex::Autolock _l(mLock); sp t = track.promote(); @@ -1005,7 +1420,7 @@ void AudioFlinger::removeTrack(wp track, int name) } } -void AudioFlinger::remove_track_l(wp track, int name) +void AudioFlinger::MixerThread::remove_track_l(wp track, int name) { sp t = track.promote(); if (t!=NULL) { @@ -1016,7 +1431,7 @@ void AudioFlinger::remove_track_l(wp track, int name) mWaitWorkCV.broadcast(); } -void AudioFlinger::destroyTrack(const sp& track) +void AudioFlinger::MixerThread::destroyTrack(const sp& track) { // NOTE: We're acquiring a strong reference on the track before // acquiring the lock, this is to make sure removing it from @@ -1033,99 +1448,58 @@ void AudioFlinger::destroyTrack(const sp& track) } } -void AudioFlinger::addActiveTrack(const wp& t) + +void AudioFlinger::MixerThread::addActiveTrack(const wp& t) { mActiveTracks.add(t); -#ifdef WITH_A2DP - // disable A2DP for certain stream types - sp track = t.promote(); - if (streamDisablesA2dp(track->type())) { - if (mA2dpDisableCount++ == 0 && isA2dpEnabled()) { - setA2dpEnabled(false); - mA2dpSuppressed = true; - mMusicMuteSaved = mStreamTypes[AudioTrack::MUSIC].mute; - mStreamTypes[AudioTrack::MUSIC].mute = true; - LOGV("mA2dpSuppressed = true, track %d\n", track->name()); - } - LOGV("mA2dpDisableCount incremented to %d, track %d\n", mA2dpDisableCount, track->name()); + // Force routing to speaker for certain stream types + // The forced routing to speaker is managed by hardware mixer + if (mOutputType == AudioSystem::AUDIO_OUTPUT_HARDWARE) { + sp track = t.promote(); + if (track == NULL) return; + + if (streamForcedToSpeaker(track->type())) { + mAudioFlinger->handleForcedSpeakerRoute(ACTIVE_TRACK_ADDED); + } } -#endif -} - -void AudioFlinger::removeActiveTrack(const wp& t) -{ - mActiveTracks.remove(t); -#ifdef WITH_A2DP - // disable A2DP for certain stream types - sp track = t.promote(); - if (streamDisablesA2dp(track->type())) { - if (mA2dpDisableCount > 0) { - mA2dpDisableCount--; - LOGV("mA2dpDisableCount decremented to %d, track %d\n", mA2dpDisableCount, track->name()); - if (mA2dpDisableCount == 0 && mA2dpSuppressed) { - setA2dpEnabled(true); - mA2dpSuppressed = false; - mStreamTypes[AudioTrack::MUSIC].mute = mMusicMuteSaved; - LOGV("mA2dpSuppressed = false, track %d\n", track->name()); - } - } else - LOGE("mA2dpDisableCount is already zero"); - } -#endif -} - -int AudioFlinger::getTrackName() -{ - // Both mixers must have the same set of track used to avoid mismatches when - // switching from A2DP output to hardware output - int a2DpName; - int hwName; -#ifdef WITH_A2DP - a2DpName = mA2dpAudioMixer->getTrackName(); -#endif - hwName = mHardwareAudioMixer->getTrackName(); - - LOGW_IF((a2DpName != hwName), "getTrackName track name mismatch! A2DP %d, HW %d", a2DpName, hwName); - - return hwName; -} - -void AudioFlinger::deleteTrackName(int name) -{ - // Both mixers must have the same set of track used to avoid mismatches when - // switching from A2DP output to hardware output - mHardwareAudioMixer->deleteTrackName(name); -#ifdef WITH_A2DP - mA2dpAudioMixer->deleteTrackName(name); -#endif } -// ---------------------------------------------------------------------------- +void AudioFlinger::MixerThread::removeActiveTrack(const wp& t) +{ + mActiveTracks.remove(t); -AudioFlinger::Client::Client(const sp& audioFlinger, pid_t pid) - : RefBase(), - mAudioFlinger(audioFlinger), - mMemoryDealer(new MemoryDealer(1024*1024)), - mPid(pid) + // Force routing to speaker for certain stream types + // The forced routing to speaker is managed by hardware mixer + if (mOutputType == AudioSystem::AUDIO_OUTPUT_HARDWARE) { + sp track = t.promote(); + if (track == NULL) return; + + if (streamForcedToSpeaker(track->type())) { + mAudioFlinger->handleForcedSpeakerRoute(ACTIVE_TRACK_REMOVED); + } + } +} + +int AudioFlinger::MixerThread::getTrackName() { - // 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer + return mAudioMixer->getTrackName(); } -AudioFlinger::Client::~Client() +void AudioFlinger::MixerThread::deleteTrackName(int name) { - mAudioFlinger->removeClient(mPid); + mAudioMixer->deleteTrackName(name); } -const sp& AudioFlinger::Client::heap() const +size_t AudioFlinger::MixerThread::getOutputFrameCount() { - return mMemoryDealer; + return mOutput->bufferSize() / mOutput->channelCount() / sizeof(int16_t); } // ---------------------------------------------------------------------------- -AudioFlinger::TrackBase::TrackBase( - const sp& audioFlinger, +AudioFlinger::MixerThread::TrackBase::TrackBase( + const sp& mixerThread, const sp& client, int streamType, uint32_t sampleRate, @@ -1134,7 +1508,7 @@ AudioFlinger::TrackBase::TrackBase( int frameCount, const sp& sharedBuffer) : RefBase(), - mAudioFlinger(audioFlinger), + mMixerThread(mixerThread), mClient(client), mStreamType(streamType), mFrameCount(0), @@ -1143,7 +1517,7 @@ AudioFlinger::TrackBase::TrackBase( mFormat(format), mFlags(0) { - mName = audioFlinger->getTrackName(); + mName = mixerThread->getTrackName(); LOGV("TrackBase contructor name %d, calling thread %d", mName, IPCThreadState::self()->getCallingPid()); if (mName < 0) { LOGE("no more track names availlable"); @@ -1160,41 +1534,60 @@ AudioFlinger::TrackBase::TrackBase( size += bufferSize; } - mCblkMemory = client->heap()->allocate(size); - if (mCblkMemory != 0) { - mCblk = static_cast(mCblkMemory->pointer()); - if (mCblk) { // construct the shared structure in-place. - new(mCblk) audio_track_cblk_t(); - // clear all buffers - mCblk->frameCount = frameCount; - mCblk->sampleRate = sampleRate; - mCblk->channels = channelCount; - if (sharedBuffer == 0) { - mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t); - memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t)); - // Force underrun condition to avoid false underrun callback until first data is - // written to buffer - mCblk->flowControlFlag = 1; - } else { - mBuffer = sharedBuffer->pointer(); + if (client != NULL) { + mCblkMemory = client->heap()->allocate(size); + if (mCblkMemory != 0) { + mCblk = static_cast(mCblkMemory->pointer()); + if (mCblk) { // construct the shared structure in-place. + new(mCblk) audio_track_cblk_t(); + // clear all buffers + mCblk->frameCount = frameCount; + mCblk->sampleRate = sampleRate; + mCblk->channels = channelCount; + if (sharedBuffer == 0) { + mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t); + memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t)); + // Force underrun condition to avoid false underrun callback until first data is + // written to buffer + mCblk->flowControlFlag = 1; + } else { + mBuffer = sharedBuffer->pointer(); + } + mBufferEnd = (uint8_t *)mBuffer + bufferSize; } - mBufferEnd = (uint8_t *)mBuffer + bufferSize; + } else { + LOGE("not enough memory for AudioTrack size=%u", size); + client->heap()->dump("AudioTrack"); + return; } - } else { - LOGE("not enough memory for AudioTrack size=%u", size); - client->heap()->dump("AudioTrack"); - return; - } + } else { + mCblk = (audio_track_cblk_t *)(new uint8_t[size]); + if (mCblk) { // construct the shared structure in-place. + new(mCblk) audio_track_cblk_t(); + // clear all buffers + mCblk->frameCount = frameCount; + mCblk->sampleRate = sampleRate; + mCblk->channels = channelCount; + mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t); + memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t)); + // Force underrun condition to avoid false underrun callback until first data is + // written to buffer + mCblk->flowControlFlag = 1; + mBufferEnd = (uint8_t *)mBuffer + bufferSize; + } + } } -AudioFlinger::TrackBase::~TrackBase() +AudioFlinger::MixerThread::TrackBase::~TrackBase() { - mCblk->~audio_track_cblk_t(); // destroy our shared-structure. + if (mCblk) { + mCblk->~audio_track_cblk_t(); // destroy our shared-structure. + } mCblkMemory.clear(); // and free the shared memory mClient.clear(); } -void AudioFlinger::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer) +void AudioFlinger::MixerThread::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer) { buffer->raw = 0; mFrameCount = buffer->frameCount; @@ -1202,7 +1595,7 @@ void AudioFlinger::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer) buffer->frameCount = 0; } -bool AudioFlinger::TrackBase::step() { +bool AudioFlinger::MixerThread::TrackBase::step() { bool result; audio_track_cblk_t* cblk = this->cblk(); @@ -1214,7 +1607,7 @@ bool AudioFlinger::TrackBase::step() { return result; } -void AudioFlinger::TrackBase::reset() { +void AudioFlinger::MixerThread::TrackBase::reset() { audio_track_cblk_t* cblk = this->cblk(); cblk->user = 0; @@ -1225,20 +1618,20 @@ void AudioFlinger::TrackBase::reset() { LOGV("TrackBase::reset"); } -sp AudioFlinger::TrackBase::getCblk() const +sp AudioFlinger::MixerThread::TrackBase::getCblk() const { return mCblkMemory; } -int AudioFlinger::TrackBase::sampleRate() const { +int AudioFlinger::MixerThread::TrackBase::sampleRate() const { return mCblk->sampleRate; } -int AudioFlinger::TrackBase::channelCount() const { +int AudioFlinger::MixerThread::TrackBase::channelCount() const { return mCblk->channels; } -void* AudioFlinger::TrackBase::getBuffer(uint32_t offset, uint32_t frames) const { +void* AudioFlinger::MixerThread::TrackBase::getBuffer(uint32_t offset, uint32_t frames) const { audio_track_cblk_t* cblk = this->cblk(); int16_t *bufferStart = (int16_t *)mBuffer + (offset-cblk->serverBase)*cblk->channels; int16_t *bufferEnd = bufferStart + frames * cblk->channels; @@ -1257,8 +1650,8 @@ void* AudioFlinger::TrackBase::getBuffer(uint32_t offset, uint32_t frames) const // ---------------------------------------------------------------------------- -AudioFlinger::Track::Track( - const sp& audioFlinger, +AudioFlinger::MixerThread::Track::Track( + const sp& mixerThread, const sp& client, int streamType, uint32_t sampleRate, @@ -1266,7 +1659,7 @@ AudioFlinger::Track::Track( int channelCount, int frameCount, const sp& sharedBuffer) - : TrackBase(audioFlinger, client, streamType, sampleRate, format, channelCount, frameCount, sharedBuffer) + : TrackBase(mixerThread, client, streamType, sampleRate, format, channelCount, frameCount, sharedBuffer) { mVolume[0] = 1.0f; mVolume[1] = 1.0f; @@ -1274,23 +1667,23 @@ AudioFlinger::Track::Track( mSharedBuffer = sharedBuffer; } -AudioFlinger::Track::~Track() +AudioFlinger::MixerThread::Track::~Track() { wp weak(this); // never create a strong ref from the dtor mState = TERMINATED; - mAudioFlinger->removeTrack(weak, mName); + mMixerThread->removeTrack(weak, mName); } -void AudioFlinger::Track::destroy() +void AudioFlinger::MixerThread::Track::destroy() { - mAudioFlinger->destroyTrack(this); + mMixerThread->destroyTrack(this); } -void AudioFlinger::Track::dump(char* buffer, size_t size) +void AudioFlinger::MixerThread::Track::dump(char* buffer, size_t size) { snprintf(buffer, size, " %5d %5d %3u %3u %3u %3u %1d %1d %1d %5u %5u %5u %04x %04x\n", mName - AudioMixer::TRACK0, - mClient->pid(), + (mClient == NULL) ? getpid() : mClient->pid(), mStreamType, mFormat, mCblk->channels, @@ -1305,7 +1698,7 @@ void AudioFlinger::Track::dump(char* buffer, size_t size) mCblk->user); } -status_t AudioFlinger::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer) +status_t AudioFlinger::MixerThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer) { audio_track_cblk_t* cblk = this->cblk(); uint32_t framesReady; @@ -1345,53 +1738,54 @@ getNextBuffer_exit: return NOT_ENOUGH_DATA; } -bool AudioFlinger::Track::isReady() const { +bool AudioFlinger::MixerThread::Track::isReady() const { if (mFillingUpStatus != FS_FILLING) return true; if (mCblk->framesReady() >= mCblk->frameCount || mCblk->forceReady) { mFillingUpStatus = FS_FILLED; mCblk->forceReady = 0; + LOGV("Track::isReady() track %d for output %d", mName, mMixerThread->mOutputType); return true; } return false; } -status_t AudioFlinger::Track::start() +status_t AudioFlinger::MixerThread::Track::start() { - LOGV("start(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); - mAudioFlinger->addTrack(this); + LOGV("start(%d), calling thread %d for output %d", mName, IPCThreadState::self()->getCallingPid(), mMixerThread->mOutputType); + mMixerThread->addTrack(this); return NO_ERROR; } -void AudioFlinger::Track::stop() +void AudioFlinger::MixerThread::Track::stop() { - LOGV("stop(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); - Mutex::Autolock _l(mAudioFlinger->mLock); + LOGV("stop(%d), calling thread %d for output %d", mName, IPCThreadState::self()->getCallingPid(), mMixerThread->mOutputType); + Mutex::Autolock _l(mMixerThread->mLock); if (mState > STOPPED) { mState = STOPPED; // If the track is not active (PAUSED and buffers full), flush buffers - if (mAudioFlinger->mActiveTracks.indexOf(this) < 0) { + if (mMixerThread->mActiveTracks.indexOf(this) < 0) { reset(); } LOGV("(> STOPPED) => STOPPED (%d)", mName); } } -void AudioFlinger::Track::pause() +void AudioFlinger::MixerThread::Track::pause() { LOGV("pause(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); - Mutex::Autolock _l(mAudioFlinger->mLock); + Mutex::Autolock _l(mMixerThread->mLock); if (mState == ACTIVE || mState == RESUMING) { mState = PAUSING; LOGV("ACTIVE/RESUMING => PAUSING (%d)", mName); } } -void AudioFlinger::Track::flush() +void AudioFlinger::MixerThread::Track::flush() { LOGV("flush(%d)", mName); - Mutex::Autolock _l(mAudioFlinger->mLock); + Mutex::Autolock _l(mMixerThread->mLock); if (mState != STOPPED && mState != PAUSED && mState != PAUSING) { return; } @@ -1407,7 +1801,7 @@ void AudioFlinger::Track::flush() reset(); } -void AudioFlinger::Track::reset() +void AudioFlinger::MixerThread::Track::reset() { // Do not reset twice to avoid discarding data written just after a flush and before // the audioflinger thread detects the track is stopped. @@ -1422,12 +1816,12 @@ void AudioFlinger::Track::reset() } } -void AudioFlinger::Track::mute(bool muted) +void AudioFlinger::MixerThread::Track::mute(bool muted) { mMute = muted; } -void AudioFlinger::Track::setVolume(float left, float right) +void AudioFlinger::MixerThread::Track::setVolume(float left, float right) { mVolume[0] = left; mVolume[1] = right; @@ -1435,7 +1829,289 @@ void AudioFlinger::Track::setVolume(float left, float right) // ---------------------------------------------------------------------------- -AudioFlinger::TrackHandle::TrackHandle(const sp& track) +AudioFlinger::MixerThread::RecordTrack::RecordTrack( + const sp& mixerThread, + const sp& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount) + : TrackBase(mixerThread, client, streamType, sampleRate, format, + channelCount, frameCount, 0), + mOverflow(false) +{ +} + +AudioFlinger::MixerThread::RecordTrack::~RecordTrack() +{ + mMixerThread->deleteTrackName(mName); +} + +status_t AudioFlinger::MixerThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer) +{ + audio_track_cblk_t* cblk = this->cblk(); + uint32_t framesAvail; + uint32_t framesReq = buffer->frameCount; + + // Check if last stepServer failed, try to step now + if (mFlags & TrackBase::STEPSERVER_FAILED) { + if (!step()) goto getNextBuffer_exit; + LOGV("stepServer recovered"); + mFlags &= ~TrackBase::STEPSERVER_FAILED; + } + + framesAvail = cblk->framesAvailable_l(); + + if (LIKELY(framesAvail)) { + uint32_t s = cblk->server; + uint32_t bufferEnd = cblk->serverBase + cblk->frameCount; + + if (framesReq > framesAvail) { + framesReq = framesAvail; + } + if (s + framesReq > bufferEnd) { + framesReq = bufferEnd - s; + } + + buffer->raw = getBuffer(s, framesReq); + if (buffer->raw == 0) goto getNextBuffer_exit; + + buffer->frameCount = framesReq; + return NO_ERROR; + } + +getNextBuffer_exit: + buffer->raw = 0; + buffer->frameCount = 0; + return NOT_ENOUGH_DATA; +} + +status_t AudioFlinger::MixerThread::RecordTrack::start() +{ + return mMixerThread->mAudioFlinger->startRecord(this); +} + +void AudioFlinger::MixerThread::RecordTrack::stop() +{ + mMixerThread->mAudioFlinger->stopRecord(this); + TrackBase::reset(); + // Force overerrun condition to avoid false overrun callback until first data is + // read from buffer + mCblk->flowControlFlag = 1; +} + + +// ---------------------------------------------------------------------------- + +AudioFlinger::MixerThread::OutputTrack::OutputTrack( + const sp& mixerThread, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount) + : Track(mixerThread, NULL, AudioSystem::SYSTEM, sampleRate, format, channelCount, frameCount, NULL), + mOutputMixerThread(mixerThread) +{ + + mCblk->out = 1; + mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t); + mCblk->volume[0] = mCblk->volume[1] = 0x1000; + mOutBuffer.frameCount = 0; + mCblk->bufferTimeoutMs = 10; + + LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, mCblk->frameCount %d, mCblk->sampleRate %d, mCblk->channels %d mBufferEnd %p", + mCblk, mBuffer, mCblk->buffers, mCblk->frameCount, mCblk->sampleRate, mCblk->channels, mBufferEnd); + +} + +AudioFlinger::MixerThread::OutputTrack::~OutputTrack() +{ + stop(); +} + +status_t AudioFlinger::MixerThread::OutputTrack::start() +{ + status_t status = Track::start(); + + mRetryCount = 127; + return status; +} + +void AudioFlinger::MixerThread::OutputTrack::stop() +{ + Track::stop(); + clearBufferQueue(); + mOutBuffer.frameCount = 0; +} + +void AudioFlinger::MixerThread::OutputTrack::write(int16_t* data, uint32_t frames) +{ + Buffer *pInBuffer; + Buffer inBuffer; + uint32_t channels = mCblk->channels; + + inBuffer.frameCount = frames; + inBuffer.i16 = data; + + if (mCblk->user == 0) { + if (mOutputMixerThread->isMusicActive()) { + mCblk->forceReady = 1; + LOGV("OutputTrack::start() force ready"); + } else if (mCblk->frameCount > frames){ + if (mBufferQueue.size() < kMaxOutputTrackBuffers) { + uint32_t startFrames = (mCblk->frameCount - frames); + LOGV("OutputTrack::start() write %d frames", startFrames); + pInBuffer = new Buffer; + pInBuffer->mBuffer = new int16_t[startFrames * channels]; + pInBuffer->frameCount = startFrames; + pInBuffer->i16 = pInBuffer->mBuffer; + memset(pInBuffer->raw, 0, startFrames * channels * sizeof(int16_t)); + mBufferQueue.add(pInBuffer); + } else { + LOGW ("OutputTrack::write() no more buffers"); + } + } + } + + while (1) { + // First write pending buffers, then new data + if (mBufferQueue.size()) { + pInBuffer = mBufferQueue.itemAt(0); + } else { + pInBuffer = &inBuffer; + } + + if (pInBuffer->frameCount == 0) { + break; + } + + if (mOutBuffer.frameCount == 0) { + mOutBuffer.frameCount = pInBuffer->frameCount; + if (obtainBuffer(&mOutBuffer) == (status_t)AudioTrack::NO_MORE_BUFFERS) { + break; + } + } + + uint32_t outFrames = pInBuffer->frameCount > mOutBuffer.frameCount ? mOutBuffer.frameCount : pInBuffer->frameCount; + memcpy(mOutBuffer.raw, pInBuffer->raw, outFrames * channels * sizeof(int16_t)); + mCblk->stepUser(outFrames); + pInBuffer->frameCount -= outFrames; + pInBuffer->i16 += outFrames * channels; + mOutBuffer.frameCount -= outFrames; + mOutBuffer.i16 += outFrames * channels; + + if (pInBuffer->frameCount == 0) { + if (mBufferQueue.size()) { + mBufferQueue.removeAt(0); + delete [] pInBuffer->mBuffer; + delete pInBuffer; + } else { + break; + } + } + } + + // If we could not write all frames, allocate a buffer and queue it for next time. + if (inBuffer.frameCount) { + if (mBufferQueue.size() < kMaxOutputTrackBuffers) { + pInBuffer = new Buffer; + pInBuffer->mBuffer = new int16_t[inBuffer.frameCount * channels]; + pInBuffer->frameCount = inBuffer.frameCount; + pInBuffer->i16 = pInBuffer->mBuffer; + memcpy(pInBuffer->raw, inBuffer.raw, inBuffer.frameCount * channels * sizeof(int16_t)); + mBufferQueue.add(pInBuffer); + } else { + LOGW("OutputTrack::write() no more buffers"); + } + } + + // Calling write() with a 0 length buffer, means that no more data will be written: + // If no more buffers are pending, fill output track buffer to make sure it is started + // by output mixer. + if (frames == 0 && mBufferQueue.size() == 0 && mCblk->user < mCblk->frameCount) { + frames = mCblk->frameCount - mCblk->user; + pInBuffer = new Buffer; + pInBuffer->mBuffer = new int16_t[frames * channels]; + pInBuffer->frameCount = frames; + pInBuffer->i16 = pInBuffer->mBuffer; + memset(pInBuffer->raw, 0, frames * channels * sizeof(int16_t)); + mBufferQueue.add(pInBuffer); + } + +} + +status_t AudioFlinger::MixerThread::OutputTrack::obtainBuffer(AudioBufferProvider::Buffer* buffer) +{ + int active; + int timeout = 0; + status_t result; + audio_track_cblk_t* cblk = mCblk; + uint32_t framesReq = buffer->frameCount; + + LOGV("OutputTrack::obtainBuffer user %d, server %d", cblk->user, cblk->server); + buffer->frameCount = 0; + + uint32_t framesAvail = cblk->framesAvailable(); + + if (framesAvail == 0) { + return AudioTrack::NO_MORE_BUFFERS; + } + + if (framesReq > framesAvail) { + framesReq = framesAvail; + } + + uint32_t u = cblk->user; + uint32_t bufferEnd = cblk->userBase + cblk->frameCount; + + if (u + framesReq > bufferEnd) { + framesReq = bufferEnd - u; + } + + buffer->frameCount = framesReq; + buffer->raw = (void *)cblk->buffer(u); + return NO_ERROR; +} + + +void AudioFlinger::MixerThread::OutputTrack::clearBufferQueue() +{ + size_t size = mBufferQueue.size(); + Buffer *pBuffer; + + for (size_t i = 0; i < size; i++) { + pBuffer = mBufferQueue.itemAt(i); + delete [] pBuffer->mBuffer; + delete pBuffer; + } + mBufferQueue.clear(); +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::Client::Client(const sp& audioFlinger, pid_t pid) + : RefBase(), + mAudioFlinger(audioFlinger), + mMemoryDealer(new MemoryDealer(1024*1024)), + mPid(pid) +{ + // 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer +} + +AudioFlinger::Client::~Client() +{ + mAudioFlinger->removeClient(mPid); +} + +const sp& AudioFlinger::Client::heap() const +{ + return mMemoryDealer; +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::TrackHandle::TrackHandle(const sp& track) : BnAudioTrack(), mTrack(track) { @@ -1496,7 +2172,7 @@ sp AudioFlinger::openRecord( status_t *status) { sp thread; - sp recordTrack; + sp recordTrack; sp recordHandle; sp client; wp wclient; @@ -1523,12 +2199,6 @@ sp AudioFlinger::openRecord( goto Exit; } - if (mSampleRate == 0) { - LOGE("Audio driver not initialized"); - lStatus = NO_INIT; - goto Exit; - } - if (mAudioRecordThread == 0) { LOGE("Audio record thread not started"); lStatus = NO_INIT; @@ -1561,7 +2231,7 @@ sp AudioFlinger::openRecord( frameCount = ((frameCount - 1)/inFrameCount + 1) * inFrameCount; // create new record track and pass to record thread - recordTrack = new RecordTrack(this, client, streamType, sampleRate, + recordTrack = new MixerThread::RecordTrack(mHardwareMixerThread, client, streamType, sampleRate, format, channelCount, frameCount); // return to handle to client @@ -1575,97 +2245,22 @@ Exit: return recordHandle; } -status_t AudioFlinger::startRecord(RecordTrack* recordTrack) { +status_t AudioFlinger::startRecord(MixerThread::RecordTrack* recordTrack) { if (mAudioRecordThread != 0) { return mAudioRecordThread->start(recordTrack); } return NO_INIT; } -void AudioFlinger::stopRecord(RecordTrack* recordTrack) { +void AudioFlinger::stopRecord(MixerThread::RecordTrack* recordTrack) { if (mAudioRecordThread != 0) { mAudioRecordThread->stop(recordTrack); } } - -// ---------------------------------------------------------------------------- - -AudioFlinger::RecordTrack::RecordTrack( - const sp& audioFlinger, - const sp& client, - int streamType, - uint32_t sampleRate, - int format, - int channelCount, - int frameCount) - : TrackBase(audioFlinger, client, streamType, sampleRate, format, - channelCount, frameCount, 0), - mOverflow(false) -{ -} - -AudioFlinger::RecordTrack::~RecordTrack() -{ - mAudioFlinger->deleteTrackName(mName); -} - -status_t AudioFlinger::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer) -{ - audio_track_cblk_t* cblk = this->cblk(); - uint32_t framesAvail; - uint32_t framesReq = buffer->frameCount; - - // Check if last stepServer failed, try to step now - if (mFlags & TrackBase::STEPSERVER_FAILED) { - if (!step()) goto getNextBuffer_exit; - LOGV("stepServer recovered"); - mFlags &= ~TrackBase::STEPSERVER_FAILED; - } - - framesAvail = cblk->framesAvailable_l(); - - if (LIKELY(framesAvail)) { - uint32_t s = cblk->server; - uint32_t bufferEnd = cblk->serverBase + cblk->frameCount; - - if (framesReq > framesAvail) { - framesReq = framesAvail; - } - if (s + framesReq > bufferEnd) { - framesReq = bufferEnd - s; - } - - buffer->raw = getBuffer(s, framesReq); - if (buffer->raw == 0) goto getNextBuffer_exit; - - buffer->frameCount = framesReq; - return NO_ERROR; - } - -getNextBuffer_exit: - buffer->raw = 0; - buffer->frameCount = 0; - return NOT_ENOUGH_DATA; -} - -status_t AudioFlinger::RecordTrack::start() -{ - return mAudioFlinger->startRecord(this); -} - -void AudioFlinger::RecordTrack::stop() -{ - mAudioFlinger->stopRecord(this); - TrackBase::reset(); - // Force overerrun condition to avoid false overrun callback until first data is - // read from buffer - mCblk->flowControlFlag = 1; -} - // ---------------------------------------------------------------------------- -AudioFlinger::RecordHandle::RecordHandle(const sp& recordTrack) +AudioFlinger::RecordHandle::RecordHandle(const sp& recordTrack) : BnAudioRecord(), mRecordTrack(recordTrack) { @@ -1786,7 +2381,7 @@ bool AudioFlinger::AudioRecordThread::threadLoop() return false; } -status_t AudioFlinger::AudioRecordThread::start(RecordTrack* recordTrack) +status_t AudioFlinger::AudioRecordThread::start(MixerThread::RecordTrack* recordTrack) { LOGV("AudioRecordThread::start"); AutoMutex lock(&mLock); @@ -1807,7 +2402,7 @@ status_t AudioFlinger::AudioRecordThread::start(RecordTrack* recordTrack) return mStartStatus; } -void AudioFlinger::AudioRecordThread::stop(RecordTrack* recordTrack) { +void AudioFlinger::AudioRecordThread::stop(MixerThread::RecordTrack* recordTrack) { LOGV("AudioRecordThread::stop"); AutoMutex lock(&mLock); if (mActive && (recordTrack == mRecordTrack.get())) { diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h index 38fa0017e7d1..3b5932deea5a 100644 --- a/libs/audioflinger/AudioFlinger.h +++ b/libs/audioflinger/AudioFlinger.h @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -55,18 +56,13 @@ class AudioBuffer; static const nsecs_t kStandbyTimeInNsecs = seconds(3); -class AudioFlinger : public BnAudioFlinger, protected Thread, public IBinder::DeathRecipient +class AudioFlinger : public BnAudioFlinger, public IBinder::DeathRecipient { public: static void instantiate(); virtual status_t dump(int fd, const Vector& args); - // Thread virtuals - virtual bool threadLoop(); - virtual status_t readyToRun(); - virtual void onFirstRef(); - // IAudioFlinger interface virtual sp createTrack( pid_t pid, @@ -79,11 +75,11 @@ public: const sp& sharedBuffer, status_t *status); - virtual uint32_t sampleRate() const; - virtual int channelCount() const; - virtual int format() const; - virtual size_t frameCount() const; - virtual size_t latency() const; + virtual uint32_t sampleRate(int output) const; + virtual int channelCount(int output) const; + virtual int format(int output) const; + virtual size_t frameCount(int output) const; + virtual uint32_t latency(int output) const; virtual status_t setMasterVolume(float value); virtual status_t setMasterMute(bool muted); @@ -108,6 +104,8 @@ public: virtual bool isMusicActive() const; + virtual bool isA2dpEnabled() const; + virtual status_t setParameter(const char* key, const char* value); virtual void registerClient(const sp& client); @@ -159,23 +157,26 @@ private: AudioFlinger(); virtual ~AudioFlinger(); - void setOutput(AudioStreamOut* output); - void doSetOutput(AudioStreamOut* output); - size_t getOutputFrameCount(AudioStreamOut* output); + void setOutput(int outputType); + void doSetOutput(int outputType); #ifdef WITH_A2DP - static bool streamDisablesA2dp(int streamType); - inline bool isA2dpEnabled() const { - return (mRequestedOutput == mA2dpOutput || - (mOutput && mOutput == mA2dpOutput)); - } void setA2dpEnabled(bool enable); #endif + static bool streamForcedToSpeaker(int streamType); + + // Management of forced route to speaker for certain track types. + enum force_speaker_command { + ACTIVE_TRACK_ADDED = 0, + ACTIVE_TRACK_REMOVED, + CHECK_ROUTE_RESTORE_TIME, + FORCE_ROUTE_RESTORE + }; + void handleForcedSpeakerRoute(int command); // Internal dump utilites. status_t dumpPermissionDenial(int fd, const Vector& args); status_t dumpClients(int fd, const Vector& args); - status_t dumpTracks(int fd, const Vector& args); status_t dumpInternals(int fd, const Vector& args); // --- Client --- @@ -194,168 +195,348 @@ private: }; - // --- Track --- class TrackHandle; class RecordHandle; class AudioRecordThread; - // base for record and playback - class TrackBase : public AudioBufferProvider, public RefBase { - + + // --- MixerThread --- + class MixerThread : public Thread { public: - enum track_state { - IDLE, - TERMINATED, - STOPPED, - RESUMING, - ACTIVE, - PAUSING, - PAUSED + + // --- Track --- + + // base for record and playback + class TrackBase : public AudioBufferProvider, public RefBase { + + public: + enum track_state { + IDLE, + TERMINATED, + STOPPED, + RESUMING, + ACTIVE, + PAUSING, + PAUSED + }; + + enum track_flags { + STEPSERVER_FAILED = 0x01 // StepServer could not acquire cblk->lock mutex + }; + + TrackBase( const sp& mixerThread, + const sp& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + const sp& sharedBuffer); + ~TrackBase(); + + virtual status_t start() = 0; + virtual void stop() = 0; + sp getCblk() const; + + protected: + friend class MixerThread; + friend class RecordHandle; + friend class AudioRecordThread; + + TrackBase(const TrackBase&); + TrackBase& operator = (const TrackBase&); + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0; + virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); + + audio_track_cblk_t* cblk() const { + return mCblk; + } + + int type() const { + return mStreamType; + } + + int format() const { + return mFormat; + } + + int channelCount() const ; + + int sampleRate() const; + + void* getBuffer(uint32_t offset, uint32_t frames) const; + + int name() const { + return mName; + } + + bool isStopped() const { + return mState == STOPPED; + } + + bool isTerminated() const { + return mState == TERMINATED; + } + + bool step(); + void reset(); + + sp mMixerThread; + sp mClient; + sp mCblkMemory; + audio_track_cblk_t* mCblk; + int mStreamType; + void* mBuffer; + void* mBufferEnd; + uint32_t mFrameCount; + int mName; + // we don't really need a lock for these + int mState; + int mClientTid; + uint8_t mFormat; + uint8_t mFlags; }; - enum track_flags { - STEPSERVER_FAILED = 0x01 // StepServer could not acquire cblk->lock mutex + // playback track + class Track : public TrackBase { + public: + Track( const sp& mixerThread, + const sp& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + const sp& sharedBuffer); + ~Track(); + + void dump(char* buffer, size_t size); + virtual status_t start(); + virtual void stop(); + void pause(); + + void flush(); + void destroy(); + void mute(bool); + void setVolume(float left, float right); + + protected: + friend class MixerThread; + friend class AudioFlinger; + friend class AudioFlinger::TrackHandle; + + Track(const Track&); + Track& operator = (const Track&); + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + + bool isMuted() const { + return (mMute || mMixerThread->mStreamTypes[mStreamType].mute); + } + + bool isPausing() const { + return mState == PAUSING; + } + + bool isPaused() const { + return mState == PAUSED; + } + + bool isReady() const; + + void setPaused() { mState = PAUSED; } + void reset(); + + // we don't really need a lock for these + float mVolume[2]; + volatile bool mMute; + // FILLED state is used for suppressing volume ramp at begin of playing + enum {FS_FILLING, FS_FILLED, FS_ACTIVE}; + mutable uint8_t mFillingUpStatus; + int8_t mRetryCount; + sp mSharedBuffer; + bool mResetDone; + }; // end of Track + + // record track + class RecordTrack : public TrackBase { + public: + RecordTrack( const sp& mixerThread, + const sp& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount); + ~RecordTrack(); + + virtual status_t start(); + virtual void stop(); + + bool overflow() { bool tmp = mOverflow; mOverflow = false; return tmp; } + bool setOverflow() { bool tmp = mOverflow; mOverflow = true; return tmp; } + + private: + friend class AudioFlinger; + friend class AudioFlinger::RecordHandle; + friend class AudioFlinger::AudioRecordThread; + friend class MixerThread; + + RecordTrack(const Track&); + RecordTrack& operator = (const Track&); + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + + bool mOverflow; }; - TrackBase( const sp& audioFlinger, - const sp& client, - int streamType, - uint32_t sampleRate, - int format, - int channelCount, - int frameCount, - const sp& sharedBuffer); - ~TrackBase(); - - virtual status_t start() = 0; - virtual void stop() = 0; - sp getCblk() const; - - protected: - friend class AudioFlinger; - friend class RecordHandle; - friend class AudioRecordThread; - - TrackBase(const TrackBase&); - TrackBase& operator = (const TrackBase&); - - virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0; - virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); - - audio_track_cblk_t* cblk() const { - return mCblk; - } - - int type() const { - return mStreamType; - } - - int format() const { - return mFormat; - } - - int channelCount() const ; - - int sampleRate() const; - - void* getBuffer(uint32_t offset, uint32_t frames) const; - - int name() const { - return mName; - } - - bool isStopped() const { - return mState == STOPPED; - } - - bool isTerminated() const { - return mState == TERMINATED; - } - - bool step(); - void reset(); - - sp mAudioFlinger; - sp mClient; - sp mCblkMemory; - audio_track_cblk_t* mCblk; - int mStreamType; - void* mBuffer; - void* mBufferEnd; - uint32_t mFrameCount; - int mName; - // we don't really need a lock for these - int mState; - int mClientTid; - uint8_t mFormat; - uint8_t mFlags; - }; - - // playback track - class Track : public TrackBase { - public: - Track( const sp& audioFlinger, - const sp& client, + // playback track + class OutputTrack : public Track { + public: + + class Buffer: public AudioBufferProvider::Buffer { + public: + int16_t *mBuffer; + }; + + OutputTrack( const sp& mixerThread, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount); + ~OutputTrack(); + + virtual status_t start(); + virtual void stop(); + void write(int16_t* data, uint32_t frames); + bool bufferQueueEmpty() { return (mBufferQueue.size() == 0) ? true : false; } + + private: + + status_t obtainBuffer(AudioBufferProvider::Buffer* buffer); + void clearBufferQueue(); + + sp mOutputMixerThread; + Vector < Buffer* > mBufferQueue; + AudioBufferProvider::Buffer mOutBuffer; + uint32_t mFramesWritten; + + }; // end of OutputTrack + + MixerThread (const sp& audioFlinger, AudioStreamOut* output, int outputType); + virtual ~MixerThread(); + + virtual status_t dump(int fd, const Vector& args); + + // Thread virtuals + virtual bool threadLoop(); + virtual status_t readyToRun(); + virtual void onFirstRef(); + + virtual uint32_t sampleRate() const; + virtual int channelCount() const; + virtual int format() const; + virtual size_t frameCount() const; + virtual uint32_t latency() const; + + virtual status_t setMasterVolume(float value); + virtual status_t setMasterMute(bool muted); + + virtual float masterVolume() const; + virtual bool masterMute() const; + + virtual status_t setStreamVolume(int stream, float value); + virtual status_t setStreamMute(int stream, bool muted); + + virtual float streamVolume(int stream) const; + virtual bool streamMute(int stream) const; + + bool isMusicActive() const; + + + sp createTrack( + const sp& client, int streamType, uint32_t sampleRate, int format, int channelCount, int frameCount, - const sp& sharedBuffer); - ~Track(); - - void dump(char* buffer, size_t size); - virtual status_t start(); - virtual void stop(); - void pause(); - - void flush(); - void destroy(); - void mute(bool); - void setVolume(float left, float right); + const sp& sharedBuffer, + status_t *status); + + void wakeUp() { mWaitWorkCV.broadcast(); } + + void getTracks(SortedVector < sp >& tracks, + SortedVector < wp >& activeTracks); + void putTracks(SortedVector < sp >& tracks, + SortedVector < wp >& activeTracks); + void setOuputTrack(OutputTrack *track) { mOutputTrack = track; } + + struct stream_type_t { + stream_type_t() + : volume(1.0f), + mute(false) + { + } + float volume; + bool mute; + }; private: - friend class AudioFlinger; - friend class TrackHandle; - - Track(const Track&); - Track& operator = (const Track&); - - virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); - - bool isMuted() const { - return (mMute || mAudioFlinger->mStreamTypes[mStreamType].mute); - } - - bool isPausing() const { - return mState == PAUSING; - } - - bool isPaused() const { - return mState == PAUSED; - } - - bool isReady() const; - void setPaused() { mState = PAUSED; } - void reset(); - // we don't really need a lock for these - float mVolume[2]; - volatile bool mMute; - // FILLED state is used for suppressing volume ramp at begin of playing - enum {FS_FILLING, FS_FILLED, FS_ACTIVE}; - mutable uint8_t mFillingUpStatus; - int8_t mRetryCount; - sp mSharedBuffer; - bool mResetDone; - }; // end of Track + friend class AudioFlinger; + friend class Track; + friend class TrackBase; + friend class RecordTrack; + + MixerThread(const Client&); + MixerThread& operator = (const MixerThread&); + + status_t addTrack(const sp& track); + void removeTrack(wp track, int name); + void remove_track_l(wp track, int name); + void destroyTrack(const sp& track); + int getTrackName(); + void deleteTrackName(int name); + void addActiveTrack(const wp& t); + void removeActiveTrack(const wp& t); + size_t getOutputFrameCount(); + + status_t dumpInternals(int fd, const Vector& args); + status_t dumpTracks(int fd, const Vector& args); + + sp mAudioFlinger; + mutable Mutex mLock; + mutable Condition mWaitWorkCV; + SortedVector< wp > mActiveTracks; + SortedVector< sp > mTracks; + stream_type_t mStreamTypes[AudioSystem::NUM_STREAM_TYPES]; + AudioMixer* mAudioMixer; + AudioStreamOut* mOutput; + int mOutputType; + uint32_t mSampleRate; + size_t mFrameCount; + int mChannelCount; + int mFormat; + int16_t* mMixBuffer; + float mMasterVolume; + bool mMasterMute; + nsecs_t mLastWriteTime; + int mNumWrites; + int mNumDelayedWrites; + bool mStandby; + bool mInWrite; + sp mOutputTrack; + }; + friend class AudioBuffer; class TrackHandle : public android::BnAudioTrack { public: - TrackHandle(const sp& track); + TrackHandle(const sp& track); virtual ~TrackHandle(); virtual status_t start(); virtual void stop(); @@ -367,72 +548,20 @@ private: virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); private: - sp mTrack; - }; - - struct stream_type_t { - stream_type_t() - : volume(1.0f), - mute(false) - { - } - float volume; - bool mute; + sp mTrack; }; friend class Client; - friend class Track; + friend class MixerThread::Track; void removeClient(pid_t pid); - status_t addTrack(const sp& track); - void removeTrack(wp track, int name); - void remove_track_l(wp track, int name); - void destroyTrack(const sp& track); - void addActiveTrack(const wp& track); - void removeActiveTrack(const wp& track); - int getTrackName(); - void deleteTrackName(int name); - - AudioMixer* audioMixer() { - return mAudioMixer; - } - - // record track - class RecordTrack : public TrackBase { - public: - RecordTrack( const sp& audioFlinger, - const sp& client, - int streamType, - uint32_t sampleRate, - int format, - int channelCount, - int frameCount); - ~RecordTrack(); - - virtual status_t start(); - virtual void stop(); - - bool overflow() { bool tmp = mOverflow; mOverflow = false; return tmp; } - bool setOverflow() { bool tmp = mOverflow; mOverflow = true; return tmp; } - - private: - friend class AudioFlinger; - friend class RecordHandle; - friend class AudioRecordThread; - - RecordTrack(const Track&); - RecordTrack& operator = (const Track&); - virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); - - bool mOverflow; - }; class RecordHandle : public android::BnAudioRecord { public: - RecordHandle(const sp& recordTrack); + RecordHandle(const sp& recordTrack); virtual ~RecordHandle(); virtual status_t start(); virtual void stop(); @@ -440,7 +569,7 @@ private: virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); private: - sp mRecordTrack; + sp mRecordTrack; }; // record thread @@ -453,14 +582,14 @@ private: virtual status_t readyToRun() { return NO_ERROR; } virtual void onFirstRef() {} - status_t start(RecordTrack* recordTrack); - void stop(RecordTrack* recordTrack); + status_t start(MixerThread::RecordTrack* recordTrack); + void stop(MixerThread::RecordTrack* recordTrack); void exit(); private: AudioRecordThread(); AudioHardwareInterface *mAudioHardware; - sp mRecordTrack; + sp mRecordTrack; Mutex mLock; Condition mWaitWorkCV; volatile bool mActive; @@ -468,48 +597,31 @@ private: }; friend class AudioRecordThread; + friend class MixerThread; - status_t startRecord(RecordTrack* recordTrack); - void stopRecord(RecordTrack* recordTrack); - - void notifyOutputChange_l(); + status_t startRecord(MixerThread::RecordTrack* recordTrack); + void stopRecord(MixerThread::RecordTrack* recordTrack); + + void handleOutputSwitch(); mutable Mutex mHardwareLock; mutable Mutex mLock; - mutable Condition mWaitWorkCV; DefaultKeyedVector< pid_t, wp > mClients; - SortedVector< wp > mActiveTracks; - SortedVector< sp > mTracks; - float mMasterVolume; - uint32_t mMasterRouting; - bool mMasterMute; - stream_type_t mStreamTypes[AudioTrack::NUM_STREAM_TYPES]; - - AudioMixer* mHardwareAudioMixer; - AudioMixer* mA2dpAudioMixer; - AudioMixer* mAudioMixer; + + sp mA2dpMixerThread; + sp mHardwareMixerThread; AudioHardwareInterface* mAudioHardware; AudioHardwareInterface* mA2dpAudioInterface; - AudioStreamOut* mHardwareOutput; - AudioStreamOut* mA2dpOutput; - AudioStreamOut* mOutput; - AudioStreamOut* mRequestedOutput; sp mAudioRecordThread; - uint32_t mSampleRate; - size_t mFrameCount; - int mChannelCount; - int mFormat; - int16_t* mMixBuffer; + bool mA2dpEnabled; + bool mA2dpEnabledReq; mutable int mHardwareStatus; - nsecs_t mLastWriteTime; - int mNumWrites; - int mNumDelayedWrites; - bool mStandby; - bool mInWrite; - int mA2dpDisableCount; - bool mA2dpSuppressed; - bool mMusicMuteSaved; SortedVector< wp > mNotificationClients; + int mForcedSpeakerCount; + uint32_t mSavedRoute; + uint32_t mForcedRoute; + nsecs_t mRouteRestoreTime; + bool mMusicMuteSaved; }; // ---------------------------------------------------------------------------- diff --git a/libs/surfaceflinger/Android.mk b/libs/surfaceflinger/Android.mk index 53ba3bc6e4e0..496e2717fb4a 100644 --- a/libs/surfaceflinger/Android.mk +++ b/libs/surfaceflinger/Android.mk @@ -16,9 +16,7 @@ LOCAL_SRC_FILES:= \ LayerBitmap.cpp \ LayerDim.cpp \ LayerOrientationAnim.cpp \ - LayerScreenshot.cpp \ OrientationAnimation.cpp \ - RFBServer.cpp \ SurfaceFlinger.cpp \ Tokenizer.cpp \ Transform.cpp \ diff --git a/libs/surfaceflinger/Layer.cpp b/libs/surfaceflinger/Layer.cpp index 31e63ef2c6b1..f65d66984d7f 100644 --- a/libs/surfaceflinger/Layer.cpp +++ b/libs/surfaceflinger/Layer.cpp @@ -186,7 +186,9 @@ void Layer::onDraw(const Region& clip) const copybit_device_t* copybit = mFlinger->getBlitEngine(); copybit->set_parameter(copybit, COPYBIT_TRANSFORM, getOrientation()); copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, s.alpha); - copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE); + copybit->set_parameter(copybit, COPYBIT_DITHER, + s.flags & ISurfaceComposer::eLayerDither ? + COPYBIT_ENABLE : COPYBIT_DISABLE); region_iterator it(clip); err = copybit->stretch(copybit, &dst, &src, &drect, &srect, &it); diff --git a/libs/surfaceflinger/LayerBase.cpp b/libs/surfaceflinger/LayerBase.cpp index 9277a64daa4c..0cf53f796b6b 100644 --- a/libs/surfaceflinger/LayerBase.cpp +++ b/libs/surfaceflinger/LayerBase.cpp @@ -26,6 +26,8 @@ #include #include +#include + #include "clz.h" #include "LayerBase.h" #include "LayerBlur.h" @@ -229,15 +231,10 @@ Point LayerBase::getPhysicalSize() const return Point(front.w, front.h); } -Transform LayerBase::getDrawingStateTransform() const -{ - return drawingState().transform; -} - void LayerBase::validateVisibility(const Transform& planeTransform) { const Layer::State& s(drawingState()); - const Transform tr(planeTransform * getDrawingStateTransform()); + const Transform tr(planeTransform * s.transform); const bool transformed = tr.transformed(); const Point size(getPhysicalSize()); @@ -420,7 +417,7 @@ void LayerBase::clearWithOpenGL(const Region& clip) const } void LayerBase::drawWithOpenGL(const Region& clip, - GLint textureName, const GGLSurface& t) const + GLint textureName, const GGLSurface& t, int transform) const { const DisplayHardware& hw(graphicPlane(0).displayHardware()); const uint32_t fbHeight = hw.getHeight(); @@ -492,6 +489,12 @@ void LayerBase::drawWithOpenGL(const Region& clip, glMatrixMode(GL_TEXTURE); glLoadIdentity(); + + if (transform == HAL_TRANSFORM_ROT_90) { + glTranslatef(0, 1, 0); + glRotatef(-90, 0, 0, 1); + } + if (!(mFlags & DisplayHardware::NPOT_EXTENSION)) { // find the smallest power-of-two that will accommodate our surface GLuint tw = 1 << (31 - clz(t.width)); diff --git a/libs/surfaceflinger/LayerBase.h b/libs/surfaceflinger/LayerBase.h index 2377a148650e..a020f44e8746 100644 --- a/libs/surfaceflinger/LayerBase.h +++ b/libs/surfaceflinger/LayerBase.h @@ -169,13 +169,6 @@ public: virtual void validateVisibility(const Transform& globalTransform); /** - * getDrawingStateTransform - returns the drawing state's transform. - * This is used in validateVisibility() and can be use to override or - * modify the transform (if so make sure to trigger a transaction). - */ - virtual Transform getDrawingStateTransform() const; - - /** * lockPageFlip - called each time the screen is redrawn and returns whether * the visible regions need to be recomputed (this is a fairly heavy * operation, so this should be set only if needed). Typically this is used @@ -200,10 +193,15 @@ public: * needsBlending - true if this surface needs blending */ virtual bool needsBlending() const { return false; } - + + /** + * transformed -- true is this surface needs a to be transformed + */ + virtual bool transformed() const { return mTransformed; } + /** - * isSecure - true if this surface is secure, that is if it prevents a - * screenshot to be taken, + * isSecure - true if this surface is secure, that is if it prevents + * screenshots or vns servers. */ virtual bool isSecure() const { return false; } @@ -222,7 +220,6 @@ public: } int32_t getOrientation() const { return mOrientation; } - bool transformed() const { return mTransformed; } int tx() const { return mLeft; } int ty() const { return mTop; } @@ -233,7 +230,9 @@ protected: GLuint createTexture() const; void drawWithOpenGL(const Region& clip, - GLint textureName, const GGLSurface& surface) const; + GLint textureName, + const GGLSurface& surface, + int transform = 0) const; void clearWithOpenGL(const Region& clip) const; diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp index fc0a603f7f50..00fab70d491d 100644 --- a/libs/surfaceflinger/LayerBuffer.cpp +++ b/libs/surfaceflinger/LayerBuffer.cpp @@ -103,15 +103,6 @@ void LayerBuffer::unregisterBuffers() source->unregisterBuffers(); } -Transform LayerBuffer::getDrawingStateTransform() const -{ - Transform tr(LayerBaseClient::getDrawingStateTransform()); - sp source(getSource()); - if (source != 0) - source->updateTransform(&tr); - return tr; -} - uint32_t LayerBuffer::doTransaction(uint32_t flags) { sp source(getSource()); @@ -141,6 +132,14 @@ void LayerBuffer::onDraw(const Region& clip) const } } +bool LayerBuffer::transformed() const +{ + sp source(getSource()); + if (LIKELY(source != 0)) + return source->transformed(); + return false; +} + /** * This creates a "buffer" source for this surface */ @@ -316,7 +315,8 @@ void LayerBuffer::Source::postBuffer(ssize_t offset) { } void LayerBuffer::Source::unregisterBuffers() { } -void LayerBuffer::Source::updateTransform(Transform* tr) const { +bool LayerBuffer::Source::transformed() const { + return mLayer.mTransformed; } // --------------------------------------------------------------------------- @@ -363,6 +363,7 @@ LayerBuffer::BufferSource::BufferSource(LayerBuffer& layer, mLayer.setNeedsBlending((info.h_alpha - info.l_alpha) > 0); mBufferSize = info.getScanlineSize(buffers.hor_stride)*buffers.ver_stride; mLayer.forceVisibilityTransaction(); + } LayerBuffer::BufferSource::~BufferSource() @@ -419,15 +420,9 @@ void LayerBuffer::BufferSource::setBuffer(const sp& buffer) mBuffer = buffer; } -void LayerBuffer::BufferSource::updateTransform(Transform* tr) const +bool LayerBuffer::BufferSource::transformed() const { - uint32_t bufTransform = mBufferHeap.transform; - // TODO: handle all transforms - if (bufTransform == ISurface::BufferHeap::ROT_90) { - Transform rot90; - rot90.set(0, -1, 1, 0); - *tr = (*tr) * rot90; - } + return mBufferHeap.transform ? true : Source::transformed(); } void LayerBuffer::BufferSource::onDraw(const Region& clip) const @@ -476,7 +471,7 @@ void LayerBuffer::BufferSource::onDraw(const Region& clip) const if (UNLIKELY(mTemporaryDealer == 0)) { // allocate a memory-dealer for this the first time mTemporaryDealer = mLayer.mFlinger->getSurfaceHeapManager() - ->createHeap(ISurfaceComposer::eHardware); + ->createHeap(ISurfaceComposer::eHardware); mTempBitmap.init(mTemporaryDealer); } @@ -506,10 +501,23 @@ void LayerBuffer::BufferSource::onDraw(const Region& clip) const copybit_image_t dst; hw.getDisplaySurface(&dst); const copybit_rect_t& drect - = reinterpret_cast(transformedBounds); + = reinterpret_cast(transformedBounds); const State& s(mLayer.drawingState()); region_iterator it(clip); - copybit->set_parameter(copybit, COPYBIT_TRANSFORM, mLayer.getOrientation()); + + // pick the right orientation for this buffer + int orientation = mLayer.getOrientation(); + if (UNLIKELY(mBufferHeap.transform)) { + Transform rot90; + GraphicPlane::orientationToTransfrom( + ISurfaceComposer::eOrientation90, 0, 0, &rot90); + const Transform& planeTransform(mLayer.graphicPlane(0).transform()); + const Layer::State& s(mLayer.drawingState()); + Transform tr(planeTransform * s.transform * rot90); + orientation = tr.getOrientation(); + } + + copybit->set_parameter(copybit, COPYBIT_TRANSFORM, orientation); copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, s.alpha); copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE); @@ -536,10 +544,11 @@ void LayerBuffer::BufferSource::onDraw(const Region& clip) const t.data = (GGLubyte*)(intptr_t(src.img.base) + src.img.offset); const Region dirty(Rect(t.width, t.height)); mLayer.loadTexture(dirty, mTextureName, t, w, h); - mLayer.drawWithOpenGL(clip, mTextureName, t); + mLayer.drawWithOpenGL(clip, mTextureName, t, mBufferHeap.transform); } } + // --------------------------------------------------------------------------- LayerBuffer::OverlaySource::OverlaySource(LayerBuffer& layer, diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h index 5532532f044f..2dc77f1501e9 100644 --- a/libs/surfaceflinger/LayerBuffer.h +++ b/libs/surfaceflinger/LayerBuffer.h @@ -46,7 +46,7 @@ class LayerBuffer : public LayerBaseClient virtual void onVisibilityResolved(const Transform& planeTransform); virtual void postBuffer(ssize_t offset); virtual void unregisterBuffers(); - virtual void updateTransform(Transform* tr) const; + virtual bool transformed() const; protected: LayerBuffer& mLayer; }; @@ -68,7 +68,7 @@ public: virtual void onDraw(const Region& clip) const; virtual uint32_t doTransaction(uint32_t flags); virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion); - virtual Transform getDrawingStateTransform() const; + virtual bool transformed() const; status_t registerBuffers(const ISurface::BufferHeap& buffers); void postBuffer(ssize_t offset); @@ -116,10 +116,10 @@ private: sp getBuffer() const; void setBuffer(const sp& buffer); - virtual void updateTransform(Transform* tr) const; virtual void onDraw(const Region& clip) const; virtual void postBuffer(ssize_t offset); virtual void unregisterBuffers(); + virtual bool transformed() const; private: mutable Mutex mLock; sp mBuffer; diff --git a/libs/surfaceflinger/LayerOrientationAnim.cpp b/libs/surfaceflinger/LayerOrientationAnim.cpp index 46b3b196c72e..2b72d7ce9487 100644 --- a/libs/surfaceflinger/LayerOrientationAnim.cpp +++ b/libs/surfaceflinger/LayerOrientationAnim.cpp @@ -84,7 +84,7 @@ Point LayerOrientationAnim::getPhysicalSize() const void LayerOrientationAnim::validateVisibility(const Transform&) { const Layer::State& s(drawingState()); - const Transform tr(getDrawingStateTransform()); + const Transform tr(s.transform); const Point size(getPhysicalSize()); uint32_t w = size.x; uint32_t h = size.y; diff --git a/libs/surfaceflinger/LayerScreenshot.cpp b/libs/surfaceflinger/LayerScreenshot.cpp deleted file mode 100644 index 40c47b0b7d87..000000000000 --- a/libs/surfaceflinger/LayerScreenshot.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "SurfaceFlinger" - -#include -#include -#include - -#include -#include - -#include - -#include - -#include "LayerBase.h" -#include "LayerScreenshot.h" -#include "SurfaceFlinger.h" -#include "DisplayHardware/DisplayHardware.h" - -namespace android { -// --------------------------------------------------------------------------- - -const uint32_t LayerScreenshot::typeInfo = LayerBase::typeInfo | 0x40; -const char* const LayerScreenshot::typeID = "LayerScreenshot"; - -// --------------------------------------------------------------------------- - -LayerScreenshot::LayerScreenshot(SurfaceFlinger* flinger, DisplayID display) - : LayerBase(flinger, display), mReply(0) -{ -} - -LayerScreenshot::~LayerScreenshot() -{ -} - -void LayerScreenshot::onDraw(const Region& clip) const -{ - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - copybit_image_t dst; - hw.getDisplaySurface(&dst); - if (dst.base != 0) { - uint8_t const* src = (uint8_t const*)(intptr_t(dst.base) + dst.offset); - const int fbWidth = dst.w; - const int fbHeight = dst.h; - const int fbFormat = dst.format; - - int x = mTransformedBounds.left; - int y = mTransformedBounds.top; - int w = mTransformedBounds.width(); - int h = mTransformedBounds.height(); - Parcel* const reply = mReply; - if (reply) { - const size_t Bpp = bytesPerPixel(fbFormat); - const size_t size = w * h * Bpp; - int32_t cfg = SkBitmap::kNo_Config; - switch (fbFormat) { - case PIXEL_FORMAT_RGBA_4444: cfg = SkBitmap::kARGB_4444_Config; - case PIXEL_FORMAT_RGBA_8888: cfg = SkBitmap::kARGB_8888_Config; - case PIXEL_FORMAT_RGB_565: cfg = SkBitmap::kRGB_565_Config; - case PIXEL_FORMAT_A_8: cfg = SkBitmap::kA8_Config; - } - reply->writeInt32(0); - reply->writeInt32(cfg); - reply->writeInt32(w); - reply->writeInt32(h); - reply->writeInt32(w * Bpp); - void* data = reply->writeInplace(size); - if (data) { - uint8_t* d = (uint8_t*)data; - uint8_t const* s = src + (x + y*fbWidth) * Bpp; - if (w == fbWidth) { - memcpy(d, s, w*h*Bpp); - } else { - for (int y=0 ; y -#include -#include -#include - -#include "LayerBase.h" - -namespace android { - -// --------------------------------------------------------------------------- - -class LayerScreenshot : public LayerBase -{ -public: - static const uint32_t typeInfo; - static const char* const typeID; - virtual char const* getTypeID() const { return typeID; } - virtual uint32_t getTypeInfo() const { return typeInfo; } - - LayerScreenshot(SurfaceFlinger* flinger, DisplayID display); - virtual ~LayerScreenshot(); - - virtual void onDraw(const Region& clip) const; - virtual bool needsBlending() const { return true; } - virtual bool isSecure() const { return false; } - - void takeScreenshot(Mutex& lock, Parcel* reply); - -private: - mutable Condition mCV; - Parcel* mReply; -}; - -// --------------------------------------------------------------------------- - -}; // namespace android - -#endif // ANDROID_LAYER_SCREENSHOT_H diff --git a/libs/surfaceflinger/RFBServer.cpp b/libs/surfaceflinger/RFBServer.cpp deleted file mode 100644 index c2c198941388..000000000000 --- a/libs/surfaceflinger/RFBServer.cpp +++ /dev/null @@ -1,722 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "RFBServer" - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -#include -#include - -#ifdef HAVE_ANDROID_OS -#include -#endif - -#include "RFBServer.h" -#include "SurfaceFlinger.h" - -/* BUG=773511: this is a temporary hack required while developing the new - set of "clean kernel headers" for the Bionic C library. */ -#ifndef KEY_STAR -#define KEY_STAR 227 -#endif -#ifndef KEY_SHARP -#define KEY_SHARP 228 -#endif -#ifndef KEY_SOFT1 -#define KEY_SOFT1 229 -#endif -#ifndef KEY_SOFT2 -#define KEY_SOFT2 230 -#endif -#ifndef KEY_CENTER -#define KEY_CENTER 232 -#endif - -// ---------------------------------------------------------------------------- - -#define DEBUG_MSG 0 - -// ---------------------------------------------------------------------------- - -namespace android { - -const int VNC_PORT = 5900; - -RFBServer::RFBServer(uint32_t w, uint32_t h, android::PixelFormat format) - : Thread(false), mFD(-1), mStatus(NO_INIT), mIoVec(0) -{ - mFrameBuffer.version = sizeof(mFrameBuffer); - mFrameBuffer.width = w; - mFrameBuffer.height = h; - mFrameBuffer.stride = w; - mFrameBuffer.format = format; - mFrameBuffer.data = 0; -} - -RFBServer::~RFBServer() -{ - if (mRobinThread != 0) { - // ask the thread to exit first - mRobinThread->exitAndWait(); - } - - free(mFrameBuffer.data); - - delete [] mIoVec; -} - -void RFBServer::onFirstRef() -{ - run("Batman"); -} - -status_t RFBServer::readyToRun() -{ - LOGI("RFB server ready to run"); - return NO_ERROR; -} - -bool RFBServer::threadLoop() -{ - struct sockaddr addr; - socklen_t alen; - int serverfd = -1; - int port = VNC_PORT; - - do { - retry: - if (serverfd < 0) { - serverfd = socket_loopback_server(port, SOCK_STREAM); - if (serverfd < 0) { - if ((errno == EADDRINUSE) && (port < (VNC_PORT+10))) { - LOGW("port %d already in use, trying %d", port, port+1); - port++; - goto retry; - } - LOGE("couldn't create socket, port=%d, error %d (%s)", - port, errno, strerror(errno)); - sleep(1); - break; - } - fcntl(serverfd, F_SETFD, FD_CLOEXEC); - } - - alen = sizeof(addr); - mFD = accept(serverfd, &addr, &alen); - - if (mFD < 0) { - LOGE("couldn't accept(), error %d (%s)", errno, strerror(errno)); - // we could have run out of file descriptors, wait a bit and - // try again. - sleep(1); - goto retry; - } - fcntl(mFD, F_SETFD, FD_CLOEXEC); - - // send protocol version and Authentication method - mStatus = NO_ERROR; - handshake(3, 3, Authentication::None); - - if (alive()) { - // create the thread we use to send data to the client - mRobinThread = new ServerThread(this); - } - - while( alive() ) { - // client message must be destroyed at each iteration - // (most of the time this is a no-op) - ClientMessage msg; - waitForClientMessage(msg); - if (alive()) { - handleClientMessage(msg); - } - } - - } while( alive() ); - - // free-up some resources - if (mRobinThread != 0) { - mRobinThread->exitAndWait(); - mRobinThread.clear(); - } - - free(mFrameBuffer.data); - mFrameBuffer.data = 0; - - close(mFD); - close(serverfd); - mFD = -1; - - // we'll try again - return true; -} - -// ---------------------------------------------------------------------------- - -RFBServer::ServerThread::ServerThread(const sp& receiver) - : Thread(false), mReceiver(receiver) -{ - LOGD("RFB Server Thread created"); -} - -RFBServer::ServerThread::~ServerThread() -{ - LOGD("RFB Server Thread destroyed"); -} - -void RFBServer::ServerThread::onFirstRef() -{ - mUpdateBarrier.close(); - run("Robin"); -} - -status_t RFBServer::ServerThread::readyToRun() -{ - return NO_ERROR; -} - -void RFBServer::ServerThread::wake() -{ - mUpdateBarrier.open(); -} - -void RFBServer::ServerThread::exitAndWait() -{ - requestExit(); - mUpdateBarrier.open(); - requestExitAndWait(); -} - -bool RFBServer::ServerThread::threadLoop() -{ - sp receiver(mReceiver.promote()); - if (receiver == 0) - return false; - - // wait for something to do - mUpdateBarrier.wait(); - - // we're asked to quit, abort everything - if (exitPending()) - return false; - - mUpdateBarrier.close(); - - // process updates - receiver->sendFrameBufferUpdates(); - return !exitPending(); -} - -// ---------------------------------------------------------------------------- - -void RFBServer::handshake(uint8_t major, uint8_t minor, uint32_t auth) -{ - ProtocolVersion protocolVersion(major, minor); - if( !write(protocolVersion) ) - return; - - if ( !read(protocolVersion) ) - return; - - int maj, min; - if ( protocolVersion.decode(maj, min) != NO_ERROR ) { - mStatus = -1; - return; - } - -#if DEBUG_MSG - LOGD("client protocol string: <%s>", (char*)protocolVersion.payload()); - LOGD("client wants protocol version %d.%d\n", maj, min); -#endif - - Authentication authentication(auth); - if( !write(authentication) ) - return; - - ClientInitialization clientInit; - if ( !read(clientInit) ) - return; - -#if DEBUG_MSG - LOGD("client initialization: sharedFlags = %d\n", clientInit.sharedFlags()); -#endif - - ServerInitialization serverInit("Android RFB"); - ServerInitialization::Payload& message(serverInit.message()); - message.framebufferWidth = htons(mFrameBuffer.width); - message.framebufferHeight = htons(mFrameBuffer.height); - message.serverPixelFormat.bitsPerPixel = 16; - message.serverPixelFormat.depth = 16; - message.serverPixelFormat.bigEndianFlag = 0; - message.serverPixelFormat.trueColorFlag = 1; - message.serverPixelFormat.redMax = htons((1<<5)-1); - message.serverPixelFormat.greenMax = htons((1<<6)-1); - message.serverPixelFormat.blueMax = htons((1<<5)-1); - message.serverPixelFormat.redShift = 11; - message.serverPixelFormat.greenShift = 5; - message.serverPixelFormat.blueShift = 0; - - mIoVec = new iovec[mFrameBuffer.height]; - - write(serverInit); -} - -void RFBServer::handleClientMessage(const ClientMessage& msg) -{ - switch(msg.type()) { - case SET_PIXEL_FORMAT: - handleSetPixelFormat(msg.messages().setPixelFormat); - break; - case SET_ENCODINGS: - handleSetEncodings(msg.messages().setEncodings); - break; - case FRAME_BUFFER_UPDATE_REQ: - handleFrameBufferUpdateReq(msg.messages().frameBufferUpdateRequest); - break; - case KEY_EVENT: - handleKeyEvent(msg.messages().keyEvent); - break; - } -} - -void RFBServer::handleSetPixelFormat(const SetPixelFormat& msg) -{ - if (!validatePixelFormat(msg.pixelFormat)) { - LOGE("The builtin VNC server only supports the RGB 565 pixel format"); - LOGD("requested pixel format:"); - LOGD("bitsPerPixel: %d", msg.pixelFormat.bitsPerPixel); - LOGD("depth: %d", msg.pixelFormat.depth); - LOGD("bigEndianFlag: %d", msg.pixelFormat.bigEndianFlag); - LOGD("trueColorFlag: %d", msg.pixelFormat.trueColorFlag); - LOGD("redmax: %d", ntohs(msg.pixelFormat.redMax)); - LOGD("bluemax: %d", ntohs(msg.pixelFormat.greenMax)); - LOGD("greenmax: %d", ntohs(msg.pixelFormat.blueMax)); - LOGD("redshift: %d", msg.pixelFormat.redShift); - LOGD("greenshift: %d", msg.pixelFormat.greenShift); - LOGD("blueshift: %d", msg.pixelFormat.blueShift); - mStatus = -1; - } -} - -bool RFBServer::validatePixelFormat(const PixelFormat& pf) -{ - if ((pf.bitsPerPixel != 16) || (pf.depth != 16)) - return false; - - if (pf.bigEndianFlag || !pf.trueColorFlag) - return false; - - if (ntohs(pf.redMax)!=0x1F || - ntohs(pf.greenMax)!=0x3F || - ntohs(pf.blueMax)!=0x1F) { - return false; - } - - if (pf.redShift!=11 || pf.greenShift!=5 || pf.blueShift!=0) - return false; - - return true; -} - -void RFBServer::handleSetEncodings(const SetEncodings& msg) -{ - /* From the RFB specification: - Sets the encoding types in which pixel data can be sent by the server. - The order of the encoding types given in this message is a hint by the - client as to its preference (the first encoding specified being most - preferred). The server may or may not choose to make use of this hint. - Pixel data may always be sent in raw encoding even if not specified - explicitly here. - */ - - LOGW("SetEncodings received. Only RAW is supported."); -} - -void RFBServer::handleFrameBufferUpdateReq(const FrameBufferUpdateRequest& msg) -{ -#if DEBUG_MSG - LOGD("handle FrameBufferUpdateRequest"); -#endif - - Rect r; - r.left = ntohs(msg.x); - r.top = ntohs(msg.y); - r.right = r.left + ntohs(msg.width); - r.bottom = r.top + ntohs(msg.height); - - Mutex::Autolock _l(mRegionLock); - mClientRegionRequest.set(r); - if (!msg.incremental) - mDirtyRegion.orSelf(r); - - mRobinThread->wake(); -} - -void RFBServer::handleKeyEvent(const KeyEvent& msg) -{ -#ifdef HAVE_ANDROID_OS - - int scancode = 0; - int code = ntohl(msg.key); - - if (code>='0' && code<='9') { - scancode = (code & 0xF) - 1; - if (scancode<0) scancode += 10; - scancode += KEY_1; - } else if (code>=0xFF50 && code<=0xFF58) { - static const uint16_t map[] = - { KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, - KEY_SOFT1, KEY_SOFT2, KEY_END, 0 }; - scancode = map[code & 0xF]; - } else if (code>=0xFFE1 && code<=0xFFEE) { - static const uint16_t map[] = - { KEY_LEFTSHIFT, KEY_LEFTSHIFT, - KEY_COMPOSE, KEY_COMPOSE, - KEY_LEFTSHIFT, KEY_LEFTSHIFT, - 0,0, - KEY_LEFTALT, KEY_RIGHTALT, - 0, 0, 0, 0 }; - scancode = map[code & 0xF]; - } else if ((code>='A' && code<='Z') || (code>='a' && code<='z')) { - static const uint16_t map[] = { - KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, - KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, - KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, - KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, - KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z }; - scancode = map[(code & 0x5F) - 'A']; - } else { - switch (code) { - case 0x0003: scancode = KEY_CENTER; break; - case 0x0020: scancode = KEY_SPACE; break; - case 0x0023: scancode = KEY_SHARP; break; - case 0x0033: scancode = KEY_SHARP; break; - case 0x002C: scancode = KEY_COMMA; break; - case 0x003C: scancode = KEY_COMMA; break; - case 0x002E: scancode = KEY_DOT; break; - case 0x003E: scancode = KEY_DOT; break; - case 0x002F: scancode = KEY_SLASH; break; - case 0x003F: scancode = KEY_SLASH; break; - case 0x0032: scancode = KEY_EMAIL; break; - case 0x0040: scancode = KEY_EMAIL; break; - case 0xFF08: scancode = KEY_BACKSPACE; break; - case 0xFF1B: scancode = KEY_BACK; break; - case 0xFF09: scancode = KEY_TAB; break; - case 0xFF0D: scancode = KEY_ENTER; break; - case 0x002A: scancode = KEY_STAR; break; - case 0xFFBE: scancode = KEY_SEND; break; // F1 - case 0xFFBF: scancode = KEY_END; break; // F2 - case 0xFFC0: scancode = KEY_HOME; break; // F3 - case 0xFFC5: scancode = KEY_POWER; break; // F8 - } - } - -#if DEBUG_MSG - LOGD("handle KeyEvent 0x%08x, %d, scancode=%d\n", code, msg.downFlag, scancode); -#endif - - if (scancode) { - mEventInjector.injectKey(uint16_t(scancode), - msg.downFlag ? EventInjector::DOWN : EventInjector::UP); - } -#endif -} - -void RFBServer::waitForClientMessage(ClientMessage& msg) -{ - if ( !read(msg.payload(), 1) ) - return; - - switch(msg.type()) { - - case SET_PIXEL_FORMAT: - read(msg.payload(1), sizeof(SetPixelFormat)-1); - break; - - case FIX_COLOUR_MAP_ENTRIES: - mStatus = UNKNOWN_ERROR; - return; - - case SET_ENCODINGS: - { - if ( !read(msg.payload(1), sizeof(SetEncodings)-1) ) - return; - - size_t size = ntohs( msg.messages().setEncodings.numberOfEncodings ) * 4; - if (msg.resize(sizeof(SetEncodings) + size) != NO_ERROR) { - mStatus = NO_MEMORY; - return; - } - - if ( !read(msg.payload(sizeof(SetEncodings)), size) ) - return; - - break; - } - - case FRAME_BUFFER_UPDATE_REQ: - read(msg.payload(1), sizeof(FrameBufferUpdateRequest)-1); - break; - - case KEY_EVENT: - read(msg.payload(1), sizeof(KeyEvent)-1); - break; - - case POINTER_EVENT: - read(msg.payload(1), sizeof(PointerEvent)-1); - break; - - case CLIENT_CUT_TEXT: - { - if ( !read(msg.payload(1), sizeof(ClientCutText)-1) ) - return; - - size_t size = ntohl( msg.messages().clientCutText.length ); - if (msg.resize(sizeof(ClientCutText) + size) != NO_ERROR) { - mStatus = NO_MEMORY; - return; - } - - if ( !read(msg.payload(sizeof(SetEncodings)), size) ) - return; - - break; - } - - default: - LOGE("Unknown Message %d", msg.type()); - mStatus = UNKNOWN_ERROR; - return; - } -} - -// ---------------------------------------------------------------------------- - -bool RFBServer::write(const Message& msg) -{ - write(msg.payload(), msg.size()); - return alive(); -} - -bool RFBServer::read(Message& msg) -{ - read(msg.payload(), msg.size()); - return alive(); -} - -bool RFBServer::write(const void* buffer, int size) -{ - int wr = ::write(mFD, buffer, size); - if (wr != size) { - //LOGE("write(%d) error %d (%s)", size, wr, strerror(errno)); - mStatus = (wr == -1) ? errno : -1; - } - return alive(); -} - -bool RFBServer::read(void* buffer, int size) -{ - int rd = ::read(mFD, buffer, size); - if (rd != size) { - //LOGE("read(%d) error %d (%s)", size, rd, strerror(errno)); - mStatus = (rd == -1) ? errno : -1; - } - return alive(); -} - -bool RFBServer::alive() const -{ - return mStatus == 0; -} - -bool RFBServer::isConnected() const -{ - return alive(); -} - -// ---------------------------------------------------------------------------- - -void RFBServer::frameBufferUpdated(const GGLSurface& front, const Region& reg) -{ - Mutex::Autolock _l(mRegionLock); - - // update dirty region - mDirtyRegion.orSelf(reg); - - // remember the front-buffer - mFrontBuffer = front; - - // The client has not requested anything, don't do anything more - if (mClientRegionRequest.isEmpty()) - return; - - // wake the sending thread up - mRobinThread->wake(); -} - -void RFBServer::sendFrameBufferUpdates() -{ - Vector rects; - size_t countRects; - GGLSurface fb; - - { // Scope for the lock - Mutex::Autolock _l(mRegionLock); - if (mFrontBuffer.data == 0) - return; - - const Region reg( mDirtyRegion.intersect(mClientRegionRequest) ); - if (reg.isEmpty()) - return; - - mDirtyRegion.subtractSelf(reg); - countRects = reg.rects(rects); - - // copy the frame-buffer so we can stay responsive - size_t bytesPerPix = bytesPerPixel(mFrameBuffer.format); - size_t bpr = mFrameBuffer.stride * bytesPerPix; - if (mFrameBuffer.data == 0) { - mFrameBuffer.data = (GGLubyte*)malloc(bpr * mFrameBuffer.height); - if (mFrameBuffer.data == 0) - return; - } - - memcpy(mFrameBuffer.data, mFrontBuffer.data, bpr*mFrameBuffer.height); - fb = mFrameBuffer; - } - - FrameBufferUpdate msgHeader; - msgHeader.type = 0; - msgHeader.numberOfRectangles = htons(countRects); - write(&msgHeader, sizeof(msgHeader)); - - Rectangle rectangle; - for (size_t i=0 ; i(fb.data) + offset; - iovec* iov = mIoVec; - while (h--) { - iov->iov_base = src; - iov->iov_len = bytes; - src += bpr; - iov++; - } - size_t iovcnt = iov - mIoVec; - int wr = ::writev(mFD, mIoVec, iovcnt); - if (wr < 0) { - //LOGE("write(%d) error %d (%s)", size, wr, strerror(errno)); - mStatus = errno; - } - } -} - -// ---------------------------------------------------------------------------- - -RFBServer::Message::Message(size_t size) - : mSize(size), mAllocatedSize(size) -{ - mPayload = malloc(size); -} - -RFBServer::Message::Message(void* payload, size_t size) - : mPayload(payload), mSize(size), mAllocatedSize(0) -{ -} - -RFBServer::Message::~Message() -{ - if (mAllocatedSize) - free(mPayload); -} - -status_t RFBServer::Message::resize(size_t size) -{ - if (size > mAllocatedSize) { - void* newp; - if (mAllocatedSize) { - newp = realloc(mPayload, size); - if (!newp) return NO_MEMORY; - } else { - newp = malloc(size); - if (!newp) return NO_MEMORY; - memcpy(newp, mPayload, mSize); - mAllocatedSize = size; - } - mPayload = newp; - } - mSize = size; - return NO_ERROR; -} - -// ---------------------------------------------------------------------------- - -RFBServer::EventInjector::EventInjector() - : mFD(-1) -{ -} - -RFBServer::EventInjector::~EventInjector() -{ -} - -void RFBServer::EventInjector::injectKey(uint16_t code, uint16_t value) -{ -#ifdef HAVE_ANDROID_OS - // XXX: we need to open the right event device - int version; - mFD = open("/dev/input/event0", O_RDWR); - ioctl(mFD, EVIOCGVERSION, &version); - - input_event ev; - memset(&ev, 0, sizeof(ev)); - ev.type = EV_KEY; - ev.code = code; - ev.value = value; - ::write(mFD, &ev, sizeof(ev)); - - close(mFD); - mFD = -1; -#endif -} - - -}; // namespace android - diff --git a/libs/surfaceflinger/RFBServer.h b/libs/surfaceflinger/RFBServer.h deleted file mode 100644 index 420912ed724d..000000000000 --- a/libs/surfaceflinger/RFBServer.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_RFB_SERVER_H -#define ANDROID_RFB_SERVER_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "Barrier.h" - -namespace android { - -class SurfaceFlinger; - -class RFBServer : public Thread -{ -public: - RFBServer(uint32_t w, uint32_t h, android::PixelFormat format); - virtual ~RFBServer(); - - void frameBufferUpdated(const GGLSurface& front, const Region& reg); - bool isConnected() const; - -private: - typedef uint8_t card8; - typedef uint16_t card16; - typedef uint32_t card32; - - struct Message { - Message(size_t size); - virtual ~Message(); - void* payload(int offset=0) { - return static_cast(mPayload)+offset; - } - void const * payload(int offset=0) const { - return static_cast(mPayload)+offset; - } - size_t size() const { return mSize; } - protected: - Message(void* payload, size_t size); - status_t resize(size_t size); - private: - void* mPayload; - size_t mSize; - size_t mAllocatedSize; - }; - - struct ProtocolVersion : public Message { - ProtocolVersion(uint8_t major, uint8_t minor) - : Message(&messageData, 12) { - char* p = static_cast(payload()); - snprintf(p, 13, "RFB %03u.%03u%c", major, minor, 0xA); - } - status_t decode(int& maj, int& min) { - char* p = static_cast(payload()); - int n = sscanf(p, "RFB %03u.%03u", &maj, &min); - return (n == 2) ? NO_ERROR : NOT_ENOUGH_DATA; - } - private: - char messageData[12+1]; - }; - - struct Authentication : public Message { - enum { Failed=0, None=1, Vnc=2 }; - Authentication(int auth) : Message(&messageData, 4) { - *static_cast(payload()) = htonl(auth); - } - private: - card32 messageData; - }; - - struct ClientInitialization : public Message { - ClientInitialization() : Message(&messageData, 1) { } - int sharedFlags() { - return messageData; - } - private: - card8 messageData; - }; - - struct PixelFormat { - card8 bitsPerPixel; - card8 depth; - card8 bigEndianFlag; - card8 trueColorFlag; - card16 redMax; - card16 greenMax; - card16 blueMax; - card8 redShift; - card8 greenShift; - card8 blueShift; - uint8_t padding[3]; - } __attribute__((packed)); - - struct ServerInitialization : public Message { - ServerInitialization(char const * name) - : Message(sizeof(Payload) + strlen(name)) - { - const size_t nameLength = size() - sizeof(Payload); - message().nameLength = htonl(nameLength); - memcpy((char*)message().nameString, name,nameLength); - } - struct Payload { - card16 framebufferWidth; - card16 framebufferHeight; - PixelFormat serverPixelFormat; - card32 nameLength; - card8 nameString[0]; - } __attribute__((packed)); - Payload& message() { - return *static_cast(payload()); - } - }; - - // client messages... - - struct SetPixelFormat { - card8 type; - uint8_t padding[3]; - PixelFormat pixelFormat; - } __attribute__((packed)); - - struct SetEncodings { - enum { Raw=0, CoR=1, RRE=2, CoRRE=4, Hextile=5 }; - card8 type; - uint8_t padding; - card16 numberOfEncodings; - card32 encodings[0]; - } __attribute__((packed)); - - struct FrameBufferUpdateRequest { - card8 type; - card8 incremental; - card16 x; - card16 y; - card16 width; - card16 height; - } __attribute__((packed)); - - struct KeyEvent { - card8 type; - card8 downFlag; - uint8_t padding[2]; - card32 key; - } __attribute__((packed)); - - struct PointerEvent { - card8 type; - card8 buttonMask; - card16 x; - card16 y; - } __attribute__((packed)); - - struct ClientCutText { - card8 type; - uint8_t padding[3]; - card32 length; - card8 text[0]; - } __attribute__((packed)); - - union ClientMessages { - card8 type; - SetPixelFormat setPixelFormat; - SetEncodings setEncodings; - FrameBufferUpdateRequest frameBufferUpdateRequest; - KeyEvent keyEvent; - PointerEvent pointerEvent; - ClientCutText clientCutText; - }; - - struct Rectangle { - card16 x; - card16 y; - card16 w; - card16 h; - card32 encoding; - } __attribute__((packed)); - - struct FrameBufferUpdate { - card8 type; - uint8_t padding; - card16 numberOfRectangles; - Rectangle rectangles[0]; - } __attribute__((packed)); - - enum { - SET_PIXEL_FORMAT = 0, - FIX_COLOUR_MAP_ENTRIES = 1, - SET_ENCODINGS = 2, - FRAME_BUFFER_UPDATE_REQ = 3, - KEY_EVENT = 4, - POINTER_EVENT = 5, - CLIENT_CUT_TEXT = 6, - }; - - struct ClientMessage : public Message { - ClientMessage() - : Message(&messageData, sizeof(messageData)) { - } - const ClientMessages& messages() const { - return *static_cast(payload()); - } - const int type() const { - return messages().type; - } - status_t resize(size_t size) { - return Message::resize(size); - } - - ClientMessages messageData; - }; - - - class ServerThread : public Thread - { - friend class RFBServer; - public: - ServerThread(const sp& receiver); - virtual ~ServerThread(); - void wake(); - void exitAndWait(); - private: - virtual bool threadLoop(); - virtual status_t readyToRun(); - virtual void onFirstRef(); - wp mReceiver; - bool (RFBServer::*mAction)(); - Barrier mUpdateBarrier; - }; - - class EventInjector { - public: - enum { UP=0, DOWN=1 }; - EventInjector(); - ~EventInjector(); - void injectKey(uint16_t code, uint16_t value); - private: - struct input_event { - struct timeval time; - uint16_t type; - uint16_t code; - uint32_t value; - }; - int mFD; - }; - - void handshake(uint8_t major, uint8_t minor, uint32_t auth); - void waitForClientMessage(ClientMessage& msg); - void handleClientMessage(const ClientMessage& msg); - void handleSetPixelFormat(const SetPixelFormat& msg); - void handleSetEncodings(const SetEncodings& msg); - void handleFrameBufferUpdateReq(const FrameBufferUpdateRequest& msg); - void handleKeyEvent(const KeyEvent& msg); - void sendFrameBufferUpdates(); - - bool validatePixelFormat(const PixelFormat& pf); - bool alive() const; - bool write(const Message& msg); - bool read(Message& msg); - - bool write(const void* buffer, int size); - bool read(void* buffer, int size); - - virtual bool threadLoop(); - virtual status_t readyToRun(); - virtual void onFirstRef(); - - sp mRobinThread; - - int mFD; - int mStatus; - iovec* mIoVec; - - EventInjector mEventInjector; - - Mutex mRegionLock; - // This is the region requested by the client since the last - // time we updated it - Region mClientRegionRequest; - // This is the region of the screen that needs to be sent to the - // client since the last time we updated it. - // Typically this is the dirty region, but not necessarily, for - // instance if the client asked for a non incremental update. - Region mDirtyRegion; - - GGLSurface mFrameBuffer; - GGLSurface mFrontBuffer; -}; - -}; // namespace android - -#endif // ANDROID_RFB_SERVER_H diff --git a/libs/surfaceflinger/SurfaceFlinger.cpp b/libs/surfaceflinger/SurfaceFlinger.cpp index 4e457c9c258f..900282a274d5 100644 --- a/libs/surfaceflinger/SurfaceFlinger.cpp +++ b/libs/surfaceflinger/SurfaceFlinger.cpp @@ -53,21 +53,14 @@ #include "LayerDim.h" #include "LayerBitmap.h" #include "LayerOrientationAnim.h" -#include "LayerScreenshot.h" #include "OrientationAnimation.h" #include "SurfaceFlinger.h" -#include "RFBServer.h" #include "VRamHeap.h" #include "DisplayHardware/DisplayHardware.h" #include "GPUHardware/GPUHardware.h" -// the VNC server even on local ports presents a significant -// thread as it can allow an application to control and "see" other -// applications, de-facto bypassing security permissions. -#define ENABLE_VNC_SERVER 0 - #define DISPLAY_COUNT 1 namespace android { @@ -460,9 +453,6 @@ status_t SurfaceFlinger::readyToRun() if (mDebugNoBootAnimation == false) mBootAnimation = new BootAnimation(this); - if (ENABLE_VNC_SERVER) - mRFBServer = new RFBServer(w, h, f); - return NO_ERROR; } @@ -572,18 +562,6 @@ void SurfaceFlinger::postFramebuffer() debugShowFPS(); } - if (UNLIKELY(ENABLE_VNC_SERVER && - mRFBServer!=0 && mRFBServer->isConnected())) { - if (!mSecureFrameBuffer) { - GGLSurface fb; - // backbufer, is going to become the front buffer really soon - hw.getDisplaySurface(&fb); - if (LIKELY(fb.data != 0)) { - mRFBServer->frameBufferUpdated(fb, mInvalidRegion); - } - } - } - hw.flip(mInvalidRegion); mInvalidRegion.clear(); @@ -1571,55 +1549,17 @@ status_t SurfaceFlinger::onTransact( status_t err = BnSurfaceComposer::onTransact(code, data, reply, flags); if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) { - if (code == 1012) { - // take screen-shot of the front buffer - if (UNLIKELY(checkCallingPermission( - String16("android.permission.READ_FRAME_BUFFER")) == false)) - { // not allowed - LOGE("Permission Denial: " - "can't take screenshots from pid=%d, uid=%d\n", - IPCThreadState::self()->getCallingPid(), - IPCThreadState::self()->getCallingUid()); - return PERMISSION_DENIED; - } - - if (UNLIKELY(mSecureFrameBuffer)) { - LOGE("A secure window is on screen: " - "can't take screenshots from pid=%d, uid=%d\n", - IPCThreadState::self()->getCallingPid(), - IPCThreadState::self()->getCallingUid()); - return PERMISSION_DENIED; - } - - LOGI("Taking a screenshot..."); - - LayerScreenshot* l = new LayerScreenshot(this, 0); - - Mutex::Autolock _l(mStateLock); - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - l->initStates(hw.getWidth(), hw.getHeight(), 0); - l->setLayer(INT_MAX); - - addLayer_l(l); - setTransactionFlags(eTransactionNeeded|eTraversalNeeded); - - l->takeScreenshot(mStateLock, reply); - - removeLayer_l(l); - setTransactionFlags(eTransactionNeeded); - return NO_ERROR; - } else { - // HARDWARE_TEST stuff... - if (UNLIKELY(checkCallingPermission( - String16("android.permission.HARDWARE_TEST")) == false)) - { // not allowed - LOGE("Permission Denial: pid=%d, uid=%d\n", - IPCThreadState::self()->getCallingPid(), - IPCThreadState::self()->getCallingUid()); - return PERMISSION_DENIED; - } - int n; - switch (code) { + // HARDWARE_TEST stuff... + if (UNLIKELY(checkCallingPermission( + String16("android.permission.HARDWARE_TEST")) == false)) + { // not allowed + LOGE("Permission Denial: pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + return PERMISSION_DENIED; + } + int n; + switch (code) { case 1000: // SHOW_CPU n = data.readInt32(); mDebugCpu = n ? 1 : 0; @@ -1652,8 +1592,8 @@ status_t SurfaceFlinger::onTransact( const DisplayHardware& hw(graphicPlane(0).displayHardware()); mDirtyRegion.set(hw.bounds()); // careful that's not thread-safe signalEvent(); - } - return NO_ERROR; + } + return NO_ERROR; case 1005: // ask GPU revoke mGPU->friendlyRevoke(); return NO_ERROR; @@ -1669,13 +1609,12 @@ status_t SurfaceFlinger::onTransact( reply->writeInt32(mDebugRegion); reply->writeInt32(mDebugBackground); return NO_ERROR; - case 1013: { // screenshot + case 1013: { Mutex::Autolock _l(mStateLock); const DisplayHardware& hw(graphicPlane(0).displayHardware()); reply->writeInt32(hw.getPageFlipCount()); } return NO_ERROR; - } } } return err; @@ -1829,10 +1768,33 @@ void GraphicPlane::setTransform(const Transform& tr) { mGlobalTransform = mOrientationTransform * mTransform; } -status_t GraphicPlane::setOrientation(int orientation) -{ +status_t GraphicPlane::orientationToTransfrom( + int orientation, int w, int h, Transform* tr) +{ float a, b, c, d, x, y; + switch (orientation) { + case ISurfaceComposer::eOrientationDefault: + a=1; b=0; c=0; d=1; x=0; y=0; + break; + case ISurfaceComposer::eOrientation90: + a=0; b=-1; c=1; d=0; x=w; y=0; + break; + case ISurfaceComposer::eOrientation180: + a=-1; b=0; c=0; d=-1; x=w; y=h; + break; + case ISurfaceComposer::eOrientation270: + a=0; b=1; c=-1; d=0; x=0; y=h; + break; + default: + return BAD_VALUE; + } + tr->set(a, b, c, d); + tr->set(x, y); + return NO_ERROR; +} +status_t GraphicPlane::setOrientation(int orientation) +{ const DisplayHardware& hw(displayHardware()); const float w = hw.getWidth(); const float h = hw.getHeight(); @@ -1846,30 +1808,21 @@ status_t GraphicPlane::setOrientation(int orientation) // If the rotation can be handled in hardware, this is where // the magic should happen. - - switch (orientation) { - case ISurfaceComposer::eOrientation90: - a=0; b=-1; c=1; d=0; x=w; y=0; - break; - case ISurfaceComposer::eOrientation180: - a=-1; b=0; c=0; d=-1; x=w; y=h; - break; - case ISurfaceComposer::eOrientation270: - a=0; b=1; c=-1; d=0; x=0; y=h; - break; - case 42: { + if (UNLIKELY(orientation == 42)) { + float a, b, c, d, x, y; const float r = (3.14159265f / 180.0f) * 42.0f; const float si = sinf(r); const float co = cosf(r); a=co; b=-si; c=si; d=co; x = si*(h*0.5f) + (1-co)*(w*0.5f); y =-si*(w*0.5f) + (1-co)*(h*0.5f); - } break; - default: - return BAD_VALUE; + mOrientationTransform.set(a, b, c, d); + mOrientationTransform.set(x, y); + } else { + GraphicPlane::orientationToTransfrom(orientation, w, h, + &mOrientationTransform); } - mOrientationTransform.set(a, b, c, d); - mOrientationTransform.set(x, y); + mGlobalTransform = mOrientationTransform * mTransform; return NO_ERROR; } diff --git a/libs/surfaceflinger/SurfaceFlinger.h b/libs/surfaceflinger/SurfaceFlinger.h index 8e5fd885d5a5..f7d77640ae3c 100644 --- a/libs/surfaceflinger/SurfaceFlinger.h +++ b/libs/surfaceflinger/SurfaceFlinger.h @@ -58,7 +58,6 @@ class Layer; class LayerBuffer; class LayerOrientationAnim; class OrientationAnimation; -class RFBServer; class SurfaceHeapManager; typedef int32_t ClientID; @@ -112,6 +111,8 @@ private: class GraphicPlane { public: + static status_t orientationToTransfrom(int orientation, int w, int h, + Transform* tr); GraphicPlane(); ~GraphicPlane(); @@ -344,7 +345,6 @@ private: sp mGPU; GLuint mWormholeTexName; sp mBootAnimation; - sp mRFBServer; nsecs_t mBootTime; // Can only accessed from the main thread, these members diff --git a/libs/ui/ISurface.cpp b/libs/ui/ISurface.cpp index abd3634b9a34..d5e9f812a40b 100644 --- a/libs/ui/ISurface.cpp +++ b/libs/ui/ISurface.cpp @@ -37,7 +37,7 @@ ISurface::BufferHeap::BufferHeap(uint32_t w, uint32_t h, int32_t hor_stride, int32_t ver_stride, PixelFormat format, const sp& heap) : w(w), h(h), hor_stride(hor_stride), ver_stride(ver_stride), - format(format), heap(heap) + format(format), transform(0), flags(0), heap(heap) { } diff --git a/location/java/com/android/internal/location/INetworkLocationManager.java b/location/java/com/android/internal/location/INetworkLocationManager.java index 83bbe1fdf526..6b632fde15f1 100644 --- a/location/java/com/android/internal/location/INetworkLocationManager.java +++ b/location/java/com/android/internal/location/INetworkLocationManager.java @@ -16,6 +16,8 @@ package com.android.internal.location; +import android.content.Context; + /** * Used to register network location and collection services * with the Location Manager Service. @@ -23,6 +25,13 @@ package com.android.internal.location; * {@hide} */ public interface INetworkLocationManager { + + /* callback to allow installation to occur in Location Manager's thread */ + public interface InstallCallback { + void installNetworkLocationProvider(Context context, INetworkLocationManager manager); + } + + void setInstallCallback(InstallCallback callback); void setNetworkLocationProvider(INetworkLocationProvider provider); void setLocationCollector(ILocationCollector collector); } \ No newline at end of file diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 297877418a58..4d2e7250271c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -135,7 +135,7 @@ public class AudioManager { public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL; /** The audio stream for system sounds */ public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM; - /** The audio stream for the phone ring and message alerts */ + /** The audio stream for the phone ring */ public static final int STREAM_RING = AudioSystem.STREAM_RING; /** The audio stream for music playback */ public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC; @@ -214,14 +214,13 @@ public class AudioManager { /** * Whether to include ringer modes as possible options when changing volume. * For example, if true and volume level is 0 and the volume is adjusted - * with {@link #ADJUST_LOWER}, then the ringer mode may switch the silent - * or vibrate mode. + * with {@link #ADJUST_LOWER}, then the ringer mode may switch the silent or + * vibrate mode. *

      - * By default this is on for stream types that are affected by the ringer - * mode (for example, the ring stream type). If this flag is included, this - * behavior will be present regardless of the stream type being affected by - * the ringer mode. - * + * By default this is on for the ring stream. If this flag is included, + * this behavior will be present regardless of the stream type being + * affected by the ringer mode. + * * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) */ diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 1314ba14648b..fd990fe756cb 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -339,7 +339,11 @@ public class AudioRecord * Releases the native AudioRecord resources. */ public void release() { - stop(); + try { + stop(); + } catch(IllegalStateException ise) { + // don't raise an exception, we're releasing the resources. + } native_release(); mState = STATE_UNINITIALIZED; } @@ -427,6 +431,56 @@ public class AudioRecord public int getPositionNotificationPeriod() { return native_get_pos_update_period(); } + + /** + * {@hide} + * Returns the minimum buffer size required for the successful creation of an AudioRecord + * object. + * @param sampleRateInHz the sample rate expressed in Hertz. + * @param channelConfig describes the configuration of the audio channels. + * See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO} and + * {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO} + * @param audioFormat the format in which the audio data is represented. + * See {@link AudioFormat#ENCODING_PCM_16BIT}. + * @return {@link #ERROR_BAD_VALUE} if the recording parameters are not supported by the + * hardware, or an invalid parameter was passed, + * or {@link #ERROR} if the implementation was unable to query the hardware for its + * output properties, + * or the minimum buffer size expressed in of bytes. + */ + static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { + int channelCount = 0; + switch(channelConfig) { + case AudioFormat.CHANNEL_CONFIGURATION_DEFAULT: + case AudioFormat.CHANNEL_CONFIGURATION_MONO: + channelCount = 1; + break; + case AudioFormat.CHANNEL_CONFIGURATION_STEREO: + channelCount = 2; + break; + case AudioFormat.CHANNEL_CONFIGURATION_INVALID: + default: + loge("getMinBufferSize(): Invalid channel configuration."); + return AudioRecord.ERROR_BAD_VALUE; + } + + // PCM_8BIT is not supported at the moment + if (audioFormat != AudioFormat.ENCODING_PCM_16BIT) { + loge("getMinBufferSize(): Invalid audio format."); + return AudioRecord.ERROR_BAD_VALUE; + } + + int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat); + if (size == 0) { + return AudioRecord.ERROR_BAD_VALUE; + } + else if (size == -1) { + return AudioRecord.ERROR; + } + else { + return size; + } + } //--------------------------------------------------------- @@ -699,6 +753,9 @@ public class AudioRecord private native final int native_set_pos_update_period(int updatePeriod); private native final int native_get_pos_update_period(); + + static private native final int native_get_min_buff_size( + int sampleRateInHz, int channelCount, int audioFormat); //--------------------------------------------------------- diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index f3d895703b98..bdabda7b29c2 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -349,9 +349,9 @@ public class AudioService extends IAudioService.Stub { // If either the client forces allowing ringer modes for this adjustment, // or the stream type is one that is affected by ringer modes if ((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0 - || isStreamAffectedByRingerMode(streamType)) { + || streamType == AudioManager.STREAM_RING) { // Check if the ringer mode changes with this volume adjustment. If - // it does, it will handle adjusting the volome, so we won't below + // it does, it will handle adjusting the volume, so we won't below adjustVolume = checkForRingerModeChange(oldIndex, direction); } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 9bb1df94080a..74ffc1a97a7d 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -395,7 +395,11 @@ public class AudioTrack public void release() { // even though native_release() stops the native AudioTrack, we need to stop // AudioTrack subclasses too. - stop(); + try { + stop(); + } catch(IllegalStateException ise) { + // don't raise an exception, we're releasing the resources. + } native_release(); mState = STATE_UNINITIALIZED; } @@ -1007,4 +1011,4 @@ public class AudioTrack Log.e(TAG, "[ android.media.AudioTrack ] " + msg); } -} \ No newline at end of file +} diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 4a301149da20..3609826b5500 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -19,6 +19,10 @@ package android.media; import android.view.Surface; import android.hardware.Camera; import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileDescriptor; +import android.util.Log; /** * Used to record audio and video. The recording control is based on a @@ -50,6 +54,7 @@ public class MediaRecorder static { System.loadLibrary("media_jni"); } + private final static String TAG = "MediaRecorder"; // The two fields below are accessed by native methods @SuppressWarnings("unused") @@ -57,7 +62,10 @@ public class MediaRecorder @SuppressWarnings("unused") private Surface mSurface; - + + private String mPath; + private FileDescriptor mFd; + /** * Default constructor. */ @@ -71,8 +79,6 @@ public class MediaRecorder * the camera object. Must call before prepare(). * * @param c the Camera to use for recording - * FIXME: Temporarily hidden until API approval - * @hide */ public native void setCamera(Camera c); @@ -104,7 +110,6 @@ public class MediaRecorder /** * Defines the video source. These constants are used with * {@link MediaRecorder#setVideoSource(int)}. - * @hide */ public final class VideoSource { /* Do not change these values without updating their counterparts @@ -152,7 +157,6 @@ public class MediaRecorder /** * Defines the video encoding. These constants are used with * {@link MediaRecorder#setVideoEncoder(int)}. - * @hide */ public final class VideoEncoder { /* Do not change these values without updating their counterparts @@ -187,7 +191,6 @@ public class MediaRecorder * @param video_source the video source to use * @throws IllegalStateException if it is called after setOutputFormat() * @see android.media.MediaRecorder.VideoSource - * @hide */ public native void setVideoSource(int video_source) throws IllegalStateException; @@ -214,7 +217,6 @@ public class MediaRecorder * @param height the height of the video to be captured * @throws IllegalStateException if it is called after * prepare() or before setOutputFormat() - * @hide */ public native void setVideoSize(int width, int height) throws IllegalStateException; @@ -227,7 +229,10 @@ public class MediaRecorder * @param rate the number of frames per second of video to capture * @throws IllegalStateException if it is called after * prepare() or before setOutputFormat(). - * @hide + * + * NOTE: On some devices that have auto-frame rate, this sets the + * maximum frame rate, not a constant frame rate. Actual frame rate + * will vary according to lighting conditions. */ public native void setVideoFrameRate(int rate) throws IllegalStateException; @@ -253,21 +258,43 @@ public class MediaRecorder * @throws IllegalStateException if it is called before * setOutputFormat() or after prepare() * @see android.media.MediaRecorder.VideoEncoder - * @hide */ public native void setVideoEncoder(int video_encoder) throws IllegalStateException; /** + * Pass in the file descriptor of the file to be written. Call this after + * setOutputFormat() but before prepare(). + * + * @param fd an open file descriptor to be written into. + * @throws IllegalStateException if it is called before + * setOutputFormat() or after prepare() + */ + public void setOutputFile(FileDescriptor fd) throws IllegalStateException + { + mPath = null; + mFd = fd; + } + + /** * Sets the path of the output file to be produced. Call this after * setOutputFormat() but before prepare(). * - * @param path The pathname to use() + * @param path The pathname to use. * @throws IllegalStateException if it is called before * setOutputFormat() or after prepare() */ - public native void setOutputFile(String path) throws IllegalStateException; + public void setOutputFile(String path) throws IllegalStateException + { + mFd = null; + mPath = path; + } + // native implementation + private native void _setOutputFile(FileDescriptor fd, long offset, long length) + throws IllegalStateException, IOException; + private native void _prepare() throws IllegalStateException, IOException; + /** * Prepares the recorder to begin capturing and encoding data. This method * must be called after setting up the desired audio and video sources, @@ -277,7 +304,18 @@ public class MediaRecorder * start() or before setOutputFormat(). * @throws IOException if prepare fails otherwise. */ - public native void prepare() throws IllegalStateException, IOException; + public void prepare() throws IllegalStateException, IOException + { + if (mPath != null) { + FileOutputStream f = new FileOutputStream(mPath); + _setOutputFile(f.getFD(), 0, 0); + } else if (mFd != null) { + _setOutputFile(mFd, 0, 0); + } else { + throw new IOException("No valid output file"); + } + _prepare(); + } /** * Begins capturing and encoding data to the file specified with diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 8eb638ec1920..095749b75e50 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -143,25 +143,17 @@ android_media_MediaRecorder_setAudioEncoder(JNIEnv *env, jobject thiz, jint ae) } static void -android_media_MediaRecorder_setOutputFile(JNIEnv *env, jobject thiz, jstring path) +android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) { LOGV("setOutputFile"); - MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); - - if (path == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Path is a NULL pointer"); - return; - } - const char *pathStr = env->GetStringUTFChars(path, NULL); - if (pathStr == NULL) { // Out of memory - jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + if (fileDescriptor == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } - status_t opStatus = mr->setOutputFile(pathStr); - - // Make sure that local ref is released before a potential exception - env->ReleaseStringUTFChars(path, pathStr); - process_media_recorder_call(env, opStatus, "java/lang/RuntimeException", "setOutputFile failed."); + int fd = getParcelFileDescriptorFD(env, fileDescriptor); + MediaRecorder *mr = (MediaRecorder *)env->GetIntField(thiz, fields.context); + status_t opStatus = mr->setOutputFile(fd, offset, length); + process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed."); } static void @@ -273,23 +265,23 @@ android_media_MediaRecorder_native_finalize(JNIEnv *env, jobject thiz) // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { - {"setCamera", "(Landroid/hardware/Camera;)V",(void *)android_media_MediaRecorder_setCamera}, - {"setVideoSource", "(I)V", (void *)android_media_MediaRecorder_setVideoSource}, - {"setAudioSource", "(I)V", (void *)android_media_MediaRecorder_setAudioSource}, - {"setOutputFormat", "(I)V", (void *)android_media_MediaRecorder_setOutputFormat}, - {"setVideoEncoder", "(I)V", (void *)android_media_MediaRecorder_setVideoEncoder}, - {"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder}, - {"setOutputFile", "(Ljava/lang/String;)V", (void *)android_media_MediaRecorder_setOutputFile}, - {"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize}, - {"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate}, - {"prepare", "()V", (void *)android_media_MediaRecorder_prepare}, - {"getMaxAmplitude", "()I", (void *)android_media_MediaRecorder_native_getMaxAmplitude}, - {"start", "()V", (void *)android_media_MediaRecorder_start}, - {"stop", "()V", (void *)android_media_MediaRecorder_stop}, - {"reset", "()V", (void *)android_media_MediaRecorder_reset}, - {"release", "()V", (void *)android_media_MediaRecorder_release}, - {"native_setup", "()V", (void *)android_media_MediaRecorder_native_setup}, - {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, + {"setCamera", "(Landroid/hardware/Camera;)V", (void *)android_media_MediaRecorder_setCamera}, + {"setVideoSource", "(I)V", (void *)android_media_MediaRecorder_setVideoSource}, + {"setAudioSource", "(I)V", (void *)android_media_MediaRecorder_setAudioSource}, + {"setOutputFormat", "(I)V", (void *)android_media_MediaRecorder_setOutputFormat}, + {"setVideoEncoder", "(I)V", (void *)android_media_MediaRecorder_setVideoEncoder}, + {"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder}, + {"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD}, + {"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize}, + {"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate}, + {"_prepare", "()V", (void *)android_media_MediaRecorder_prepare}, + {"getMaxAmplitude", "()I", (void *)android_media_MediaRecorder_native_getMaxAmplitude}, + {"start", "()V", (void *)android_media_MediaRecorder_start}, + {"stop", "()V", (void *)android_media_MediaRecorder_stop}, + {"reset", "()V", (void *)android_media_MediaRecorder_reset}, + {"release", "()V", (void *)android_media_MediaRecorder_release}, + {"native_setup", "()V", (void *)android_media_MediaRecorder_native_setup}, + {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, }; static const char* const kClassPathName = "android/media/MediaRecorder"; diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index 559f9d5ba3f0..02731825e087 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -491,10 +491,11 @@ void SoundChannel::play(const sp& sample, int nextChannelID, float leftV // initialize track int afFrameCount; int afSampleRate; - if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) { + int streamType = mSoundPool->streamType(); + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { afFrameCount = kDefaultFrameCount; } - if (AudioSystem::getOutputSamplingRate(&afSampleRate) != NO_ERROR) { + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { afSampleRate = kDefaultSampleRate; } int numChannels = sample->numChannels(); @@ -522,10 +523,10 @@ void SoundChannel::play(const sp& sample, int nextChannelID, float leftV void *userData = (void *)((unsigned long)this | toggle); #ifdef USE_SHARED_MEM_BUFFER - newTrack = new AudioTrack(mSoundPool->streamType(), sampleRate, sample->format(), + newTrack = new AudioTrack(streamType, sampleRate, sample->format(), numChannels, sample->getIMemory(), 0, callback, userData); #else - newTrack = new AudioTrack(mSoundPool->streamType(), sampleRate, sample->format(), + newTrack = new AudioTrack(streamType, sampleRate, sample->format(), numChannels, frameCount, 0, callback, userData, bufferFrames); #endif if (newTrack->initCheck() != NO_ERROR) { diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index a987b9228d08..3d39181922f1 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -128,22 +128,8 @@ status_t AudioRecord::set( return BAD_VALUE; } - size_t inputBuffSizeInBytes = -1; - if (AudioSystem::getInputBufferSize(sampleRate, format, channelCount, &inputBuffSizeInBytes) - != NO_ERROR) { - LOGE("AudioSystem could not query the input buffer size."); - return NO_INIT; - } - if (inputBuffSizeInBytes == 0) { - LOGE("Recording parameters are not supported: sampleRate %d, channelCount %d, format %d", - sampleRate, channelCount, format); - return BAD_VALUE; - } - int frameSizeInBytes = channelCount * (format == AudioSystem::PCM_16_BIT ? 2 : 1); - - // We use 2* size of input buffer for ping pong use of record buffer. - int minFrameCount = 2 * inputBuffSizeInBytes / frameSizeInBytes; - LOGV("AudioRecord::set() minFrameCount = %d", minFrameCount); + // TODO: Get input frame count from hardware. + int minFrameCount = 1024*2; if (frameCount == 0) { frameCount = minFrameCount; diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp index cf911058e411..63dfc3b368b3 100644 --- a/media/libmedia/AudioSystem.cpp +++ b/media/libmedia/AudioSystem.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include namespace android { @@ -31,9 +30,10 @@ sp AudioSystem::gAudioFlinger; sp AudioSystem::gAudioFlingerClient; audio_error_callback AudioSystem::gAudioErrorCallback = NULL; // Cached values -int AudioSystem::gOutSamplingRate = 0; -int AudioSystem::gOutFrameCount = 0; -uint32_t AudioSystem::gOutLatency = 0; +int AudioSystem::gOutSamplingRate[NUM_AUDIO_OUTPUT_TYPES]; +int AudioSystem::gOutFrameCount[NUM_AUDIO_OUTPUT_TYPES]; +uint32_t AudioSystem::gOutLatency[NUM_AUDIO_OUTPUT_TYPES]; +bool AudioSystem::gA2dpEnabled; // Cached values for recording queries uint32_t AudioSystem::gPrevInSamplingRate = 16000; int AudioSystem::gPrevInFormat = AudioSystem::PCM_16_BIT; @@ -66,9 +66,12 @@ const sp& AudioSystem::get_audio_flinger() gAudioFlinger = interface_cast(binder); gAudioFlinger->registerClient(gAudioFlingerClient); // Cache frequently accessed parameters - gOutFrameCount = (int)gAudioFlinger->frameCount(); - gOutSamplingRate = (int)gAudioFlinger->sampleRate(); - gOutLatency = gAudioFlinger->latency(); + for (int output = 0; output < NUM_AUDIO_OUTPUT_TYPES; output++) { + gOutFrameCount[output] = (int)gAudioFlinger->frameCount(output); + gOutSamplingRate[output] = (int)gAudioFlinger->sampleRate(output); + gOutLatency[output] = gAudioFlinger->latency(output); + } + gA2dpEnabled = gAudioFlinger->isA2dpEnabled(); } LOGE_IF(gAudioFlinger==0, "no AudioFlinger!?"); return gAudioFlinger; @@ -147,7 +150,7 @@ status_t AudioSystem::getMasterMute(bool* mute) status_t AudioSystem::setStreamVolume(int stream, float value) { - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) return BAD_VALUE; + if (uint32_t(stream) >= NUM_STREAM_TYPES) return BAD_VALUE; const sp& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; af->setStreamVolume(stream, value); @@ -156,7 +159,7 @@ status_t AudioSystem::setStreamVolume(int stream, float value) status_t AudioSystem::setStreamMute(int stream, bool mute) { - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) return BAD_VALUE; + if (uint32_t(stream) >= NUM_STREAM_TYPES) return BAD_VALUE; const sp& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; af->setStreamMute(stream, mute); @@ -165,7 +168,7 @@ status_t AudioSystem::setStreamMute(int stream, bool mute) status_t AudioSystem::getStreamVolume(int stream, float* volume) { - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) return BAD_VALUE; + if (uint32_t(stream) >= NUM_STREAM_TYPES) return BAD_VALUE; const sp& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; *volume = af->streamVolume(stream); @@ -174,7 +177,7 @@ status_t AudioSystem::getStreamVolume(int stream, float* volume) status_t AudioSystem::getStreamMute(int stream, bool* mute) { - if (uint32_t(stream) >= AudioTrack::NUM_STREAM_TYPES) return BAD_VALUE; + if (uint32_t(stream) >= NUM_STREAM_TYPES) return BAD_VALUE; const sp& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; *mute = af->streamMute(stream); @@ -252,37 +255,48 @@ int AudioSystem::logToLinear(float volume) return volume ? 100 - int(dBConvertInverse * log(volume) + 0.5) : 0; } -status_t AudioSystem::getOutputSamplingRate(int* samplingRate) +status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType) { - if (gOutSamplingRate == 0) { + int output = getOutput(streamType); + + if (gOutSamplingRate[output] == 0) { const sp& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; // gOutSamplingRate is updated by get_audio_flinger() } - *samplingRate = gOutSamplingRate; + LOGV("getOutputSamplingRate() streamType %d, output %d, sampling rate %d", streamType, output, gOutSamplingRate[output]); + *samplingRate = gOutSamplingRate[output]; return NO_ERROR; } -status_t AudioSystem::getOutputFrameCount(int* frameCount) +status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) { - if (gOutFrameCount == 0) { + int output = getOutput(streamType); + + if (gOutFrameCount[output] == 0) { const sp& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; // gOutFrameCount is updated by get_audio_flinger() } - *frameCount = gOutFrameCount; + LOGV("getOutputFrameCount() streamType %d, output %d, frame count %d", streamType, output, gOutFrameCount[output]); + + *frameCount = gOutFrameCount[output]; return NO_ERROR; } -status_t AudioSystem::getOutputLatency(uint32_t* latency) +status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType) { - if (gOutLatency == 0) { + int output = getOutput(streamType); + + if (gOutLatency[output] == 0) { const sp& af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; // gOutLatency is updated by get_audio_flinger() - } - *latency = gOutLatency; + } + LOGV("getOutputLatency() streamType %d, output %d, latency %d", streamType, output, gOutLatency[output]); + + *latency = gOutLatency[output]; return NO_ERROR; } @@ -315,24 +329,23 @@ status_t AudioSystem::getInputBufferSize(uint32_t sampleRate, int format, int ch void AudioSystem::AudioFlingerClient::binderDied(const wp& who) { Mutex::Autolock _l(AudioSystem::gLock); AudioSystem::gAudioFlinger.clear(); - AudioSystem::gOutSamplingRate = 0; - AudioSystem::gOutFrameCount = 0; - AudioSystem::gOutLatency = 0; + + for (int output = 0; output < NUM_AUDIO_OUTPUT_TYPES; output++) { + gOutFrameCount[output] = 0; + gOutSamplingRate[output] = 0; + gOutLatency[output] = 0; + } AudioSystem::gInBuffSize = 0; - + if (gAudioErrorCallback) { gAudioErrorCallback(DEAD_OBJECT); } LOGW("AudioFlinger server died!"); } -void AudioSystem::AudioFlingerClient::audioOutputChanged(uint32_t frameCount, uint32_t samplingRate, uint32_t latency) { - - AudioSystem::gOutFrameCount = frameCount; - AudioSystem::gOutSamplingRate = samplingRate; - AudioSystem::gOutLatency = latency; - - LOGV("AudioFlinger output changed!"); +void AudioSystem::AudioFlingerClient::a2dpEnabledChanged(bool enabled) { + gA2dpEnabled = enabled; + LOGV("AudioFlinger A2DP enabled status changed! %d", enabled); } void AudioSystem::setErrorCallback(audio_error_callback cb) { @@ -340,5 +353,31 @@ void AudioSystem::setErrorCallback(audio_error_callback cb) { gAudioErrorCallback = cb; } +int AudioSystem::getOutput(int streamType) +{ + if (streamType == DEFAULT) { + streamType = MUSIC; + } + if (gA2dpEnabled && routedToA2dpOutput(streamType)) { + return AUDIO_OUTPUT_A2DP; + } else { + return AUDIO_OUTPUT_HARDWARE; + } +} + +bool AudioSystem::routedToA2dpOutput(int streamType) { + switch(streamType) { + case MUSIC: + case VOICE_CALL: + case BLUETOOTH_SCO: + case SYSTEM: + return true; + default: + return false; + } +} + + + }; // namespace android diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index 63b2012fed31..1ffad463710a 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -128,22 +128,21 @@ status_t AudioTrack::set( return NO_INIT; } int afSampleRate; - if (AudioSystem::getOutputSamplingRate(&afSampleRate) != NO_ERROR) { + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { return NO_INIT; } int afFrameCount; - if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) { + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { return NO_INIT; } uint32_t afLatency; - if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) { + if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) { return NO_INIT; } - // handle default values first. - if (streamType == DEFAULT) { - streamType = MUSIC; + if (streamType == AudioSystem::DEFAULT) { + streamType = AudioSystem::MUSIC; } if (sampleRate == 0) { sampleRate = afSampleRate; @@ -260,7 +259,7 @@ status_t AudioTrack::set( mMarkerPosition = 0; mNewPosition = 0; mUpdatePeriod = 0; - + return NO_ERROR; } @@ -434,7 +433,7 @@ void AudioTrack::setSampleRate(int rate) { int afSamplingRate; - if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) { + if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) { return; } // Resampler implementation limits input sampling rate to 2 x output sampling rate. diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp index 4215820b0e0d..5cbb25ca1fc9 100644 --- a/media/libmedia/IAudioFlinger.cpp +++ b/media/libmedia/IAudioFlinger.cpp @@ -53,7 +53,8 @@ enum { SET_PARAMETER, REGISTER_CLIENT, GET_INPUTBUFFERSIZE, - WAKE_UP + WAKE_UP, + IS_A2DP_ENABLED }; class BpAudioFlinger : public BpInterface @@ -123,42 +124,47 @@ public: return interface_cast(reply.readStrongBinder()); } - virtual uint32_t sampleRate() const + virtual uint32_t sampleRate(int output) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + data.writeInt32(output); remote()->transact(SAMPLE_RATE, data, &reply); return reply.readInt32(); } - virtual int channelCount() const + virtual int channelCount(int output) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + data.writeInt32(output); remote()->transact(CHANNEL_COUNT, data, &reply); return reply.readInt32(); } - virtual int format() const + virtual int format(int output) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + data.writeInt32(output); remote()->transact(FORMAT, data, &reply); return reply.readInt32(); } - virtual size_t frameCount() const + virtual size_t frameCount(int output) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + data.writeInt32(output); remote()->transact(FRAME_COUNT, data, &reply); return reply.readInt32(); } - virtual uint32_t latency() const + virtual uint32_t latency(int output) const { Parcel data, reply; data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + data.writeInt32(output); remote()->transact(LATENCY, data, &reply); return reply.readInt32(); } @@ -333,6 +339,14 @@ public: remote()->transact(WAKE_UP, data, &reply); return; } + + virtual bool isA2dpEnabled() const + { + Parcel data, reply; + data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + remote()->transact(IS_A2DP_ENABLED, data, &reply); + return (bool)reply.readInt32(); + } }; IMPLEMENT_META_INTERFACE(AudioFlinger, "android.media.IAudioFlinger"); @@ -385,27 +399,32 @@ status_t BnAudioFlinger::onTransact( } break; case SAMPLE_RATE: { CHECK_INTERFACE(IAudioFlinger, data, reply); - reply->writeInt32( sampleRate() ); + int output = data.readInt32(); + reply->writeInt32( sampleRate(output) ); return NO_ERROR; } break; case CHANNEL_COUNT: { CHECK_INTERFACE(IAudioFlinger, data, reply); - reply->writeInt32( channelCount() ); + int output = data.readInt32(); + reply->writeInt32( channelCount(output) ); return NO_ERROR; } break; case FORMAT: { CHECK_INTERFACE(IAudioFlinger, data, reply); - reply->writeInt32( format() ); + int output = data.readInt32(); + reply->writeInt32( format(output) ); return NO_ERROR; } break; case FRAME_COUNT: { CHECK_INTERFACE(IAudioFlinger, data, reply); - reply->writeInt32( frameCount() ); + int output = data.readInt32(); + reply->writeInt32( frameCount(output) ); return NO_ERROR; } break; case LATENCY: { CHECK_INTERFACE(IAudioFlinger, data, reply); - reply->writeInt32( latency() ); + int output = data.readInt32(); + reply->writeInt32( latency(output) ); return NO_ERROR; } break; case SET_MASTER_VOLUME: { @@ -519,7 +538,11 @@ status_t BnAudioFlinger::onTransact( wakeUp(); return NO_ERROR; } break; - + case IS_A2DP_ENABLED: { + CHECK_INTERFACE(IAudioFlinger, data, reply); + reply->writeInt32( (int)isA2dpEnabled() ); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/IAudioFlingerClient.cpp b/media/libmedia/IAudioFlingerClient.cpp index d9562665d00a..5feb11fb10bb 100644 --- a/media/libmedia/IAudioFlingerClient.cpp +++ b/media/libmedia/IAudioFlingerClient.cpp @@ -38,13 +38,11 @@ public: { } - void audioOutputChanged(uint32_t frameCount, uint32_t samplingRate, uint32_t latency) + void a2dpEnabledChanged(bool enabled) { Parcel data, reply; data.writeInterfaceToken(IAudioFlingerClient::getInterfaceDescriptor()); - data.writeInt32(frameCount); - data.writeInt32(samplingRate); - data.writeInt32(latency); + data.writeInt32((int)enabled); remote()->transact(AUDIO_OUTPUT_CHANGED, data, &reply); } }; @@ -65,10 +63,8 @@ status_t BnAudioFlingerClient::onTransact( switch(code) { case AUDIO_OUTPUT_CHANGED: { CHECK_INTERFACE(IAudioFlingerClient, data, reply); - uint32_t frameCount = data.readInt32(); - uint32_t samplingRate = data.readInt32(); - uint32_t latency = data.readInt32(); - audioOutputChanged(frameCount, samplingRate, latency); + bool enabled = (bool)data.readInt32(); + a2dpEnabledChanged(enabled); return NO_ERROR; } break; default: diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp index 1f6d59990abf..507d03ea0da6 100644 --- a/media/libmedia/IMediaRecorder.cpp +++ b/media/libmedia/IMediaRecorder.cpp @@ -39,7 +39,8 @@ enum { SET_OUTPUT_FORMAT, SET_VIDEO_ENCODER, SET_AUDIO_ENCODER, - SET_OUTPUT_FILE, + SET_OUTPUT_FILE_PATH, + SET_OUTPUT_FILE_FD, SET_VIDEO_SIZE, SET_VIDEO_FRAMERATE, SET_PREVIEW_SURFACE, @@ -139,7 +140,18 @@ public: Parcel data, reply; data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor()); data.writeCString(path); - remote()->transact(SET_OUTPUT_FILE, data, &reply); + remote()->transact(SET_OUTPUT_FILE_PATH, data, &reply); + return reply.readInt32(); + } + + status_t setOutputFile(int fd, int64_t offset, int64_t length) { + LOGV("setOutputFile(%d, %lld, %lld)", fd, offset, length); + Parcel data, reply; + data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor()); + data.writeFileDescriptor(fd); + data.writeInt64(offset); + data.writeInt64(length); + remote()->transact(SET_OUTPUT_FILE_FD, data, &reply); return reply.readInt32(); } @@ -330,13 +342,22 @@ status_t BnMediaRecorder::onTransact( return NO_ERROR; } break; - case SET_OUTPUT_FILE: { - LOGV("SET_OUTPUT_FILE"); + case SET_OUTPUT_FILE_PATH: { + LOGV("SET_OUTPUT_FILE_PATH"); CHECK_INTERFACE(IMediaRecorder, data, reply); const char* path = data.readCString(); reply->writeInt32(setOutputFile(path)); return NO_ERROR; } break; + case SET_OUTPUT_FILE_FD: { + LOGV("SET_OUTPUT_FILE_FD"); + CHECK_INTERFACE(IMediaRecorder, data, reply); + int fd = dup(data.readFileDescriptor()); + int64_t offset = data.readInt64(); + int64_t length = data.readInt64(); + reply->writeInt32(setOutputFile(fd, offset, length)); + return NO_ERROR; + } break; case SET_VIDEO_SIZE: { LOGV("SET_VIDEO_SIZE"); CHECK_INTERFACE(IMediaRecorder, data, reply); diff --git a/media/libmedia/JetPlayer.cpp b/media/libmedia/JetPlayer.cpp index ead24d497f23..9bd75c29ddf9 100644 --- a/media/libmedia/JetPlayer.cpp +++ b/media/libmedia/JetPlayer.cpp @@ -96,7 +96,7 @@ int JetPlayer::init() // create the output AudioTrack mAudioTrack = new AudioTrack(); - mAudioTrack->set(AudioTrack::MUSIC, //TODO parametrize this + mAudioTrack->set(AudioSystem::MUSIC, //TODO parametrize this pLibConfig->sampleRate, 1, // format = PCM 16bits per sample, pLibConfig->numChannels, diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp index fa36460a77a0..7fafc562894e 100644 --- a/media/libmedia/ToneGenerator.cpp +++ b/media/libmedia/ToneGenerator.cpp @@ -93,7 +93,7 @@ ToneGenerator::ToneGenerator(int streamType, float volume) { mState = TONE_IDLE; - if (AudioSystem::getOutputSamplingRate(&mSamplingRate) != NO_ERROR) { + if (AudioSystem::getOutputSamplingRate(&mSamplingRate, streamType) != NO_ERROR) { LOGE("Unable to marshal AudioFlinger"); return; } @@ -182,7 +182,7 @@ bool ToneGenerator::startTone(int toneType) { mLock.lock(); if (mState == TONE_STARTING) { if (mWaitCbkCond.waitRelative(mLock, seconds(1)) != NO_ERROR) { - LOGE("--- timed out"); + LOGE("--- Immediate start timed out"); mState = TONE_IDLE; } } @@ -200,7 +200,7 @@ bool ToneGenerator::startTone(int toneType) { } LOGV("cond received"); } else { - LOGE("--- timed out"); + LOGE("--- Delayed start timed out"); mState = TONE_IDLE; } } @@ -235,7 +235,7 @@ void ToneGenerator::stopTone() { if (lStatus == NO_ERROR) { LOGV("track stop complete, time %d", (unsigned int)(systemTime()/1000000)); } else { - LOGE("--- timed out"); + LOGE("--- Stop timed out"); mState = TONE_IDLE; mpAudioTrack->stop(); } diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp index 31ff50730d7e..bd8579ca8d84 100644 --- a/media/libmedia/mediaplayer.cpp +++ b/media/libmedia/mediaplayer.cpp @@ -82,7 +82,7 @@ MediaPlayer::MediaPlayer() mListener = NULL; mCookie = NULL; mDuration = -1; - mStreamType = AudioTrack::MUSIC; + mStreamType = AudioSystem::MUSIC; mCurrentPosition = -1; mSeekPosition = -1; mCurrentState = MEDIA_PLAYER_IDLE; diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp index 6ee4c0d45ef2..4ab26ac0c384 100644 --- a/media/libmedia/mediarecorder.cpp +++ b/media/libmedia/mediarecorder.cpp @@ -248,7 +248,33 @@ status_t MediaRecorder::setOutputFile(const char* path) status_t ret = mMediaRecorder->setOutputFile(path); if (OK != ret) { - LOGV("setAudioEncoder failed: %d", ret); + LOGV("setOutputFile failed: %d", ret); + mCurrentState = MEDIA_RECORDER_ERROR; + return UNKNOWN_ERROR; + } + mIsOutputFileSet = true; + return ret; +} + +status_t MediaRecorder::setOutputFile(int fd, int64_t offset, int64_t length) +{ + LOGV("setOutputFile(%d, %lld, %lld)", fd, offset, length); + if(mMediaRecorder == NULL) { + LOGE("media recorder is not initialized yet"); + return INVALID_OPERATION; + } + if (mIsOutputFileSet) { + LOGE("output file has already been set"); + return INVALID_OPERATION; + } + if (!(mCurrentState & MEDIA_RECORDER_DATASOURCE_CONFIGURED)) { + LOGE("setOutputFile called in an invalid state(%d)", mCurrentState); + return INVALID_OPERATION; + } + + status_t ret = mMediaRecorder->setOutputFile(fd, offset, length); + if (OK != ret) { + LOGV("setOutputFile failed: %d", ret); mCurrentState = MEDIA_RECORDER_ERROR; return UNKNOWN_ERROR; } @@ -306,7 +332,7 @@ status_t MediaRecorder::prepare() return INVALID_OPERATION; } if (!(mCurrentState & MEDIA_RECORDER_DATASOURCE_CONFIGURED)) { - LOGE("setVideoFrameRate called in an invalid state: %d", mCurrentState); + LOGE("prepare called in an invalid state: %d", mCurrentState); return INVALID_OPERATION; } @@ -328,7 +354,7 @@ status_t MediaRecorder::getMaxAmplitude(int* max) return INVALID_OPERATION; } if (mCurrentState & MEDIA_RECORDER_ERROR) { - LOGE("setVideoFrameRate called in an invalid state: %d", mCurrentState); + LOGE("getMaxAmplitude called in an invalid state: %d", mCurrentState); return INVALID_OPERATION; } diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 9e366e2660aa..97e353653c64 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -818,7 +818,7 @@ Exit: MediaPlayerService::AudioOutput::AudioOutput() { mTrack = 0; - mStreamType = AudioTrack::MUSIC; + mStreamType = AudioSystem::MUSIC; mLeftVolume = 1.0; mRightVolume = 1.0; mLatency = 0; @@ -900,15 +900,15 @@ status_t MediaPlayerService::AudioOutput::open(uint32_t sampleRate, int channelC int afFrameCount; int frameCount; - if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) { + if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) { return NO_INIT; } - if (AudioSystem::getOutputSamplingRate(&afSampleRate) != NO_ERROR) { + if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) { return NO_INIT; } - frameCount = (sampleRate*afFrameCount)/afSampleRate; - AudioTrack *t = new AudioTrack(mStreamType, sampleRate, format, channelCount, frameCount*bufferCount); + frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate; + AudioTrack *t = new AudioTrack(mStreamType, sampleRate, format, channelCount, frameCount); if ((t == 0) || (t->initCheck() != NO_ERROR)) { LOGE("Unable to create audio track"); delete t; diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index f326a0e3af1c..e8ba17f86750 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -121,6 +121,17 @@ status_t MediaRecorderClient::setOutputFile(const char* path) return mRecorder->setOutputFile(path); } +status_t MediaRecorderClient::setOutputFile(int fd, int64_t offset, int64_t length) +{ + LOGV("setOutputFile(%d, %lld, %lld)", fd, offset, length); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + LOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setOutputFile(fd, offset, length); +} + status_t MediaRecorderClient::setVideoSize(int width, int height) { LOGV("setVideoSize(%dx%d)", width, height); diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h index 3158017ba7fa..2b80c10056ff 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.h +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -36,6 +36,7 @@ public: virtual status_t setVideoEncoder(int ve); virtual status_t setAudioEncoder(int ae); virtual status_t setOutputFile(const char* path); + virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); virtual status_t setVideoSize(int width, int height); virtual status_t setVideoFrameRate(int frames_per_second); virtual status_t prepare(); diff --git a/media/libmediaplayerservice/MidiFile.cpp b/media/libmediaplayerservice/MidiFile.cpp index 7ce2fab041e0..d03caa5e5d9f 100644 --- a/media/libmediaplayerservice/MidiFile.cpp +++ b/media/libmediaplayerservice/MidiFile.cpp @@ -58,7 +58,7 @@ static const S_EAS_LIB_CONFIG* pLibConfig = NULL; MidiFile::MidiFile() : mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL), mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR), - mStreamType(AudioTrack::MUSIC), mLoop(false), mExit(false), + mStreamType(AudioSystem::MUSIC), mLoop(false), mExit(false), mPaused(false), mRender(false), mTid(-1) { LOGV("constructor"); diff --git a/media/libmediaplayerservice/VorbisPlayer.cpp b/media/libmediaplayerservice/VorbisPlayer.cpp index 009d6286a0f3..0ad335f84d10 100644 --- a/media/libmediaplayerservice/VorbisPlayer.cpp +++ b/media/libmediaplayerservice/VorbisPlayer.cpp @@ -55,7 +55,7 @@ static status_t STATE_OPEN = 2; VorbisPlayer::VorbisPlayer() : mAudioBuffer(NULL), mPlayTime(-1), mDuration(-1), mState(STATE_ERROR), - mStreamType(AudioTrack::MUSIC), mLoop(false), mAndroidLoop(false), + mStreamType(AudioSystem::MUSIC), mLoop(false), mAndroidLoop(false), mExit(false), mPaused(false), mRender(false), mRenderTid(-1) { LOGV("constructor\n"); diff --git a/preloaded-classes b/preloaded-classes index 1ace628942e8..61390db126ba 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -340,7 +340,7 @@ android.view.TouchDelegate android.view.VelocityTracker android.view.View android.view.View$AttachInfo -android.view.View$AttachInfo$SoundEffectPlayer +android.view.View$AttachInfo$Callbacks android.view.View$BaseSavedState$1 android.view.View$MeasureSpec android.view.View$ScrollabilityCache diff --git a/services/java/com/android/server/GadgetService.java b/services/java/com/android/server/GadgetService.java index 5ef0fb9f38a9..ddf3afef1786 100644 --- a/services/java/com/android/server/GadgetService.java +++ b/services/java/com/android/server/GadgetService.java @@ -31,9 +31,10 @@ import android.content.pm.PackageItemInfo; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.gadget.GadgetManager; -import android.gadget.GadgetInfo; +import android.gadget.GadgetProviderInfo; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; @@ -78,7 +79,7 @@ class GadgetService extends IGadgetService.Stub static class Provider { int uid; - GadgetInfo info; + GadgetProviderInfo info; ArrayList instances = new ArrayList(); PendingIntent broadcast; @@ -106,7 +107,7 @@ class GadgetService extends IGadgetService.Stub PackageManager mPackageManager; AlarmManager mAlarmManager; ArrayList mInstalledProviders = new ArrayList(); - int mNextGadgetId = 1; + int mNextGadgetId = GadgetManager.INVALID_GADGET_ID + 1; ArrayList mGadgetIds = new ArrayList(); ArrayList mHosts = new ArrayList(); @@ -128,7 +129,6 @@ class GadgetService extends IGadgetService.Stub // update the provider list. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); @@ -148,7 +148,7 @@ class GadgetService extends IGadgetService.Stub int N = mInstalledProviders.size(); pw.println("Providers: (size=" + N + ")"); for (int i=0; i broadcastReceivers = pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); @@ -538,11 +535,14 @@ class GadgetService extends IGadgetService.Stub } } - void addProviderLocked(ResolveInfo ri) { - Provider p = parseGadgetInfoXml(new ComponentName(ri.activityInfo.packageName, + boolean addProviderLocked(ResolveInfo ri) { + Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name), ri); if (p != null) { mInstalledProviders.add(p); + return true; + } else { + return false; } } @@ -568,16 +568,18 @@ class GadgetService extends IGadgetService.Stub } void sendEnableIntentLocked(Provider p) { - Intent intent = new Intent(GadgetManager.GADGET_ENABLED_ACTION); + Intent intent = new Intent(GadgetManager.ACTION_GADGET_ENABLED); intent.setComponent(p.info.provider); mContext.sendBroadcast(intent); } void sendUpdateIntentLocked(Provider p, int[] gadgetIds) { - Intent intent = new Intent(GadgetManager.GADGET_UPDATE_ACTION); - intent.putExtra(GadgetManager.EXTRA_GADGET_IDS, gadgetIds); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + if (gadgetIds != null && gadgetIds.length > 0) { + Intent intent = new Intent(GadgetManager.ACTION_GADGET_UPDATE); + intent.putExtra(GadgetManager.EXTRA_GADGET_IDS, gadgetIds); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent); + } } void registerForBroadcastsLocked(Provider p, int[] gadgetIds) { @@ -587,7 +589,7 @@ class GadgetService extends IGadgetService.Stub // PendingIntent.getBroadcast will update the extras. boolean alreadyRegistered = p.broadcast != null; int instancesSize = p.instances.size(); - Intent intent = new Intent(GadgetManager.GADGET_UPDATE_ACTION); + Intent intent = new Intent(GadgetManager.ACTION_GADGET_UPDATE); intent.putExtra(GadgetManager.EXTRA_GADGET_IDS, gadgetIds); intent.setComponent(p.info.provider); long token = Binder.clearCallingIdentity(); @@ -614,16 +616,16 @@ class GadgetService extends IGadgetService.Stub return gadgetIds; } - private Provider parseGadgetInfoXml(ComponentName component, ResolveInfo ri) { + private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { Provider p = null; ActivityInfo activityInfo = ri.activityInfo; XmlResourceParser parser = null; try { parser = activityInfo.loadXmlMetaData(mPackageManager, - GadgetManager.GADGET_PROVIDER_META_DATA); + GadgetManager.META_DATA_GADGET_PROVIDER); if (parser == null) { - Log.w(TAG, "No " + GadgetManager.GADGET_PROVIDER_META_DATA + " meta-data for " + Log.w(TAG, "No " + GadgetManager.META_DATA_GADGET_PROVIDER + " meta-data for " + "gadget provider '" + component + '\''); return null; } @@ -644,7 +646,7 @@ class GadgetService extends IGadgetService.Stub } p = new Provider(); - GadgetInfo info = p.info = new GadgetInfo(); + GadgetProviderInfo info = p.info = new GadgetProviderInfo(); info.provider = component; p.uid = activityInfo.applicationInfo.uid; @@ -672,6 +674,7 @@ class GadgetService extends IGadgetService.Stub // of what a client process passes to us should not be fatal for the // system process. Log.w(TAG, "XML parsing failed for gadget provider '" + component + '\'', e); + return null; } finally { if (parser != null) parser.close(); } @@ -979,20 +982,26 @@ class GadgetService extends IGadgetService.Stub if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { synchronized (mGadgetIds) { - addProvidersForPackageLocked(pkgName); - saveStateLocked(); - } - } - else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - synchronized (mGadgetIds) { - updateProvidersForPackageLocked(pkgName); + Bundle extras = intent.getExtras(); + if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + // The package was just upgraded + updateProvidersForPackageLocked(pkgName); + } else { + // The package was just added + addProvidersForPackageLocked(pkgName); + } saveStateLocked(); } } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - synchronized (mGadgetIds) { - removeProvidersForPackageLocked(pkgName); - saveStateLocked(); + Bundle extras = intent.getExtras(); + if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + // The package is being updated. We'll receive a PACKAGE_ADDED shortly. + } else { + synchronized (mGadgetIds) { + removeProvidersForPackageLocked(pkgName); + saveStateLocked(); + } } } } @@ -1002,7 +1011,7 @@ class GadgetService extends IGadgetService.Stub // TODO: If there's a better way of matching an intent filter against the // packages for a given package, use that. void addProvidersForPackageLocked(String pkgName) { - Intent intent = new Intent(GadgetManager.GADGET_UPDATE_ACTION); + Intent intent = new Intent(GadgetManager.ACTION_GADGET_UPDATE); List broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); @@ -1021,7 +1030,7 @@ class GadgetService extends IGadgetService.Stub // packages for a given package, use that. void updateProvidersForPackageLocked(String pkgName) { HashSet keep = new HashSet(); - Intent intent = new Intent(GadgetManager.GADGET_UPDATE_ACTION); + Intent intent = new Intent(GadgetManager.ACTION_GADGET_UPDATE); List broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); @@ -1031,17 +1040,53 @@ class GadgetService extends IGadgetService.Stub ResolveInfo ri = broadcastReceivers.get(i); ActivityInfo ai = ri.activityInfo; if (pkgName.equals(ai.packageName)) { - Provider p = lookupProviderLocked(new ComponentName(ai.packageName, ai.name)); + ComponentName component = new ComponentName(ai.packageName, ai.name); + Provider p = lookupProviderLocked(component); if (p == null) { - addProviderLocked(ri); + if (addProviderLocked(ri)) { + keep.add(ai.name); + } + } else { + Provider parsed = parseProviderInfoXml(component, ri); + if (parsed != null) { + keep.add(ai.name); + // Use the new GadgetProviderInfo. + GadgetProviderInfo oldInfo = p.info; + p.info = parsed.info; + // If it's enabled + final int M = p.instances.size(); + if (M > 0) { + int[] gadgetIds = getGadgetIds(p); + // Reschedule for the new updatePeriodMillis (don't worry about handling + // it specially if updatePeriodMillis didn't change because we just sent + // an update, and the next one will be updatePeriodMillis from now). + cancelBroadcasts(p); + registerForBroadcastsLocked(p, gadgetIds); + // If it's currently showing, call back with the new GadgetProviderInfo. + for (int j=0; j=0; i--) { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName()) && !keep.contains(p.info.provider.getClassName())) { @@ -1052,7 +1097,7 @@ class GadgetService extends IGadgetService.Stub void removeProvidersForPackageLocked(String pkgName) { int N = mInstalledProviders.size(); - for (int i=0; i=0; i--) { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName())) { removeProviderLocked(i, p); @@ -1064,7 +1109,7 @@ class GadgetService extends IGadgetService.Stub // By now, we have removed any gadgets that were in any hosts here, // so we don't need to worry about sending DISABLE broadcasts to them. N = mHosts.size(); - for (int i=0; i=0; i--) { Host host = mHosts.get(i); if (pkgName.equals(host.packageName)) { deleteHostLocked(host); diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index ee49365324fa..758812920ee2 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -55,8 +55,10 @@ import android.os.Message; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; +import android.util.EventLog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -98,6 +100,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_UNBIND_METHOD = 3000; static final int MSG_BIND_METHOD = 3010; + static final long TIME_TO_RECONNECT = 10*1000; + + static final int LOG_IMF_FORCE_RECONNECT_IME = 32000; + final Context mContext; final Handler mHandler; final SettingsObserver mSettingsObserver; @@ -248,6 +254,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub IInputMethod mCurMethod; /** + * Time that we last initiated a bind to the input method, to determine + * if we should try to disconnect and reconnect to it. + */ + long mLastBindTime; + + /** * Have we called mCurMethod.bindInput()? */ boolean mBoundToMethod; @@ -486,7 +498,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } public void systemReady() { - } public List getInputMethodList() { @@ -571,7 +582,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private int getShowFlags() { + private int getImeShowFlags() { int flags = 0; if (mShowForced) { flags |= InputMethod.SHOW_FORCED @@ -582,6 +593,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return flags; } + private int getAppShowFlags() { + int flags = 0; + if (mShowForced) { + flags |= InputMethodManager.SHOW_FORCED; + } else if (!mShowExplicitlyRequested) { + flags |= InputMethodManager.SHOW_IMPLICIT; + } + return flags; + } + InputBindResult attachNewInputLocked(boolean initial, boolean needResult) { if (!mBoundToMethod) { executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( @@ -598,7 +619,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (mShowRequested) { if (DEBUG) Log.v(TAG, "Attach new input asks to show input"); - showCurrentInputLocked(getShowFlags()); + showCurrentInputLocked(getAppShowFlags()); } return needResult ? new InputBindResult(session.session, mCurId, mCurSeq) @@ -666,14 +687,31 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return attachNewInputLocked(initial, needResult); } if (mHaveConnection) { - if (mCurMethod != null && !cs.sessionRequested) { - cs.sessionRequested = true; - if (DEBUG) Log.v(TAG, "Creating new session for client " + cs); - executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( - MSG_CREATE_SESSION, mCurMethod, - new MethodCallback(mCurMethod))); + if (mCurMethod != null) { + if (!cs.sessionRequested) { + cs.sessionRequested = true; + if (DEBUG) Log.v(TAG, "Creating new session for client " + cs); + executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( + MSG_CREATE_SESSION, mCurMethod, + new MethodCallback(mCurMethod))); + } + // Return to client, and we will get back with it when + // we have had a session made for it. + return new InputBindResult(null, mCurId, mCurSeq); + } else if (SystemClock.uptimeMillis() + < (mLastBindTime+TIME_TO_RECONNECT)) { + // In this case we have connected to the service, but + // don't yet have its interface. If it hasn't been too + // long since we did the connection, we'll return to + // the client and wait to get the service interface so + // we can report back. If it has been too long, we want + // to fall through so we can try a disconnect/reconnect + // to see if we can get back in touch with the service. + return new InputBindResult(null, mCurId, mCurSeq); + } else { + EventLog.writeEvent(LOG_IMF_FORCE_RECONNECT_IME, mCurMethodId, + SystemClock.uptimeMillis()-mLastBindTime, 0); } - return new InputBindResult(null, mCurId, mCurSeq); } } @@ -682,6 +720,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub throw new IllegalArgumentException("Unknown id: " + mCurMethodId); } + if (mHaveConnection) { + mContext.unbindService(this); + mHaveConnection = false; + } + if (mCurToken != null) { try { if (DEBUG) Log.v(TAG, "Removing window token: " + mCurToken); @@ -691,16 +734,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurToken = null; } - if (mHaveConnection) { - mContext.unbindService(this); - mHaveConnection = false; - } - clearCurMethod(); mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); mCurIntent.setComponent(info.getComponent()); if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) { + mLastBindTime = SystemClock.uptimeMillis(); mHaveConnection = true; mCurId = info.getId(); mCurToken = new Binder(); @@ -758,7 +797,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub void onSessionCreated(IInputMethod method, IInputMethodSession session) { synchronized (mMethodMap) { - if (mCurMethod == method) { + if (mCurMethod != null && method != null + && mCurMethod.asBinder() == method.asBinder()) { if (mCurClient != null) { mCurClient.curSession = new SessionState(mCurClient, method, session); @@ -781,6 +821,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } mCurMethod = null; } + mStatusBar.setIconVisibility(mInputMethodIcon, false); } public void onServiceDisconnected(ComponentName name) { @@ -790,6 +831,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mCurMethod != null && mCurIntent != null && name.equals(mCurIntent.getComponent())) { clearCurMethod(); + // We consider this to be a new bind attempt, since the system + // should now try to restart the service for us. + mLastBindTime = SystemClock.uptimeMillis(); mShowRequested = mInputShown; mInputShown = false; if (mCurClient != null) { @@ -800,23 +844,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - public void updateStatusIcon(int iconId, String iconPackage) { - if (iconId == 0) { - Log.d(TAG, "hide the small icon for the input method"); - mStatusBar.setIconVisibility(mInputMethodIcon, false); - } else { - Log.d(TAG, "show a small icon for the input method"); - - if (iconPackage != null - && iconPackage - .equals(InputMethodManager.BUILDIN_INPUTMETHOD_PACKAGE)) { - iconPackage = null; + public void updateStatusIcon(IBinder token, String packageName, int iconId) { + long ident = Binder.clearCallingIdentity(); + try { + if (token == null || mCurToken != token) { + Log.w(TAG, "Ignoring setInputMethod of token: " + token); + return; } - - mInputMethodData.iconId = iconId; - mInputMethodData.iconPackage = iconPackage; - mStatusBar.updateIcon(mInputMethodIcon, mInputMethodData, null); - mStatusBar.setIconVisibility(mInputMethodIcon, true); + + synchronized (mMethodMap) { + if (iconId == 0) { + if (DEBUG) Log.d(TAG, "hide the small icon for the input method"); + mStatusBar.setIconVisibility(mInputMethodIcon, false); + } else if (packageName != null) { + if (DEBUG) Log.d(TAG, "show a small icon for the input method"); + mInputMethodData.iconId = iconId; + mInputMethodData.iconPackage = packageName; + mStatusBar.updateIcon(mInputMethodIcon, mInputMethodData, null); + mStatusBar.setIconVisibility(mInputMethodIcon, true); + } + } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -860,23 +909,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } public void showSoftInput(IInputMethodClient client, int flags) { - synchronized (mMethodMap) { - if (mCurClient == null || client == null - || mCurClient.client.asBinder() != client.asBinder()) { - try { - // We need to check if this is the current client with - // focus in the window manager, to allow this call to - // be made before input is started in it. - if (!mIWindowManager.inputMethodClientHasFocus(client)) { - Log.w(TAG, "Ignoring showSoftInput of: " + client); - return; + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mMethodMap) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + try { + // We need to check if this is the current client with + // focus in the window manager, to allow this call to + // be made before input is started in it. + if (!mIWindowManager.inputMethodClientHasFocus(client)) { + Log.w(TAG, "Ignoring showSoftInput of: " + client); + return; + } + } catch (RemoteException e) { } - } catch (RemoteException e) { } + + if (DEBUG) Log.v(TAG, "Client requesting input be shown"); + showCurrentInputLocked(flags); } - - if (DEBUG) Log.v(TAG, "Client requesting input be shown"); - showCurrentInputLocked(flags); + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -891,29 +945,44 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (mCurMethod != null) { executeOrSendMessage(mCurMethod, mCaller.obtainMessageIO( - MSG_SHOW_SOFT_INPUT, getShowFlags(), mCurMethod)); + MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod)); mInputShown = true; + } else if (mHaveConnection && SystemClock.uptimeMillis() + < (mLastBindTime+TIME_TO_RECONNECT)) { + // The client has asked to have the input method shown, but + // we have been sitting here too long with a connection to the + // service and no interface received, so let's disconnect/connect + // to try to prod things along. + EventLog.writeEvent(LOG_IMF_FORCE_RECONNECT_IME, mCurMethodId, + SystemClock.uptimeMillis()-mLastBindTime,1); + mContext.unbindService(this); + mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE); } } public void hideSoftInput(IInputMethodClient client, int flags) { - synchronized (mMethodMap) { - if (mCurClient == null || client == null - || mCurClient.client.asBinder() != client.asBinder()) { - try { - // We need to check if this is the current client with - // focus in the window manager, to allow this call to - // be made before input is started in it. - if (!mIWindowManager.inputMethodClientHasFocus(client)) { - Log.w(TAG, "Ignoring hideSoftInput of: " + client); - return; + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mMethodMap) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + try { + // We need to check if this is the current client with + // focus in the window manager, to allow this call to + // be made before input is started in it. + if (!mIWindowManager.inputMethodClientHasFocus(client)) { + Log.w(TAG, "Ignoring hideSoftInput of: " + client); + return; + } + } catch (RemoteException e) { } - } catch (RemoteException e) { } + + if (DEBUG) Log.v(TAG, "Client requesting input be hidden"); + hideCurrentInputLocked(flags); } - - if (DEBUG) Log.v(TAG, "Client requesting input be hidden"); - hideCurrentInputLocked(flags); + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -942,70 +1011,75 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void windowGainedFocus(IInputMethodClient client, boolean viewHasFocus, boolean isTextEditor, int softInputMode, boolean first, int windowFlags) { - synchronized (mMethodMap) { - if (DEBUG) Log.v(TAG, "windowGainedFocus: " + client.asBinder() - + " viewHasFocus=" + viewHasFocus - + " isTextEditor=" + isTextEditor - + " softInputMode=#" + Integer.toHexString(softInputMode) - + " first=" + first + " flags=#" - + Integer.toHexString(windowFlags)); - - if (mCurClient == null || client == null - || mCurClient.client.asBinder() != client.asBinder()) { - try { - // We need to check if this is the current client with - // focus in the window manager, to allow this call to - // be made before input is started in it. - if (!mIWindowManager.inputMethodClientHasFocus(client)) { - Log.w(TAG, "Ignoring focus gain of: " + client); - return; + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mMethodMap) { + if (DEBUG) Log.v(TAG, "windowGainedFocus: " + client.asBinder() + + " viewHasFocus=" + viewHasFocus + + " isTextEditor=" + isTextEditor + + " softInputMode=#" + Integer.toHexString(softInputMode) + + " first=" + first + " flags=#" + + Integer.toHexString(windowFlags)); + + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + try { + // We need to check if this is the current client with + // focus in the window manager, to allow this call to + // be made before input is started in it. + if (!mIWindowManager.inputMethodClientHasFocus(client)) { + Log.w(TAG, "Ignoring focus gain of: " + client); + return; + } + } catch (RemoteException e) { } - } catch (RemoteException e) { } - } - - switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { - case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: - if (!isTextEditor || (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) - != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { - if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { - // There is no focus view, and this window will - // be behind any soft input window, so hide the - // soft input window if it is shown. - if (DEBUG) Log.v(TAG, "Unspecified window will hide input"); - hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS); + + switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: + if (!isTextEditor || (softInputMode & + WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { + if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { + // There is no focus view, and this window will + // be behind any soft input window, so hide the + // soft input window if it is shown. + if (DEBUG) Log.v(TAG, "Unspecified window will hide input"); + hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS); + } + } else if (isTextEditor && (softInputMode & + WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + && (softInputMode & + WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { + // There is a focus view, and we are navigating forward + // into the window, so show the input window for the user. + if (DEBUG) Log.v(TAG, "Unspecified window will show input"); + showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT); } - } else if (isTextEditor && (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) - == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - && (softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { - // There is a focus view, and we are navigating forward - // into the window, so show the input window for the user. - if (DEBUG) Log.v(TAG, "Unspecified window will show input"); - showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT); - } - break; - case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: - // Do nothing. - break; - case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: - if (DEBUG) Log.v(TAG, "Window asks to hide input"); - hideCurrentInputLocked(0); - break; - case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: - if ((softInputMode & - WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { - if (DEBUG) Log.v(TAG, "Window asks to show input going forward"); + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: + // Do nothing. + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: + if (DEBUG) Log.v(TAG, "Window asks to hide input"); + hideCurrentInputLocked(0); + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: + if ((softInputMode & + WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { + if (DEBUG) Log.v(TAG, "Window asks to show input going forward"); + showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT); + } + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: + if (DEBUG) Log.v(TAG, "Window asks to always show input"); showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT); - } - break; - case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: - if (DEBUG) Log.v(TAG, "Window asks to always show input"); - showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT); - break; + break; + } } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -1022,7 +1096,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void setInputMethod(IBinder token, String id) { synchronized (mMethodMap) { - if (mCurToken == null) { + if (token == null) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { @@ -1032,19 +1106,31 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } else if (mCurToken != token) { Log.w(TAG, "Ignoring setInputMethod of token: " + token); + return; } - setInputMethodLocked(id); + long ident = Binder.clearCallingIdentity(); + try { + setInputMethodLocked(id); + } finally { + Binder.restoreCallingIdentity(ident); + } } } public void hideMySoftInput(IBinder token, int flags) { synchronized (mMethodMap) { - if (mCurToken == null || mCurToken != token) { + if (token == null || mCurToken != token) { Log.w(TAG, "Ignoring hideInputMethod of token: " + token); + return; } - hideCurrentInputLocked(flags); + long ident = Binder.clearCallingIdentity(); + try { + hideCurrentInputLocked(flags); + } finally { + Binder.restoreCallingIdentity(ident); + } } } @@ -1209,7 +1295,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- - public void showInputMethodMenu() { + void showInputMethodMenu() { if (DEBUG) Log.v(TAG, "Show switching menu"); hideInputMethodMenu(); @@ -1309,83 +1395,88 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + android.Manifest.permission.WRITE_SECURE_SETTINGS); } - // Make sure this is a valid input method. - InputMethodInfo imm = mMethodMap.get(id); - if (imm == null) { + long ident = Binder.clearCallingIdentity(); + try { + // Make sure this is a valid input method. + InputMethodInfo imm = mMethodMap.get(id); if (imm == null) { - throw new IllegalArgumentException("Unknown id: " + mCurMethodId); + if (imm == null) { + throw new IllegalArgumentException("Unknown id: " + mCurMethodId); + } } - } - - StringBuilder builder = new StringBuilder(256); - - boolean removed = false; - String firstId = null; - - // Look through the currently enabled input methods. - String enabledStr = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - if (enabledStr != null) { - final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(enabledStr); - while (splitter.hasNext()) { - String curId = splitter.next(); - if (curId.equals(id)) { - if (enabled) { - // We are enabling this input method, but it is - // already enabled. Nothing to do. The previous - // state was enabled. - return true; + + StringBuilder builder = new StringBuilder(256); + + boolean removed = false; + String firstId = null; + + // Look through the currently enabled input methods. + String enabledStr = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.ENABLED_INPUT_METHODS); + if (enabledStr != null) { + final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; + splitter.setString(enabledStr); + while (splitter.hasNext()) { + String curId = splitter.next(); + if (curId.equals(id)) { + if (enabled) { + // We are enabling this input method, but it is + // already enabled. Nothing to do. The previous + // state was enabled. + return true; + } + // We are disabling this input method, and it is + // currently enabled. Skip it to remove from the + // new list. + removed = true; + } else if (!enabled) { + // We are building a new list of input methods that + // doesn't contain the given one. + if (firstId == null) firstId = curId; + if (builder.length() > 0) builder.append(':'); + builder.append(curId); } - // We are disabling this input method, and it is - // currently enabled. Skip it to remove from the - // new list. - removed = true; - } else if (!enabled) { - // We are building a new list of input methods that - // doesn't contain the given one. - if (firstId == null) firstId = curId; - if (builder.length() > 0) builder.append(':'); - builder.append(curId); } } - } - - if (!enabled) { - if (!removed) { - // We are disabling the input method but it is already - // disabled. Nothing to do. The previous state was - // disabled. - return false; - } - // Update the setting with the new list of input methods. - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); - // We the disabled input method is currently selected, switch - // to another one. - String selId = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD); - if (id.equals(selId)) { + + if (!enabled) { + if (!removed) { + // We are disabling the input method but it is already + // disabled. Nothing to do. The previous state was + // disabled. + return false; + } + // Update the setting with the new list of input methods. Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - firstId != null ? firstId : ""); + Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); + // We the disabled input method is currently selected, switch + // to another one. + String selId = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD); + if (id.equals(selId)) { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD, + firstId != null ? firstId : ""); + } + // Previous state was enabled. + return true; } - // Previous state was enabled. - return true; - } - - // Add in the newly enabled input method. - if (enabledStr == null || enabledStr.length() == 0) { - enabledStr = id; - } else { - enabledStr = enabledStr + ':' + id; + + // Add in the newly enabled input method. + if (enabledStr == null || enabledStr.length() == 0) { + enabledStr = id; + } else { + enabledStr = enabledStr + ':' + id; + } + + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ENABLED_INPUT_METHODS, enabledStr); + + // Previous state was disabled. + return false; + } finally { + Binder.restoreCallingIdentity(ident); } - - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, enabledStr); - - // Previous state was disabled. - return false; } } diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index bc6fd71850d7..db4daa561c1c 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -132,7 +132,7 @@ public class LocationManagerService extends ILocationManager.Stub private static final int MESSAGE_HEARTBEAT = 1; private static final int MESSAGE_ACQUIRE_WAKE_LOCK = 2; private static final int MESSAGE_RELEASE_WAKE_LOCK = 3; - private static final int MESSAGE_SET_NETWORK_LOCATION_PROVIDER = 4; + private static final int MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER = 4; // Alarm manager and wakelock variables private final static String ALARM_INTENT = "com.android.location.ALARM_INTENT"; @@ -565,13 +565,21 @@ public class LocationManagerService extends ILocationManager.Stub } } - public void setNetworkLocationProvider(INetworkLocationProvider provider) { - mLocationHandler.removeMessages(MESSAGE_SET_NETWORK_LOCATION_PROVIDER); + public void setInstallCallback(InstallCallback callback) { + mLocationHandler.removeMessages(MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER); Message m = Message.obtain(mLocationHandler, - MESSAGE_SET_NETWORK_LOCATION_PROVIDER, provider); + MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER, callback); mLocationHandler.sendMessageAtFrontOfQueue(m); } + public void setNetworkLocationProvider(INetworkLocationProvider provider) { + mNetworkLocationInterface = provider; + provider.addListener(getPackageNames()); + mNetworkLocationProvider = (LocationProviderImpl)provider; + LocationProviderImpl.addProvider(mNetworkLocationProvider); + updateProviders(); + } + public void setLocationCollector(ILocationCollector collector) { mCollector = collector; if (mGpsLocationProvider != null) { @@ -1598,16 +1606,12 @@ public class LocationManagerService extends ILocationManager.Stub // Update wakelock status so the next alarm is set before releasing wakelock updateWakelockStatus(mScreenOn); releaseWakeLock(); - } else if (msg.what == MESSAGE_SET_NETWORK_LOCATION_PROVIDER) { + } else if (msg.what == MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER) { synchronized (LocationManagerService.class) { - Log.d(TAG, "adding network location provider"); - mNetworkLocationInterface = - (INetworkLocationProvider)msg.obj; - mNetworkLocationInterface.addListener(getPackageNames()); - mNetworkLocationProvider = - (LocationProviderImpl)mNetworkLocationInterface; - LocationProviderImpl.addProvider(mNetworkLocationProvider); - updateProviders(); + Log.d(TAG, "installing network location provider"); + INetworkLocationManager.InstallCallback callback = + (INetworkLocationManager.InstallCallback)msg.obj; + callback.installNetworkLocationProvider(mContext, LocationManagerService.this); } } } catch (Exception e) { diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 221ba46de6e4..c490e42e3e93 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -3219,12 +3219,15 @@ class PackageManagerService extends IPackageManager.Stub { Log.i(TAG, "Observer no longer exists."); } } - // There appears to be a subtle deadlock condition if the sendPackageBroadcast call appears - // in the synchronized block above. + // There appears to be a subtle deadlock condition if the sendPackageBroadcast + // call appears in the synchronized block above. if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { res.removedInfo.sendBroadcast(false, true); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); + if (res.removedInfo.removedPackage != null) { + extras.putBoolean(Intent.EXTRA_REPLACING, true); + } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName, extras); diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 7c111d3d8c8b..6974b5ef5792 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -1001,11 +1001,9 @@ class PowerManagerService extends IPowerManager.Stub implements LocalPowerManage } } else { - synchronized (mLocks) { - EventLog.writeEvent(LOG_POWER_SCREEN_BROADCAST_STOP, 4, - mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - } + // If we're in this case, then this handler is running for a previous + // paired transaction. mBroadcastWakeLock will already have been released + // in sendNotificationLocked. } } }; diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index c009fac18d69..eece581fca49 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -87,10 +87,13 @@ public class WifiService extends IWifiManager.Stub { private final LockList mLocks = new LockList(); /** - * See {@link Settings.System#WIFI_IDLE_MS}. This is the default value if a - * Settings.System value is not present. + * See {@link Settings.Gservices#WIFI_IDLE_MS}. This is the default value if a + * Settings.Gservices value is not present. This timeout value is chosen as + * the approximate point at which the battery drain caused by Wi-Fi + * being enabled but not active exceeds the battery drain caused by + * re-establishing a connection to the mobile data network. */ - private static final long DEFAULT_IDLE_MILLIS = 2 * 60 * 1000; /* 2 minutes */ + private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */ private static final String WAKELOCK_TAG = "WifiService"; @@ -101,7 +104,7 @@ public class WifiService extends IWifiManager.Stub { * provides a bit of extra margin. *

      * See {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}. - * This is the default value if a Settings.System value is not present. + * This is the default value if a Settings.Secure value is not present. */ private static final int DEFAULT_WAKELOCK_TIMEOUT = 8000; @@ -1364,7 +1367,7 @@ public class WifiService extends IWifiManager.Stub { * in the current regulatory domain. This method should be used only * if the correct number of channels cannot be determined automatically * for some reason. If the operation is successful, the new value is - * persisted as a System setting. + * persisted as a Secure setting. * @param numChannels the number of allowed channels. Must be greater than 0 * and less than or equal to 16. * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g., @@ -1446,8 +1449,8 @@ public class WifiService extends IWifiManager.Stub { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - long idleMillis = Settings.System.getLong(mContext.getContentResolver(), - Settings.System.WIFI_IDLE_MS, DEFAULT_IDLE_MILLIS); + long idleMillis = Settings.Gservices.getLong(mContext.getContentResolver(), + Settings.Gservices.WIFI_IDLE_MS, DEFAULT_IDLE_MILLIS); int stayAwakeConditions = Settings.System.getInt(mContext.getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0); diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 09f5d8f1ecaa..fed6d1281899 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -768,27 +768,55 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (willMove && w != null) { final WindowState curTarget = mInputMethodTarget; if (curTarget != null && curTarget.mAppToken != null) { - int curIndex = -1; - if (DEBUG_INPUT_METHOD) Log.v(TAG, "mNextAppTransition=" - + mNextAppTransition + " curTarget animating=" - + curTarget.isAnimating() - + " layer=" + curTarget.mAnimLayer - + " new layer=" + w.mAnimLayer); - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { - // If we are currently setting up for an animation, - // hold everything until we can find out what will happen. - mInputMethodTargetWaitingAnim = true; - curIndex = localmWindows.indexOf(curTarget); - } else if (curTarget.isAnimating() && - curTarget.mAnimLayer > w.mAnimLayer) { - // If the window we are currently targeting is involved - // with an animation, and it is on top of the next target - // we will be over, then hold off on moving until - // that is done. - curIndex = localmWindows.indexOf(curTarget); - } - if (curIndex >= 0) { - return curIndex + 1; + + // Now some fun for dealing with window animations that + // modify the Z order. We need to look at all windows below + // the current target that are in this app, finding the highest + // visible one in layering. + AppWindowToken token = curTarget.mAppToken; + WindowState highestTarget = null; + int highestPos = 0; + if (token.animating || token.animation != null) { + int pos = 0; + pos = localmWindows.indexOf(curTarget); + while (pos >= 0) { + WindowState win = (WindowState)localmWindows.get(pos); + if (win.mAppToken != token) { + break; + } + if (!win.mRemoved) { + if (highestTarget == null || win.mAnimLayer > + highestTarget.mAnimLayer) { + highestTarget = win; + highestPos = pos; + } + } + pos--; + } + } + + if (highestTarget != null) { + if (DEBUG_INPUT_METHOD) Log.v(TAG, "mNextAppTransition=" + + mNextAppTransition + " " + highestTarget + + " animating=" + highestTarget.isAnimating() + + " layer=" + highestTarget.mAnimLayer + + " new layer=" + w.mAnimLayer); + + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_NONE) { + // If we are currently setting up for an animation, + // hold everything until we can find out what will happen. + mInputMethodTargetWaitingAnim = true; + mInputMethodTarget = highestTarget; + return highestPos + 1; + } else if (highestTarget.isAnimating() && + highestTarget.mAnimLayer > w.mAnimLayer) { + // If the window we are currently targeting is involved + // with an animation, and it is on top of the next target + // we will be over, then hold off on moving until + // that is done. + mInputMethodTarget = highestTarget; + return highestPos + 1; + } } } } @@ -891,12 +919,27 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + void logWindowList(String prefix) { + int N = mWindows.size(); + while (N > 0) { + N--; + Log.v(TAG, prefix + "#" + N + ": " + mWindows.get(N)); + } + } + void moveInputMethodDialogsLocked(int pos) { ArrayList dialogs = mInputMethodDialogs; + final int N = dialogs.size(); + if (DEBUG_INPUT_METHOD) Log.v(TAG, "Removing " + N + " dialogs w/pos=" + pos); for (int i=0; i= 0) { final AppWindowToken targetAppToken = mInputMethodTarget.mAppToken; if (pos < mWindows.size()) { @@ -905,17 +948,26 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pos++; } } + if (DEBUG_INPUT_METHOD) Log.v(TAG, "Adding " + N + " dialogs at pos=" + pos); for (int i=0; i 0) moveInputMethodDialogsLocked(imPos+1); } else { moveInputMethodDialogsLocked(imPos); @@ -984,9 +1048,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // because they aren't currently associated with a focus window. if (imWin != null) { + if (DEBUG_INPUT_METHOD) Log.v(TAG, "Moving IM from " + imPos); tmpRemoveWindowLocked(0, imWin); imWin.mTargetAppToken = null; reAddWindowToListInOrderLocked(imWin); + if (DEBUG_INPUT_METHOD) { + Log.v(TAG, "List with no IM target:"); + logWindowList(" "); + } if (DN > 0) moveInputMethodDialogsLocked(-1);; } else { moveInputMethodDialogsLocked(-1);; @@ -1233,7 +1302,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (win.mSurface != null && !mDisplayFrozen) { // If we are not currently running the exit animation, we // need to see about starting one. - if (wasVisible=win.isVisibleLw()) { + if (wasVisible=win.isWinVisibleLw()) { int transit = WindowManagerPolicy.TRANSIT_EXIT; if (win.getAttrs().type == TYPE_APPLICATION_STARTING) { @@ -1277,6 +1346,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mKeyWaiter.releasePendingPointerLocked(win.mSession); mKeyWaiter.releasePendingTrackballLocked(win.mSession); + win.mRemoved = true; + + if (mInputMethodTarget == win) { + moveInputMethodWindowsIfNeededLocked(false); + } + mPolicy.removeWindowLw(win); win.removeLocked(); @@ -1501,7 +1576,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (win.getAttrs().type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } - if (win.isVisibleLw() && + if (win.isWinVisibleLw() && applyAnimationLocked(win, transit, false)) { win.mExiting = true; mKeyWaiter.finishedKey(session, client, true, @@ -1511,13 +1586,13 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // an exit. win.mExiting = true; } else { + if (mInputMethodWindow == win) { + mInputMethodWindow = null; + } win.destroySurfaceLocked(); } } } - if (mInputMethodWindow == win) { - mInputMethodWindow = null; - } outSurface.clear(); } @@ -1620,7 +1695,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo private boolean applyAnimationLocked(WindowState win, int transit, boolean isEntrance) { - if (win.mAnimating && win.mAnimationIsEntrance == isEntrance) { + if (win.mLocalAnimating && win.mAnimationIsEntrance == isEntrance) { // If we are trying to apply an animation, but already running // an animation of the same type, then just leave that one alone. return true; @@ -1666,6 +1741,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo Log.v(TAG, "Loaded animation " + a + " for " + win, e); } win.setAnimation(a); + win.mAnimationIsEntrance = isEntrance; } } else { win.clearAnimation(); @@ -2724,7 +2800,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo boolean added = false; for (int j=0; j= 0) { + if (!added && cwin.mSubLayer >= 0) { mWindows.add(index, win); index++; added = true; @@ -4645,8 +4721,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo KeyQ() { super(mContext); PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK - | PowerManager.ON_AFTER_RELEASE, "KEEP_SCREEN_ON_FLAG"); + mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + "KEEP_SCREEN_ON_FLAG"); mHoldingScreen.setReferenceCounted(false); } @@ -4769,8 +4845,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (holding) { mHoldingScreen.acquire(); } else { - long curTime = SystemClock.uptimeMillis(); - mPowerManager.userActivity(curTime, false, LocalPowerManager.OTHER_EVENT); + mPolicy.screenOnStopped(); mHoldingScreen.release(); } } @@ -5095,6 +5170,19 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } + public boolean performHapticFeedback(IWindow window, int effectId, + boolean always) { + synchronized(mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + return mPolicy.performHapticFeedback( + windowForClientLocked(this, window), effectId, always); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + void windowAddedLocked() { if (mSurfaceSession == null) { if (localLOGV) Log.v( @@ -5255,9 +5343,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // Currently running animation. boolean mAnimating; + boolean mLocalAnimating; Animation mAnimation; boolean mAnimationIsEntrance; boolean mHasTransformation; + boolean mHasLocalTransformation; final Transformation mTransformation = new Transformation(); // This is set after IWindowSession.relayout() has been called at @@ -5297,6 +5387,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // been updated for the new orientation. boolean mOrientationChanging; + // Is this window now (or just being) removed? + boolean mRemoved; + WindowState(Session s, IWindow c, WindowToken token, WindowState attachedWindow, WindowManager.LayoutParams a, int viewVisibility) { @@ -5516,6 +5609,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (localLOGV) Log.v( TAG, "Setting animation in " + this + ": " + anim); mAnimating = false; + mLocalAnimating = false; mAnimation = anim; mAnimation.restrictDuration(MAX_ANIMATION_DURATION); mAnimation.scaleCurrentDuration(mWindowAnimationScale); @@ -5524,6 +5618,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo public void clearAnimation() { if (mAnimation != null) { mAnimating = true; + mLocalAnimating = false; mAnimation = null; } } @@ -5752,13 +5847,15 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (!mDrawPending && !mCommitDrawPending && mAnimation != null) { mHasTransformation = true; - if (!mAnimating) { + mHasLocalTransformation = true; + if (!mLocalAnimating) { if (DEBUG_ANIM) Log.v( TAG, "Starting animation in " + this + " @ " + currentTime + ": ww=" + mFrame.width() + " wh=" + mFrame.height() + " dw=" + dw + " dh=" + dh + " scale=" + mWindowAnimationScale); mAnimation.initialize(mFrame.width(), mFrame.height(), dw, dh); mAnimation.setStartTime(currentTime); + mLocalAnimating = true; mAnimating = true; } mTransformation.clear(); @@ -5767,9 +5864,6 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_ANIM) Log.v( TAG, "Stepped animation in " + this + ": more=" + more + ", xform=" + mTransformation); - if (mAppToken != null && mAppToken.hasTransformation) { - mTransformation.compose(mAppToken.transformation); - } if (more) { // we're not done! return true; @@ -5780,10 +5874,19 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo mAnimation = null; //WindowManagerService.this.dump(); } - if (mAppToken != null && mAppToken.hasTransformation) { + mHasLocalTransformation = false; + if ((!mLocalAnimating || mAnimationIsEntrance) && mAppToken != null + && mAppToken.hasTransformation) { + // When our app token is animating, we kind-of pretend like + // we are as well. Note the mLocalAnimating mAnimationIsEntrance + // part of this check means that we will only do this if + // our window is not currently exiting, or it is not + // locally animating itself. The idea being that one that + // is exiting and doing a local animation should be removed + // once that animation is done. mAnimating = true; mHasTransformation = true; - mTransformation.set(mAppToken.transformation); + mTransformation.clear(); return false; } else if (mHasTransformation) { // Little trick to get through the path below to act like @@ -5796,10 +5899,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // If the display is frozen, and there is a pending animation, // clear it and make sure we run the cleanup code. mAnimating = true; + mLocalAnimating = true; mAnimation = null; } - if (!mAnimating) { + if (!mAnimating && !mLocalAnimating) { return false; } @@ -5809,6 +5913,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo + (mAppToken != null ? mAppToken.reportedVisible : false)); mAnimating = false; + mLocalAnimating = false; mAnimation = null; mAnimLayer = mLayer; if (mIsImWindow) { @@ -5817,6 +5922,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_LAYERS) Log.v(TAG, "Stepping win " + this + " anim layer: " + mAnimLayer); mHasTransformation = false; + mHasLocalTransformation = false; mPolicyVisibility = mPolicyVisibilityAfterAnim; mTransformation.clear(); if (mHasDrawn @@ -5891,10 +5997,15 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } void computeShownFrameLocked() { - final boolean selfTransformation = mHasTransformation; - final boolean attachedTransformation = (mAttachedWindow != null - && mAttachedWindow.mHasTransformation); - if (selfTransformation || attachedTransformation) { + final boolean selfTransformation = mHasLocalTransformation; + Transformation attachedTransformation = + (mAttachedWindow != null && mAttachedWindow.mHasLocalTransformation) + ? mAttachedWindow.mTransformation : null; + Transformation appTransformation = + (mAppToken != null && mAppToken.hasTransformation) + ? mAppToken.transformation : null; + if (selfTransformation || attachedTransformation != null + || appTransformation != null) { // cache often used attributes locally final Rect frame = mFrame; final float tmpFloats[] = mTmpFloats; @@ -5905,8 +6016,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (selfTransformation) { tmpMatrix.preConcat(mTransformation.getMatrix()); } - if (attachedTransformation) { - tmpMatrix.preConcat(mAttachedWindow.mTransformation.getMatrix()); + if (attachedTransformation != null) { + tmpMatrix.preConcat(attachedTransformation.getMatrix()); + } + if (appTransformation != null) { + tmpMatrix.preConcat(appTransformation.getMatrix()); } // "convert" it into SurfaceFlinger's format @@ -5940,8 +6054,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (selfTransformation) { mShownAlpha *= mTransformation.getAlpha(); } - if (attachedTransformation) { - mShownAlpha *= mAttachedWindow.mTransformation.getAlpha(); + if (attachedTransformation != null) { + mShownAlpha *= attachedTransformation.getAlpha(); + } + if (appTransformation != null) { + mShownAlpha *= appTransformation.getAlpha(); } } else { //Log.i(TAG, "Not applying alpha transform"); @@ -5965,7 +6082,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo /** * Is this window visible? It is not visible if there is no * surface, or we are in the process of running an exit animation - * that will remove the surface. + * that will remove the surface, or its app token has been hidden. */ public boolean isVisibleLw() { final AppWindowToken atoken = mAppToken; @@ -5975,6 +6092,18 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } /** + * Is this window visible, ignoring its app token? It is not visible + * if there is no surface, or we are in the process of running an exit animation + * that will remove the surface. + */ + public boolean isWinVisibleLw() { + final AppWindowToken atoken = mAppToken; + return mSurface != null && mPolicyVisibility && !mAttachedHidden + && (atoken == null || !atoken.hiddenRequested || atoken.animating) + && !mExiting && !mDestroying; + } + + /** * The same as isVisible(), but follows the current hidden state of * the associated app token, not the pending requested hidden state. */ @@ -6203,6 +6332,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.println(prefix + "mShownAlpha=" + mShownAlpha + " mAlpha=" + mAlpha + " mLastAlpha=" + mLastAlpha); pw.println(prefix + "mAnimating=" + mAnimating + + " mLocalAnimating=" + mLocalAnimating + " mAnimationIsEntrance=" + mAnimationIsEntrance + " mAnimation=" + mAnimation); pw.println(prefix + "XForm: has=" + mHasTransformation @@ -6213,7 +6343,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo + " mHasDrawn=" + mHasDrawn); pw.println(prefix + "mExiting=" + mExiting + " mRemoveOnExit=" + mRemoveOnExit - + " mDestroying=" + mDestroying); + + " mDestroying=" + mDestroying + + " mRemoved=" + mRemoved); pw.println(prefix + "mOrientationChanging=" + mOrientationChanging + " mAppFreezing=" + mAppFreezing); } @@ -7959,6 +8090,9 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo i--; WindowState win = mDestroySurface.get(i); win.mDestroying = false; + if (mInputMethodWindow == win) { + mInputMethodWindow = null; + } win.destroySurfaceLocked(); } while (i > 0); mDestroySurface.clear(); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index f5efe4b591fe..e391da337983 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -159,7 +159,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final int LOG_AM_BROADCAST_DISCARD_APP = 30025; static final int LOG_AM_CREATE_SERVICE = 30030; static final int LOG_AM_DESTROY_SERVICE = 30031; - + static final int LOG_AM_PROCESS_CRASHED_TOO_MUCH = 30032; + static final int LOG_AM_DROP_PROCESS = 30033; + static final int LOG_AM_SERVICE_CRASHED_TOO_MUCH = 30034; + static final int LOG_AM_SCHEDULE_SERVICE_RESTART = 30035; + static final int LOG_AM_PROVIDER_LOST_PROCESS = 30036; + static final int LOG_BOOT_PROGRESS_AMS_READY = 3040; static final int LOG_BOOT_PROGRESS_ENABLE_SCREEN = 3050; @@ -1787,7 +1792,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void startPausingLocked(boolean userLeaving) { + private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { if (mPausingActivity != null) { RuntimeException e = new RuntimeException(); Log.e(TAG, "Trying to pause when pause is already pending for " @@ -1840,9 +1845,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mPausingActivity != null) { // Have the window manager pause its key dispatching until the new - // activity has started... - prev.pauseKeyDispatchingLocked(); - + // activity has started. If we're pausing the activity just because + // the screen is being turned off and the UI is sleeping, don't interrupt + // key dispatch; the same activity will pick it up again on wakeup. + if (!uiSleeping) { + prev.pauseKeyDispatchingLocked(); + } else { + if (DEBUG_PAUSE) Log.v(TAG, "Key dispatch not paused for screen off"); + } + // Schedule a pause timeout in case the app doesn't respond. // We don't give it much time because this directly impacts the // responsiveness seen by the user. @@ -2188,7 +2199,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // can be resumed... if (mResumedActivity != null) { if (DEBUG_SWITCH) Log.v(TAG, "Skip resume: need to start pausing"); - startPausingLocked(userLeaving); + startPausingLocked(userLeaving, false); return true; } @@ -2605,8 +2616,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.app.thread.scheduleNewIntent(ar, r); sent = true; } catch (Exception e) { - Log.w(TAG, - "Exception thrown sending new intent to " + r, e); + Log.w(TAG, "Exception thrown sending new intent to " + r, e); } } if (!sent) { @@ -3456,7 +3466,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mPausingActivity == null) { if (DEBUG_PAUSE) Log.v(TAG, "Finish needs to pause: " + r); if (DEBUG_USER_LEAVING) Log.v(TAG, "finish() => pause with userLeaving=false"); - startPausingLocked(false); + startPausingLocked(false, false); } } else if (r.state != ActivityState.PAUSING) { @@ -3590,9 +3600,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.app.thread.scheduleSendResult(r, list); return; } catch (Exception e) { - Log.w(TAG, - "Exception thrown sending result to " + r, - e); + Log.w(TAG, "Exception thrown sending result to " + r, e); } } @@ -3778,8 +3786,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.configChangeFlags = 0; if (!mLRUActivities.remove(r)) { - Log.w(TAG, "Activity " + r - + " being finished, but not in LRU list"); + Log.w(TAG, "Activity " + r + " being finished, but not in LRU list"); } return removedFromHistory; @@ -4217,7 +4224,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } catch (RemoteException e) { } if (pkgUid == -1) { - Log.w(TAG, "Invalid packageName:"+packageName); + Log.w(TAG, "Invalid packageName:" + packageName); return false; } if (uid == pkgUid || checkComponentPermission( @@ -4270,7 +4277,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } catch (RemoteException e) { } if (pkgUid == -1) { - Log.w(TAG, "Invalid packageName:"+packageName); + Log.w(TAG, "Invalid packageName: " + packageName); return; } restartPackageLocked(packageName, pkgUid); @@ -4423,6 +4430,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (app == null) { Log.w(TAG, "No pending application record for pid " + pid + " (IApplicationThread " + thread + "); dropping process"); + EventLog.writeEvent(LOG_AM_DROP_PROCESS, pid); if (pid > 0 && pid != MY_PID) { Process.killProcess(pid); } else { @@ -6579,10 +6587,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (caller != null) { r = getRecordForAppLocked(caller); if (r == null) { - throw new SecurityException( - "Unable to find app for caller " + caller - + " (pid=" + Binder.getCallingPid() - + ") when getting content provider " + name); + throw new SecurityException( + "Unable to find app for caller " + caller + + " (pid=" + Binder.getCallingPid() + + ") when getting content provider " + name); } } @@ -6739,6 +6747,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + cpi.applicationInfo.packageName + "/" + cpi.applicationInfo.uid + " for provider " + name + ": launching app became null"); + EventLog.writeEvent(LOG_AM_PROVIDER_LOST_PROCESS, + cpi.applicationInfo.packageName, + cpi.applicationInfo.uid, name); + return null; } try { cpr.wait(); @@ -6844,9 +6856,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ContentProviderRecord dst = (ContentProviderRecord)r.pubProviders.get(src.info.name); if (dst != null) { - dst.provider = src.provider; - dst.app = r; - mProvidersByClass.put(dst.info.name, dst); String names[] = dst.info.authority.split(";"); for (int j = 0; j < names.length; j++) { @@ -6863,6 +6872,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } synchronized (dst) { + dst.provider = src.provider; + dst.app = r; dst.notifyAll(); } updateOomAdjLocked(r); @@ -6993,7 +7004,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mPausingActivity == null) { if (DEBUG_PAUSE) Log.v(TAG, "Sleep needs to pause..."); if (DEBUG_USER_LEAVING) Log.v(TAG, "Sleep => pause with userLeaving=false"); - startPausingLocked(false); + startPausingLocked(false, true); } } } @@ -7499,6 +7510,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // This process loses! Log.w(TAG, "Process " + app.info.processName + " has crashed too many times: killing!"); + EventLog.writeEvent(LOG_AM_PROCESS_CRASHED_TOO_MUCH, + app.info.processName, app.info.uid); killServicesLocked(app, false); for (int i=mHistory.size()-1; i>=0; i--) { HistoryRecord r = (HistoryRecord)mHistory.get(i); @@ -8429,6 +8442,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (sr.crashCount >= 2) { Log.w(TAG, "Service crashed " + sr.crashCount + " times, stopping: " + sr); + EventLog.writeEvent(LOG_AM_SERVICE_CRASHED_TOO_MUCH, + sr.crashCount, sr.shortName, app.pid); bringDownServiceLocked(sr, true); } else if (!allowRestart) { bringDownServiceLocked(sr, true); @@ -8447,7 +8462,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final void removeDyingProviderLocked(ProcessRecord proc, ContentProviderRecord cpr) { - cpr.launchingApp = null; + synchronized (cpr) { + cpr.launchingApp = null; + cpr.notifyAll(); + } mProvidersByClass.remove(cpr.info.name); String names[] = cpr.info.authority.split(";"); @@ -8529,6 +8547,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen break; } } + } else { + i = NL; } if (i >= NL) { @@ -8602,7 +8622,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mProcessesOnHold.remove(app); if (restart) { - // We have component that still need to be running in the + // We have components that still need to be running in the // process, so re-launch it. mProcessNames.put(app.processName, app.info.uid, app); startProcessLocked(app, "restart", app.processName); @@ -8952,6 +8972,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; Log.w(TAG, "Scheduling restart of crashed service " + r.shortName + " in " + r.restartDelay + "ms"); + EventLog.writeEvent(LOG_AM_SCHEDULE_SERVICE_RESTART, + r.shortName, r.restartDelay); Message msg = Message.obtain(); msg.what = SERVICE_ERROR_MSG; @@ -11660,7 +11682,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // We can finish this one if we have its icicle saved and // it is not persistent. - if ((r.haveState || !r.stateNotNeeded) + if ((r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped && !r.persistent && !r.finishing) { final int origSize = mLRUActivities.size(); destroyActivityLocked(r, true); diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index 001987fd0ff3..3922f39f52ba 100755 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -22,13 +22,24 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import com.android.internal.os.PkgUsageStats; +import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; import android.os.SystemClock; import android.util.Log; +import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -44,35 +55,257 @@ public final class UsageStatsService extends IUsageStats.Stub { private static final String TAG = "UsageStats"; static IUsageStats sService; private Context mContext; - private String mFileName; + // structure used to maintain statistics since the last checkin. final private Map mStats; + // Lock to update package stats. Methods suffixed by SLOCK should invoked with + // this lock held + final Object mStatsLock; + // Lock to write to file. Methods suffixed by FLOCK should invoked with + // this lock held. + final Object mFileLock; + // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks private String mResumedPkg; + private File mFile; + //private File mBackupFile; + private long mLastWriteRealTime; + private int _FILE_WRITE_INTERVAL = 30*60*1000; //ms + private static final String _PREFIX_DELIMIT="."; + private String mFilePrefix; + private Calendar mCal; + private static final int _MAX_NUM_FILES = 10; + private long mLastTime; private class PkgUsageStatsExtended { int mLaunchCount; long mUsageTime; - long mChgTime; + long mPausedTime; + long mResumedTime; + PkgUsageStatsExtended() { mLaunchCount = 0; mUsageTime = 0; - mChgTime = SystemClock.elapsedRealtime(); } void updateResume() { mLaunchCount ++; - mChgTime = SystemClock.elapsedRealtime(); + mResumedTime = SystemClock.elapsedRealtime(); } void updatePause() { - long currTime = SystemClock.elapsedRealtime(); - mUsageTime += (currTime - mChgTime); - mChgTime = currTime; + mPausedTime = SystemClock.elapsedRealtime(); + mUsageTime += (mPausedTime - mResumedTime); + } + void clear() { + mLaunchCount = 0; + mUsageTime = 0; } } - UsageStatsService(String filename) { - mFileName = filename; + UsageStatsService(String fileName) { mStats = new HashMap(); + mStatsLock = new Object(); + mFileLock = new Object(); + mFilePrefix = fileName; + mCal = Calendar.getInstance(); + // Update current stats which are binned by date + String uFileName = getCurrentDateStr(mFilePrefix); + mFile = new File(uFileName); + readStatsFromFile(); + mLastWriteRealTime = SystemClock.elapsedRealtime(); + mLastTime = new Date().getTime(); + } + + /* + * Utility method to convert date into string. + */ + private String getCurrentDateStr(String prefix) { + mCal.setTime(new Date()); + StringBuilder sb = new StringBuilder(); + if (prefix != null) { + sb.append(prefix); + sb.append("."); + } + int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1; + if (mm < 10) { + sb.append("0"); + } + sb.append(mm); + int dd = mCal.get(Calendar.DAY_OF_MONTH); + if (dd < 10) { + sb.append("0"); + } + sb.append(dd); + sb.append(mCal.get(Calendar.YEAR)); + return sb.toString(); + } + + private Parcel getParcelForFile(File file) throws IOException { + FileInputStream stream = new FileInputStream(file); + byte[] raw = readFully(stream); + Parcel in = Parcel.obtain(); + in.unmarshall(raw, 0, raw.length); + in.setDataPosition(0); + stream.close(); + return in; + } + + private void readStatsFromFile() { + File newFile = mFile; + synchronized (mFileLock) { + try { + if (newFile.exists()) { + readStatsFLOCK(newFile); + } else { + // Check for file limit before creating a new file + checkFileLimitFLOCK(); + newFile.createNewFile(); + } + } catch (IOException e) { + Log.w(TAG,"Error : " + e + " reading data from file:" + newFile); + } + } + } + + private void readStatsFLOCK(File file) throws IOException { + Parcel in = getParcelForFile(file); + while (in.dataAvail() > 0) { + String pkgName = in.readString(); + PkgUsageStatsExtended pus = new PkgUsageStatsExtended(); + pus.mLaunchCount = in.readInt(); + pus.mUsageTime = in.readLong(); + synchronized (mStatsLock) { + mStats.put(pkgName, pus); + } + } + } + + private ArrayList getUsageStatsFileListFLOCK() { + File dir = getUsageFilesDir(); + if (dir == null) { + Log.w(TAG, "Couldnt find writable directory for usage stats file"); + return null; + } + // Check if there are too many files in the system and delete older files + String fList[] = dir.list(); + if (fList == null) { + return null; + } + File pre = new File(mFilePrefix); + String filePrefix = pre.getName(); + // file name followed by dot + int prefixLen = filePrefix.length()+1; + ArrayList fileList = new ArrayList(); + for (String file : fList) { + int index = file.indexOf(filePrefix); + if (index == -1) { + continue; + } + if (file.endsWith(".bak")) { + continue; + } + fileList.add(file); + } + return fileList; + } + + private File getUsageFilesDir() { + if (mFilePrefix == null) { + return null; + } + File pre = new File(mFilePrefix); + return new File(pre.getParent()); } + private void checkFileLimitFLOCK() { + File dir = getUsageFilesDir(); + if (dir == null) { + Log.w(TAG, "Couldnt find writable directory for usage stats file"); + return; + } + // Get all usage stats output files + ArrayList fileList = getUsageStatsFileListFLOCK(); + if (fileList == null) { + // Strange but we dont have to delete any thing + return; + } + int count = fileList.size(); + if (count <= _MAX_NUM_FILES) { + return; + } + // Sort files + Collections.sort(fileList); + count -= _MAX_NUM_FILES; + // Delete older files + for (int i = 0; i < count; i++) { + String fileName = fileList.get(i); + File file = new File(dir, fileName); + Log.i(TAG, "Deleting file : "+fileName); + file.delete(); + } + } + + private void writeStatsToFile() { + synchronized (mFileLock) { + long currTime = new Date().getTime(); + boolean dayChanged = ((currTime - mLastTime) >= (24*60*60*1000)); + long currRealTime = SystemClock.elapsedRealtime(); + if (((currRealTime-mLastWriteRealTime) < _FILE_WRITE_INTERVAL) && + (!dayChanged)) { + // wait till the next update + return; + } + // Get the most recent file + String todayStr = getCurrentDateStr(mFilePrefix); + // Copy current file to back up + File backupFile = new File(mFile.getPath() + ".bak"); + mFile.renameTo(backupFile); + try { + checkFileLimitFLOCK(); + mFile.createNewFile(); + // Write mStats to file + writeStatsFLOCK(); + mLastWriteRealTime = currRealTime; + mLastTime = currTime; + if (dayChanged) { + // clear stats + synchronized (mStats) { + mStats.clear(); + } + mFile = new File(todayStr); + } + // Delete the backup file + if (backupFile != null) { + backupFile.delete(); + } + } catch (IOException e) { + Log.w(TAG, "Failed writing stats to file:" + mFile); + if (backupFile != null) { + backupFile.renameTo(mFile); + } + } + } + } + + private void writeStatsFLOCK() throws IOException { + FileOutputStream stream = new FileOutputStream(mFile); + Parcel out = Parcel.obtain(); + writeStatsToParcelFLOCK(out); + stream.write(out.marshall()); + out.recycle(); + stream.flush(); + stream.close(); + } + + private void writeStatsToParcelFLOCK(Parcel out) { + synchronized (mStatsLock) { + Set keys = mStats.keySet(); + for (String key : keys) { + PkgUsageStatsExtended pus = mStats.get(key); + out.writeString(key); + out.writeInt(pus.mLaunchCount); + out.writeLong(pus.mUsageTime); + } + } + } + public void publish(Context context) { mContext = context; ServiceManager.addService(SERVICE_NAME, asBinder()); @@ -99,12 +332,14 @@ public final class UsageStatsService extends IUsageStats.Stub { return; } if (localLOGV) Log.i(TAG, "started component:"+pkgName); - PkgUsageStatsExtended pus = mStats.get(pkgName); - if (pus == null) { - pus = new PkgUsageStatsExtended(); - mStats.put(pkgName, pus); + synchronized (mStatsLock) { + PkgUsageStatsExtended pus = mStats.get(pkgName); + if (pus == null) { + pus = new PkgUsageStatsExtended(); + mStats.put(pkgName, pus); + } + pus.updateResume(); } - pus.updateResume(); mResumedPkg = pkgName; } @@ -120,13 +355,17 @@ public final class UsageStatsService extends IUsageStats.Stub { return; } if (localLOGV) Log.i(TAG, "paused component:"+pkgName); - PkgUsageStatsExtended pus = mStats.get(pkgName); - if (pus == null) { - // Weird some error here - Log.w(TAG, "No package stats for pkg:"+pkgName); - return; + synchronized (mStatsLock) { + PkgUsageStatsExtended pus = mStats.get(pkgName); + if (pus == null) { + // Weird some error here + Log.w(TAG, "No package stats for pkg:"+pkgName); + return; + } + pus.updatePause(); } - pus.updatePause(); + // Persist data to file + writeStatsToFile(); } public void enforceCallingPermission() { @@ -145,17 +384,19 @@ public final class UsageStatsService extends IUsageStats.Stub { ((pkgName = componentName.getPackageName()) == null)) { return null; } - PkgUsageStatsExtended pus = mStats.get(pkgName); - if (pus == null) { - return null; + synchronized (mStatsLock) { + PkgUsageStatsExtended pus = mStats.get(pkgName); + if (pus == null) { + return null; + } + return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime); } - return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime); } public PkgUsageStats[] getAllPkgUsageStats() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.PACKAGE_USAGE_STATS, null); - synchronized (mStats) { + synchronized (mStatsLock) { Set keys = mStats.keySet(); int size = keys.size(); if (size <= 0) { @@ -172,21 +413,120 @@ public final class UsageStatsService extends IUsageStats.Stub { } } - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + static byte[] readFully(FileInputStream stream) throws java.io.IOException { + int pos = 0; + int avail = stream.available(); + byte[] data = new byte[avail]; + while (true) { + int amt = stream.read(data, pos, data.length-pos); + if (amt <= 0) { + return data; + } + pos += amt; + avail = stream.available(); + if (avail > data.length-pos) { + byte[] newData = new byte[pos+avail]; + System.arraycopy(data, 0, newData, 0, pos); + data = newData; + } + } + } + + private void collectDumpInfoFLOCK(PrintWriter pw, String[] args) { + List fileList = getUsageStatsFileListFLOCK(); + if (fileList == null) { + return; + } + final boolean isCheckinRequest = scanArgs(args, "-c"); + Collections.sort(fileList); + File usageFile = new File(mFilePrefix); + String dirName = usageFile.getParent(); + File dir = new File(dirName); + String filePrefix = usageFile.getName(); + // file name followed by dot + int prefixLen = filePrefix.length()+1; + String todayStr = getCurrentDateStr(null); + for (String file : fileList) { + File dFile = new File(dir, file); + String dateStr = file.substring(prefixLen); + try { + Parcel in = getParcelForFile(dFile); + collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCheckinRequest); + if (isCheckinRequest && !todayStr.equalsIgnoreCase(dateStr)) { + // Delete old file after collecting info only for checkin requests + dFile.delete(); + } + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file); + return; + } catch (IOException e) { + Log.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file); + } + } + } + + private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw, + String date, boolean isCheckinRequest) { StringBuilder sb = new StringBuilder(); - synchronized (mStats) { - Set keys = mStats.keySet(); - for (String key: keys) { - PkgUsageStatsExtended ps = mStats.get(key); - sb.append("pkg="); - sb.append(key); + sb.append("Date:"); + sb.append(date); + boolean first = true; + while (in.dataAvail() > 0) { + String pkgName = in.readString(); + int launchCount = in.readInt(); + long usageTime = in.readLong(); + if (isCheckinRequest) { + if (!first) { + sb.append(","); + } + sb.append(pkgName); + sb.append(","); + sb.append(launchCount); + sb.append(","); + sb.append(usageTime); + sb.append("ms"); + } else { + if (first) { + sb.append("\n"); + } + sb.append("pkg="); + sb.append(pkgName); sb.append(", launchCount="); - sb.append(ps.mLaunchCount); + sb.append(launchCount); sb.append(", usageTime="); - sb.append(ps.mUsageTime+" ms\n"); + sb.append(usageTime); + sb.append(" ms\n"); } + first = false; } pw.write(sb.toString()); } + + /** + * Searches array of arguments for the specified string + * @param args array of argument strings + * @param value value to search for + * @return true if the value is contained in the array + */ + private static boolean scanArgs(String[] args, String value) { + if (args != null) { + for (String arg : args) { + if (value.equals(arg)) { + return true; + } + } + } + return false; + } + + @Override + /* + * The data persisted to file is parsed and the stats are computed. + */ + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (mFileLock) { + collectDumpInfoFLOCK(pw, args); + } + } + } diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml index 230456a8dd66..36f7b9be52df 100644 --- a/tests/AndroidTests/AndroidManifest.xml +++ b/tests/AndroidTests/AndroidManifest.xml @@ -44,6 +44,7 @@ + diff --git a/tests/AndroidTests/src/com/android/unit_tests/MenuTest.java b/tests/AndroidTests/src/com/android/unit_tests/MenuTest.java index d184543dfb25..c436726a0fc8 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/MenuTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/MenuTest.java @@ -131,20 +131,7 @@ public class MenuTest extends AndroidTestCase { makeKeyEvent(KeyEvent.KEYCODE_A, KeyEvent.META_SYM_ON))); } - - @LargeTest - public void testIsNotShortcutWithMultipleMetas() throws Exception { - mMenu.setQwertyMode(true); - mMenu.add(0, 0, 0, "test").setShortcut('2', 'a'); - Assert.assertFalse(mMenu.isShortcutKey( - 'a', - makeKeyEvent(KeyEvent.KEYCODE_A, KeyEvent.META_SYM_ON & KeyEvent.META_ALT_ON))); - Assert.assertFalse(mMenu.isShortcutKey( - 'a', makeKeyEvent(KeyEvent.KEYCODE_A, KeyEvent.META_SYM_ON))); - Assert.assertFalse(mMenu.isShortcutKey( - 'a', makeKeyEvent(KeyEvent.KEYCODE_A, KeyEvent.META_SHIFT_ON))); - } - + @SmallTest public void testIsShortcutWithUpperCaseAlpha() throws Exception { mMenu.setQwertyMode(true); diff --git a/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java b/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java index 8c1c91f4cd31..51e841cb8d3a 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java @@ -262,7 +262,7 @@ public class TextUtilsTest extends TestCase { Map fixes = Maps.newHashMap(); fixes.put("a", ""); fixes.put("a b", ""); - fixes.put("a@b", ""); + fixes.put("a@b", ""); for (Map.Entry e : fixes.entrySet()) { assertEquals(e.getValue(), validator.fixText(e.getKey()).toString()); diff --git a/tests/DumpRenderTree/compare_layout_results.py b/tests/DumpRenderTree/compare_layout_results.py index 4383dbbd2d11..c4285f1e7d78 100644 --- a/tests/DumpRenderTree/compare_layout_results.py +++ b/tests/DumpRenderTree/compare_layout_results.py @@ -6,6 +6,7 @@ results to a file. import optparse import os +import sys def DiffResults(marker, new_results, old_results, diff_results, strip_reason): """ Given two result files, generate diff and @@ -45,7 +46,7 @@ def DiffResults(marker, new_results, old_results, diff_results, strip_reason): diff_file.writelines("- " + line) missing_count += 1 - print marker + " >>> New = " + str(new_count) + " , Missing = " + str(missing_count) + print marker + " >>> added " + str(new_count) + " tests, removed " + str(missing_count) + " tests" diff_file.writelines("\n\n") @@ -55,24 +56,32 @@ def DiffResults(marker, new_results, old_results, diff_results, strip_reason): return def main(options, args): - results_dir = options.results_directory + results_dir = os.path.abspath(options.results_directory) ref_dir = options.ref_directory - if os.path.exists(results_dir + "/layout_tests_diff.txt"): - os.remove(results_dir + "/layout_tests_diff.txt") - files=["passed", "nontext", "crashed"] - for f in files: - DiffResults(f, results_dir + "layout_tests_" + f + ".txt", - ref_dir + "layout_tests_" + f + ".txt", results_dir + "layout_tests_diff.txt", False) + # if ref_dir is null, cannonify ref_dir to the script dir. + if not ref_dir: + script_self = sys.argv[0] + script_dir = os.path.dirname(script_self) + ref_dir = os.path.join(script_dir, "results") + + ref_dir = os.path.abspath(ref_dir) - for f in ["failed"]: - DiffResults(f, results_dir + "layout_tests_" + f + ".txt", - ref_dir + "layout_tests_" + f + ".txt", results_dir + "layout_tests_diff.txt", True) + diff_result = os.path.join(results_dir, "layout_tests_diff.txt") + if os.path.exists(diff_result): + os.remove(diff_result) + + files=["passed", "failed", "nontext", "crashed"] + for f in files: + result_file_name = "layout_tests_" + f + ".txt" + DiffResults(f, os.path.join(results_dir, result_file_name), + os.path.join(ref_dir, result_file_name), diff_result, + f == "failed") if '__main__' == __name__: option_parser = optparse.OptionParser() option_parser.add_option("", "--ref-directory", - default="results/", + default=None, dest="ref_directory", help="directory name under which results are stored.") diff --git a/tests/DumpRenderTree/run_layout_tests.py b/tests/DumpRenderTree/run_layout_tests.py index b4eb685068b6..433271e38104 100755 --- a/tests/DumpRenderTree/run_layout_tests.py +++ b/tests/DumpRenderTree/run_layout_tests.py @@ -48,6 +48,86 @@ def CountLineNumber(filename): fp.close() return lines +def DumpRenderTreeFinished(adb_cmd): + """ Check if DumpRenderTree finished running tests + + Args: + output: adb_cmd string + """ + + # pull /sdcard/running_test.txt, if the content is "#DONE", it's done + shell_cmd_str = adb_cmd + " shell cat /sdcard/running_test.txt" + adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] + return adb_output.strip() == "#DONE" + +def DiffResults(marker, new_results, old_results, diff_results, strip_reason): + """ Given two result files, generate diff and + write to diff_results file. All arguments are absolute paths + to files. + """ + old_file = open(old_results, "r") + new_file = open(new_results, "r") + diff_file = open(diff_results, "a") + + # Read lines from each file + ndict = new_file.readlines() + cdict = old_file.readlines() + + # Write marker to diff file + diff_file.writelines(marker + "\n") + diff_file.writelines("###############\n") + + # Strip reason from result lines + if strip_reason is True: + for i in range(0, len(ndict)): + ndict[i] = ndict[i].split(' ')[0] + "\n" + for i in range(0, len(cdict)): + cdict[i] = cdict[i].split(' ')[0] + "\n" + + # Find results in new_results missing in old_results + new_count=0 + for line in ndict: + if line not in cdict: + diff_file.writelines("+ " + line) + new_count += 1 + + # Find results in old_results missing in new_results + missing_count=0 + for line in cdict: + if line not in ndict: + diff_file.writelines("- " + line) + missing_count += 1 + + logging.info(marker + " >>> " + str(new_count) + " new, " + str(missing_count) + " misses") + + diff_file.writelines("\n\n") + + old_file.close() + new_file.close() + diff_file.close() + return + +def CompareResults(ref_dir, results_dir): + """Compare results in two directories + + Args: + ref_dir: the reference directory having layout results as references + results_dir: the results directory + """ + logging.info("Comparing results to " + ref_dir) + + diff_result = os.path.join(results_dir, "layout_tests_diff.txt") + if os.path.exists(diff_result): + os.remove(diff_result) + + files=["passed", "failed", "nontext", "crashed"] + for f in files: + result_file_name = "layout_tests_" + f + ".txt" + DiffResults(f, os.path.join(results_dir, result_file_name), + os.path.join(ref_dir, result_file_name), diff_result, + f == "failed") + logging.info("Detailed diffs are in " + diff_result) + def main(options, args): """Run the tests. Will call sys.exit when complete. @@ -84,7 +164,7 @@ def main(options, args): sys.exit(1) - logging.info("Starting tests") + logging.info("Running tests") # Count crashed tests. crashed_tests = [] @@ -98,7 +178,7 @@ def main(options, args): # Call LayoutTestsAutoTest::startLayoutTests. shell_cmd_str = adb_cmd + " shell am instrument -e class com.android.dumprendertree.LayoutTestsAutoTest#startLayoutTests -e path \"" + path + "\" -e timeout " + timeout_ms + " -w com.android.dumprendertree/.LayoutTestsAutoRunner" adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] - while adb_output.find('Process crashed') != -1: + while not DumpRenderTreeFinished(adb_cmd): # Get the running_test.txt logging.error("DumpRenderTree crashed, output:\n" + adb_output) @@ -118,8 +198,8 @@ def main(options, args): logging.error("Error happened : " + adb_output) sys.exit(1) - logging.info("Done"); logging.debug(adb_output); + logging.info("Done\n"); # Pull results from /sdcard results_dir = options.results_directory @@ -152,8 +232,21 @@ def main(options, args): nontext_tests = CountLineNumber(results_dir + "/layout_tests_nontext.txt") logging.info(str(nontext_tests) + " no dumpAsText") - logging.info("Results are stored under: " + results_dir) + logging.info("Results are stored under: " + results_dir + "\n") + + # Comparing results to references to find new fixes and regressions. + results_dir = os.path.abspath(options.results_directory) + ref_dir = options.ref_directory + # if ref_dir is null, cannonify ref_dir to the script dir. + if not ref_dir: + script_self = sys.argv[0] + script_dir = os.path.dirname(script_self) + ref_dir = os.path.join(script_dir, "results") + + ref_dir = os.path.abspath(ref_dir) + + CompareResults(ref_dir, results_dir) if '__main__' == __name__: option_parser = optparse.OptionParser() @@ -171,6 +264,11 @@ if '__main__' == __name__: help="pass options to adb, such as -d -e, etc"); option_parser.add_option("", "--results-directory", default="layout-test-results", - help="directory name under which results are stored.") + help="directory which results are stored.") + option_parser.add_option("", "--ref-directory", + default=None, + dest="ref_directory", + help="directory where reference results are stored.") + options, args = option_parser.parse_args(); main(options, args) diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostActivity.java index c77d98ae55c9..86bfad7cb8bd 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostActivity.java @@ -68,9 +68,12 @@ class TestRecorder { } } - public void nontext(String layout_file) { + public void nontext(String layout_file, boolean has_results) { try { mBufferedOutputNontextStream.write(layout_file.getBytes()); + if (has_results) { + mBufferedOutputNontextStream.write(" : has expected results".getBytes()); + } mBufferedOutputNontextStream.write('\n'); mBufferedOutputNontextStream.flush(); } catch(Exception e) { @@ -299,6 +302,9 @@ public class HTMLHostActivity extends Activity resetTestStatus(); if (testIndex == mTestList.size()) { + if (!mSingleTestMode) { + updateTestStatus("#DONE"); + } finished(); return; } @@ -385,9 +391,9 @@ public class HTMLHostActivity extends Activity } } - public void nontextCase(String file) { + public void nontextCase(String file, boolean has_expected_results) { Log.v("Layout test:", file + " nontext"); - mResultRecorder.nontext(file); + mResultRecorder.nontext(file, has_expected_results); } public void setCallback(HTMLHostCallbackInterface callback) { @@ -447,8 +453,11 @@ public class HTMLHostActivity extends Activity } File nontext_result = new File(short_file + "-android-results.txt"); - if (nontext_result.exists()) - mResultRecorder.nontext(test_path); + if (nontext_result.exists()) { + // Check if the test has expected results. + File expected = new File(short_file + "-expected.txt"); + nontextCase(test_path, expected.exists()); + } } public void finished() { diff --git a/tests/ImfTest/AndroidManifest.xml b/tests/ImfTest/AndroidManifest.xml index 627ee6dc3dcb..55e5d386a235 100755 --- a/tests/ImfTest/AndroidManifest.xml +++ b/tests/ImfTest/AndroidManifest.xml @@ -19,7 +19,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -43,7 +43,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -59,7 +59,7 @@ - + @@ -130,14 +130,6 @@ - - - - - - - - diff --git a/tests/ImfTest/res/values/config.xml b/tests/ImfTest/res/values/config.xml new file mode 100644 index 000000000000..5ae40a381523 --- /dev/null +++ b/tests/ImfTest/res/values/config.xml @@ -0,0 +1,21 @@ + + + + false + diff --git a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollablePanScan.java b/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollablePanScan.java deleted file mode 100644 index 15a29c800928..000000000000 --- a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollablePanScan.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.android.imftest.samples; - -import com.android.imftest.R; - -import android.app.Activity; -import android.os.Bundle; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.LinearLayout; - -public class AppAdjustmentBigEditTextNonScrollablePanScan extends Activity { - - private LinearLayout mLayout; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); - - mLayout = new LinearLayout(this); - mLayout.setOrientation(LinearLayout.VERTICAL); - mLayout.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT)); - - EditText editText = (EditText) getLayoutInflater().inflate(R.layout.full_screen_edit_text, mLayout, false); - - mLayout.addView(editText); - - setContentView(mLayout); - } - -} diff --git a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollableResize.java b/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollableResize.java deleted file mode 100644 index 07268235670f..000000000000 --- a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextNonScrollableResize.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.android.imftest.samples; - -import com.android.imftest.R; - -import android.app.Activity; -import android.os.Bundle; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.LinearLayout; - -public class AppAdjustmentBigEditTextNonScrollableResize extends Activity { - - private LinearLayout mLayout; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - - mLayout = new LinearLayout(this); - mLayout.setOrientation(LinearLayout.VERTICAL); - mLayout.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT)); - - EditText editText = (EditText) getLayoutInflater().inflate(R.layout.full_screen_edit_text, mLayout, false); - - mLayout.addView(editText); - - setContentView(mLayout); - } - -} diff --git a/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScan.java b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScan.java new file mode 100644 index 000000000000..9754381e2ce3 --- /dev/null +++ b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScan.java @@ -0,0 +1,49 @@ +package com.android.imftest.samples; + +import com.android.imftest.R; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +public class BigEditTextActivityNonScrollablePanScan extends Activity { + + private View mRootView; + private View mDefaultFocusedView; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + + mRootView = new LinearLayout(this); + ((LinearLayout) mRootView).setOrientation(LinearLayout.VERTICAL); + mRootView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT)); + + View view = getLayoutInflater().inflate( + R.layout.full_screen_edit_text, ((LinearLayout) mRootView), false); + + ((LinearLayout) mRootView).addView(view); + + mDefaultFocusedView = view.findViewById(R.id.data); + + setContentView(mRootView); + } + + public View getRootView() { + return mRootView; + } + + public View getDefaultFocusedView() { + return mDefaultFocusedView; + } + +} diff --git a/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResize.java b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResize.java new file mode 100644 index 000000000000..701795fe4a7b --- /dev/null +++ b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResize.java @@ -0,0 +1,49 @@ +package com.android.imftest.samples; + +import com.android.imftest.R; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +public class BigEditTextActivityNonScrollableResize extends Activity { + + private View mRootView; + private View mDefaultFocusedView; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + + mRootView = new LinearLayout(this); + ((LinearLayout) mRootView).setOrientation(LinearLayout.VERTICAL); + mRootView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT)); + + View view = getLayoutInflater().inflate( + R.layout.full_screen_edit_text, ((LinearLayout) mRootView), false); + + ((LinearLayout) mRootView).addView(view); + + mDefaultFocusedView = view.findViewById(R.id.data); + + setContentView(mRootView); + } + + public View getRootView() { + return mRootView; + } + + public View getDefaultFocusedView() { + return mDefaultFocusedView; + } + +} diff --git a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextScrollablePanScan.java b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityScrollablePanScan.java similarity index 54% rename from tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextScrollablePanScan.java rename to tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityScrollablePanScan.java index 50a980b325c6..bb3f7671a860 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextScrollablePanScan.java +++ b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityScrollablePanScan.java @@ -4,15 +4,17 @@ import com.android.imftest.R; import android.app.Activity; import android.os.Bundle; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ScrollView; -public class AppAdjustmentBigEditTextScrollablePanScan extends Activity { +public class BigEditTextActivityScrollablePanScan extends Activity { - private ScrollView mScrollView; + private View mRootView; + private View mDefaultFocusedView; private LinearLayout mLayout; @Override @@ -21,9 +23,9 @@ public class AppAdjustmentBigEditTextScrollablePanScan extends Activity { getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); - mScrollView = new ScrollView(this); - mScrollView.setFillViewport(true); - mScrollView.setLayoutParams(new ViewGroup.LayoutParams( + mRootView = new ScrollView(this); + ((ScrollView) mRootView).setFillViewport(true); + mRootView.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); @@ -33,12 +35,23 @@ public class AppAdjustmentBigEditTextScrollablePanScan extends Activity { ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - EditText editText = (EditText) getLayoutInflater().inflate(R.layout.full_screen_edit_text, mScrollView, false); + View view = getLayoutInflater().inflate( + R.layout.full_screen_edit_text, ((ScrollView) mRootView), false); - mLayout.addView(editText); - mScrollView.addView(mLayout); + mLayout.addView(view); - setContentView(mScrollView); + ((ScrollView) mRootView).addView(mLayout); + mDefaultFocusedView = view.findViewById(R.id.data); + + setContentView(mRootView); + } + + public View getRootView() { + return mRootView; + } + + public View getDefaultFocusedView() { + return mDefaultFocusedView; } } diff --git a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextScrollableResize.java b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityScrollableResize.java similarity index 54% rename from tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextScrollableResize.java rename to tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityScrollableResize.java index a256878c5573..f2cae1cdfca7 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentBigEditTextScrollableResize.java +++ b/tests/ImfTest/src/com/android/imftest/samples/BigEditTextActivityScrollableResize.java @@ -4,15 +4,17 @@ import com.android.imftest.R; import android.app.Activity; import android.os.Bundle; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ScrollView; -public class AppAdjustmentBigEditTextScrollableResize extends Activity { +public class BigEditTextActivityScrollableResize extends Activity { - private ScrollView mScrollView; + private View mRootView; + private View mDefaultFocusedView; private LinearLayout mLayout; @Override @@ -21,9 +23,9 @@ public class AppAdjustmentBigEditTextScrollableResize extends Activity { getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - mScrollView = new ScrollView(this); - mScrollView.setFillViewport(true); - mScrollView.setLayoutParams(new ViewGroup.LayoutParams( + mRootView = new ScrollView(this); + ((ScrollView) mRootView).setFillViewport(true); + mRootView.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); @@ -33,12 +35,23 @@ public class AppAdjustmentBigEditTextScrollableResize extends Activity { ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - EditText editText = (EditText) getLayoutInflater().inflate(R.layout.full_screen_edit_text, mScrollView, false); + View view = getLayoutInflater().inflate( + R.layout.full_screen_edit_text, ((ScrollView) mRootView), false); - mLayout.addView(editText); - mScrollView.addView(mLayout); + mLayout.addView(view); - setContentView(mScrollView); + ((ScrollView) mRootView).addView(mLayout); + mDefaultFocusedView = view.findViewById(R.id.data); + + setContentView(mRootView); + } + + public View getRootView() { + return mRootView; + } + + public View getDefaultFocusedView() { + return mDefaultFocusedView; } } diff --git a/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityPanScan.java b/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityPanScan.java index d74b9dd9d9eb..51f5045ea202 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityPanScan.java +++ b/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityPanScan.java @@ -17,21 +17,30 @@ import com.android.imftest.R; */ public class BottomEditTextActivityPanScan extends Activity { - private LayoutInflater mInflater; + private View mRootView; + private View mDefaultFocusedView; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - LinearLayout layout = new LinearLayout(this); - layout.setOrientation(LinearLayout.VERTICAL); + mRootView = new LinearLayout(this); + ((LinearLayout) mRootView).setOrientation(LinearLayout.VERTICAL); - mInflater = getLayoutInflater(); - - View view = mInflater.inflate(R.layout.one_edit_text_activity, layout, false); - layout.addView(view); + View view = getLayoutInflater().inflate(R.layout.one_edit_text_activity, ((LinearLayout) mRootView), false); + mDefaultFocusedView = view.findViewById(R.id.dialog_edit_text); + ((LinearLayout) mRootView).addView(view); - setContentView(layout); + setContentView(mRootView); this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); } + + public View getRootView() { + return mRootView; + } + + public View getDefaultFocusedView() { + return mDefaultFocusedView; + } } \ No newline at end of file diff --git a/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityResize.java b/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityResize.java index 82da29a3af38..eb94b4f2ee38 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityResize.java +++ b/tests/ImfTest/src/com/android/imftest/samples/BottomEditTextActivityResize.java @@ -17,21 +17,30 @@ import com.android.imftest.R; */ public class BottomEditTextActivityResize extends Activity { - private LayoutInflater mInflater; + private View mRootView; + private View mDefaultFocusedView; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - LinearLayout layout = new LinearLayout(this); - layout.setOrientation(LinearLayout.VERTICAL); + mRootView = new LinearLayout(this); + ((LinearLayout) mRootView).setOrientation(LinearLayout.VERTICAL); - mInflater = getLayoutInflater(); - - View view = mInflater.inflate(R.layout.one_edit_text_activity, layout, false); - layout.addView(view); + View view = getLayoutInflater().inflate(R.layout.one_edit_text_activity, ((LinearLayout) mRootView), false); + mDefaultFocusedView = view.findViewById(R.id.dialog_edit_text); + ((LinearLayout) mRootView).addView(view); - setContentView(layout); + setContentView(mRootView); this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } + + public View getRootView() { + return mRootView; + } + + public View getDefaultFocusedView() { + return mDefaultFocusedView; + } } \ No newline at end of file diff --git a/tests/ImfTest/src/com/android/imftest/samples/ButtonActivity.java b/tests/ImfTest/src/com/android/imftest/samples/ButtonActivity.java index 4233811a60ed..1191f198198a 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/ButtonActivity.java +++ b/tests/ImfTest/src/com/android/imftest/samples/ButtonActivity.java @@ -13,6 +13,8 @@ import android.widget.TextView; public class ButtonActivity extends Activity { static boolean mKeyboardIsActive = false; + public static final int BUTTON_ID = 0; + private View mRootView; @Override public void onCreate(Bundle savedInstanceState) @@ -23,6 +25,8 @@ public class ButtonActivity extends Activity final Button myButton = new Button(this); myButton.setClickable(true); myButton.setText("Keyboard UP!"); + myButton.setId(BUTTON_ID); + myButton.setFocusableInTouchMode(true); myButton.setOnClickListener(new View.OnClickListener() { public void onClick (View v) @@ -36,7 +40,8 @@ public class ButtonActivity extends Activity } else { - imm.showSoftInput(null, 0); + myButton.requestFocusFromTouch(); + imm.showSoftInput(v, 0); myButton.setText("Keyboard DOWN!"); } @@ -48,5 +53,10 @@ public class ButtonActivity extends Activity layout.setOrientation(LinearLayout.VERTICAL); layout.addView(myButton); setContentView(layout); + mRootView = layout; + } + + public View getRootView() { + return mRootView; } } diff --git a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentEditTextDialog.java b/tests/ImfTest/src/com/android/imftest/samples/EditTextActivityDialog.java similarity index 88% rename from tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentEditTextDialog.java rename to tests/ImfTest/src/com/android/imftest/samples/EditTextActivityDialog.java index e82f1d50238c..bd1e934364f6 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/AppAdjustmentEditTextDialog.java +++ b/tests/ImfTest/src/com/android/imftest/samples/EditTextActivityDialog.java @@ -14,7 +14,7 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ScrollView; -public class AppAdjustmentEditTextDialog extends Activity { +public class EditTextActivityDialog extends Activity { private static final int SCROLLABLE_DIALOG_ID = 0; private static final int NONSCROLLABLE_DIALOG_ID = 1; @@ -75,18 +75,18 @@ public class AppAdjustmentEditTextDialog extends Activity { EditText editText; if (scrollable) { - layout = new ScrollView(AppAdjustmentEditTextDialog.this); + layout = new ScrollView(EditTextActivityDialog.this); ((ScrollView) layout).setMinimumHeight(mLayout.getHeight()); ((ScrollView) layout).addView(( - LinearLayout) View.inflate(AppAdjustmentEditTextDialog.this, + LinearLayout) View.inflate(EditTextActivityDialog.this, R.layout.dialog_edit_text_no_scroll, null)); } else { - layout = View.inflate(AppAdjustmentEditTextDialog.this, + layout = View.inflate(EditTextActivityDialog.this, R.layout.dialog_edit_text_no_scroll, null); } - Dialog d = new Dialog(AppAdjustmentEditTextDialog.this); + Dialog d = new Dialog(EditTextActivityDialog.this); d.setTitle(getString(R.string.test_dialog)); d.setCancelable(true); d.setContentView(layout); diff --git a/tests/ImfTest/src/com/android/imftest/samples/EditTextActivityNoScrollPanScan.java b/tests/ImfTest/src/com/android/imftest/samples/EditTextActivityNoScrollPanScan.java deleted file mode 100644 index b59602310856..000000000000 --- a/tests/ImfTest/src/com/android/imftest/samples/EditTextActivityNoScrollPanScan.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.android.imftest.samples; - -import android.app.Activity; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.LinearLayout; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.Button; -import android.widget.TextView; -import android.widget.ScrollView; - -import com.android.internal.R; - -public class EditTextActivityNoScrollPanScan extends Activity -{ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - LinearLayout layout = new LinearLayout(this); - layout.setOrientation(LinearLayout.VERTICAL); - - String string = new String(); - for (int i=0; i<9; i++) - { - final EditText editText = new EditText(this); - editText.setText(string.valueOf(i)); - layout.addView(editText); - } - setContentView(layout); - this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); - } -} diff --git a/tests/ImfTest/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScan.java b/tests/ImfTest/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScan.java index 4cb3af6ab81c..54ab57a498bb 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScan.java +++ b/tests/ImfTest/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScan.java @@ -20,22 +20,31 @@ import com.android.internal.R; */ public class ManyEditTextActivityNoScrollPanScan extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - LinearLayout layout = new LinearLayout(this); - layout.setOrientation(LinearLayout.VERTICAL); - - String string = new String(); - for (int i=0; i<9; i++) - { - final EditText editText = new EditText(this); - editText.setText(string.valueOf(i)); - layout.addView(editText); - } - setContentView(layout); - this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + public static final int NUM_EDIT_TEXTS = 9; + + private View mRootView; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mRootView = new LinearLayout(this); + ((LinearLayout) mRootView).setOrientation(LinearLayout.VERTICAL); + + for (int i=0; i + + + + + + + + + + + + + diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScanTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScanTests.java new file mode 100755 index 000000000000..a1c5fd285c34 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollablePanScanTests.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + +import com.android.imftest.R; + + +public class BigEditTextActivityNonScrollablePanScanTests extends ImfBaseTestCase { + + public final String TAG = "BigEditTextActivityNonScrollablePanScanTests"; + + public BigEditTextActivityNonScrollablePanScanTests() { + super(BigEditTextActivityNonScrollablePanScan.class); + } + + @LargeTest + public void testAppAdjustmentPanScan() { + // Give the IME 2 seconds to appear. + pause(2000); + + View rootView = ((BigEditTextActivityNonScrollablePanScan) mTargetActivity).getRootView(); + View servedView = ((BigEditTextActivityNonScrollablePanScan) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResizeTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResizeTests.java new file mode 100755 index 000000000000..2e0b0eb6e903 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityNonScrollableResizeTests.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + +import com.android.imftest.R; + + +public class BigEditTextActivityNonScrollableResizeTests extends ImfBaseTestCase { + + public final String TAG = "BigEditTextActivityNonScrollableResizeTests"; + + public BigEditTextActivityNonScrollableResizeTests() { + super(BigEditTextActivityNonScrollableResize.class); + } + + @LargeTest + public void testAppAdjustmentPanScan() { // Give the IME 2 seconds to appear. + pause(2000); + + View rootView = ((BigEditTextActivityNonScrollableResize) mTargetActivity).getRootView(); + View servedView = ((BigEditTextActivityNonScrollableResize) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollablePanScanTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollablePanScanTests.java new file mode 100755 index 000000000000..d3eefb5b48c1 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollablePanScanTests.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + +import com.android.imftest.R; + + +public class BigEditTextActivityScrollablePanScanTests extends ImfBaseTestCase { + + public final String TAG = "BigEditTextActivityScrollablePanScanTests"; + + public BigEditTextActivityScrollablePanScanTests() { + super(BigEditTextActivityScrollablePanScan.class); + } + + @LargeTest + public void testAppAdjustmentPanScan() { // Give the IME 2 seconds to appear. + pause(2000); + + View rootView = ((BigEditTextActivityScrollablePanScan) mTargetActivity).getRootView(); + View servedView = ((BigEditTextActivityScrollablePanScan) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollableResizeTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollableResizeTests.java new file mode 100755 index 000000000000..5c40e6d966d6 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/BigEditTextActivityScrollableResizeTests.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + +import com.android.imftest.R; + + +public class BigEditTextActivityScrollableResizeTests extends ImfBaseTestCase { + + public final String TAG = "BigEditTextActivityScrollableResizeTests"; + + public BigEditTextActivityScrollableResizeTests() { + super(BigEditTextActivityScrollableResize.class); + } + + @LargeTest + public void testAppAdjustmentPanScan() { + // Give the IME 2 seconds to appear. + pause(2000); + + View rootView = ((BigEditTextActivityScrollableResize) mTargetActivity).getRootView(); + View servedView = ((BigEditTextActivityScrollableResize) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityPanScanTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityPanScanTests.java new file mode 100755 index 000000000000..9a931331adcd --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityPanScanTests.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + +import com.android.imftest.R; + + +public class BottomEditTextActivityPanScanTests extends ImfBaseTestCase { + + public final String TAG = "BottomEditTextActivityPanScanTests"; + + public BottomEditTextActivityPanScanTests() { + super(BottomEditTextActivityPanScan.class); + } + + @LargeTest + public void testAppAdjustmentPanScan() { + // Give the IME 2 seconds to appear. + pause(2000); + + View rootView = ((BottomEditTextActivityPanScan) mTargetActivity).getRootView(); + View servedView = ((BottomEditTextActivityPanScan) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityResizeTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityResizeTests.java new file mode 100755 index 000000000000..9a69fd509844 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/BottomEditTextActivityResizeTests.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + +import com.android.imftest.R; + + +public class BottomEditTextActivityResizeTests extends ImfBaseTestCase { + + public final String TAG = "BottomEditTextActivityResizeTests"; + + public BottomEditTextActivityResizeTests() { + super(BottomEditTextActivityResize.class); + } + + @LargeTest + public void testAppAdjustmentResize() { + // Give the IME 2 seconds to appear. + pause(2000); + + View rootView = ((BottomEditTextActivityResize) mTargetActivity).getRootView(); + View servedView = ((BottomEditTextActivityResize) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/ButtonActivityTest.java b/tests/ImfTest/tests/src/com/android/imftest/samples/ButtonActivityTest.java new file mode 100755 index 000000000000..ae900c34ba3b --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/ButtonActivityTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.KeyEvent; +import android.widget.Button; + + +public class ButtonActivityTest extends ImfBaseTestCase { + + final public String TAG = "ButtonActivityTest"; + + public ButtonActivityTest() { + super(ButtonActivity.class); + } + + @LargeTest + public void testButtonActivatesIme() { + + final Button button = (Button) mTargetActivity.findViewById(ButtonActivity.BUTTON_ID); + + // Push button + // Bring the target EditText into focus. + mTargetActivity.runOnUiThread(new Runnable() { + public void run() { + button.requestFocus(); + } + }); + + sendKeys(KeyEvent.KEYCODE_DPAD_CENTER); + + // Give it a couple seconds + pause(2000); + + // We should have initialized imm.mServedView and imm.mCurrentTextBoxAttribute + assertTrue(mImm.isActive()); + // imm.mServedInputConnection should be null since Button doesn't override onCreateInputConnection(). + assertFalse(mImm.isAcceptingText()); + + destructiveCheckImeInitialState(mTargetActivity.getRootView(), button); + + } +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java b/tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java new file mode 100755 index 000000000000..61dc61193a95 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.app.Activity; +import android.os.SystemClock; +import android.test.InstrumentationTestCase; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import com.android.imftest.R; + +public abstract class ImfBaseTestCase extends InstrumentationTestCase { + + /* + * The amount of time we are willing to wait for the IME to appear after a user action + * before we give up and fail the test. + */ + public final long WAIT_FOR_IME = 5000; + + /* + * Unfortunately there is now way for us to know how tall the IME is, + * so we have to hard code a minimum and maximum value. + */ + public final int IME_MIN_HEIGHT = 150; + public final int IME_MAX_HEIGHT = 300; + + public final String TARGET_PACKAGE_NAME = "com.android.imftest"; + protected InputMethodManager mImm; + protected T mTargetActivity; + protected boolean mExpectAutoPop; + private Class mTargetActivityClass; + + public ImfBaseTestCase(Class activityClass) { + mTargetActivityClass = activityClass; + } + + @Override + public void setUp() throws Exception { + super.setUp(); + + mTargetActivity = launchActivity(TARGET_PACKAGE_NAME, mTargetActivityClass, null); + mExpectAutoPop = mTargetActivity.getResources().getBoolean(R.bool.def_expect_ime_autopop); + mImm = InputMethodManager.getInstance(mTargetActivity); + } + + // Utility test methods + public void verifyEditTextAdjustment(final View editText, int rootViewHeight) { + + int[] origLocation = new int[2]; + int[] newLocation = new int[2]; + + // Tell the keyboard to go away. + mImm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + + // Bring the target EditText into focus. + mTargetActivity.runOnUiThread(new Runnable() { + public void run() { + editText.requestFocus(); + } + }); + + // Get the original location of the EditText. + editText.getLocationOnScreen(origLocation); + + // Tap the EditText to bring up the IME. + sendKeys(KeyEvent.KEYCODE_DPAD_CENTER); + + // Wait until the EditText pops above the IME or until we hit the timeout. + editText.getLocationOnScreen(newLocation); + long timeoutTime = SystemClock.uptimeMillis() + WAIT_FOR_IME; + while (newLocation[1] > rootViewHeight - IME_MIN_HEIGHT && SystemClock.uptimeMillis() < timeoutTime) { + editText.getLocationOnScreen(newLocation); + pause(100); + } + + assertTrue(newLocation[1] <= rootViewHeight - IME_MIN_HEIGHT); + + // Tell the keyboard to go away. + mImm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + } + + public void destructiveCheckImeInitialState(View rootView, View servedView) { + if (mExpectAutoPop && (mTargetActivity.getWindow().getAttributes(). + softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { + assertTrue(destructiveCheckImeUp(rootView, servedView)); + } else { + assertFalse(destructiveCheckImeUp(rootView, servedView)); + } + } + + public boolean destructiveCheckImeUp(View rootView, View servedView) { + int origHeight; + int newHeight; + + origHeight = rootView.getHeight(); + + // Tell the keyboard to go away. + mImm.hideSoftInputFromWindow(servedView.getWindowToken(), 0); + + // Give it five seconds to adjust + newHeight = rootView.getHeight(); + long timeoutTime = SystemClock.uptimeMillis() + WAIT_FOR_IME; + while (Math.abs(newHeight - origHeight) < IME_MIN_HEIGHT && SystemClock.uptimeMillis() < timeoutTime) { + newHeight = rootView.getHeight(); + } + + return (Math.abs(origHeight - newHeight) >= IME_MIN_HEIGHT); + } + + void pause(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + } + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityBaseTestCase.java b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityBaseTestCase.java new file mode 100755 index 000000000000..278efb1f68db --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityBaseTestCase.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.app.Activity; +import android.widget.EditText; + + +public abstract class ManyEditTextActivityBaseTestCase extends ImfBaseTestCase { + + public ManyEditTextActivityBaseTestCase(Class activityClass){ + super(activityClass); + } + + public abstract void testAllEditTextsAdjust(); + + public void verifyAllEditTextAdjustment(int numEditTexts, int rootViewHeight) { + + for (int i = 0; i < numEditTexts; i++) { + final EditText lastEditText = (EditText) mTargetActivity.findViewById(i); + verifyEditTextAdjustment(lastEditText, rootViewHeight); + } + + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScanTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScanTests.java new file mode 100755 index 000000000000..4f8d14e88ad5 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityNoScrollPanScanTests.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; + + +public class ManyEditTextActivityNoScrollPanScanTests extends ManyEditTextActivityBaseTestCase { + + public final String TAG = "ManyEditTextActivityNoScrollPanScanTests"; + + public ManyEditTextActivityNoScrollPanScanTests() { + super(ManyEditTextActivityNoScrollPanScan.class); + } + + + @LargeTest + public void testAllEditTextsAdjust() { + verifyAllEditTextAdjustment(mTargetActivity.NUM_EDIT_TEXTS, + mTargetActivity.getRootView().getMeasuredHeight()); + } +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollPanScanTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollPanScanTests.java new file mode 100755 index 000000000000..7f98f7fbdf21 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollPanScanTests.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; + + +public class ManyEditTextActivityScrollPanScanTests extends ManyEditTextActivityBaseTestCase { + + public final String TAG = "ManyEditTextActivityScrollPanScanTests"; + + + public ManyEditTextActivityScrollPanScanTests() { + super(ManyEditTextActivityScrollPanScan.class); + } + + @LargeTest + public void testAllEditTextsAdjust() { + verifyAllEditTextAdjustment(mTargetActivity.NUM_EDIT_TEXTS, + mTargetActivity.getRootView().getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollResizeTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollResizeTests.java new file mode 100755 index 000000000000..68dae87ea5c9 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/ManyEditTextActivityScrollResizeTests.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; + + +public class ManyEditTextActivityScrollResizeTests extends ManyEditTextActivityBaseTestCase { + + public final String TAG = "ManyEditTextActivityScrollResizeTests"; + + + public ManyEditTextActivityScrollResizeTests() { + super(ManyEditTextActivityScrollResize.class); + } + + @LargeTest + public void testAllEditTextsAdjust() { + verifyAllEditTextAdjustment(mTargetActivity.NUM_EDIT_TEXTS, + mTargetActivity.getRootView().getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivityNotSelectedTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivityNotSelectedTests.java new file mode 100755 index 000000000000..ed5b0c9f1728 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivityNotSelectedTests.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.View; + + +public class OneEditTextActivityNotSelectedTests extends ImfBaseTestCase { + + public final String TAG = "OneEditTextActivityNotSelectedTests"; + + public OneEditTextActivityNotSelectedTests() { + super(OneEditTextActivityNotSelected.class); + } + + @LargeTest + public void testSoftKeyboardNoAutoPop() { + + // Give the IME 2 seconds to appear. + pause(2000); + + assertFalse(mImm.isAcceptingText()); + + View rootView = ((OneEditTextActivityNotSelected) mTargetActivity).getRootView(); + View servedView = ((OneEditTextActivityNotSelected) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivitySelectedTests.java b/tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivitySelectedTests.java new file mode 100755 index 000000000000..42fcd66f7922 --- /dev/null +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/OneEditTextActivitySelectedTests.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.imftest.samples; + +import com.android.imftest.R; + +import android.test.suitebuilder.annotation.LargeTest; +import android.view.KeyEvent; +import android.view.View; + + +public class OneEditTextActivitySelectedTests extends ImfBaseTestCase { + + public final String TAG = "OneEditTextActivitySelectedTests"; + + public OneEditTextActivitySelectedTests() { + super(OneEditTextActivitySelected.class); + } + + @LargeTest + public void testSoftKeyboardAutoPop() { + + // Give the IME 2 seconds to appear. + pause(2000); + + assertTrue(mImm.isAcceptingText()); + + View rootView = ((OneEditTextActivitySelected) mTargetActivity).getRootView(); + View servedView = ((OneEditTextActivitySelected) mTargetActivity).getDefaultFocusedView(); + + assertNotNull(rootView); + assertNotNull(servedView); + + destructiveCheckImeInitialState(rootView, servedView); + + verifyEditTextAdjustment(servedView, rootView.getMeasuredHeight()); + } + +} diff --git a/tests/gadgets/GadgetHostTest/AndroidManifest.xml b/tests/gadgets/GadgetHostTest/AndroidManifest.xml index cac2776d3a34..52e314f6bb4f 100644 --- a/tests/gadgets/GadgetHostTest/AndroidManifest.xml +++ b/tests/gadgets/GadgetHostTest/AndroidManifest.xml @@ -17,12 +17,20 @@ - + + + - + + + diff --git a/tests/gadgets/GadgetHostTest/res/xml/gadget_info.xml b/tests/gadgets/GadgetHostTest/res/xml/gadget_info.xml index 353df307821d..e0c4222c8cf4 100644 --- a/tests/gadgets/GadgetHostTest/res/xml/gadget_info.xml +++ b/tests/gadgets/GadgetHostTest/res/xml/gadget_info.xml @@ -1,8 +1,10 @@ + + diff --git a/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/GadgetHostActivity.java b/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/GadgetHostActivity.java index d3dcf41deab3..0bd89261a71d 100644 --- a/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/GadgetHostActivity.java +++ b/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/GadgetHostActivity.java @@ -23,7 +23,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.gadget.GadgetHost; import android.gadget.GadgetHostView; -import android.gadget.GadgetInfo; +import android.gadget.GadgetProviderInfo; import android.gadget.GadgetManager; import android.os.Bundle; import android.util.Log; @@ -73,14 +73,13 @@ public class GadgetHostActivity extends Activity }; void discoverGadget(int requestCode) { - Intent intent = new Intent(GadgetManager.GADGET_PICK_ACTION); - intent.putExtra(GadgetManager.EXTRA_HOST_ID, HOST_ID); + Intent intent = new Intent(GadgetManager.ACTION_GADGET_PICK); intent.putExtra(GadgetManager.EXTRA_GADGET_ID, mHost.allocateGadgetId()); startActivityForResult(intent, requestCode); } void configureGadget(int requestCode, int gadgetId, ComponentName configure) { - Intent intent = new Intent(GadgetManager.GADGET_CONFIGURE_ACTION); + Intent intent = new Intent(GadgetManager.ACTION_GADGET_CONFIGURE); intent.setComponent(configure); intent.putExtra(GadgetManager.EXTRA_GADGET_ID, gadgetId); SharedPreferences.Editor prefs = getPreferences(0).edit(); @@ -89,11 +88,13 @@ public class GadgetHostActivity extends Activity startActivityForResult(intent, requestCode); } - void handleGadgetPickResult(int resultCode, Intent data) { - Bundle extras = data.getExtras(); + void handleGadgetPickResult(int resultCode, Intent intent) { + // BEGIN_INCLUDE(getExtra_EXTRA_GADGET_ID) + Bundle extras = intent.getExtras(); int gadgetId = extras.getInt(GadgetManager.EXTRA_GADGET_ID); + // END_INCLUDE(getExtra_EXTRA_GADGET_ID) if (resultCode == RESULT_OK) { - GadgetInfo gadget = mGadgetManager.getGadgetInfo(gadgetId); + GadgetProviderInfo gadget = mGadgetManager.getGadgetInfo(gadgetId); if (gadget.configure != null) { // configure the gadget if we should @@ -115,14 +116,14 @@ public class GadgetHostActivity extends Activity return; } if (resultCode == RESULT_OK) { - GadgetInfo gadget = mGadgetManager.getGadgetInfo(gadgetId); + GadgetProviderInfo gadget = mGadgetManager.getGadgetInfo(gadgetId); addGadgetView(gadgetId, gadget); } else { mHost.deleteGadgetId(gadgetId); } } - void addGadgetView(int gadgetId, GadgetInfo gadget) { + void addGadgetView(int gadgetId, GadgetProviderInfo gadget) { // Inflate the gadget's RemoteViews GadgetHostView view = mHost.createView(this, gadgetId, gadget); @@ -188,11 +189,10 @@ public class GadgetHostActivity extends Activity } GadgetHost mHost = new GadgetHost(this, HOST_ID) { - protected GadgetHostView onCreateView(Context context, int gadgetId, GadgetInfo gadget) { + protected GadgetHostView onCreateView(Context context, int gadgetId, GadgetProviderInfo gadget) { return new MyGadgetView(gadgetId); } }; - } diff --git a/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/TestGadgetProvider.java b/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/TestGadgetProvider.java index 7614c9ef0cd9..370a50b94e16 100644 --- a/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/TestGadgetProvider.java +++ b/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/TestGadgetProvider.java @@ -37,16 +37,18 @@ public class TestGadgetProvider extends BroadcastReceiver { String action = intent.getAction(); Log.d(TAG, "intent=" + intent); - if (GadgetManager.GADGET_ENABLED_ACTION.equals(action)) { + if (GadgetManager.ACTION_GADGET_ENABLED.equals(action)) { Log.d(TAG, "ENABLED"); } - else if (GadgetManager.GADGET_DISABLED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_DISABLED.equals(action)) { Log.d(TAG, "DISABLED"); } - else if (GadgetManager.GADGET_UPDATE_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { Log.d(TAG, "UPDATE"); + // BEGIN_INCLUDE(getExtra_EXTRA_GADGET_IDS) Bundle extras = intent.getExtras(); int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS); + // END_INCLUDE(getExtra_EXTRA_GADGET_IDS) SharedPreferences prefs = context.getSharedPreferences( TestGadgetProvider.PREFS_NAME, 0); diff --git a/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml b/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml index 0b8ca2edff7e..0fc78124bdf6 100644 --- a/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml +++ b/tests/gadgets/GadgetProviderTest/res/xml/gadget_info.xml @@ -1,7 +1,7 @@ diff --git a/tests/gadgets/GadgetProviderTest/src/com/android/tests/gadgetprovider/TestGadgetProvider.java b/tests/gadgets/GadgetProviderTest/src/com/android/tests/gadgetprovider/TestGadgetProvider.java index 8622bc761173..b81575f0c271 100644 --- a/tests/gadgets/GadgetProviderTest/src/com/android/tests/gadgetprovider/TestGadgetProvider.java +++ b/tests/gadgets/GadgetProviderTest/src/com/android/tests/gadgetprovider/TestGadgetProvider.java @@ -34,13 +34,13 @@ public class TestGadgetProvider extends BroadcastReceiver { String action = intent.getAction(); Log.d(TAG, "intent=" + intent); - if (GadgetManager.GADGET_ENABLED_ACTION.equals(action)) { + if (GadgetManager.ACTION_GADGET_ENABLED.equals(action)) { Log.d(TAG, "ENABLED"); } - else if (GadgetManager.GADGET_DISABLED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_DISABLED.equals(action)) { Log.d(TAG, "DISABLED"); } - else if (GadgetManager.GADGET_UPDATE_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { Log.d(TAG, "UPDATE"); Bundle extras = intent.getExtras(); int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 39200a1c3b46..82fe365a6d7a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -789,6 +789,12 @@ public final class Bridge implements ILayoutBridge { } @SuppressWarnings("unused") + public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { + // pass for now. + return false; + } + + @SuppressWarnings("unused") public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { // pass for now. return null; diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index 0e70f8bea92b..f0009be7ea49 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -1142,6 +1142,7 @@ public class WifiStateTracker extends NetworkStateTracker { // Stop DHCP if (mDhcpTarget != null) { mDhcpTarget.setCancelCallback(true); + mDhcpTarget.removeMessages(EVENT_DHCP_START); } if (!NetworkUtils.stopDhcp(mInterfaceName)) { Log.e(TAG, "Could not stop DHCP"); -- 2.11.0