summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt15
-rw-r--r--cmds/content/Android.mk35
-rw-r--r--cmds/content/MODULE_LICENSE_APACHE20
-rw-r--r--cmds/content/NOTICE190
-rwxr-xr-xcmds/content/content5
-rw-r--r--cmds/content/src/com/android/commands/content/Content.java442
-rw-r--r--cmds/keystore/Android.mk32
-rw-r--r--cmds/keystore/keystore.cpp810
-rw-r--r--cmds/keystore/keystore.h43
-rw-r--r--cmds/keystore/keystore_cli.cpp95
-rw-r--r--cmds/keystore/keystore_get.h80
-rwxr-xr-xcmds/keystore/test-keystore273
-rw-r--r--cmds/servicemanager/service_manager.c2
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java24
-rw-r--r--core/java/android/app/ActivityManagerNative.java59
-rw-r--r--core/java/android/app/ContextImpl.java13
-rw-r--r--core/java/android/app/IActivityManager.java7
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/content/ContextWrapper.java6
-rw-r--r--core/java/android/net/http/CertificateChainValidator.java16
-rw-r--r--core/java/android/net/http/HttpResponseCache.java20
-rw-r--r--core/java/android/os/CommonClock.java414
-rw-r--r--core/java/android/os/CommonTimeConfig.java448
-rw-r--r--core/java/android/os/CommonTimeUtils.java291
-rw-r--r--core/java/android/os/Message.java50
-rw-r--r--core/java/android/os/MessageQueue.java104
-rw-r--r--core/java/android/view/Choreographer.java408
-rw-r--r--core/java/android/view/View.java32
-rw-r--r--core/java/android/view/ViewGroup.java38
-rw-r--r--core/java/android/view/ViewRootImpl.java73
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java36
-rw-r--r--core/java/android/webkit/WebView.java93
-rw-r--r--core/java/android/webkit/WebViewCore.java24
-rw-r--r--core/java/android/widget/CheckedTextView.java6
-rw-r--r--core/java/android/widget/TextView.java24
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java5
-rw-r--r--core/res/AndroidManifest.xml11
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.pngbin2219 -> 917 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.pngbin1807 -> 963 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.pngbin0 -> 662 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.pngbin0 -> 665 bytes
-rw-r--r--core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.pngbin0 -> 1275 bytes
-rw-r--r--core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.pngbin0 -> 1278 bytes
-rw-r--r--core/res/res/values-af/strings.xml5
-rw-r--r--core/res/res/values-am/strings.xml5
-rw-r--r--core/res/res/values-ar/strings.xml5
-rw-r--r--core/res/res/values-be/strings.xml5
-rw-r--r--core/res/res/values-bg/strings.xml5
-rw-r--r--core/res/res/values-ca/strings.xml5
-rw-r--r--core/res/res/values-cs/strings.xml5
-rw-r--r--core/res/res/values-da/strings.xml5
-rw-r--r--core/res/res/values-de/strings.xml5
-rw-r--r--core/res/res/values-el/strings.xml11
-rw-r--r--core/res/res/values-en-rGB/strings.xml5
-rw-r--r--core/res/res/values-es-rUS/strings.xml9
-rw-r--r--core/res/res/values-es/strings.xml5
-rw-r--r--core/res/res/values-et/strings.xml5
-rw-r--r--core/res/res/values-fa/strings.xml5
-rw-r--r--core/res/res/values-fi/strings.xml5
-rw-r--r--core/res/res/values-fr/strings.xml5
-rw-r--r--core/res/res/values-hi/strings.xml5
-rw-r--r--core/res/res/values-hr/strings.xml5
-rw-r--r--core/res/res/values-hu/strings.xml5
-rw-r--r--core/res/res/values-in/strings.xml5
-rw-r--r--core/res/res/values-it/strings.xml5
-rw-r--r--core/res/res/values-iw/strings.xml5
-rw-r--r--core/res/res/values-ja/strings.xml5
-rw-r--r--core/res/res/values-ko/strings.xml5
-rw-r--r--core/res/res/values-lt/strings.xml5
-rw-r--r--core/res/res/values-lv/strings.xml5
-rw-r--r--core/res/res/values-ms/strings.xml5
-rw-r--r--core/res/res/values-nb/strings.xml5
-rw-r--r--core/res/res/values-nl/strings.xml5
-rw-r--r--core/res/res/values-pl/strings.xml5
-rw-r--r--core/res/res/values-pt-rPT/strings.xml5
-rw-r--r--core/res/res/values-pt/strings.xml5
-rw-r--r--core/res/res/values-rm/strings.xml10
-rw-r--r--core/res/res/values-ro/strings.xml5
-rw-r--r--core/res/res/values-ru/strings.xml5
-rw-r--r--core/res/res/values-sk/strings.xml5
-rw-r--r--core/res/res/values-sl/strings.xml5
-rw-r--r--core/res/res/values-sr/strings.xml5
-rw-r--r--core/res/res/values-sv/strings.xml5
-rw-r--r--core/res/res/values-sw/strings.xml5
-rw-r--r--core/res/res/values-th/strings.xml5
-rw-r--r--core/res/res/values-tl/strings.xml5
-rw-r--r--core/res/res/values-tr/strings.xml5
-rw-r--r--core/res/res/values-uk/strings.xml5
-rw-r--r--core/res/res/values-vi/strings.xml5
-rw-r--r--core/res/res/values-zh-rCN/strings.xml5
-rw-r--r--core/res/res/values-zh-rTW/strings.xml5
-rw-r--r--core/res/res/values-zu/strings.xml7
-rwxr-xr-xcore/res/res/values/config.xml7
-rw-r--r--core/res/res/values/public.xml11
-rwxr-xr-xcore/res/res/values/strings.xml14
-rw-r--r--core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java3
-rw-r--r--core/tests/coretests/Android.mk2
-rw-r--r--core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java32
-rw-r--r--core/tests/coretests/src/android/widget/focus/RequestFocus.java2
-rw-r--r--core/tests/coretests/src/android/widget/focus/RequestFocusTest.java105
-rw-r--r--data/etc/platform.xml1
-rw-r--r--docs/html/guide/developing/device.jd31
-rw-r--r--docs/html/index.jd2
-rw-r--r--docs/html/sdk/oem-usb.jd200
-rw-r--r--docs/html/sdk/win-usb.jd187
-rw-r--r--graphics/java/android/renderscript/Allocation.java95
-rw-r--r--graphics/java/android/renderscript/Element.java43
-rw-r--r--graphics/java/android/renderscript/Program.java35
-rw-r--r--graphics/java/android/renderscript/ProgramFragment.java4
-rw-r--r--graphics/java/android/renderscript/ProgramFragmentFixedFunction.java4
-rw-r--r--graphics/java/android/renderscript/ProgramVertex.java4
-rw-r--r--graphics/java/android/renderscript/ProgramVertexFixedFunction.java4
-rw-r--r--graphics/java/android/renderscript/RenderScript.java28
-rw-r--r--graphics/jni/android_renderscript_RenderScript.cpp128
-rw-r--r--include/common_time/ICommonClock.h108
-rw-r--r--include/common_time/ICommonTimeConfig.h73
-rw-r--r--include/common_time/cc_helper.h72
-rw-r--r--include/common_time/local_clock.h47
-rw-r--r--include/media/AudioTrack.h25
-rw-r--r--include/media/IAudioFlinger.h1
-rw-r--r--include/media/IAudioTrack.h19
-rw-r--r--include/media/MediaPlayerInterface.h3
-rw-r--r--keystore/java/android/security/KeyChain.java18
-rw-r--r--libs/common_time/Android.mk21
-rw-r--r--libs/common_time/ICommonClock.cpp432
-rw-r--r--libs/common_time/ICommonTimeConfig.cpp508
-rw-r--r--libs/common_time/cc_helper.cpp129
-rw-r--r--libs/common_time/local_clock.cpp92
-rw-r--r--libs/common_time/utils.cpp89
-rw-r--r--libs/common_time/utils.h34
-rw-r--r--libs/rs/driver/rsdAllocation.cpp101
-rw-r--r--libs/rs/driver/rsdAllocation.h8
-rw-r--r--libs/rs/driver/rsdCore.cpp3
-rw-r--r--libs/rs/driver/rsdProgram.cpp14
-rw-r--r--libs/rs/driver/rsdProgramFragment.h4
-rw-r--r--libs/rs/driver/rsdProgramVertex.h4
-rw-r--r--libs/rs/driver/rsdShader.cpp47
-rw-r--r--libs/rs/driver/rsdShader.h28
-rw-r--r--libs/rs/rs.spec396
-rw-r--r--libs/rs/rsAllocation.cpp39
-rw-r--r--libs/rs/rsAllocation.h6
-rw-r--r--libs/rs/rsFont.cpp27
-rw-r--r--libs/rs/rsProgram.cpp4
-rw-r--r--libs/rs/rsProgram.h4
-rw-r--r--libs/rs/rsProgramFragment.cpp25
-rw-r--r--libs/rs/rsProgramFragment.h6
-rw-r--r--libs/rs/rsProgramVertex.cpp25
-rw-r--r--libs/rs/rsProgramVertex.h5
-rw-r--r--libs/rs/rs_hal.h24
-rw-r--r--media/java/android/media/AudioService.java14
-rwxr-xr-xmedia/java/android/media/audiofx/Visualizer.java87
-rw-r--r--media/jni/audioeffect/android_media_Visualizer.cpp108
-rw-r--r--media/libaah_rtp/Android.mk40
-rw-r--r--media/libaah_rtp/aah_decoder_pump.cpp520
-rw-r--r--media/libaah_rtp/aah_decoder_pump.h107
-rw-r--r--media/libaah_rtp/aah_rx_player.cpp288
-rw-r--r--media/libaah_rtp/aah_rx_player.h313
-rw-r--r--media/libaah_rtp/aah_rx_player_core.cpp807
-rw-r--r--media/libaah_rtp/aah_rx_player_ring_buffer.cpp366
-rw-r--r--media/libaah_rtp/aah_rx_player_substream.cpp498
-rw-r--r--media/libaah_rtp/aah_tx_packet.cpp331
-rw-r--r--media/libaah_rtp/aah_tx_packet.h191
-rw-r--r--media/libaah_rtp/aah_tx_player.cpp1139
-rw-r--r--media/libaah_rtp/aah_tx_player.h179
-rw-r--r--media/libaah_rtp/aah_tx_sender.cpp602
-rw-r--r--media/libaah_rtp/aah_tx_sender.h162
-rw-r--r--media/libaah_rtp/pipe_event.cpp86
-rw-r--r--media/libaah_rtp/pipe_event.h51
-rw-r--r--media/libmedia/AudioEffect.cpp14
-rw-r--r--media/libmedia/AudioTrack.cpp81
-rw-r--r--media/libmedia/IAudioFlinger.cpp5
-rw-r--r--media/libmedia/IAudioTrack.cpp81
-rw-r--r--media/libmedia/IEffect.cpp11
-rw-r--r--media/libmedia/Visualizer.cpp2
-rw-r--r--media/libmediaplayerservice/Android.mk3
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.cpp39
-rw-r--r--opengl/libs/EGL/egl.cpp28
-rw-r--r--opengl/libs/EGL/eglApi.cpp197
-rw-r--r--opengl/libs/EGL/egl_object.h40
-rw-r--r--opengl/libs/GLES2/gl2.cpp24
-rw-r--r--opengl/libs/GLES2/gl2ext_api.in4
-rw-r--r--opengl/libs/GLES_CM/gl.cpp24
-rw-r--r--opengl/libs/GLES_CM/glext_api.in4
-rw-r--r--opengl/libs/egl_impl.h1
-rwxr-xr-xopengl/libs/tools/glapigen6
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/Choreographer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java1
-rw-r--r--services/audioflinger/Android.mk2
-rw-r--r--services/audioflinger/AudioBufferProvider.cpp28
-rw-r--r--services/audioflinger/AudioBufferProvider.h11
-rw-r--r--services/audioflinger/AudioFlinger.cpp639
-rw-r--r--services/audioflinger/AudioFlinger.h143
-rw-r--r--services/audioflinger/AudioMixer.cpp61
-rw-r--r--services/audioflinger/AudioMixer.h23
-rw-r--r--services/audioflinger/AudioResampler.cpp26
-rw-r--r--services/audioflinger/AudioResampler.h8
-rw-r--r--services/audioflinger/AudioResamplerCubic.cpp11
-rw-r--r--services/audioflinger/AudioResamplerSinc.cpp4
-rw-r--r--services/common_time/Android.mk32
-rw-r--r--services/common_time/clock_recovery.cpp381
-rw-r--r--services/common_time/clock_recovery.h123
-rw-r--r--services/common_time/common_clock.cpp150
-rw-r--r--services/common_time/common_clock.h57
-rw-r--r--services/common_time/common_clock_service.cpp157
-rw-r--r--services/common_time/common_clock_service.h90
-rw-r--r--services/common_time/common_time_config_service.cpp112
-rw-r--r--services/common_time/common_time_config_service.h59
-rw-r--r--services/common_time/common_time_server.cpp1380
-rw-r--r--services/common_time/common_time_server.h331
-rw-r--r--services/common_time/common_time_server_api.cpp435
-rw-r--r--services/common_time/common_time_server_packets.cpp293
-rw-r--r--services/common_time/common_time_server_packets.h189
-rw-r--r--services/common_time/diag_thread.cpp329
-rw-r--r--services/common_time/diag_thread.h80
-rw-r--r--services/common_time/main.cpp43
-rw-r--r--services/java/com/android/server/AppWidgetServiceImpl.java15
-rw-r--r--services/java/com/android/server/CommonTimeManagementService.java377
-rw-r--r--services/java/com/android/server/SystemServer.java15
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java73
-rw-r--r--services/java/com/android/server/am/ContentProviderRecord.java106
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java32
-rw-r--r--services/java/com/android/server/wm/WindowState.java4
-rw-r--r--telephony/java/android/telephony/PhoneNumberUtils.java14
-rw-r--r--telephony/java/com/android/internal/telephony/DataConnectionTracker.java3
-rw-r--r--telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java47
-rw-r--r--telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java31
-rw-r--r--test-runner/src/android/test/mock/MockContext.java6
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java4
230 files changed, 17261 insertions, 3006 deletions
diff --git a/api/current.txt b/api/current.txt
index 97be454b50f4..6c7833b40e35 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -614,8 +614,10 @@ package android {
field public static final int layout_height = 16842997; // 0x10100f5
field public static final int layout_margin = 16842998; // 0x10100f6
field public static final int layout_marginBottom = 16843002; // 0x10100fa
+ field public static final int layout_marginEnd = 16843692; // 0x10103ac
field public static final int layout_marginLeft = 16842999; // 0x10100f7
field public static final int layout_marginRight = 16843001; // 0x10100f9
+ field public static final int layout_marginStart = 16843691; // 0x10103ab
field public static final int layout_marginTop = 16843000; // 0x10100f8
field public static final int layout_row = 16843643; // 0x101037b
field public static final int layout_rowSpan = 16843644; // 0x101037c
@@ -711,8 +713,10 @@ package android {
field public static final int packageNames = 16843649; // 0x1010381
field public static final int padding = 16842965; // 0x10100d5
field public static final int paddingBottom = 16842969; // 0x10100d9
+ field public static final int paddingEnd = 16843690; // 0x10103aa
field public static final int paddingLeft = 16842966; // 0x10100d6
field public static final int paddingRight = 16842968; // 0x10100d8
+ field public static final int paddingStart = 16843689; // 0x10103a9
field public static final int paddingTop = 16842967; // 0x10100d7
field public static final int panelBackground = 16842846; // 0x101005e
field public static final int panelColorBackground = 16842849; // 0x1010061
@@ -23179,8 +23183,10 @@ package android.view {
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method public int getOverScrollMode();
method public int getPaddingBottom();
+ method public int getPaddingEnd();
method public int getPaddingLeft();
method public int getPaddingRight();
+ method public int getPaddingStart();
method public int getPaddingTop();
method public final android.view.ViewParent getParent();
method public float getPivotX();
@@ -23256,6 +23262,7 @@ package android.view {
method public boolean isLongClickable();
method public boolean isOpaque();
method protected boolean isPaddingOffsetRequired();
+ method public boolean isPaddingRelative();
method public boolean isPressed();
method public boolean isSaveEnabled();
method public boolean isSaveFromParentEnabled();
@@ -23303,6 +23310,7 @@ package android.view {
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+ method public void onResolvePadding(int);
method protected void onRestoreInstanceState(android.os.Parcelable);
method protected android.os.Parcelable onSaveInstanceState();
method protected void onScrollChanged(int, int, int, int);
@@ -23338,6 +23346,7 @@ package android.view {
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method protected void resetResolvedTextDirection();
+ method public void resolvePadding();
method public static int resolveSize(int, int);
method public static int resolveSizeAndState(int, int, int);
method protected void resolveTextDirection();
@@ -23399,6 +23408,7 @@ package android.view {
method public void setOnTouchListener(android.view.View.OnTouchListener);
method public void setOverScrollMode(int);
method public void setPadding(int, int, int, int);
+ method public void setPaddingRelative(int, int, int, int);
method public void setPivotX(float);
method public void setPivotY(float);
method public void setPressed(boolean);
@@ -23843,10 +23853,15 @@ package android.view {
ctor public ViewGroup.MarginLayoutParams(int, int);
ctor public ViewGroup.MarginLayoutParams(android.view.ViewGroup.MarginLayoutParams);
ctor public ViewGroup.MarginLayoutParams(android.view.ViewGroup.LayoutParams);
+ method public int getMarginEnd();
+ method public int getMarginStart();
+ method public boolean isMarginRelative();
method public void setMargins(int, int, int, int);
field public int bottomMargin;
+ field public int endMargin;
field public int leftMargin;
field public int rightMargin;
+ field public int startMargin;
field public int topMargin;
}
diff --git a/cmds/content/Android.mk b/cmds/content/Android.mk
new file mode 100644
index 000000000000..a3d83cf8848a
--- /dev/null
+++ b/cmds/content/Android.mk
@@ -0,0 +1,35 @@
+# Copyright 2012 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_MODULE := content
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+ALL_PREBUILT += $(TARGET_OUT)/bin/content
+$(TARGET_OUT)/bin/content : $(LOCAL_PATH)/content | $(ACP)
+ $(transform-prebuilt-to-target)
+
+NOTICE_FILE := NOTICE
+files_noticed := bin/content
+
+# Generate rules for a single file. The argument is the file path relative to
+# the installation root
+define make-notice-file
+
+$(TARGET_OUT_NOTICE_FILES)/src/$(1).txt: $(LOCAL_PATH)/$(NOTICE_FILE)
+ @echo Notice file: $$< -- $$@
+ @mkdir -p $$(dir $$@)
+ @cat $$< >> $$@
+
+$(TARGET_OUT_NOTICE_FILES)/hash-timestamp: $(TARGET_OUT_NOTICE_FILES)/src/$(1).txt
+
+endef
+
+$(foreach file,$(files_noticed),$(eval $(call make-notice-file,$(file))))
diff --git a/cmds/content/MODULE_LICENSE_APACHE2 b/cmds/content/MODULE_LICENSE_APACHE2
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/cmds/content/MODULE_LICENSE_APACHE2
diff --git a/cmds/content/NOTICE b/cmds/content/NOTICE
new file mode 100644
index 000000000000..33ff96160b77
--- /dev/null
+++ b/cmds/content/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/cmds/content/content b/cmds/content/content
new file mode 100755
index 000000000000..a8e056d04d69
--- /dev/null
+++ b/cmds/content/content
@@ -0,0 +1,5 @@
+# Script to start "content" on the device, which has a very rudimentary shell.
+base=/system
+export CLASSPATH=$base/framework/content.jar
+exec app_process $base/bin com.android.commands.content.Content "$@"
+
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
new file mode 100644
index 000000000000..1dcba70405fc
--- /dev/null
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -0,0 +1,442 @@
+/*
+** Copyright 2012, 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.commands.content;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.IActivityManager.ContentProviderHolder;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.text.TextUtils;
+
+/**
+ * This class is a command line utility for manipulating content. A client
+ * can insert, update, and remove records in a content provider. For example,
+ * some settings may be configured before running the CTS tests, etc.
+ * <p>
+ * Examples:
+ * <ul>
+ * <li>
+ * # Add "new_setting" secure setting with value "new_value".</br>
+ * adb shell content insert --uri content://settings/secure --bind name:s:new_setting
+ * --bind value:s:new_value
+ * </li>
+ * <li>
+ * # Change "new_setting" secure setting to "newer_value" (You have to escape single quotes in
+ * the where clause).</br>
+ * adb shell content update --uri content://settings/secure --bind value:s:newer_value
+ * --where "name=\'new_setting\'"
+ * </li>
+ * <li>
+ * # Remove "new_setting" secure setting.</br>
+ * adb shell content delete --uri content://settings/secure --where "name=\'new_setting\'"
+ * </li>
+ * <li>
+ * # Query \"name\" and \"value\" columns from secure settings where \"name\" is equal to"
+ * \"new_setting\" and sort the result by name in ascending order.\n"
+ * adb shell content query --uri content://settings/secure --projection name:value
+ * --where "name=\'new_setting\'" --sort \"name ASC\"
+ * </li>
+ * </ul>
+ * </p>
+ */
+public class Content {
+
+ private static final String USAGE =
+ "usage: adb shell content [subcommand] [options]\n"
+ + "\n"
+ + "usage: adb shell content insert --uri <URI> --bind <BINDING> [--bind <BINDING>...]\n"
+ + " <URI> a content provider URI.\n"
+ + " <BINDING> binds a typed value to a column and is formatted:\n"
+ + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n"
+ + " <TYPE> specifies data type such as:\n"
+ + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n"
+ + " Example:\n"
+ + " # Add \"new_setting\" secure setting with value \"new_value\".\n"
+ + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting"
+ + " --bind value:s:new_value\n"
+ + "\n"
+ + "usage: adb shell content update --uri <URI> [--where <WHERE>]\n"
+ + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes"
+ + " - see example below).\n"
+ + " Example:\n"
+ + " # Change \"new_setting\" secure setting to \"newer_value\".\n"
+ + " adb shell content update --uri content://settings/secure --bind"
+ + " value:s:newer_value --where \"name=\'new_setting\'\"\n"
+ + "\n"
+ + "usage: adb shell content delete --uri <URI> --bind <BINDING>"
+ + " [--bind <BINDING>...] [--where <WHERE>]\n"
+ + " Example:\n"
+ + " # Remove \"new_setting\" secure setting.\n"
+ + " adb shell content delete --uri content://settings/secure "
+ + "--where \"name=\'new_setting\'\"\n"
+ + "\n"
+ + "usage: adb shell content query --uri <URI> [--projection <PROJECTION>]"
+ + " [--where <WHERE>] [--sort <SORT_ORDER>]\n"
+ + " <PROJECTION> is a list of colon separated column names and is formatted:\n"
+ + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n"
+ + " <SORT_OREDER> is the order in which rows in the result should be sorted.\n"
+ + " Example:\n"
+ + " # Select \"name\" and \"value\" columns from secure settings where \"name\" is "
+ + "equal to \"new_setting\" and sort the result by name in ascending order.\n"
+ + " adb shell content query --uri content://settings/secure --projection name:value"
+ + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n"
+ + "\n";
+
+ private static class Parser {
+ private static final String ARGUMENT_INSERT = "insert";
+ private static final String ARGUMENT_DELETE = "delete";
+ private static final String ARGUMENT_UPDATE = "update";
+ private static final String ARGUMENT_QUERY = "query";
+ private static final String ARGUMENT_WHERE = "--where";
+ private static final String ARGUMENT_BIND = "--bind";
+ private static final String ARGUMENT_URI = "--uri";
+ private static final String ARGUMENT_PROJECTION = "--projection";
+ private static final String ARGUMENT_SORT = "--sort";
+ private static final String TYPE_BOOLEAN = "b";
+ private static final String TYPE_STRING = "s";
+ private static final String TYPE_INTEGER = "i";
+ private static final String TYPE_LONG = "l";
+ private static final String TYPE_FLOAT = "f";
+ private static final String TYPE_DOUBLE = "d";
+ private static final String COLON = ":";
+ private static final String ARGUMENT_PREFIX = "--";
+
+ private final Tokenizer mTokenizer;
+
+ public Parser(String[] args) {
+ mTokenizer = new Tokenizer(args);
+ }
+
+ public Command parseCommand() {
+ try {
+ String operation = mTokenizer.nextArg();
+ if (ARGUMENT_INSERT.equals(operation)) {
+ return parseInsertCommand();
+ } else if (ARGUMENT_DELETE.equals(operation)) {
+ return parseDeleteCommand();
+ } else if (ARGUMENT_UPDATE.equals(operation)) {
+ return parseUpdateCommand();
+ } else if (ARGUMENT_QUERY.equals(operation)) {
+ return parseQueryCommand();
+ } else {
+ throw new IllegalArgumentException("Unsupported operation: " + operation);
+ }
+ } catch (IllegalArgumentException iae) {
+ System.out.println(USAGE);
+ System.out.println("[ERROR] " + iae.getMessage());
+ return null;
+ }
+ }
+
+ private InsertCommand parseInsertCommand() {
+ Uri uri = null;
+ ContentValues values = new ContentValues();
+ for (String argument; (argument = mTokenizer.nextArg()) != null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_BIND.equals(argument)) {
+ parseBindValue(values);
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ if (values.size() == 0) {
+ throw new IllegalArgumentException("Bindings not specified."
+ + " Did you specify --bind argument(s)?");
+ }
+ return new InsertCommand(uri, values);
+ }
+
+ private DeleteCommand parseDeleteCommand() {
+ Uri uri = null;
+ String where = null;
+ for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_WHERE.equals(argument)) {
+ where = argumentValueRequired(argument);
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ return new DeleteCommand(uri, where);
+ }
+
+ private UpdateCommand parseUpdateCommand() {
+ Uri uri = null;
+ String where = null;
+ ContentValues values = new ContentValues();
+ for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_WHERE.equals(argument)) {
+ where = argumentValueRequired(argument);
+ } else if (ARGUMENT_BIND.equals(argument)) {
+ parseBindValue(values);
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ if (values.size() == 0) {
+ throw new IllegalArgumentException("Bindings not specified."
+ + " Did you specify --bind argument(s)?");
+ }
+ return new UpdateCommand(uri, values, where);
+ }
+
+ public QueryCommand parseQueryCommand() {
+ Uri uri = null;
+ String[] projection = null;
+ String sort = null;
+ String where = null;
+ for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_WHERE.equals(argument)) {
+ where = argumentValueRequired(argument);
+ } else if (ARGUMENT_SORT.equals(argument)) {
+ sort = argumentValueRequired(argument);
+ } else if (ARGUMENT_PROJECTION.equals(argument)) {
+ projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*");
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ return new QueryCommand(uri, projection, where, sort);
+ }
+
+ private void parseBindValue(ContentValues values) {
+ String argument = mTokenizer.nextArg();
+ if (TextUtils.isEmpty(argument)) {
+ throw new IllegalArgumentException("Binding not well formed: " + argument);
+ }
+ String[] binding = argument.split(COLON);
+ if (binding.length != 3) {
+ throw new IllegalArgumentException("Binding not well formed: " + argument);
+ }
+ String column = binding[0];
+ String type = binding[1];
+ String value = binding[2];
+ if (TYPE_STRING.equals(type)) {
+ values.put(column, value);
+ } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) {
+ values.put(column, Boolean.parseBoolean(value));
+ } else if (TYPE_INTEGER.equalsIgnoreCase(type) || TYPE_LONG.equalsIgnoreCase(type)) {
+ values.put(column, Long.parseLong(value));
+ } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) {
+ values.put(column, Double.parseDouble(value));
+ } else {
+ throw new IllegalArgumentException("Unsupported type: " + type);
+ }
+ }
+
+ private String argumentValueRequired(String argument) {
+ String value = mTokenizer.nextArg();
+ if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) {
+ throw new IllegalArgumentException("No value for argument: " + argument);
+ }
+ return value;
+ }
+ }
+
+ private static class Tokenizer {
+ private final String[] mArgs;
+ private int mNextArg;
+
+ public Tokenizer(String[] args) {
+ mArgs = args;
+ }
+
+ private String nextArg() {
+ if (mNextArg < mArgs.length) {
+ return mArgs[mNextArg++];
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private static abstract class Command {
+ final Uri mUri;
+
+ public Command(Uri uri) {
+ mUri = uri;
+ }
+
+ public final void execute() {
+ String providerName = mUri.getAuthority();
+ try {
+ IActivityManager activityManager = ActivityManagerNative.getDefault();
+ IContentProvider provider = null;
+ IBinder token = new Binder();
+ try {
+ ContentProviderHolder holder = activityManager.getContentProviderExternal(
+ providerName, token);
+ if (holder == null) {
+ throw new IllegalStateException("Could not find provider: " + providerName);
+ }
+ provider = holder.provider;
+ onExecute(provider);
+ } finally {
+ if (provider != null) {
+ activityManager.removeContentProviderExternal(providerName, token);
+ }
+ }
+ } catch (Exception e) {
+ System.err.println("Error while accessing provider:" + providerName);
+ e.printStackTrace();
+ }
+ }
+
+ protected abstract void onExecute(IContentProvider provider) throws Exception;
+ }
+
+ private static class InsertCommand extends Command {
+ final ContentValues mContentValues;
+
+ public InsertCommand(Uri uri, ContentValues contentValues) {
+ super(uri);
+ mContentValues = contentValues;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ provider.insert(mUri, mContentValues);
+ }
+ }
+
+ private static class DeleteCommand extends Command {
+ final String mWhere;
+
+ public DeleteCommand(Uri uri, String where) {
+ super(uri);
+ mWhere = where;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ provider.delete(mUri, mWhere, null);
+ }
+ }
+
+ private static class QueryCommand extends DeleteCommand {
+ final String[] mProjection;
+ final String mSortOrder;
+
+ public QueryCommand(Uri uri, String[] projection, String where, String sortOrder) {
+ super(uri, where);
+ mProjection = projection;
+ mSortOrder = sortOrder;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ Cursor cursor = provider.query(mUri, mProjection, mWhere, null, mSortOrder, null);
+ if (cursor == null) {
+ System.out.println("No result found.");
+ return;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ int rowIndex = 0;
+ StringBuilder builder = new StringBuilder();
+ do {
+ builder.setLength(0);
+ builder.append("Row: ").append(rowIndex).append(" ");
+ rowIndex++;
+ final int columnCount = cursor.getColumnCount();
+ for (int i = 0; i < columnCount; i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ String columnName = cursor.getColumnName(i);
+ String columnValue = null;
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ final int type = cursor.getType(columnIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_FLOAT:
+ columnValue = String.valueOf(cursor.getFloat(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ columnValue = String.valueOf(cursor.getInt(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ columnValue = cursor.getString(columnIndex);
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ columnValue = "BLOB";
+ break;
+ case Cursor.FIELD_TYPE_NULL:
+ columnValue = "NULL";
+ break;
+ }
+ builder.append(columnName).append("=").append(columnValue);
+ }
+ System.out.println(builder);
+ } while (cursor.moveToNext());
+ } else {
+ System.out.println("No reuslt found.");
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ private static class UpdateCommand extends InsertCommand {
+ final String mWhere;
+
+ public UpdateCommand(Uri uri, ContentValues contentValues, String where) {
+ super(uri, contentValues);
+ mWhere = where;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ provider.update(mUri, mContentValues, mWhere, null);
+ }
+ }
+
+ public static void main(String[] args) {
+ Parser parser = new Parser(args);
+ Command command = parser.parseCommand();
+ if (command != null) {
+ command.execute();
+ }
+ }
+}
diff --git a/cmds/keystore/Android.mk b/cmds/keystore/Android.mk
deleted file mode 100644
index 5a9b979c6406..000000000000
--- a/cmds/keystore/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := keystore.cpp
-LOCAL_C_INCLUDES := external/openssl/include
-LOCAL_SHARED_LIBRARIES := libcutils libcrypto
-LOCAL_MODULE:= keystore
-include $(BUILD_EXECUTABLE)
-
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := keystore_cli.cpp
-LOCAL_C_INCLUDES := external/openssl/include
-LOCAL_SHARED_LIBRARIES := libcutils libcrypto
-LOCAL_MODULE:= keystore_cli
-LOCAL_MODULE_TAGS := debug
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp
deleted file mode 100644
index 2c9cb35b7b25..000000000000
--- a/cmds/keystore/keystore.cpp
+++ /dev/null
@@ -1,810 +0,0 @@
-/*
- * 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.
- */
-
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <unistd.h>
-#include <signal.h>
-#include <errno.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <arpa/inet.h>
-
-#include <openssl/aes.h>
-#include <openssl/evp.h>
-#include <openssl/md5.h>
-
-#define LOG_TAG "keystore"
-#include <cutils/log.h>
-#include <cutils/sockets.h>
-#include <private/android_filesystem_config.h>
-
-#include "keystore.h"
-
-/* KeyStore is a secured storage for key-value pairs. In this implementation,
- * each file stores one key-value pair. Keys are encoded in file names, and
- * values are encrypted with checksums. The encryption key is protected by a
- * user-defined password. To keep things simple, buffers are always larger than
- * the maximum space we needed, so boundary checks on buffers are omitted. */
-
-#define KEY_SIZE ((NAME_MAX - 15) / 2)
-#define VALUE_SIZE 32768
-#define PASSWORD_SIZE VALUE_SIZE
-
-struct Value {
- int length;
- uint8_t value[VALUE_SIZE];
-};
-
-/* Here is the encoding of keys. This is necessary in order to allow arbitrary
- * characters in keys. Characters in [0-~] are not encoded. Others are encoded
- * into two bytes. The first byte is one of [+-.] which represents the first
- * two bits of the character. The second byte encodes the rest of the bits into
- * [0-o]. Therefore in the worst case the length of a key gets doubled. Note
- * that Base64 cannot be used here due to the need of prefix match on keys. */
-
-static int encode_key(char* out, uid_t uid, const Value* key) {
- int n = snprintf(out, NAME_MAX, "%u_", uid);
- out += n;
- const uint8_t* in = key->value;
- int length = key->length;
- for (int i = length; i > 0; --i, ++in, ++out) {
- if (*in >= '0' && *in <= '~') {
- *out = *in;
- } else {
- *out = '+' + (*in >> 6);
- *++out = '0' + (*in & 0x3F);
- ++length;
- }
- }
- *out = '\0';
- return n + length;
-}
-
-static int decode_key(uint8_t* out, char* in, int length) {
- for (int i = 0; i < length; ++i, ++in, ++out) {
- if (*in >= '0' && *in <= '~') {
- *out = *in;
- } else {
- *out = (*in - '+') << 6;
- *out |= (*++in - '0') & 0x3F;
- --length;
- }
- }
- *out = '\0';
- return length;
-}
-
-static size_t readFully(int fd, uint8_t* data, size_t size) {
- size_t remaining = size;
- while (remaining > 0) {
- ssize_t n = TEMP_FAILURE_RETRY(read(fd, data, size));
- if (n == -1 || n == 0) {
- return size-remaining;
- }
- data += n;
- remaining -= n;
- }
- return size;
-}
-
-static size_t writeFully(int fd, uint8_t* data, size_t size) {
- size_t remaining = size;
- while (remaining > 0) {
- ssize_t n = TEMP_FAILURE_RETRY(write(fd, data, size));
- if (n == -1 || n == 0) {
- return size-remaining;
- }
- data += n;
- remaining -= n;
- }
- return size;
-}
-
-class Entropy {
-public:
- Entropy() : mRandom(-1) {}
- ~Entropy() {
- if (mRandom != -1) {
- close(mRandom);
- }
- }
-
- bool open() {
- const char* randomDevice = "/dev/urandom";
- mRandom = ::open(randomDevice, O_RDONLY);
- if (mRandom == -1) {
- ALOGE("open: %s: %s", randomDevice, strerror(errno));
- return false;
- }
- return true;
- }
-
- bool generate_random_data(uint8_t* data, size_t size) {
- return (readFully(mRandom, data, size) == size);
- }
-
-private:
- int mRandom;
-};
-
-/* Here is the file format. There are two parts in blob.value, the secret and
- * the description. The secret is stored in ciphertext, and its original size
- * can be found in blob.length. The description is stored after the secret in
- * plaintext, and its size is specified in blob.info. The total size of the two
- * parts must be no more than VALUE_SIZE bytes. The first three bytes of the
- * file are reserved for future use and are always set to zero. Fields other
- * than blob.info, blob.length, and blob.value are modified by encryptBlob()
- * and decryptBlob(). Thus they should not be accessed from outside. */
-
-struct __attribute__((packed)) blob {
- uint8_t reserved[3];
- uint8_t info;
- uint8_t vector[AES_BLOCK_SIZE];
- uint8_t encrypted[0];
- uint8_t digest[MD5_DIGEST_LENGTH];
- uint8_t digested[0];
- int32_t length; // in network byte order when encrypted
- uint8_t value[VALUE_SIZE + AES_BLOCK_SIZE];
-};
-
-class Blob {
-public:
- Blob(uint8_t* value, int32_t valueLength, uint8_t* info, uint8_t infoLength) {
- mBlob.length = valueLength;
- memcpy(mBlob.value, value, valueLength);
-
- mBlob.info = infoLength;
- memcpy(mBlob.value + valueLength, info, infoLength);
- }
-
- Blob(blob b) {
- mBlob = b;
- }
-
- Blob() {}
-
- uint8_t* getValue() {
- return mBlob.value;
- }
-
- int32_t getLength() {
- return mBlob.length;
- }
-
- uint8_t getInfo() {
- return mBlob.info;
- }
-
- ResponseCode encryptBlob(const char* filename, AES_KEY *aes_key, Entropy* entropy) {
- if (!entropy->generate_random_data(mBlob.vector, AES_BLOCK_SIZE)) {
- return SYSTEM_ERROR;
- }
-
- // data includes the value and the value's length
- size_t dataLength = mBlob.length + sizeof(mBlob.length);
- // pad data to the AES_BLOCK_SIZE
- size_t digestedLength = ((dataLength + AES_BLOCK_SIZE - 1)
- / AES_BLOCK_SIZE * AES_BLOCK_SIZE);
- // encrypted data includes the digest value
- size_t encryptedLength = digestedLength + MD5_DIGEST_LENGTH;
- // move info after space for padding
- memmove(&mBlob.encrypted[encryptedLength], &mBlob.value[mBlob.length], mBlob.info);
- // zero padding area
- memset(mBlob.value + mBlob.length, 0, digestedLength - dataLength);
-
- mBlob.length = htonl(mBlob.length);
- MD5(mBlob.digested, digestedLength, mBlob.digest);
-
- uint8_t vector[AES_BLOCK_SIZE];
- memcpy(vector, mBlob.vector, AES_BLOCK_SIZE);
- AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength,
- aes_key, vector, AES_ENCRYPT);
-
- memset(mBlob.reserved, 0, sizeof(mBlob.reserved));
- size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob);
- size_t fileLength = encryptedLength + headerLength + mBlob.info;
-
- const char* tmpFileName = ".tmp";
- int out = open(tmpFileName, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
- if (out == -1) {
- return SYSTEM_ERROR;
- }
- size_t writtenBytes = writeFully(out, (uint8_t*) &mBlob, fileLength);
- if (close(out) != 0) {
- return SYSTEM_ERROR;
- }
- if (writtenBytes != fileLength) {
- unlink(tmpFileName);
- return SYSTEM_ERROR;
- }
- return (rename(tmpFileName, filename) == 0) ? NO_ERROR : SYSTEM_ERROR;
- }
-
- ResponseCode decryptBlob(const char* filename, AES_KEY *aes_key) {
- int in = open(filename, O_RDONLY);
- if (in == -1) {
- return (errno == ENOENT) ? KEY_NOT_FOUND : SYSTEM_ERROR;
- }
- // fileLength may be less than sizeof(mBlob) since the in
- // memory version has extra padding to tolerate rounding up to
- // the AES_BLOCK_SIZE
- size_t fileLength = readFully(in, (uint8_t*) &mBlob, sizeof(mBlob));
- if (close(in) != 0) {
- return SYSTEM_ERROR;
- }
- size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob);
- if (fileLength < headerLength) {
- return VALUE_CORRUPTED;
- }
-
- ssize_t encryptedLength = fileLength - (headerLength + mBlob.info);
- if (encryptedLength < 0 || encryptedLength % AES_BLOCK_SIZE != 0) {
- return VALUE_CORRUPTED;
- }
- AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, aes_key,
- mBlob.vector, AES_DECRYPT);
- size_t digestedLength = encryptedLength - MD5_DIGEST_LENGTH;
- uint8_t computedDigest[MD5_DIGEST_LENGTH];
- MD5(mBlob.digested, digestedLength, computedDigest);
- if (memcmp(mBlob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) {
- return VALUE_CORRUPTED;
- }
-
- ssize_t maxValueLength = digestedLength - sizeof(mBlob.length);
- mBlob.length = ntohl(mBlob.length);
- if (mBlob.length < 0 || mBlob.length > maxValueLength) {
- return VALUE_CORRUPTED;
- }
- if (mBlob.info != 0) {
- // move info from after padding to after data
- memmove(&mBlob.value[mBlob.length], &mBlob.value[maxValueLength], mBlob.info);
- }
- return NO_ERROR;
- }
-
-private:
- struct blob mBlob;
-};
-
-class KeyStore {
-public:
- KeyStore(Entropy* entropy) : mEntropy(entropy), mRetry(MAX_RETRY) {
- if (access(MASTER_KEY_FILE, R_OK) == 0) {
- setState(STATE_LOCKED);
- } else {
- setState(STATE_UNINITIALIZED);
- }
- }
-
- State getState() {
- return mState;
- }
-
- int8_t getRetry() {
- return mRetry;
- }
-
- ResponseCode initialize(Value* pw) {
- if (!generateMasterKey()) {
- return SYSTEM_ERROR;
- }
- ResponseCode response = writeMasterKey(pw);
- if (response != NO_ERROR) {
- return response;
- }
- setupMasterKeys();
- return NO_ERROR;
- }
-
- ResponseCode writeMasterKey(Value* pw) {
- uint8_t passwordKey[MASTER_KEY_SIZE_BYTES];
- generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, mSalt);
- AES_KEY passwordAesKey;
- AES_set_encrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey);
- Blob masterKeyBlob(mMasterKey, sizeof(mMasterKey), mSalt, sizeof(mSalt));
- return masterKeyBlob.encryptBlob(MASTER_KEY_FILE, &passwordAesKey, mEntropy);
- }
-
- ResponseCode readMasterKey(Value* pw) {
- int in = open(MASTER_KEY_FILE, O_RDONLY);
- if (in == -1) {
- return SYSTEM_ERROR;
- }
-
- // we read the raw blob to just to get the salt to generate
- // the AES key, then we create the Blob to use with decryptBlob
- blob rawBlob;
- size_t length = readFully(in, (uint8_t*) &rawBlob, sizeof(rawBlob));
- if (close(in) != 0) {
- return SYSTEM_ERROR;
- }
- // find salt at EOF if present, otherwise we have an old file
- uint8_t* salt;
- if (length > SALT_SIZE && rawBlob.info == SALT_SIZE) {
- salt = (uint8_t*) &rawBlob + length - SALT_SIZE;
- } else {
- salt = NULL;
- }
- uint8_t passwordKey[MASTER_KEY_SIZE_BYTES];
- generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, salt);
- AES_KEY passwordAesKey;
- AES_set_decrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey);
- Blob masterKeyBlob(rawBlob);
- ResponseCode response = masterKeyBlob.decryptBlob(MASTER_KEY_FILE, &passwordAesKey);
- if (response == SYSTEM_ERROR) {
- return SYSTEM_ERROR;
- }
- if (response == NO_ERROR && masterKeyBlob.getLength() == MASTER_KEY_SIZE_BYTES) {
- // if salt was missing, generate one and write a new master key file with the salt.
- if (salt == NULL) {
- if (!generateSalt()) {
- return SYSTEM_ERROR;
- }
- response = writeMasterKey(pw);
- }
- if (response == NO_ERROR) {
- memcpy(mMasterKey, masterKeyBlob.getValue(), MASTER_KEY_SIZE_BYTES);
- setupMasterKeys();
- }
- return response;
- }
- if (mRetry <= 0) {
- reset();
- return UNINITIALIZED;
- }
- --mRetry;
- switch (mRetry) {
- case 0: return WRONG_PASSWORD_0;
- case 1: return WRONG_PASSWORD_1;
- case 2: return WRONG_PASSWORD_2;
- case 3: return WRONG_PASSWORD_3;
- default: return WRONG_PASSWORD_3;
- }
- }
-
- bool reset() {
- clearMasterKeys();
- setState(STATE_UNINITIALIZED);
-
- DIR* dir = opendir(".");
- struct dirent* file;
-
- if (!dir) {
- return false;
- }
- while ((file = readdir(dir)) != NULL) {
- unlink(file->d_name);
- }
- closedir(dir);
- return true;
- }
-
- bool isEmpty() {
- DIR* dir = opendir(".");
- struct dirent* file;
- if (!dir) {
- return true;
- }
- bool result = true;
- while ((file = readdir(dir)) != NULL) {
- if (isKeyFile(file->d_name)) {
- result = false;
- break;
- }
- }
- closedir(dir);
- return result;
- }
-
- void lock() {
- clearMasterKeys();
- setState(STATE_LOCKED);
- }
-
- ResponseCode get(const char* filename, Blob* keyBlob) {
- return keyBlob->decryptBlob(filename, &mMasterKeyDecryption);
- }
-
- ResponseCode put(const char* filename, Blob* keyBlob) {
- return keyBlob->encryptBlob(filename, &mMasterKeyEncryption, mEntropy);
- }
-
-private:
- static const char* MASTER_KEY_FILE;
- static const int MASTER_KEY_SIZE_BYTES = 16;
- static const int MASTER_KEY_SIZE_BITS = MASTER_KEY_SIZE_BYTES * 8;
-
- static const int MAX_RETRY = 4;
- static const size_t SALT_SIZE = 16;
-
- Entropy* mEntropy;
-
- State mState;
- int8_t mRetry;
-
- uint8_t mMasterKey[MASTER_KEY_SIZE_BYTES];
- uint8_t mSalt[SALT_SIZE];
-
- AES_KEY mMasterKeyEncryption;
- AES_KEY mMasterKeyDecryption;
-
- void setState(State state) {
- mState = state;
- if (mState == STATE_NO_ERROR || mState == STATE_UNINITIALIZED) {
- mRetry = MAX_RETRY;
- }
- }
-
- bool generateSalt() {
- return mEntropy->generate_random_data(mSalt, sizeof(mSalt));
- }
-
- bool generateMasterKey() {
- if (!mEntropy->generate_random_data(mMasterKey, sizeof(mMasterKey))) {
- return false;
- }
- if (!generateSalt()) {
- return false;
- }
- return true;
- }
-
- void setupMasterKeys() {
- AES_set_encrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyEncryption);
- AES_set_decrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyDecryption);
- setState(STATE_NO_ERROR);
- }
-
- void clearMasterKeys() {
- memset(mMasterKey, 0, sizeof(mMasterKey));
- memset(mSalt, 0, sizeof(mSalt));
- memset(&mMasterKeyEncryption, 0, sizeof(mMasterKeyEncryption));
- memset(&mMasterKeyDecryption, 0, sizeof(mMasterKeyDecryption));
- }
-
- static void generateKeyFromPassword(uint8_t* key, ssize_t keySize, Value* pw, uint8_t* salt) {
- size_t saltSize;
- if (salt != NULL) {
- saltSize = SALT_SIZE;
- } else {
- // pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found
- salt = (uint8_t*) "keystore";
- // sizeof = 9, not strlen = 8
- saltSize = sizeof("keystore");
- }
- PKCS5_PBKDF2_HMAC_SHA1((char*) pw->value, pw->length, salt, saltSize, 8192, keySize, key);
- }
-
- static bool isKeyFile(const char* filename) {
- return ((strcmp(filename, MASTER_KEY_FILE) != 0)
- && (strcmp(filename, ".") != 0)
- && (strcmp(filename, "..") != 0));
- }
-};
-
-const char* KeyStore::MASTER_KEY_FILE = ".masterkey";
-
-/* Here is the protocol used in both requests and responses:
- * code [length_1 message_1 ... length_n message_n] end-of-file
- * where code is one byte long and lengths are unsigned 16-bit integers in
- * network order. Thus the maximum length of a message is 65535 bytes. */
-
-static int recv_code(int sock, int8_t* code) {
- return recv(sock, code, 1, 0) == 1;
-}
-
-static int recv_message(int sock, uint8_t* message, int length) {
- uint8_t bytes[2];
- if (recv(sock, &bytes[0], 1, 0) != 1 ||
- recv(sock, &bytes[1], 1, 0) != 1) {
- return -1;
- } else {
- int offset = bytes[0] << 8 | bytes[1];
- if (length < offset) {
- return -1;
- }
- length = offset;
- offset = 0;
- while (offset < length) {
- int n = recv(sock, &message[offset], length - offset, 0);
- if (n <= 0) {
- return -1;
- }
- offset += n;
- }
- }
- return length;
-}
-
-static int recv_end_of_file(int sock) {
- uint8_t byte;
- return recv(sock, &byte, 1, 0) == 0;
-}
-
-static void send_code(int sock, int8_t code) {
- send(sock, &code, 1, 0);
-}
-
-static void send_message(int sock, uint8_t* message, int length) {
- uint16_t bytes = htons(length);
- send(sock, &bytes, 2, 0);
- send(sock, message, length, 0);
-}
-
-/* Here are the actions. Each of them is a function without arguments. All
- * information is defined in global variables, which are set properly before
- * performing an action. The number of parameters required by each action is
- * fixed and defined in a table. If the return value of an action is positive,
- * it will be treated as a response code and transmitted to the client. Note
- * that the lengths of parameters are checked when they are received, so
- * boundary checks on parameters are omitted. */
-
-static const ResponseCode NO_ERROR_RESPONSE_CODE_SENT = (ResponseCode) 0;
-
-static ResponseCode test(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
- return (ResponseCode) keyStore->getState();
-}
-
-static ResponseCode get(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) {
- char filename[NAME_MAX];
- encode_key(filename, uid, keyName);
- Blob keyBlob;
- ResponseCode responseCode = keyStore->get(filename, &keyBlob);
- if (responseCode != NO_ERROR) {
- return responseCode;
- }
- send_code(sock, NO_ERROR);
- send_message(sock, keyBlob.getValue(), keyBlob.getLength());
- return NO_ERROR_RESPONSE_CODE_SENT;
-}
-
-static ResponseCode insert(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value* val) {
- char filename[NAME_MAX];
- encode_key(filename, uid, keyName);
- Blob keyBlob(val->value, val->length, NULL, 0);
- return keyStore->put(filename, &keyBlob);
-}
-
-static ResponseCode del(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) {
- char filename[NAME_MAX];
- encode_key(filename, uid, keyName);
- return (unlink(filename) && errno != ENOENT) ? SYSTEM_ERROR : NO_ERROR;
-}
-
-static ResponseCode exist(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) {
- char filename[NAME_MAX];
- encode_key(filename, uid, keyName);
- if (access(filename, R_OK) == -1) {
- return (errno != ENOENT) ? SYSTEM_ERROR : KEY_NOT_FOUND;
- }
- return NO_ERROR;
-}
-
-static ResponseCode saw(KeyStore* keyStore, int sock, uid_t uid, Value* keyPrefix, Value*) {
- DIR* dir = opendir(".");
- if (!dir) {
- return SYSTEM_ERROR;
- }
- char filename[NAME_MAX];
- int n = encode_key(filename, uid, keyPrefix);
- send_code(sock, NO_ERROR);
-
- struct dirent* file;
- while ((file = readdir(dir)) != NULL) {
- if (!strncmp(filename, file->d_name, n)) {
- char* p = &file->d_name[n];
- keyPrefix->length = decode_key(keyPrefix->value, p, strlen(p));
- send_message(sock, keyPrefix->value, keyPrefix->length);
- }
- }
- closedir(dir);
- return NO_ERROR_RESPONSE_CODE_SENT;
-}
-
-static ResponseCode reset(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
- return keyStore->reset() ? NO_ERROR : SYSTEM_ERROR;
-}
-
-/* Here is the history. To improve the security, the parameters to generate the
- * master key has been changed. To make a seamless transition, we update the
- * file using the same password when the user unlock it for the first time. If
- * any thing goes wrong during the transition, the new file will not overwrite
- * the old one. This avoids permanent damages of the existing data. */
-
-static ResponseCode password(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value*) {
- switch (keyStore->getState()) {
- case STATE_UNINITIALIZED: {
- // generate master key, encrypt with password, write to file, initialize mMasterKey*.
- return keyStore->initialize(pw);
- }
- case STATE_NO_ERROR: {
- // rewrite master key with new password.
- return keyStore->writeMasterKey(pw);
- }
- case STATE_LOCKED: {
- // read master key, decrypt with password, initialize mMasterKey*.
- return keyStore->readMasterKey(pw);
- }
- }
- return SYSTEM_ERROR;
-}
-
-static ResponseCode lock(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
- keyStore->lock();
- return NO_ERROR;
-}
-
-static ResponseCode unlock(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value* unused) {
- return password(keyStore, sock, uid, pw, unused);
-}
-
-static ResponseCode zero(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) {
- return keyStore->isEmpty() ? KEY_NOT_FOUND : NO_ERROR;
-}
-
-/* Here are the permissions, actions, users, and the main function. */
-
-enum perm {
- TEST = 1,
- GET = 2,
- INSERT = 4,
- DELETE = 8,
- EXIST = 16,
- SAW = 32,
- RESET = 64,
- PASSWORD = 128,
- LOCK = 256,
- UNLOCK = 512,
- ZERO = 1024,
-};
-
-static const int MAX_PARAM = 2;
-
-static const State STATE_ANY = (State) 0;
-
-static struct action {
- ResponseCode (*run)(KeyStore* keyStore, int sock, uid_t uid, Value* param1, Value* param2);
- int8_t code;
- State state;
- uint32_t perm;
- int lengths[MAX_PARAM];
-} actions[] = {
- {test, 't', STATE_ANY, TEST, {0, 0}},
- {get, 'g', STATE_NO_ERROR, GET, {KEY_SIZE, 0}},
- {insert, 'i', STATE_NO_ERROR, INSERT, {KEY_SIZE, VALUE_SIZE}},
- {del, 'd', STATE_ANY, DELETE, {KEY_SIZE, 0}},
- {exist, 'e', STATE_ANY, EXIST, {KEY_SIZE, 0}},
- {saw, 's', STATE_ANY, SAW, {KEY_SIZE, 0}},
- {reset, 'r', STATE_ANY, RESET, {0, 0}},
- {password, 'p', STATE_ANY, PASSWORD, {PASSWORD_SIZE, 0}},
- {lock, 'l', STATE_NO_ERROR, LOCK, {0, 0}},
- {unlock, 'u', STATE_LOCKED, UNLOCK, {PASSWORD_SIZE, 0}},
- {zero, 'z', STATE_ANY, ZERO, {0, 0}},
- {NULL, 0 , STATE_ANY, 0, {0, 0}},
-};
-
-static struct user {
- uid_t uid;
- uid_t euid;
- uint32_t perms;
-} users[] = {
- {AID_SYSTEM, ~0, ~0},
- {AID_VPN, AID_SYSTEM, GET},
- {AID_WIFI, AID_SYSTEM, GET},
- {AID_ROOT, AID_SYSTEM, GET},
- {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW},
-};
-
-static ResponseCode process(KeyStore* keyStore, int sock, uid_t uid, int8_t code) {
- struct user* user = users;
- struct action* action = actions;
- int i;
-
- while (~user->uid && user->uid != uid) {
- ++user;
- }
- while (action->code && action->code != code) {
- ++action;
- }
- if (!action->code) {
- return UNDEFINED_ACTION;
- }
- if (!(action->perm & user->perms)) {
- return PERMISSION_DENIED;
- }
- if (action->state != STATE_ANY && action->state != keyStore->getState()) {
- return (ResponseCode) keyStore->getState();
- }
- if (~user->euid) {
- uid = user->euid;
- }
- Value params[MAX_PARAM];
- for (i = 0; i < MAX_PARAM && action->lengths[i] != 0; ++i) {
- params[i].length = recv_message(sock, params[i].value, action->lengths[i]);
- if (params[i].length < 0) {
- return PROTOCOL_ERROR;
- }
- }
- if (!recv_end_of_file(sock)) {
- return PROTOCOL_ERROR;
- }
- return action->run(keyStore, sock, uid, &params[0], &params[1]);
-}
-
-int main(int argc, char* argv[]) {
- int controlSocket = android_get_control_socket("keystore");
- if (argc < 2) {
- ALOGE("A directory must be specified!");
- return 1;
- }
- if (chdir(argv[1]) == -1) {
- ALOGE("chdir: %s: %s", argv[1], strerror(errno));
- return 1;
- }
-
- Entropy entropy;
- if (!entropy.open()) {
- return 1;
- }
- if (listen(controlSocket, 3) == -1) {
- ALOGE("listen: %s", strerror(errno));
- return 1;
- }
-
- signal(SIGPIPE, SIG_IGN);
-
- KeyStore keyStore(&entropy);
- int sock;
- while ((sock = accept(controlSocket, NULL, 0)) != -1) {
- struct timeval tv;
- tv.tv_sec = 3;
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
-
- struct ucred cred;
- socklen_t size = sizeof(cred);
- int credResult = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &cred, &size);
- if (credResult != 0) {
- ALOGW("getsockopt: %s", strerror(errno));
- } else {
- int8_t request;
- if (recv_code(sock, &request)) {
- State old_state = keyStore.getState();
- ResponseCode response = process(&keyStore, sock, cred.uid, request);
- if (response == NO_ERROR_RESPONSE_CODE_SENT) {
- response = NO_ERROR;
- } else {
- send_code(sock, response);
- }
- ALOGI("uid: %d action: %c -> %d state: %d -> %d retry: %d",
- cred.uid,
- request, response,
- old_state, keyStore.getState(),
- keyStore.getRetry());
- }
- }
- close(sock);
- }
- ALOGE("accept: %s", strerror(errno));
- return 1;
-}
diff --git a/cmds/keystore/keystore.h b/cmds/keystore/keystore.h
deleted file mode 100644
index 5ae3d24acee0..000000000000
--- a/cmds/keystore/keystore.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef __KEYSTORE_H__
-#define __KEYSTORE_H__
-
-// note state values overlap with ResponseCode for the purposes of the state() API
-enum State {
- STATE_NO_ERROR = 1,
- STATE_LOCKED = 2,
- STATE_UNINITIALIZED = 3,
-};
-
-enum ResponseCode {
- NO_ERROR = STATE_NO_ERROR, // 1
- LOCKED = STATE_LOCKED, // 2
- UNINITIALIZED = STATE_UNINITIALIZED, // 3
- SYSTEM_ERROR = 4,
- PROTOCOL_ERROR = 5,
- PERMISSION_DENIED = 6,
- KEY_NOT_FOUND = 7,
- VALUE_CORRUPTED = 8,
- UNDEFINED_ACTION = 9,
- WRONG_PASSWORD_0 = 10,
- WRONG_PASSWORD_1 = 11,
- WRONG_PASSWORD_2 = 12,
- WRONG_PASSWORD_3 = 13, // MAX_RETRY = 4
-};
-
-#endif
diff --git a/cmds/keystore/keystore_cli.cpp b/cmds/keystore/keystore_cli.cpp
deleted file mode 100644
index dcd3bcb8fc01..000000000000
--- a/cmds/keystore/keystore_cli.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <cutils/sockets.h>
-
-#include "keystore.h"
-
-static const char* responses[] = {
- NULL,
- /* [NO_ERROR] = */ "No error",
- /* [LOCKED] = */ "Locked",
- /* [UNINITIALIZED] = */ "Uninitialized",
- /* [SYSTEM_ERROR] = */ "System error",
- /* [PROTOCOL_ERROR] = */ "Protocol error",
- /* [PERMISSION_DENIED] = */ "Permission denied",
- /* [KEY_NOT_FOUND] = */ "Key not found",
- /* [VALUE_CORRUPTED] = */ "Value corrupted",
- /* [UNDEFINED_ACTION] = */ "Undefined action",
- /* [WRONG_PASSWORD] = */ "Wrong password (last chance)",
- /* [WRONG_PASSWORD + 1] = */ "Wrong password (2 tries left)",
- /* [WRONG_PASSWORD + 2] = */ "Wrong password (3 tries left)",
- /* [WRONG_PASSWORD + 3] = */ "Wrong password (4 tries left)",
-};
-
-int main(int argc, char* argv[])
-{
- if (argc < 2) {
- printf("Usage: %s action [parameter ...]\n", argv[0]);
- return 0;
- }
-
- int sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED,
- SOCK_STREAM);
- if (sock == -1) {
- puts("Failed to connect");
- return 1;
- }
-
- send(sock, argv[1], 1, 0);
- uint8_t bytes[65536];
- for (int i = 2; i < argc; ++i) {
- uint16_t length = strlen(argv[i]);
- bytes[0] = length >> 8;
- bytes[1] = length;
- send(sock, &bytes, 2, 0);
- send(sock, argv[i], length, 0);
- }
- shutdown(sock, SHUT_WR);
-
- uint8_t code;
- if (recv(sock, &code, 1, 0) != 1) {
- puts("Failed to receive");
- return 1;
- }
- printf("%d %s\n", code , responses[code] ? responses[code] : "Unknown");
- int i;
- while ((i = recv(sock, &bytes[0], 1, 0)) == 1) {
- int length;
- int offset;
- if ((i = recv(sock, &bytes[1], 1, 0)) != 1) {
- puts("Failed to receive");
- return 1;
- }
- length = bytes[0] << 8 | bytes[1];
- for (offset = 0; offset < length; offset += i) {
- i = recv(sock, &bytes[offset], length - offset, 0);
- if (i <= 0) {
- puts("Failed to receive");
- return 1;
- }
- }
- fwrite(bytes, 1, length, stdout);
- puts("");
- }
- return 0;
-}
diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h
deleted file mode 100644
index 4b4923e5c736..000000000000
--- a/cmds/keystore/keystore_get.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef __KEYSTORE_GET_H__
-#define __KEYSTORE_GET_H__
-
-#include <stdio.h>
-#include <stdint.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <cutils/sockets.h>
-
-#define KEYSTORE_MESSAGE_SIZE 65535
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* This function is provided for native components to get values from keystore.
- * Users are required to link against libcutils. Keys and values are 8-bit safe.
- * The first two arguments are the key and its length. The third argument
- * specifies the buffer to store the retrieved value, which must be an array of
- * KEYSTORE_MESSAGE_SIZE bytes. This function returns the length of the value or
- * -1 if an error happens. */
-static int keystore_get(const char *key, int length, char *value)
-{
- uint8_t bytes[2] = {length >> 8, length};
- uint8_t code = 'g';
- int sock;
-
- if (length < 0 || length > KEYSTORE_MESSAGE_SIZE) {
- return -1;
- }
- sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED,
- SOCK_STREAM);
- if (sock == -1) {
- return -1;
- }
- if (send(sock, &code, 1, 0) == 1 && send(sock, bytes, 2, 0) == 2 &&
- send(sock, key, length, 0) == length && shutdown(sock, SHUT_WR) == 0 &&
- recv(sock, &code, 1, 0) == 1 && code == /* NO_ERROR */ 1 &&
- recv(sock, &bytes[0], 1, 0) == 1 && recv(sock, &bytes[1], 1, 0) == 1) {
- int offset = 0;
- length = bytes[0] << 8 | bytes[1];
- while (offset < length) {
- int n = recv(sock, &value[offset], length - offset, 0);
- if (n <= 0) {
- length = -1;
- break;
- }
- offset += n;
- }
- } else {
- length = -1;
- }
-
- close(sock);
- return length;
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/cmds/keystore/test-keystore b/cmds/keystore/test-keystore
deleted file mode 100755
index 3be51b3e7bfb..000000000000
--- a/cmds/keystore/test-keystore
+++ /dev/null
@@ -1,273 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2011, 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.
-
-set -e
-
-prefix=$0
-log_file=$prefix.log
-baseline_file=$prefix.baseline
-
-function cleanup_output() {
- rm -f $log_file
- rm -f $baseline_file
-}
-
-function log() {
- echo "$@"
- append $log_file \# "$@"
- append $baseline_file \# "$@"
-}
-
-function expect() {
- append $baseline_file "$@"
-}
-
-function append() {
- declare -r file=$1
- shift
- echo "$@" >> $file
-}
-
-function run() {
- # strip out carriage returns from adb
- # strip out date/time from ls -l
- "$@" | tr --delete '\r' | sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2} +[0-9]{1,2}:[0-9]{2} //' >> $log_file
-}
-
-function keystore() {
- declare -r user=$1
- shift
- run adb shell su $user keystore_cli "$@"
-}
-
-function list_keystore_directory() {
- run adb shell ls -al /data/misc/keystore
-}
-
-function compare() {
- log "comparing $baseline_file and $log_file"
- diff $baseline_file $log_file || (log $tag FAILED && exit 1)
-}
-
-function test_basic() {
-
- #
- # reset
- #
- log "reset keystore as system user"
- keystore system r
- expect "1 No error"
- list_keystore_directory
-
- #
- # basic tests as system/root
- #
- log "root does not have permission to run test"
- keystore root t
- expect "6 Permission denied"
-
- log "but system user does"
- keystore system t
- expect "3 Uninitialized"
- list_keystore_directory
-
- log "password is now bar"
- keystore system p bar
- expect "1 No error"
- list_keystore_directory
- expect "-rw------- keystore keystore 84 .masterkey"
-
- log "no error implies initialized and unlocked"
- keystore system t
- expect "1 No error"
-
- log "saw with no argument"
- keystore system s
- expect "5 Protocol error"
-
- log "saw nothing"
- keystore system s ""
- expect "1 No error"
-
- log "add key baz"
- keystore system i baz quux
- expect "1 No error"
-
- log "1000 is uid of system"
- list_keystore_directory
- expect "-rw------- keystore keystore 84 .masterkey"
- expect "-rw------- keystore keystore 52 1000_baz"
-
- log "saw baz"
- keystore system s ""
- expect "1 No error"
- expect "baz"
-
- log "get baz"
- keystore system g baz
- expect "1 No error"
- expect "quux"
-
- log "root can read system user keys (as can wifi or vpn users)"
- keystore root g baz
- expect "1 No error"
- expect "quux"
-
- #
- # app user tests
- #
-
- # app_0 has uid 10000, as seen below
- log "other uses cannot see the system keys"
- keystore app_0 g baz
- expect "7 Key not found"
-
- log "app user cannot use reset, password, lock, unlock"
- keystore app_0 r
- expect "6 Permission denied"
- keystore app_0 p
- expect "6 Permission denied"
- keystore app_0 l
- expect "6 Permission denied"
- keystore app_0 u
- expect "6 Permission denied"
-
- log "install app_0 key"
- keystore app_0 i 0x deadbeef
- expect 1 No error
- list_keystore_directory
- expect "-rw------- keystore keystore 84 .masterkey"
- expect "-rw------- keystore keystore 52 10000_0x"
- expect "-rw------- keystore keystore 52 1000_baz"
-
- log "get with no argument"
- keystore app_0 g
- expect "5 Protocol error"
-
- keystore app_0 g 0x
- expect "1 No error"
- expect "deadbeef"
-
- keystore app_0 i fred barney
- expect "1 No error"
-
- keystore app_0 s ""
- expect "1 No error"
- expect "0x"
- expect "fred"
-
- log "note that saw returns the suffix of prefix matches"
- keystore app_0 s fr # fred
- expect "1 No error"
- expect "ed" # fred
-
- #
- # lock tests
- #
- log "lock the store as system"
- keystore system l
- expect "1 No error"
- keystore system t
- expect "2 Locked"
-
- log "saw works while locked"
- keystore app_0 s ""
- expect "1 No error"
- expect "0x"
- expect "fred"
-
- log "...but cannot read keys..."
- keystore app_0 g 0x
- expect "2 Locked"
-
- log "...but they can be deleted."
- keystore app_0 e 0x
- expect "1 No error"
- keystore app_0 d 0x
- expect "1 No error"
- keystore app_0 e 0x
- expect "7 Key not found"
-
- #
- # password
- #
- log "wrong password"
- keystore system u foo
- expect "13 Wrong password (4 tries left)"
- log "right password"
- keystore system u bar
- expect "1 No error"
-
- log "make the password foo"
- keystore system p foo
- expect "1 No error"
-
- #
- # final reset
- #
- log "reset wipes everything for all users"
- keystore system r
- expect "1 No error"
- list_keystore_directory
-
- keystore system t
- expect "3 Uninitialized"
-
-}
-
-function test_4599735() {
- # http://b/4599735
- log "start regression test for b/4599735"
- keystore system r
- expect "1 No error"
-
- keystore system p foo
- expect "1 No error"
-
- keystore system i baz quux
- expect "1 No error"
-
- keystore root g baz
- expect "1 No error"
- expect "quux"
-
- keystore system l
- expect "1 No error"
-
- keystore system p foo
- expect "1 No error"
-
- log "after unlock, regression led to result of '8 Value corrupted'"
- keystore root g baz
- expect "1 No error"
- expect "quux"
-
- keystore system r
- expect "1 No error"
- log "end regression test for b/4599735"
-}
-
-function main() {
- cleanup_output
- log $tag START
- test_basic
- test_4599735
- compare
- log $tag PASSED
- cleanup_output
-}
-
-main
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index cfc2d1611628..71c5622ec382 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -43,6 +43,8 @@ static struct {
{ AID_RADIO, "isms" },
{ AID_RADIO, "iphonesubinfo" },
{ AID_RADIO, "simphonebook" },
+ { AID_MEDIA, "common_time.clock" },
+ { AID_MEDIA, "common_time.config" },
};
void *svcmgr_handle;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index cc1efb9acd98..6fbeee36e03a 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -522,8 +522,7 @@ public class ValueAnimator extends Animator {
* animations possible.
*
*/
- private static class AnimationHandler extends Handler
- implements Choreographer.OnAnimateListener {
+ private static class AnimationHandler extends Handler implements Runnable {
// The per-thread list of all active animations
private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
@@ -539,7 +538,7 @@ public class ValueAnimator extends Animator {
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
private final Choreographer mChoreographer;
- private boolean mIsChoreographed;
+ private boolean mAnimationScheduled;
private AnimationHandler() {
mChoreographer = Choreographer.getInstance();
@@ -644,22 +643,17 @@ public class ValueAnimator extends Animator {
// If there are still active or delayed animations, schedule a future call to
// onAnimate to process the next frame of the animations.
- if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
- if (!mIsChoreographed) {
- mIsChoreographed = true;
- mChoreographer.addOnAnimateListener(this);
- }
- mChoreographer.scheduleAnimation();
- } else {
- if (mIsChoreographed) {
- mIsChoreographed = false;
- mChoreographer.removeOnAnimateListener(this);
- }
+ if (!mAnimationScheduled
+ && (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) {
+ mChoreographer.postAnimationCallback(this);
+ mAnimationScheduled = true;
}
}
+ // Called by the Choreographer.
@Override
- public void onAnimate() {
+ public void run() {
+ mAnimationScheduled = false;
doAnimationFrame();
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 5a364665768d..24079a5d8a2f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -581,6 +581,21 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String name = data.readString();
+ IBinder token = data.readStrongBinder();
+ ContentProviderHolder cph = getContentProviderExternal(name, token);
+ reply.writeNoException();
+ if (cph != null) {
+ reply.writeInt(1);
+ cph.writeToParcel(reply, 0);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case PUBLISH_CONTENT_PROVIDERS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
@@ -601,7 +616,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
-
+
+ case REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String name = data.readString();
+ IBinder token = data.readStrongBinder();
+ removeContentProviderExternal(name, token);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
ComponentName comp = ComponentName.CREATOR.createFromParcel(data);
@@ -2178,6 +2202,25 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return cph;
}
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token)
+ throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(name);
+ data.writeStrongBinder(token);
+ mRemote.transact(GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ ContentProviderHolder cph = null;
+ if (res != 0) {
+ cph = ContentProviderHolder.CREATOR.createFromParcel(reply);
+ }
+ data.recycle();
+ reply.recycle();
+ return cph;
+ }
public void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) throws RemoteException
{
@@ -2204,7 +2247,19 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
-
+
+ public void removeContentProviderExternal(String name, IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(name);
+ data.writeStrongBinder(token);
+ mRemote.transact(REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
public PendingIntent getRunningServiceControlPanel(ComponentName service)
throws RemoteException
{
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ebf692a99080..6d5cce5efdc3 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -911,6 +911,19 @@ class ContextImpl extends Context {
}
}
+ /** @hide */
+ @Override
+ public void sendBroadcast(Intent intent, int userId) {
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
+ intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false,
+ userId);
+ } catch (RemoteException e) {
+ }
+ }
+
@Override
public void sendBroadcast(Intent intent, String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 7deb615f6f9f..53a71db82b14 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -150,8 +150,11 @@ public interface IActivityManager extends IInterface {
Bitmap thumbnail, CharSequence description) throws RemoteException;
public ContentProviderHolder getContentProvider(IApplicationThread caller,
String name) throws RemoteException;
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token)
+ throws RemoteException;
public void removeContentProvider(IApplicationThread caller,
String name) throws RemoteException;
+ public void removeContentProviderExternal(String name, IBinder token) throws RemoteException;
public void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) throws RemoteException;
public PendingIntent getRunningServiceControlPanel(ComponentName service)
@@ -415,7 +418,7 @@ public interface IActivityManager extends IInterface {
source.readStrongBinder());
noReleaseNeeded = source.readInt() != 0;
}
- };
+ }
/** Information returned after waiting for an activity start. */
public static class WaitResult implements Parcelable {
@@ -601,4 +604,6 @@ public interface IActivityManager extends IInterface {
int SHOW_BOOT_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+137;
int DISMISS_KEYGUARD_ON_NEXT_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+138;
int KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+139;
+ int GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+140;
+ int REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+141;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6d4cdaee1060..a1198dedef13 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -905,6 +905,16 @@ public abstract class Context {
public abstract void sendBroadcast(Intent intent);
/**
+ * Same as #sendBroadcast(Intent intent), but for a specific user. Used by the system only.
+ * @param intent the intent to broadcast
+ * @param userId user to send the intent to
+ * @hide
+ */
+ public void sendBroadcast(Intent intent, int userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Broadcast the given intent to all interested BroadcastReceivers, allowing
* an optional required permission to be enforced. This
* call is asynchronous; it returns immediately, and you will continue
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index cd8d87f9d057..5ba9dccd36b3 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -294,6 +294,12 @@ public class ContextWrapper extends Context {
mBase.sendBroadcast(intent);
}
+ /** @hide */
+ @Override
+ public void sendBroadcast(Intent intent, int userId) {
+ mBase.sendBroadcast(intent, userId);
+ }
+
@Override
public void sendBroadcast(Intent intent, String receiverPermission) {
mBase.sendBroadcast(intent, receiverPermission);
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index f94d3207f23d..06c6c6ec248f 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -25,15 +25,17 @@ import javax.net.ssl.DefaultHostnameVerifier;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
+import javax.net.ssl.X509TrustManager;
import org.apache.harmony.security.provider.cert.X509CertImpl;
import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl;
+import org.apache.harmony.xnet.provider.jsse.TrustManagerImpl;
/**
* Class responsible for all server certificate validation functionality
*
* {@hide}
*/
-class CertificateChainValidator {
+public class CertificateChainValidator {
/**
* The singleton instance of the certificate chain validator
@@ -122,6 +124,18 @@ class CertificateChainValidator {
}
/**
+ * Handles updates to credential storage.
+ */
+ public static void handleTrustStorageUpdate() {
+
+ X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager();
+ if( x509TrustManager instanceof TrustManagerImpl ) {
+ TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager;
+ trustManager.handleTrustStorageUpdate();
+ }
+ }
+
+ /**
* Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates.
* Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs.
* @param chain the cert chain in X509 cert format.
diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java
index 21736aa3d9a2..73f3d7cc339a 100644
--- a/core/java/android/net/http/HttpResponseCache.java
+++ b/core/java/android/net/http/HttpResponseCache.java
@@ -22,8 +22,10 @@ import java.io.File;
import java.io.IOException;
import java.net.CacheRequest;
import java.net.CacheResponse;
+import java.net.ExtendedResponseCache;
import java.net.HttpURLConnection;
import java.net.ResponseCache;
+import java.net.ResponseSource;
import java.net.URI;
import java.net.URLConnection;
import java.util.List;
@@ -149,7 +151,8 @@ import org.apache.http.impl.client.DefaultHttpClient;
* } catch (Exception httpResponseCacheNotAvailable) {
* }}</pre>
*/
-public final class HttpResponseCache extends ResponseCache implements Closeable {
+public final class HttpResponseCache extends ResponseCache
+ implements Closeable, ExtendedResponseCache {
private final libcore.net.http.HttpResponseCache delegate;
@@ -260,6 +263,21 @@ public final class HttpResponseCache extends ResponseCache implements Closeable
return delegate.getRequestCount();
}
+ /** @hide */
+ @Override public void trackResponse(ResponseSource source) {
+ delegate.trackResponse(source);
+ }
+
+ /** @hide */
+ @Override public void trackConditionalCacheHit() {
+ delegate.trackConditionalCacheHit();
+ }
+
+ /** @hide */
+ @Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) {
+ delegate.update(conditionalCacheHit, connection);
+ }
+
/**
* Uninstalls the cache and releases any active resources. Stored contents
* will remain on the filesystem.
diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java
new file mode 100644
index 000000000000..3a1da9761b6b
--- /dev/null
+++ b/core/java/android/os/CommonClock.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.util.NoSuchElementException;
+import static libcore.io.OsConstants.*;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.CommonTimeUtils;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * Used for accessing the android common time service's common clock and receiving notifications
+ * about common time synchronization status changes.
+ * @hide
+ */
+public class CommonClock {
+ /**
+ * Sentinel value returned by {@link #getTime()} and {@link #getEstimatedError()} when the
+ * common time service is not able to determine the current common time due to a lack of
+ * synchronization.
+ */
+ public static final long TIME_NOT_SYNCED = -1;
+
+ /**
+ * Sentinel value returned by {@link #getTimelineId()} when the common time service is not
+ * currently synced to any timeline.
+ */
+ public static final long INVALID_TIMELINE_ID = 0;
+
+ /**
+ * Sentinel value returned by {@link #getEstimatedError()} when the common time service is not
+ * currently synced to any timeline.
+ */
+ public static final int ERROR_ESTIMATE_UNKNOWN = 0x7FFFFFFF;
+
+ /**
+ * Value used by {@link #getState()} to indicate that there was an internal error while
+ * attempting to determine the state of the common time service.
+ */
+ public static final int STATE_INVALID = -1;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its initial
+ * state and attempting to find the current timeline master, if any. The service will
+ * transition to either {@link #STATE_CLIENT} if it finds an active master, or to
+ * {@link #STATE_MASTER} if no active master is found and this client becomes the master of a
+ * new timeline.
+ */
+ public static final int STATE_INITIAL = 0;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its client
+ * state and is synchronizing its time to a different timeline master on the network.
+ */
+ public static final int STATE_CLIENT = 1;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its master
+ * state and is serving as the timeline master for other common time service clients on the
+ * network.
+ */
+ public static final int STATE_MASTER = 2;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its Ronin
+ * state. Common time service instances in the client state enter the Ronin state after their
+ * timeline master becomes unreachable on the network. Common time services who enter the Ronin
+ * state will begin a new master election for the timeline they were recently clients of. As
+ * clients detect they are not the winner and drop out of the election, they will transition to
+ * the {@link #STATE_WAIT_FOR_ELECTION} state. When there is only one client remaining in the
+ * election, it will assume ownership of the timeline and transition to the
+ * {@link #STATE_MASTER} state. During the election, all clients will allow their timeline to
+ * drift without applying correction.
+ */
+ public static final int STATE_RONIN = 3;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is waiting for a
+ * master election to conclude and for the new master to announce itself before transitioning to
+ * the {@link #STATE_CLIENT} state. If no new master announces itself within the timeout
+ * threshold, the time service will transition back to the {@link #STATE_RONIN} state in order
+ * to restart the election.
+ */
+ public static final int STATE_WAIT_FOR_ELECTION = 4;
+
+ /**
+ * Name of the underlying native binder service
+ */
+ public static final String SERVICE_NAME = "common_time.clock";
+
+ /**
+ * Class constructor.
+ * @throws android.os.RemoteException
+ */
+ public CommonClock()
+ throws RemoteException {
+ mRemote = ServiceManager.getService(SERVICE_NAME);
+ if (null == mRemote)
+ throw new RemoteException();
+
+ mInterfaceDesc = mRemote.getInterfaceDescriptor();
+ mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc);
+ mRemote.linkToDeath(mDeathHandler, 0);
+ registerTimelineChangeListener();
+ }
+
+ /**
+ * Handy class factory method.
+ */
+ static public CommonClock create() {
+ CommonClock retVal;
+
+ try {
+ retVal = new CommonClock();
+ }
+ catch (RemoteException e) {
+ retVal = null;
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Release all native resources held by this {@link android.os.CommonClock} instance. Once
+ * resources have been released, the {@link android.os.CommonClock} instance is disconnected from
+ * the native service and will throw a {@link android.os.RemoteException} if any of its
+ * methods are called. Clients should always call release on their client instances before
+ * releasing their last Java reference to the instance. Failure to do this will cause
+ * non-deterministic native resource reclamation and may cause the common time service to remain
+ * active on the network for longer than it should.
+ */
+ public void release() {
+ unregisterTimelineChangeListener();
+ if (null != mRemote) {
+ try {
+ mRemote.unlinkToDeath(mDeathHandler, 0);
+ }
+ catch (NoSuchElementException e) { }
+ mRemote = null;
+ }
+ mUtils = null;
+ }
+
+ /**
+ * Gets the common clock's current time.
+ *
+ * @return a signed 64-bit value representing the current common time in microseconds, or the
+ * special value {@link #TIME_NOT_SYNCED} if the common time service is currently not
+ * synchronized.
+ * @throws android.os.RemoteException
+ */
+ public long getTime()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetLong(METHOD_GET_COMMON_TIME, TIME_NOT_SYNCED);
+ }
+
+ /**
+ * Gets the current estimation of common clock's synchronization accuracy from the common time
+ * service.
+ *
+ * @return a signed 32-bit value representing the common time service's estimation of
+ * synchronization accuracy in microseconds, or the special value
+ * {@link #ERROR_ESTIMATE_UNKNOWN} if the common time service is currently not synchronized.
+ * Negative values indicate that the local server estimates that the nominal common time is
+ * behind the local server's time (in other words, the local clock is running fast) Positive
+ * values indicate that the local server estimates that the nominal common time is ahead of the
+ * local server's time (in other words, the local clock is running slow)
+ * @throws android.os.RemoteException
+ */
+ public int getEstimatedError()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetInt(METHOD_GET_ESTIMATED_ERROR, ERROR_ESTIMATE_UNKNOWN);
+ }
+
+ /**
+ * Gets the ID of the timeline the common time service is currently synchronizing its clock to.
+ *
+ * @return a long representing the unique ID of the timeline the common time service is
+ * currently synchronizing with, or {@link #INVALID_TIMELINE_ID} if the common time service is
+ * currently not synchronized.
+ * @throws android.os.RemoteException
+ */
+ public long getTimelineId()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetLong(METHOD_GET_TIMELINE_ID, INVALID_TIMELINE_ID);
+ }
+
+ /**
+ * Gets the current state of this clock's common time service in the the master election
+ * algorithm.
+ *
+ * @return a integer indicating the current state of the this clock's common time service in the
+ * master election algorithm or {@link #STATE_INVALID} if there is an internal error.
+ * @throws android.os.RemoteException
+ */
+ public int getState()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetInt(METHOD_GET_STATE, STATE_INVALID);
+ }
+
+ /**
+ * Gets the IP address and UDP port of the current timeline master.
+ *
+ * @return an InetSocketAddress containing the IP address and UDP port of the current timeline
+ * master, or null if there is no current master.
+ * @throws android.os.RemoteException
+ */
+ public InetSocketAddress getMasterAddr()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ADDRESS);
+ }
+
+ /**
+ * The OnTimelineChangedListener interface defines a method called by the
+ * {@link android.os.CommonClock} instance to indicate that the time synchronization service has
+ * either synchronized with a new timeline, or is no longer a member of any timeline. The
+ * client application can implement this interface and register the listener with the
+ * {@link #setTimelineChangedListener(OnTimelineChangedListener)} method.
+ */
+ public interface OnTimelineChangedListener {
+ /**
+ * Method called when the time service's timeline has changed.
+ *
+ * @param newTimelineId a long which uniquely identifies the timeline the time
+ * synchronization service is now a member of, or {@link #INVALID_TIMELINE_ID} if the the
+ * service is not synchronized to any timeline.
+ */
+ void onTimelineChanged(long newTimelineId);
+ }
+
+ /**
+ * Registers an OnTimelineChangedListener interface.
+ * <p>Call this method with a null listener to stop receiving server death notifications.
+ */
+ public void setTimelineChangedListener(OnTimelineChangedListener listener) {
+ synchronized (mListenerLock) {
+ mTimelineChangedListener = listener;
+ }
+ }
+
+ /**
+ * The OnServerDiedListener interface defines a method called by the
+ * {@link android.os.CommonClock} instance to indicate that the connection to the native media
+ * server has been broken and that the {@link android.os.CommonClock} instance will need to be
+ * released and re-created. The client application can implement this interface and register
+ * the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method.
+ */
+ public interface OnServerDiedListener {
+ /**
+ * Method called when the native media server has died. <p>If the native common time
+ * service encounters a fatal error and needs to restart, the binder connection from the
+ * {@link android.os.CommonClock} instance to the common time service will be broken. To
+ * restore functionality, clients should {@link #release()} their old visualizer and create
+ * a new instance.
+ */
+ void onServerDied();
+ }
+
+ /**
+ * Registers an OnServerDiedListener interface.
+ * <p>Call this method with a null listener to stop receiving server death notifications.
+ */
+ public void setServerDiedListener(OnServerDiedListener listener) {
+ synchronized (mListenerLock) {
+ mServerDiedListener = listener;
+ }
+ }
+
+ protected void finalize() throws Throwable { release(); }
+
+ private void throwOnDeadServer() throws RemoteException {
+ if ((null == mRemote) || (null == mUtils))
+ throw new RemoteException();
+ }
+
+ private final Object mListenerLock = new Object();
+ private OnTimelineChangedListener mTimelineChangedListener = null;
+ private OnServerDiedListener mServerDiedListener = null;
+
+ private IBinder mRemote = null;
+ private String mInterfaceDesc = "";
+ private CommonTimeUtils mUtils;
+
+ private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() {
+ public void binderDied() {
+ synchronized (mListenerLock) {
+ if (null != mServerDiedListener)
+ mServerDiedListener.onServerDied();
+ }
+ }
+ };
+
+ private class TimelineChangedListener extends Binder {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ switch (code) {
+ case METHOD_CBK_ON_TIMELINE_CHANGED:
+ data.enforceInterface(DESCRIPTOR);
+ long timelineId = data.readLong();
+ synchronized (mListenerLock) {
+ if (null != mTimelineChangedListener)
+ mTimelineChangedListener.onTimelineChanged(timelineId);
+ }
+ return true;
+ }
+
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ private static final String DESCRIPTOR = "android.os.ICommonClockListener";
+ };
+
+ private TimelineChangedListener mCallbackTgt = null;
+
+ private void registerTimelineChangeListener() throws RemoteException {
+ if (null != mCallbackTgt)
+ return;
+
+ boolean success = false;
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+ mCallbackTgt = new TimelineChangedListener();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ data.writeStrongBinder(mCallbackTgt);
+ mRemote.transact(METHOD_REGISTER_LISTENER, data, reply, 0);
+ success = (0 == reply.readInt());
+ }
+ catch (RemoteException e) {
+ success = false;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+
+ // Did we catch a remote exception or fail to register our callback target? If so, our
+ // object must already be dead (or be as good as dead). Clear out all of our state so that
+ // our other methods will properly indicate a dead object.
+ if (!success) {
+ mCallbackTgt = null;
+ mRemote = null;
+ mUtils = null;
+ }
+ }
+
+ private void unregisterTimelineChangeListener() {
+ if (null == mCallbackTgt)
+ return;
+
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ data.writeStrongBinder(mCallbackTgt);
+ mRemote.transact(METHOD_UNREGISTER_LISTENER, data, reply, 0);
+ }
+ catch (RemoteException e) { }
+ finally {
+ reply.recycle();
+ data.recycle();
+ mCallbackTgt = null;
+ }
+ }
+
+ private static final int METHOD_IS_COMMON_TIME_VALID = IBinder.FIRST_CALL_TRANSACTION;
+ private static final int METHOD_COMMON_TIME_TO_LOCAL_TIME = METHOD_IS_COMMON_TIME_VALID + 1;
+ private static final int METHOD_LOCAL_TIME_TO_COMMON_TIME = METHOD_COMMON_TIME_TO_LOCAL_TIME + 1;
+ private static final int METHOD_GET_COMMON_TIME = METHOD_LOCAL_TIME_TO_COMMON_TIME + 1;
+ private static final int METHOD_GET_COMMON_FREQ = METHOD_GET_COMMON_TIME + 1;
+ private static final int METHOD_GET_LOCAL_TIME = METHOD_GET_COMMON_FREQ + 1;
+ private static final int METHOD_GET_LOCAL_FREQ = METHOD_GET_LOCAL_TIME + 1;
+ private static final int METHOD_GET_ESTIMATED_ERROR = METHOD_GET_LOCAL_FREQ + 1;
+ private static final int METHOD_GET_TIMELINE_ID = METHOD_GET_ESTIMATED_ERROR + 1;
+ private static final int METHOD_GET_STATE = METHOD_GET_TIMELINE_ID + 1;
+ private static final int METHOD_GET_MASTER_ADDRESS = METHOD_GET_STATE + 1;
+ private static final int METHOD_REGISTER_LISTENER = METHOD_GET_MASTER_ADDRESS + 1;
+ private static final int METHOD_UNREGISTER_LISTENER = METHOD_REGISTER_LISTENER + 1;
+
+ private static final int METHOD_CBK_ON_TIMELINE_CHANGED = IBinder.FIRST_CALL_TRANSACTION;
+}
diff --git a/core/java/android/os/CommonTimeConfig.java b/core/java/android/os/CommonTimeConfig.java
new file mode 100644
index 000000000000..3355ee33d486
--- /dev/null
+++ b/core/java/android/os/CommonTimeConfig.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.NoSuchElementException;
+
+import android.os.CommonTimeUtils;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * Used for configuring and controlling the status of the android common time service.
+ * @hide
+ */
+public class CommonTimeConfig {
+ /**
+ * Successful operation.
+ */
+ public static final int SUCCESS = 0;
+ /**
+ * Unspecified error.
+ */
+ public static final int ERROR = -1;
+ /**
+ * Operation failed due to bad parameter value.
+ */
+ public static final int ERROR_BAD_VALUE = -4;
+ /**
+ * Operation failed due to dead remote object.
+ */
+ public static final int ERROR_DEAD_OBJECT = -7;
+
+ /**
+ * Sentinel value returned by {@link #getMasterElectionGroupId()} when an error occurs trying to
+ * fetch the master election group.
+ */
+ public static final long INVALID_GROUP_ID = -1;
+
+ /**
+ * Name of the underlying native binder service
+ */
+ public static final String SERVICE_NAME = "common_time.config";
+
+ /**
+ * Class constructor.
+ * @throws android.os.RemoteException
+ */
+ public CommonTimeConfig()
+ throws RemoteException {
+ mRemote = ServiceManager.getService(SERVICE_NAME);
+ if (null == mRemote)
+ throw new RemoteException();
+
+ mInterfaceDesc = mRemote.getInterfaceDescriptor();
+ mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc);
+ mRemote.linkToDeath(mDeathHandler, 0);
+ }
+
+ /**
+ * Handy class factory method.
+ */
+ static public CommonTimeConfig create() {
+ CommonTimeConfig retVal;
+
+ try {
+ retVal = new CommonTimeConfig();
+ }
+ catch (RemoteException e) {
+ retVal = null;
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Release all native resources held by this {@link android.os.CommonTimeConfig} instance. Once
+ * resources have been released, the {@link android.os.CommonTimeConfig} instance is
+ * disconnected from the native service and will throw a {@link android.os.RemoteException} if
+ * any of its methods are called. Clients should always call release on their client instances
+ * before releasing their last Java reference to the instance. Failure to do this will cause
+ * non-deterministic native resource reclamation and may cause the common time service to remain
+ * active on the network for longer than it should.
+ */
+ public void release() {
+ if (null != mRemote) {
+ try {
+ mRemote.unlinkToDeath(mDeathHandler, 0);
+ }
+ catch (NoSuchElementException e) { }
+ mRemote = null;
+ }
+ mUtils = null;
+ }
+
+ /**
+ * Gets the current priority of the common time service used in the master election protocol.
+ *
+ * @return an 8 bit value indicating the priority of this common time service relative to other
+ * common time services operating in the same domain.
+ * @throws android.os.RemoteException
+ */
+ public byte getMasterElectionPriority()
+ throws RemoteException {
+ throwOnDeadServer();
+ return (byte)mUtils.transactGetInt(METHOD_GET_MASTER_ELECTION_PRIORITY, -1);
+ }
+
+ /**
+ * Sets the current priority of the common time service used in the master election protocol.
+ *
+ * @param priority priority of the common time service used in the master election protocol.
+ * Lower numbers are lower priority.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setMasterElectionPriority(byte priority) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+ return mUtils.transactSetInt(METHOD_SET_MASTER_ELECTION_PRIORITY, priority);
+ }
+
+ /**
+ * Gets the IP endpoint used by the time service to participate in the master election protocol.
+ *
+ * @return an InetSocketAddress containing the IP address and UDP port being used by the
+ * system's common time service to participate in the master election protocol.
+ * @throws android.os.RemoteException
+ */
+ public InetSocketAddress getMasterElectionEndpoint()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ELECTION_ENDPOINT);
+ }
+
+ /**
+ * Sets the IP endpoint used by the common time service to participate in the master election
+ * protocol.
+ *
+ * @param ep The IP address and UDP port to be used by the common time service to participate in
+ * the master election protocol. The supplied IP address must be either the broadcast or
+ * multicast address, unicast addresses are considered to be illegal values.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setMasterElectionEndpoint(InetSocketAddress ep) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+ return mUtils.transactSetSockaddr(METHOD_SET_MASTER_ELECTION_ENDPOINT, ep);
+ }
+
+ /**
+ * Gets the current group ID used by the common time service in the master election protocol.
+ *
+ * @return The 64-bit group ID of the common time service.
+ * @throws android.os.RemoteException
+ */
+ public long getMasterElectionGroupId()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetLong(METHOD_GET_MASTER_ELECTION_GROUP_ID, INVALID_GROUP_ID);
+ }
+
+ /**
+ * Sets the current group ID used by the common time service in the master election protocol.
+ *
+ * @param id The 64-bit group ID of the common time service.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setMasterElectionGroupId(long id) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+ return mUtils.transactSetLong(METHOD_SET_MASTER_ELECTION_GROUP_ID, id);
+ }
+
+ /**
+ * Gets the name of the network interface which the common time service attempts to bind to.
+ *
+ * @return a string with the network interface name which the common time service is bound to,
+ * or null if the service is currently unbound. Examples of interface names are things like
+ * "eth0", or "wlan0".
+ * @throws android.os.RemoteException
+ */
+ public String getInterfaceBinding()
+ throws RemoteException {
+ throwOnDeadServer();
+
+ String ifaceName = mUtils.transactGetString(METHOD_GET_INTERFACE_BINDING, null);
+
+ if ((null != ifaceName) && (0 == ifaceName.length()))
+ return null;
+
+ return ifaceName;
+ }
+
+ /**
+ * Sets the name of the network interface which the common time service should attempt to bind
+ * to.
+ *
+ * @param ifaceName The name of the network interface ("eth0", "wlan0", etc...) wich the common
+ * time service should attempt to bind to, or null to force the common time service to unbind
+ * from the network and run in networkless mode.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setNetworkBinding(String ifaceName) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+
+ return mUtils.transactSetString(METHOD_SET_INTERFACE_BINDING,
+ (null == ifaceName) ? "" : ifaceName);
+ }
+
+ /**
+ * Gets the amount of time the common time service will wait between master announcements when
+ * it is the timeline master.
+ *
+ * @return The time (in milliseconds) between master announcements.
+ * @throws android.os.RemoteException
+ */
+ public int getMasterAnnounceInterval()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetInt(METHOD_GET_MASTER_ANNOUNCE_INTERVAL, -1);
+ }
+
+ /**
+ * Sets the amount of time the common time service will wait between master announcements when
+ * it is the timeline master.
+ *
+ * @param interval The time (in milliseconds) between master announcements.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setMasterAnnounceInterval(int interval) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+ return mUtils.transactSetInt(METHOD_SET_MASTER_ANNOUNCE_INTERVAL, interval);
+ }
+
+ /**
+ * Gets the amount of time the common time service will wait between time synchronization
+ * requests when it is the client of another common time service on the network.
+ *
+ * @return The time (in milliseconds) between time sync requests.
+ * @throws android.os.RemoteException
+ */
+ public int getClientSyncInterval()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetInt(METHOD_GET_CLIENT_SYNC_INTERVAL, -1);
+ }
+
+ /**
+ * Sets the amount of time the common time service will wait between time synchronization
+ * requests when it is the client of another common time service on the network.
+ *
+ * @param interval The time (in milliseconds) between time sync requests.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setClientSyncInterval(int interval) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+ return mUtils.transactSetInt(METHOD_SET_CLIENT_SYNC_INTERVAL, interval);
+ }
+
+ /**
+ * Gets the panic threshold for the estimated error level of the common time service. When the
+ * common time service's estimated error rises above this level, the service will panic and
+ * reset, causing a discontinuity in the currently synchronized timeline.
+ *
+ * @return The threshold (in microseconds) past which the common time service will panic.
+ * @throws android.os.RemoteException
+ */
+ public int getPanicThreshold()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetInt(METHOD_GET_PANIC_THRESHOLD, -1);
+ }
+
+ /**
+ * Sets the panic threshold for the estimated error level of the common time service. When the
+ * common time service's estimated error rises above this level, the service will panic and
+ * reset, causing a discontinuity in the currently synchronized timeline.
+ *
+ * @param threshold The threshold (in microseconds) past which the common time service will
+ * panic.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setPanicThreshold(int threshold) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+ return mUtils.transactSetInt(METHOD_SET_PANIC_THRESHOLD, threshold);
+ }
+
+ /**
+ * Gets the current state of the common time service's auto disable flag.
+ *
+ * @return The current state of the common time service's auto disable flag.
+ * @throws android.os.RemoteException
+ */
+ public boolean getAutoDisable()
+ throws RemoteException {
+ throwOnDeadServer();
+ return (1 == mUtils.transactGetInt(METHOD_GET_AUTO_DISABLE, 1));
+ }
+
+ /**
+ * Sets the current state of the common time service's auto disable flag. When the time
+ * service's auto disable flag is set, it will automatically cease all network activity when
+ * it has no active local clients, resuming activity the next time the service has interested
+ * local clients. When the auto disabled flag is cleared, the common time service will continue
+ * to participate the time synchronization group even when it has no active local clients.
+ *
+ * @param autoDisable The desired state of the common time service's auto disable flag.
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int setAutoDisable(boolean autoDisable) {
+ if (checkDeadServer())
+ return ERROR_DEAD_OBJECT;
+
+ return mUtils.transactSetInt(METHOD_SET_AUTO_DISABLE, autoDisable ? 1 : 0);
+ }
+
+ /**
+ * At startup, the time service enters the initial state and remains there until it is given a
+ * network interface to bind to. Common time will be unavailable to clients of the common time
+ * service until the service joins a network (even an empty network). Devices may use the
+ * {@link #forceNetworklessMasterMode()} method to force a time service in the INITIAL state
+ * with no network configuration to assume MASTER status for a brand new timeline in order to
+ * allow clients of the common time service to operate, even though the device is isolated and
+ * not on any network. When a networkless master does join a network, it will defer to any
+ * masters already on the network, or continue to maintain the timeline it made up during its
+ * networkless state if no other masters are detected. Attempting to force a client into master
+ * mode while it is actively bound to a network will fail with the status code {@link #ERROR}
+ *
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+ */
+ public int forceNetworklessMasterMode() {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ mRemote.transact(METHOD_FORCE_NETWORKLESS_MASTER_MODE, data, reply, 0);
+
+ return reply.readInt();
+ }
+ catch (RemoteException e) {
+ return ERROR_DEAD_OBJECT;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ /**
+ * The OnServerDiedListener interface defines a method called by the
+ * {@link android.os.CommonTimeConfig} instance to indicate that the connection to the native
+ * media server has been broken and that the {@link android.os.CommonTimeConfig} instance will
+ * need to be released and re-created. The client application can implement this interface and
+ * register the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method.
+ */
+ public interface OnServerDiedListener {
+ /**
+ * Method called when the native common time service has died. <p>If the native common time
+ * service encounters a fatal error and needs to restart, the binder connection from the
+ * {@link android.os.CommonTimeConfig} instance to the common time service will be broken.
+ */
+ void onServerDied();
+ }
+
+ /**
+ * Registers an OnServerDiedListener interface.
+ * <p>Call this method with a null listener to stop receiving server death notifications.
+ */
+ public void setServerDiedListener(OnServerDiedListener listener) {
+ synchronized (mListenerLock) {
+ mServerDiedListener = listener;
+ }
+ }
+
+ protected void finalize() throws Throwable { release(); }
+
+ private boolean checkDeadServer() {
+ return ((null == mRemote) || (null == mUtils));
+ }
+
+ private void throwOnDeadServer() throws RemoteException {
+ if (checkDeadServer())
+ throw new RemoteException();
+ }
+
+ private final Object mListenerLock = new Object();
+ private OnServerDiedListener mServerDiedListener = null;
+
+ private IBinder mRemote = null;
+ private String mInterfaceDesc = "";
+ private CommonTimeUtils mUtils;
+
+ private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() {
+ public void binderDied() {
+ synchronized (mListenerLock) {
+ if (null != mServerDiedListener)
+ mServerDiedListener.onServerDied();
+ }
+ }
+ };
+
+ private static final int METHOD_GET_MASTER_ELECTION_PRIORITY = IBinder.FIRST_CALL_TRANSACTION;
+ private static final int METHOD_SET_MASTER_ELECTION_PRIORITY = METHOD_GET_MASTER_ELECTION_PRIORITY + 1;
+ private static final int METHOD_GET_MASTER_ELECTION_ENDPOINT = METHOD_SET_MASTER_ELECTION_PRIORITY + 1;
+ private static final int METHOD_SET_MASTER_ELECTION_ENDPOINT = METHOD_GET_MASTER_ELECTION_ENDPOINT + 1;
+ private static final int METHOD_GET_MASTER_ELECTION_GROUP_ID = METHOD_SET_MASTER_ELECTION_ENDPOINT + 1;
+ private static final int METHOD_SET_MASTER_ELECTION_GROUP_ID = METHOD_GET_MASTER_ELECTION_GROUP_ID + 1;
+ private static final int METHOD_GET_INTERFACE_BINDING = METHOD_SET_MASTER_ELECTION_GROUP_ID + 1;
+ private static final int METHOD_SET_INTERFACE_BINDING = METHOD_GET_INTERFACE_BINDING + 1;
+ private static final int METHOD_GET_MASTER_ANNOUNCE_INTERVAL = METHOD_SET_INTERFACE_BINDING + 1;
+ private static final int METHOD_SET_MASTER_ANNOUNCE_INTERVAL = METHOD_GET_MASTER_ANNOUNCE_INTERVAL + 1;
+ private static final int METHOD_GET_CLIENT_SYNC_INTERVAL = METHOD_SET_MASTER_ANNOUNCE_INTERVAL + 1;
+ private static final int METHOD_SET_CLIENT_SYNC_INTERVAL = METHOD_GET_CLIENT_SYNC_INTERVAL + 1;
+ private static final int METHOD_GET_PANIC_THRESHOLD = METHOD_SET_CLIENT_SYNC_INTERVAL + 1;
+ private static final int METHOD_SET_PANIC_THRESHOLD = METHOD_GET_PANIC_THRESHOLD + 1;
+ private static final int METHOD_GET_AUTO_DISABLE = METHOD_SET_PANIC_THRESHOLD + 1;
+ private static final int METHOD_SET_AUTO_DISABLE = METHOD_GET_AUTO_DISABLE + 1;
+ private static final int METHOD_FORCE_NETWORKLESS_MASTER_MODE = METHOD_SET_AUTO_DISABLE + 1;
+}
diff --git a/core/java/android/os/CommonTimeUtils.java b/core/java/android/os/CommonTimeUtils.java
new file mode 100644
index 000000000000..9081ee411d61
--- /dev/null
+++ b/core/java/android/os/CommonTimeUtils.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import static libcore.io.OsConstants.*;
+
+class CommonTimeUtils {
+ /**
+ * Successful operation.
+ */
+ public static final int SUCCESS = 0;
+ /**
+ * Unspecified error.
+ */
+ public static final int ERROR = -1;
+ /**
+ * Operation failed due to bad parameter value.
+ */
+ public static final int ERROR_BAD_VALUE = -4;
+ /**
+ * Operation failed due to dead remote object.
+ */
+ public static final int ERROR_DEAD_OBJECT = -7;
+
+ public CommonTimeUtils(IBinder remote, String interfaceDesc) {
+ mRemote = remote;
+ mInterfaceDesc = interfaceDesc;
+ }
+
+ public int transactGetInt(int method_code, int error_ret_val)
+ throws RemoteException {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+ int ret_val;
+
+ try {
+ int res;
+ data.writeInterfaceToken(mInterfaceDesc);
+ mRemote.transact(method_code, data, reply, 0);
+
+ res = reply.readInt();
+ ret_val = (0 == res) ? reply.readInt() : error_ret_val;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+
+ return ret_val;
+ }
+
+ public int transactSetInt(int method_code, int val) {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ data.writeInt(val);
+ mRemote.transact(method_code, data, reply, 0);
+
+ return reply.readInt();
+ }
+ catch (RemoteException e) {
+ return ERROR_DEAD_OBJECT;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ public long transactGetLong(int method_code, long error_ret_val)
+ throws RemoteException {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+ long ret_val;
+
+ try {
+ int res;
+ data.writeInterfaceToken(mInterfaceDesc);
+ mRemote.transact(method_code, data, reply, 0);
+
+ res = reply.readInt();
+ ret_val = (0 == res) ? reply.readLong() : error_ret_val;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+
+ return ret_val;
+ }
+
+ public int transactSetLong(int method_code, long val) {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ data.writeLong(val);
+ mRemote.transact(method_code, data, reply, 0);
+
+ return reply.readInt();
+ }
+ catch (RemoteException e) {
+ return ERROR_DEAD_OBJECT;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ public String transactGetString(int method_code, String error_ret_val)
+ throws RemoteException {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+ String ret_val;
+
+ try {
+ int res;
+ data.writeInterfaceToken(mInterfaceDesc);
+ mRemote.transact(method_code, data, reply, 0);
+
+ res = reply.readInt();
+ ret_val = (0 == res) ? reply.readString() : error_ret_val;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+
+ return ret_val;
+ }
+
+ public int transactSetString(int method_code, String val) {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ data.writeString(val);
+ mRemote.transact(method_code, data, reply, 0);
+
+ return reply.readInt();
+ }
+ catch (RemoteException e) {
+ return ERROR_DEAD_OBJECT;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ public InetSocketAddress transactGetSockaddr(int method_code)
+ throws RemoteException {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+ InetSocketAddress ret_val = null;
+
+ try {
+ int res;
+ data.writeInterfaceToken(mInterfaceDesc);
+ mRemote.transact(method_code, data, reply, 0);
+
+ res = reply.readInt();
+ if (0 == res) {
+ int type;
+ int port = 0;
+ String addrStr = null;
+
+ type = reply.readInt();
+
+ if (AF_INET == type) {
+ int addr = reply.readInt();
+ port = reply.readInt();
+ addrStr = String.format("%d.%d.%d.%d", (addr >> 24) & 0xFF,
+ (addr >> 16) & 0xFF,
+ (addr >> 8) & 0xFF,
+ addr & 0xFF);
+ } else if (AF_INET6 == type) {
+ int addr1 = reply.readInt();
+ int addr2 = reply.readInt();
+ int addr3 = reply.readInt();
+ int addr4 = reply.readInt();
+
+ port = reply.readInt();
+
+ int flowinfo = reply.readInt();
+ int scope_id = reply.readInt();
+
+ addrStr = String.format("[%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X]",
+ (addr1 >> 16) & 0xFFFF, addr1 & 0xFFFF,
+ (addr2 >> 16) & 0xFFFF, addr2 & 0xFFFF,
+ (addr3 >> 16) & 0xFFFF, addr3 & 0xFFFF,
+ (addr4 >> 16) & 0xFFFF, addr4 & 0xFFFF);
+ }
+
+ if (null != addrStr) {
+ ret_val = new InetSocketAddress(addrStr, port);
+ }
+ }
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+
+ return ret_val;
+ }
+
+ public int transactSetSockaddr(int method_code, InetSocketAddress addr) {
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+ int ret_val = ERROR;
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+
+ if (null == addr) {
+ data.writeInt(0);
+ } else {
+ data.writeInt(1);
+ final InetAddress a = addr.getAddress();
+ final byte[] b = a.getAddress();
+ final int p = addr.getPort();
+
+ if (a instanceof Inet4Address) {
+ int v4addr = (((int)b[0] & 0xFF) << 24) |
+ (((int)b[1] & 0xFF) << 16) |
+ (((int)b[2] & 0xFF) << 8) |
+ ((int)b[3] & 0xFF);
+
+ data.writeInt(AF_INET);
+ data.writeInt(v4addr);
+ data.writeInt(p);
+ } else
+ if (a instanceof Inet6Address) {
+ int i;
+ Inet6Address v6 = (Inet6Address)a;
+ data.writeInt(AF_INET6);
+ for (i = 0; i < 4; ++i) {
+ int aword = (((int)b[(i*4) + 0] & 0xFF) << 24) |
+ (((int)b[(i*4) + 1] & 0xFF) << 16) |
+ (((int)b[(i*4) + 2] & 0xFF) << 8) |
+ ((int)b[(i*4) + 3] & 0xFF);
+ data.writeInt(aword);
+ }
+ data.writeInt(p);
+ data.writeInt(0); // flow info
+ data.writeInt(v6.getScopeId());
+ } else {
+ return ERROR_BAD_VALUE;
+ }
+ }
+
+ mRemote.transact(method_code, data, reply, 0);
+ ret_val = reply.readInt();
+ }
+ catch (RemoteException e) {
+ ret_val = ERROR_DEAD_OBJECT;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+
+ return ret_val;
+ }
+
+ private IBinder mRemote;
+ private String mInterfaceDesc;
+};
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 844ed6ac5d8b..b816b110ac44 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -75,13 +75,13 @@ public final class Message implements Parcelable {
public Messenger replyTo;
/** If set message is in use */
- /*package*/ static final int FLAG_IN_USE = 1;
+ /*package*/ static final int FLAG_IN_USE = 1 << 0;
- /** Flags reserved for future use (All are reserved for now) */
- /*package*/ static final int FLAGS_RESERVED = ~FLAG_IN_USE;
+ /** If set message is asynchronous */
+ /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
/** Flags to clear in the copyFrom method */
- /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAGS_RESERVED | FLAG_IN_USE;
+ /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
@@ -363,6 +363,48 @@ public final class Message implements Parcelable {
target.sendMessage(this);
}
+ /**
+ * Returns true if the message is asynchronous.
+ *
+ * Asynchronous messages represent interrupts or events that do not require global ordering
+ * with represent to synchronous messages. Asynchronous messages are not subject to
+ * the synchronization barriers introduced by {@link MessageQueue#acquireSyncBarrier()}.
+ *
+ * @return True if the message is asynchronous.
+ *
+ * @see #setAsynchronous(boolean)
+ * @see MessageQueue#acquireSyncBarrier()
+ * @see MessageQueue#releaseSyncBarrier()
+ *
+ * @hide
+ */
+ public boolean isAsynchronous() {
+ return (flags & FLAG_ASYNCHRONOUS) != 0;
+ }
+
+ /**
+ * Sets whether the message is asynchronous.
+ *
+ * Asynchronous messages represent interrupts or events that do not require global ordering
+ * with represent to synchronous messages. Asynchronous messages are not subject to
+ * the synchronization barriers introduced by {@link MessageQueue#acquireSyncBarrier()}.
+ *
+ * @param async True if the message is asynchronous.
+ *
+ * @see #isAsynchronous()
+ * @see MessageQueue#acquireSyncBarrier()
+ * @see MessageQueue#releaseSyncBarrier()
+ *
+ * @hide
+ */
+ public void setAsynchronous(boolean async) {
+ if (async) {
+ flags |= FLAG_ASYNCHRONOUS;
+ } else {
+ flags &= ~FLAG_ASYNCHRONOUS;
+ }
+ }
+
/*package*/ void clearForRecycle() {
flags = 0;
what = 0;
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index a658fc45dae4..11dc1248c979 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -39,6 +39,9 @@ public class MessageQueue {
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
+ // Indicates the barrier nesting level.
+ private int mBarrierNestCount;
+
@SuppressWarnings("unused")
private int mPtr; // used by native code
@@ -93,7 +96,53 @@ public class MessageQueue {
mIdleHandlers.remove(handler);
}
}
-
+
+ /**
+ * Acquires a synchronization barrier.
+ *
+ * While a synchronization barrier is active, only asynchronous messages are
+ * permitted to execute. Synchronous messages are retained but are not executed
+ * until the synchronization barrier is released.
+ *
+ * This method is used to immediately postpone execution of all synchronous messages
+ * until a condition is met that releases the barrier. Asynchronous messages are
+ * exempt from the barrier and continue to be executed as usual.
+ *
+ * This call nests and must be matched by an equal number of calls to
+ * {@link #releaseSyncBarrier}.
+ *
+ * @hide
+ */
+ public final void acquireSyncBarrier() {
+ synchronized (this) {
+ mBarrierNestCount += 1;
+ }
+ }
+
+ /**
+ * Releases a synchronization barrier.
+ *
+ * This class undoes one invocation of {@link #acquireSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier is not acquired.
+ *
+ * @hide
+ */
+ public final void releaseSyncBarrier() {
+ synchronized (this) {
+ if (mBarrierNestCount == 0) {
+ throw new IllegalStateException("The message queue synchronization barrier "
+ + "has not been acquired.");
+ }
+
+ mBarrierNestCount -= 1;
+ if (!mBlocked || mMessages == null) {
+ return;
+ }
+ }
+ nativeWake(mPtr);
+ }
+
MessageQueue() {
nativeInit();
}
@@ -120,28 +169,49 @@ public class MessageQueue {
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
- final Message msg = mMessages;
- if (msg != null) {
+
+ Message prevMsg = null;
+ Message msg = mMessages;
+ for (;;) {
+ if (msg == null) {
+ // No more messages.
+ nextPollTimeoutMillis = -1;
+ break;
+ }
+
final long when = msg.when;
- if (now >= when) {
+ if (now < when) {
+ // Next message is not ready. Set a timeout to wake up when it is ready.
+ nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
+ break;
+ }
+
+ if (mBarrierNestCount == 0 || msg.isAsynchronous()) {
+ // Got a message.
mBlocked = false;
- mMessages = msg.next;
+ if (prevMsg != null) {
+ prevMsg.next = msg.next;
+ } else {
+ mMessages = msg.next;
+ }
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
- } else {
- nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
}
- } else {
- nextPollTimeoutMillis = -1;
+
+ // We have a message that we could return except that it is
+ // blocked by the sync barrier. In particular, this means that
+ // we are not idle yet, so we do not want to run the idle handlers.
+ prevMsg = msg;
+ msg = msg.next;
}
- // If first time, then get the number of idlers to run.
- if (pendingIdleHandlerCount < 0) {
+ // If first time idle, then get the number of idlers to run.
+ if (pendingIdleHandlerCount < 0 && msg == mMessages) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
- if (pendingIdleHandlerCount == 0) {
+ if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
@@ -205,10 +275,15 @@ public class MessageQueue {
//Log.d("MessageQueue", "Enqueing: " + msg);
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
+ // New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
- needWake = mBlocked; // new head, might need to wake up
+ needWake = mBlocked;
} else {
+ // Inserted within the middle of the queue. Usually we don't have to wake
+ // up the event queue unless the message is asynchronous and it might be
+ // possible for it to be returned out of sequence relative to an earlier
+ // synchronous message at the head of the queue.
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
@@ -216,7 +291,8 @@ public class MessageQueue {
}
msg.next = prev.next;
prev.next = msg;
- needWake = false; // still waiting on head, no need to wake up
+ needWake = mBlocked && mBarrierNestCount != 0 && msg.isAsynchronous()
+ && !mMessages.isAsynchronous();
}
}
if (needWake) {
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 3e2d7fc60241..a74b73754bac 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,8 +16,6 @@
package android.view;
-import com.android.internal.util.ArrayUtils;
-
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -28,8 +26,8 @@ import android.util.Log;
/**
* Coordinates animations and drawing for UI on a particular thread.
*
- * This object is thread-safe. Other threads can add and remove listeners
- * or schedule work to occur at a later time on the UI thread.
+ * This object is thread-safe. Other threads can post callbacks to run at a later time
+ * on the UI thread.
*
* Ensuring thread-safety is a little tricky because the {@link DisplayEventReceiver}
* can only be accessed from the UI thread so operations that touch the event receiver
@@ -37,14 +35,10 @@ import android.util.Log;
*
* @hide
*/
-public final class Choreographer extends Handler {
+public final class Choreographer {
private static final String TAG = "Choreographer";
private static final boolean DEBUG = false;
- // Amount of time in ms to wait before actually disposing of the display event
- // receiver after all listeners have been removed.
- private static final long DISPOSE_RECEIVER_DELAY = 200;
-
// The default amount of time in ms between animation frames.
// When vsync is not enabled, we want to have some idea of how long we should
// wait before posting the next animation message. It is important that the
@@ -87,25 +81,27 @@ public final class Choreographer extends Handler {
private static final int MSG_DO_ANIMATION = 0;
private static final int MSG_DO_DRAW = 1;
private static final int MSG_DO_SCHEDULE_VSYNC = 2;
- private static final int MSG_DO_DISPOSE_RECEIVER = 3;
private final Object mLock = new Object();
private final Looper mLooper;
+ private final FrameHandler mHandler;
+ private final FrameDisplayEventReceiver mDisplayEventReceiver;
- private OnAnimateListener[] mOnAnimateListeners;
- private OnDrawListener[] mOnDrawListeners;
+ private Callback mCallbackPool;
+
+ private Callback mAnimationCallbacks;
+ private Callback mDrawCallbacks;
private boolean mAnimationScheduled;
private boolean mDrawScheduled;
- private boolean mFrameDisplayEventReceiverNeeded;
- private FrameDisplayEventReceiver mFrameDisplayEventReceiver;
private long mLastAnimationTime;
private long mLastDrawTime;
private Choreographer(Looper looper) {
- super(looper);
mLooper = looper;
+ mHandler = new FrameHandler(looper);
+ mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
mLastAnimationTime = Long.MIN_VALUE;
mLastDrawTime = Long.MIN_VALUE;
}
@@ -154,14 +150,77 @@ public final class Choreographer extends Handler {
}
/**
- * Schedules animation (and drawing) to occur on the next frame synchronization boundary.
+ * Posts a callback to run on the next animation cycle and schedules an animation cycle.
+ * The callback only runs once and then is automatically removed.
+ *
+ * @param runnable The callback to run during the next animation cycle.
+ *
+ * @see #removeAnimationCallback
*/
- public void scheduleAnimation() {
+ public void postAnimationCallback(Runnable runnable) {
+ if (runnable == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
synchronized (mLock) {
+ mAnimationCallbacks = addCallbackLocked(mAnimationCallbacks, runnable);
scheduleAnimationLocked();
}
}
+ /**
+ * Removes an animation callback.
+ * Does nothing if the specified animation callback has not been posted or has already
+ * been removed.
+ *
+ * @param runnable The animation callback to remove.
+ *
+ * @see #postAnimationCallback
+ */
+ public void removeAnimationCallback(Runnable runnable) {
+ if (runnable == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
+ synchronized (mLock) {
+ mAnimationCallbacks = removeCallbackLocked(mAnimationCallbacks, runnable);
+ }
+ }
+
+ /**
+ * Posts a callback to run on the next draw cycle and schedules a draw cycle.
+ * The callback only runs once and then is automatically removed.
+ *
+ * @param runnable The callback to run during the next draw cycle.
+ *
+ * @see #removeDrawCallback
+ */
+ public void postDrawCallback(Runnable runnable) {
+ if (runnable == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
+ synchronized (mLock) {
+ mDrawCallbacks = addCallbackLocked(mDrawCallbacks, runnable);
+ scheduleDrawLocked();
+ }
+ }
+
+ /**
+ * Removes a draw callback.
+ * Does nothing if the specified draw callback has not been posted or has already
+ * been removed.
+ *
+ * @param runnable The draw callback to remove.
+ *
+ * @see #postDrawCallback
+ */
+ public void removeDrawCallback(Runnable runnable) {
+ if (runnable == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
+ synchronized (mLock) {
+ mDrawCallbacks = removeCallbackLocked(mDrawCallbacks, runnable);
+ }
+ }
+
private void scheduleAnimationLocked() {
if (!mAnimationScheduled) {
mAnimationScheduled = true;
@@ -173,16 +232,11 @@ public final class Choreographer extends Handler {
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
- if (!mFrameDisplayEventReceiverNeeded) {
- mFrameDisplayEventReceiverNeeded = true;
- if (mFrameDisplayEventReceiver != null) {
- removeMessages(MSG_DO_DISPOSE_RECEIVER);
- }
- }
if (isRunningOnLooperThreadLocked()) {
doScheduleVsyncLocked();
} else {
- sendMessageAtFrontOfQueue(obtainMessage(MSG_DO_SCHEDULE_VSYNC));
+ mHandler.sendMessageAtFrontOfQueue(
+ mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC));
}
} else {
final long now = SystemClock.uptimeMillis();
@@ -190,72 +244,26 @@ public final class Choreographer extends Handler {
if (DEBUG) {
Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms.");
}
- sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime);
+ mHandler.sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime);
}
}
}
- /**
- * Returns true if {@link #scheduleAnimation()} has been called but
- * {@link OnAnimateListener#onAnimate() OnAnimateListener.onAnimate()} has
- * not yet been called.
- */
- public boolean isAnimationScheduled() {
- synchronized (mLock) {
- return mAnimationScheduled;
- }
- }
-
- /**
- * Schedules drawing to occur on the next frame synchronization boundary.
- * Must be called on the UI thread.
- */
- public void scheduleDraw() {
- synchronized (mLock) {
- if (!mDrawScheduled) {
- mDrawScheduled = true;
- if (USE_ANIMATION_TIMER_FOR_DRAW) {
- scheduleAnimationLocked();
- } else {
- if (DEBUG) {
- Log.d(TAG, "Scheduling draw immediately.");
- }
- sendEmptyMessage(MSG_DO_DRAW);
+ private void scheduleDrawLocked() {
+ if (!mDrawScheduled) {
+ mDrawScheduled = true;
+ if (USE_ANIMATION_TIMER_FOR_DRAW) {
+ scheduleAnimationLocked();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Scheduling draw immediately.");
}
+ mHandler.sendEmptyMessage(MSG_DO_DRAW);
}
}
}
- /**
- * Returns true if {@link #scheduleDraw()} has been called but
- * {@link OnDrawListener#onDraw() OnDrawListener.onDraw()} has
- * not yet been called.
- */
- public boolean isDrawScheduled() {
- synchronized (mLock) {
- return mDrawScheduled;
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DO_ANIMATION:
- doAnimation();
- break;
- case MSG_DO_DRAW:
- doDraw();
- break;
- case MSG_DO_SCHEDULE_VSYNC:
- doScheduleVsync();
- break;
- case MSG_DO_DISPOSE_RECEIVER:
- doDisposeReceiver();
- break;
- }
- }
-
- private void doAnimation() {
+ void doAnimation() {
doAnimationInner();
if (USE_ANIMATION_TIMER_FOR_DRAW) {
@@ -263,9 +271,9 @@ public final class Choreographer extends Handler {
}
}
- private void doAnimationInner() {
+ void doAnimationInner() {
final long start;
- final OnAnimateListener[] listeners;
+ final Callback callbacks;
synchronized (mLock) {
if (!mAnimationScheduled) {
return; // no work to do
@@ -279,12 +287,14 @@ public final class Choreographer extends Handler {
}
mLastAnimationTime = start;
- listeners = mOnAnimateListeners;
+ callbacks = mAnimationCallbacks;
+ mAnimationCallbacks = null;
}
- if (listeners != null) {
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onAnimate();
+ if (callbacks != null) {
+ runCallbacks(callbacks);
+ synchronized (mLock) {
+ recycleCallbacksLocked(callbacks);
}
}
@@ -293,9 +303,9 @@ public final class Choreographer extends Handler {
}
}
- private void doDraw() {
+ void doDraw() {
final long start;
- final OnDrawListener[] listeners;
+ final Callback callbacks;
synchronized (mLock) {
if (!mDrawScheduled) {
return; // no work to do
@@ -309,12 +319,14 @@ public final class Choreographer extends Handler {
}
mLastDrawTime = start;
- listeners = mOnDrawListeners;
+ callbacks = mDrawCallbacks;
+ mDrawCallbacks = null;
}
- if (listeners != null) {
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onDraw();
+ if (callbacks != null) {
+ runCallbacks(callbacks);
+ synchronized (mLock) {
+ recycleCallbacksLocked(callbacks);
}
}
@@ -323,171 +335,106 @@ public final class Choreographer extends Handler {
}
}
- private void doScheduleVsync() {
+ void doScheduleVsync() {
synchronized (mLock) {
doScheduleVsyncLocked();
}
}
private void doScheduleVsyncLocked() {
- if (mFrameDisplayEventReceiverNeeded && mAnimationScheduled) {
- if (mFrameDisplayEventReceiver == null) {
- mFrameDisplayEventReceiver = new FrameDisplayEventReceiver(mLooper);
- }
- mFrameDisplayEventReceiver.scheduleVsync();
+ if (mAnimationScheduled) {
+ mDisplayEventReceiver.scheduleVsync();
}
}
- private void doDisposeReceiver() {
- synchronized (mLock) {
- if (!mFrameDisplayEventReceiverNeeded && mFrameDisplayEventReceiver != null) {
- mFrameDisplayEventReceiver.dispose();
- mFrameDisplayEventReceiver = null;
- }
- }
+ private boolean isRunningOnLooperThreadLocked() {
+ return Looper.myLooper() == mLooper;
}
- /**
- * Adds an animation listener.
- *
- * @param listener The listener to add.
- */
- public void addOnAnimateListener(OnAnimateListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- if (DEBUG) {
- Log.d(TAG, "Adding onAnimate listener: " + listener);
+ private Callback addCallbackLocked(Callback head, Runnable runnable) {
+ Callback callback = obtainCallbackLocked(runnable);
+ if (head == null) {
+ return callback;
}
-
- synchronized (mLock) {
- mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class,
- mOnAnimateListeners, listener);
+ Callback tail = head;
+ while (tail.next != null) {
+ tail = tail.next;
}
+ tail.next = callback;
+ return head;
}
- /**
- * Removes an animation listener.
- *
- * @param listener The listener to remove.
- */
- public void removeOnAnimateListener(OnAnimateListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- if (DEBUG) {
- Log.d(TAG, "Removing onAnimate listener: " + listener);
- }
-
- synchronized (mLock) {
- mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class,
- mOnAnimateListeners, listener);
- stopTimingLoopIfNoListenersLocked();
+ private Callback removeCallbackLocked(Callback head, Runnable runnable) {
+ Callback predecessor = null;
+ for (Callback callback = head; callback != null;) {
+ final Callback next = callback.next;
+ if (callback.runnable == runnable) {
+ if (predecessor != null) {
+ predecessor.next = next;
+ } else {
+ head = next;
+ }
+ recycleCallbackLocked(callback);
+ } else {
+ predecessor = callback;
+ }
+ callback = next;
}
+ return head;
}
- /**
- * Adds a draw listener.
- *
- * @param listener The listener to add.
- */
- public void addOnDrawListener(OnDrawListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- if (DEBUG) {
- Log.d(TAG, "Adding onDraw listener: " + listener);
- }
-
- synchronized (mLock) {
- mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class,
- mOnDrawListeners, listener);
+ private void runCallbacks(Callback head) {
+ while (head != null) {
+ head.runnable.run();
+ head = head.next;
}
}
- /**
- * Removes a draw listener.
- * Must be called on the UI thread.
- *
- * @param listener The listener to remove.
- */
- public void removeOnDrawListener(OnDrawListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- if (DEBUG) {
- Log.d(TAG, "Removing onDraw listener: " + listener);
- }
-
- synchronized (mLock) {
- mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class,
- mOnDrawListeners, listener);
- stopTimingLoopIfNoListenersLocked();
+ private void recycleCallbacksLocked(Callback head) {
+ while (head != null) {
+ final Callback next = head.next;
+ recycleCallbackLocked(head);
+ head = next;
}
}
- private void stopTimingLoopIfNoListenersLocked() {
- if (mOnDrawListeners == null && mOnAnimateListeners == null) {
- if (DEBUG) {
- Log.d(TAG, "Stopping timing loop.");
- }
-
- if (mAnimationScheduled) {
- mAnimationScheduled = false;
- if (USE_VSYNC) {
- removeMessages(MSG_DO_SCHEDULE_VSYNC);
- } else {
- removeMessages(MSG_DO_ANIMATION);
- }
- }
-
- if (mDrawScheduled) {
- mDrawScheduled = false;
- if (!USE_ANIMATION_TIMER_FOR_DRAW) {
- removeMessages(MSG_DO_DRAW);
- }
- }
-
- // Post a message to dispose the display event receiver if we haven't needed
- // it again after a certain amount of time has elapsed. Another reason to
- // defer disposal is that it is possible for use to attempt to dispose the
- // receiver while handling a vsync event that it dispatched, which might
- // cause a few problems...
- if (mFrameDisplayEventReceiverNeeded) {
- mFrameDisplayEventReceiverNeeded = false;
- if (mFrameDisplayEventReceiver != null) {
- sendEmptyMessageDelayed(MSG_DO_DISPOSE_RECEIVER, DISPOSE_RECEIVER_DELAY);
- }
- }
+ private Callback obtainCallbackLocked(Runnable runnable) {
+ Callback callback = mCallbackPool;
+ if (callback == null) {
+ callback = new Callback();
+ } else {
+ mCallbackPool = callback.next;
+ callback.next = null;
}
+ callback.runnable = runnable;
+ return callback;
}
- private boolean isRunningOnLooperThreadLocked() {
- return Looper.myLooper() == mLooper;
+ private void recycleCallbackLocked(Callback callback) {
+ callback.runnable = null;
+ callback.next = mCallbackPool;
+ mCallbackPool = callback;
}
- /**
- * Listens for animation frame timing events.
- */
- public static interface OnAnimateListener {
- /**
- * Called to animate properties before drawing the frame.
- */
- public void onAnimate();
- }
+ private final class FrameHandler extends Handler {
+ public FrameHandler(Looper looper) {
+ super(looper);
+ }
- /**
- * Listens for draw frame timing events.
- */
- public static interface OnDrawListener {
- /**
- * Called to draw the frame.
- */
- public void onDraw();
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DO_ANIMATION:
+ doAnimation();
+ break;
+ case MSG_DO_DRAW:
+ doDraw();
+ break;
+ case MSG_DO_SCHEDULE_VSYNC:
+ doScheduleVsync();
+ break;
+ }
+ }
}
private final class FrameDisplayEventReceiver extends DisplayEventReceiver {
@@ -500,4 +447,9 @@ public final class Choreographer extends Handler {
doAnimation();
}
}
+
+ private static final class Callback {
+ public Callback next;
+ public Runnable runnable;
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 613a766dfbac..6327c66a1ee8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3788,6 +3788,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onFocusChanged(false, 0, null);
refreshDrawableState();
+
+ // The view cleared focus and invoked the callbacks, so now is the
+ // time to give focus to the the first focusable from the top to
+ // ensure that the gain focus is announced after clear focus.
+ getRootView().requestFocus(FOCUS_FORWARD);
}
}
@@ -9575,14 +9580,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * @hide
+ * Force padding depending on layout direction.
*/
- protected void resolvePadding() {
+ public void resolvePadding() {
// If the user specified the absolute padding (either with android:padding or
// android:paddingLeft/Top/Right/Bottom), use this padding, otherwise
// use the default padding or the padding from the background drawable
// (stored at this point in mPadding*)
- switch (getResolvedLayoutDirection()) {
+ int resolvedLayoutDirection = getResolvedLayoutDirection();
+ switch (resolvedLayoutDirection) {
case LAYOUT_DIRECTION_RTL:
// Start user padding override Right user padding. Otherwise, if Right user
// padding is not defined, use the default Right padding. If Right user padding
@@ -9618,6 +9624,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom;
recomputePadding();
+ onResolvePadding(resolvedLayoutDirection);
+ }
+
+ /**
+ * Resolve padding depending on the layout direction. Subclasses that care about
+ * padding resolution should override this method. The default implementation does
+ * nothing.
+ *
+ * @param layoutDirection the direction of the layout
+ *
+ */
+ public void onResolvePadding(int layoutDirection) {
}
/**
@@ -12244,8 +12262,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param top the top padding in pixels
* @param end the end padding in pixels
* @param bottom the bottom padding in pixels
- *
- * @hide
*/
public void setPaddingRelative(int start, int top, int end, int bottom) {
mUserPaddingRelative = true;
@@ -12300,8 +12316,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* scrollbars as well.
*
* @return the start padding in pixels
- *
- * @hide
*/
public int getPaddingStart() {
return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
@@ -12325,8 +12339,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* scrollbars as well.
*
* @return the end padding in pixels
- *
- * @hide
*/
public int getPaddingEnd() {
return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
@@ -12340,8 +12352,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @attr ref android.R.styleable#View_paddingEnd
*
* @return true if the padding is relative or false if it is not.
- *
- * @hide
*/
public boolean isPaddingRelative() {
return mUserPaddingRelative;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d40d8af88ec6..e6a833494284 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -675,11 +675,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public void clearFocus() {
- super.clearFocus();
-
- // clear any child focus if it exists
- if (mFocused != null) {
+ if (DBG) {
+ System.out.println(this + " clearFocus()");
+ }
+ if (mFocused == null) {
+ super.clearFocus();
+ } else {
mFocused.clearFocus();
+ mFocused = null;
}
}
@@ -691,12 +694,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (DBG) {
System.out.println(this + " unFocus()");
}
-
- super.unFocus();
- if (mFocused != null) {
+ if (mFocused == null) {
+ super.unFocus();
+ } else {
mFocused.unFocus();
+ mFocused = null;
}
- mFocused = null;
}
/**
@@ -5125,20 +5128,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* The start margin in pixels of the child.
- *
- * @hide
- *
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
*/
@ViewDebug.ExportedProperty(category = "layout")
- protected int startMargin = DEFAULT_RELATIVE;
+ public int startMargin = DEFAULT_RELATIVE;
/**
* The end margin in pixels of the child.
- *
- * @hide
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
*/
@ViewDebug.ExportedProperty(category = "layout")
- protected int endMargin = DEFAULT_RELATIVE;
+ public int endMargin = DEFAULT_RELATIVE;
/**
* The default start and end margin.
@@ -5270,8 +5272,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
*
* @return the start margin in pixels.
- *
- * @hide
*/
public int getMarginStart() {
return startMargin;
@@ -5283,8 +5283,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
*
* @return the end margin in pixels.
- *
- * @hide
*/
public int getMarginEnd() {
return endMargin;
@@ -5297,8 +5295,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
*
* @return true if either marginStart or marginEnd has been set
- *
- * @hide
*/
public boolean isMarginRelative() {
return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1c3bbfa42d26..fbcb423dbfe7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -97,8 +97,7 @@ import java.util.List;
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl extends Handler implements ViewParent,
- View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks,
- Choreographer.OnDrawListener {
+ View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
private static final String TAG = "ViewRootImpl";
private static final boolean DBG = false;
private static final boolean LOCAL_LOGV = false;
@@ -174,6 +173,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
View mView;
View mFocusedView;
View mRealFocusedView; // this is not set to null in touch mode
+ View mOldFocusedView;
int mViewVisibility;
boolean mAppVisible = true;
int mOrigWindowType = -1;
@@ -462,8 +462,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
- mChoreographer.addOnDrawListener(this);
-
mView = view;
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
@@ -840,7 +838,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
- mChoreographer.scheduleDraw();
+ scheduleFrame();
}
}
@@ -848,8 +846,21 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mTraversalScheduled = false;
}
- @Override
- public void onDraw() {
+ void scheduleFrame() {
+ if (!mFrameScheduled) {
+ mChoreographer.postDrawCallback(mFrameRunnable);
+ mFrameScheduled = true;
+ }
+ }
+
+ void unscheduleFrame() {
+ if (mFrameScheduled) {
+ mFrameScheduled = false;
+ mChoreographer.removeDrawCallback(mFrameRunnable);
+ }
+ }
+
+ void doFrame() {
if (mInputEventReceiver != null) {
mInputEventReceiver.consumeBatchedInputEvents();
}
@@ -2272,32 +2283,33 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void requestChildFocus(View child, View focused) {
checkThread();
- if (mFocusedView != focused) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused);
- scheduleTraversals();
+
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(TAG, "Request child focus: focus now " + focused);
}
+
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused);
+ scheduleTraversals();
+
mFocusedView = mRealFocusedView = focused;
- if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now "
- + mFocusedView);
}
public void clearChildFocus(View child) {
checkThread();
- View oldFocus = mFocusedView;
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(TAG, "Clearing child focus");
+ }
- if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus");
- mFocusedView = mRealFocusedView = null;
- if (mView != null && !mView.hasFocus()) {
- // If a view gets the focus, the listener will be invoked from requestChildFocus()
- if (!mView.requestFocus(View.FOCUS_FORWARD)) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
- }
- } else if (oldFocus != null) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+ mOldFocusedView = mFocusedView;
+
+ // Invoke the listener only if there is no view to take focus
+ if (focusSearch(null, View.FOCUS_FORWARD) == null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null);
}
- }
+ mFocusedView = mRealFocusedView = null;
+ }
public void focusableViewAvailable(View v) {
checkThread();
@@ -2374,7 +2386,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mInputChannel = null;
}
- mChoreographer.removeOnDrawListener(this);
+ unscheduleFrame();
}
void updateConfiguration(Configuration config, boolean force) {
@@ -2770,6 +2782,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mView.unFocus();
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
mFocusedView = null;
+ mOldFocusedView = null;
return true;
}
}
@@ -3920,6 +3933,16 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
+ final class FrameRunnable implements Runnable {
+ @Override
+ public void run() {
+ mFrameScheduled = false;
+ doFrame();
+ }
+ }
+ final FrameRunnable mFrameRunnable = new FrameRunnable();
+ boolean mFrameScheduled;
+
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
@@ -3932,7 +3955,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
@Override
public void onBatchedInputEventPending() {
- mChoreographer.scheduleDraw();
+ scheduleFrame();
}
}
WindowInputEventReceiver mInputEventReceiver;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7171b58482d4..b1d7a182a443 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -336,6 +336,7 @@ public final class InputMethodManager {
}
case MSG_UNBIND: {
final int sequence = msg.arg1;
+ boolean startInput = false;
synchronized (mH) {
if (mBindSequence == sequence) {
if (false) {
@@ -355,7 +356,12 @@ public final class InputMethodManager {
if (mServedView != null && mServedView.isFocused()) {
mServedConnecting = true;
}
+ if (mActive) {
+ startInput = true;
+ }
}
+ }
+ if (startInput) {
startInputInner();
}
return;
@@ -1135,20 +1141,26 @@ public final class InputMethodManager {
* @hide
*/
public void checkFocus() {
+ if (checkFocusNoStartInput()) {
+ startInputInner();
+ }
+ }
+
+ private boolean checkFocusNoStartInput() {
// This is called a lot, so short-circuit before locking.
if (mServedView == mNextServedView && !mNextServedNeedsStart) {
- return;
+ return false;
}
InputConnection ic = null;
synchronized (mH) {
if (mServedView == mNextServedView && !mNextServedNeedsStart) {
- return;
+ return false;
}
if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ " next=" + mNextServedView
+ " restart=" + mNextServedNeedsStart);
-
+
mNextServedNeedsStart = false;
if (mNextServedView == null) {
finishInputLocked();
@@ -1156,22 +1168,22 @@ public final class InputMethodManager {
// but no longer do. We should make sure the input method is
// no longer shown, since it serves no purpose.
closeCurrentInput();
- return;
+ return false;
}
-
+
ic = mServedInputConnection;
-
+
mServedView = mNextServedView;
mCurrentTextBoxAttribute = null;
mCompletions = null;
mServedConnecting = true;
}
-
+
if (ic != null) {
ic.finishComposingText();
}
-
- startInputInner();
+
+ return true;
}
void closeCurrentInput() {
@@ -1200,7 +1212,7 @@ public final class InputMethodManager {
focusInLocked(focusedView != null ? focusedView : rootView);
}
- checkFocus();
+ boolean startInput = checkFocusNoStartInput();
synchronized (mH) {
try {
@@ -1212,6 +1224,10 @@ public final class InputMethodManager {
} catch (RemoteException e) {
}
}
+
+ if (startInput) {
+ startInputInner();
+ }
}
/** @hide */
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 332a0eb577dc..e0f4f59c8a89 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -59,6 +59,7 @@ import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
import android.provider.Settings;
+import android.security.KeyChain;
import android.speech.tts.TextToSpeech;
import android.text.Editable;
import android.text.InputType;
@@ -1303,6 +1304,7 @@ public class WebView extends AbsoluteLayout
init();
setupPackageListener(context);
setupProxyListener(context);
+ setupTrustStorageListener(context);
updateMultiTouchSupport(context);
if (privateBrowsing) {
@@ -1312,6 +1314,41 @@ public class WebView extends AbsoluteLayout
mAutoFillData = new WebViewCore.AutoFillData();
}
+ private static class TrustStorageListener extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
+ handleCertTrustChanged();
+ }
+ }
+ }
+ private static TrustStorageListener sTrustStorageListener;
+
+ /**
+ * Handles update to the trust storage.
+ */
+ private static void handleCertTrustChanged() {
+ // send a message for indicating trust storage change
+ WebViewCore.sendStaticMessage(EventHub.TRUST_STORAGE_UPDATED, null);
+ }
+
+ /*
+ * @param context This method expects this to be a valid context.
+ */
+ private static void setupTrustStorageListener(Context context) {
+ if (sTrustStorageListener != null ) {
+ return;
+ }
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(KeyChain.ACTION_STORAGE_CHANGED);
+ sTrustStorageListener = new TrustStorageListener();
+ Intent current =
+ context.getApplicationContext().registerReceiver(sTrustStorageListener, filter);
+ if (current != null) {
+ handleCertTrustChanged();
+ }
+ }
+
private static class ProxyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -5555,7 +5592,7 @@ public class WebView extends AbsoluteLayout
return false;
}
- if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ if (isEnterActionKey(keyCode)) {
switchOutDrawHistory();
boolean wantsKeyEvents = nativeCursorNodePointer() == 0
|| nativeCursorWantsKeyEvents();
@@ -5704,33 +5741,35 @@ public class WebView extends AbsoluteLayout
return true; // discard press if copy in progress
}
- // perform the single click
- Rect visibleRect = sendOurVisibleRect();
- // Note that sendOurVisibleRect calls viewToContent, so the
- // coordinates should be in content coordinates.
- if (!nativeCursorIntersects(visibleRect)) {
- return false;
- }
- WebViewCore.CursorData data = cursorData();
- mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
- playSoundEffect(SoundEffectConstants.CLICK);
- if (nativeCursorIsTextInput()) {
- rebuildWebTextView();
- centerKeyPressOnTextField();
- if (inEditingMode()) {
- mWebTextView.setDefaultSelection();
+ if (!sDisableNavcache) {
+ // perform the single click
+ Rect visibleRect = sendOurVisibleRect();
+ // Note that sendOurVisibleRect calls viewToContent, so the
+ // coordinates should be in content coordinates.
+ if (!nativeCursorIntersects(visibleRect)) {
+ return false;
+ }
+ WebViewCore.CursorData data = cursorData();
+ mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
+ playSoundEffect(SoundEffectConstants.CLICK);
+ if (nativeCursorIsTextInput()) {
+ rebuildWebTextView();
+ centerKeyPressOnTextField();
+ if (inEditingMode()) {
+ mWebTextView.setDefaultSelection();
+ }
+ return true;
+ }
+ clearTextEntry();
+ nativeShowCursorTimed();
+ if (mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
+ return true;
+ }
+ if (nativeCursorNodePointer() != 0 && !nativeCursorWantsKeyEvents()) {
+ mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
+ nativeCursorNodePointer());
+ return true;
}
- return true;
- }
- clearTextEntry();
- nativeShowCursorTimed();
- if (mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
- return true;
- }
- if (nativeCursorNodePointer() != 0 && !nativeCursorWantsKeyEvents()) {
- mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
- nativeCursorNodePointer());
- return true;
}
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 8a9c12d43c74..b6c5612c7e6d 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -26,6 +26,7 @@ import android.graphics.Region;
import android.media.MediaFile;
import android.net.ProxyProperties;
import android.net.Uri;
+import android.net.http.CertificateChainValidator;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -333,6 +334,15 @@ public final class WebViewCore {
}
/**
+ * Called by JNI when the focus node changed.
+ */
+ private void focusNodeChanged(WebKitHitTest hitTest) {
+ if (mWebView == null) return;
+ mWebView.mPrivateHandler.obtainMessage(WebView.HIT_TEST_RESULT, hitTest)
+ .sendToTarget();
+ }
+
+ /**
* Called by JNI. Open a file chooser to upload a file.
* @param acceptType The value of the 'accept' attribute of the
* input tag associated with this file picker.
@@ -614,7 +624,6 @@ public final class WebViewCore {
int x, int y);
private native String nativeRetrieveImageSource(int nativeClass,
int x, int y);
- private native void nativeStopPaintingCaret(int nativeClass);
private native void nativeTouchUp(int nativeClass,
int touchGeneration, int framePtr, int nodePtr, int x, int y);
@@ -767,6 +776,11 @@ public final class WebViewCore {
Message m = (Message)msg.obj;
m.sendToTarget();
break;
+ case EventHub.TRUST_STORAGE_UPDATED:
+ // post a task to network thread for updating trust manager
+ nativeCertTrustChanged();
+ CertificateChainValidator.handleTrustStorageUpdate();
+ break;
}
}
};
@@ -1125,6 +1139,9 @@ public final class WebViewCore {
static final int SELECT_WORD_AT = 214;
static final int SELECT_ALL = 215;
+ // for updating state on trust storage change
+ static final int TRUST_STORAGE_UPDATED = 220;
+
// Private handler for WebCore messages.
private Handler mHandler;
// Message queue for containing messages before the WebCore thread is
@@ -1530,9 +1547,6 @@ public final class WebViewCore {
nativeMoveMouseIfLatest(mNativeClass,
cData.mMoveGeneration,
cData.mFrame, cData.mX, cData.mY);
- if (msg.arg1 == 1) {
- nativeStopPaintingCaret(mNativeClass);
- }
break;
case REQUEST_CURSOR_HREF: {
@@ -3077,4 +3091,6 @@ public final class WebViewCore {
private native void nativeClearTextSelection(int nativeClass);
private native void nativeSelectWordAt(int nativeClass, int x, int y);
private native void nativeSelectAll(int nativeClass);
+
+ private static native void nativeCertTrustChanged();
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 5c7e5a36c31c..dd5332586b3d 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -142,12 +142,8 @@ public class CheckedTextView extends TextView implements Checkable {
resolvePadding();
}
- /**
- * @hide
- */
@Override
- protected void resolvePadding() {
- super.resolvePadding();
+ public void onResolvePadding(int layoutDirection) {
int newPadding = (mCheckMarkDrawable != null) ?
mCheckMarkWidth + mBasePadding : mBasePadding;
mNeedRequestlayout |= (mPaddingRight != newPadding);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7db8a1e6c99c..13798ef1da38 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4637,19 +4637,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Alpha is supported if and only if the drawing can be done in one pass.
// TODO text with spans with a background color currently do not respect this alpha.
if (getBackground() == null) {
- mCurrentAlpha = alpha;
- final Drawables dr = mDrawables;
- if (dr != null) {
- if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha);
- if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha);
- if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha);
- if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha);
- if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha);
- if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha);
+ if (mCurrentAlpha != alpha) {
+ mCurrentAlpha = alpha;
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha);
+ if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha);
+ if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha);
+ if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha);
+ if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha);
+ if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha);
+ }
+ mTextDisplayListIsValid = false;
}
return true;
}
+ if (mCurrentAlpha != 255) {
+ mTextDisplayListIsValid = false;
+ }
mCurrentAlpha = 255;
return false;
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 6893ffbaf6c5..acc3c1c39de1 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -857,11 +857,6 @@ public class LockPatternUtils {
* @return Whether biometric weak lock is installed and that the front facing camera exists
*/
public boolean isBiometricWeakInstalled() {
- // Check that the system flag was set
- if (!OPTION_ENABLE_FACELOCK.equals(getString(LOCKSCREEN_OPTIONS))) {
- return false;
- }
-
// Check that it's installed
PackageManager pm = mContext.getPackageManager();
try {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 68c919e92cb3..a2b1117ddf95 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1521,6 +1521,17 @@
android:description="@string/permdesc_serialPort"
android:protectionLevel="normal" />
+ <!-- Allows the holder to access content providers from outside an ApplicationThread.
+ This permission is enforced by the ActivityManagerService on the corresponding APIs,
+ in particular ActivityManagerService#getContentProviderExternal(String) and
+ ActivityManagerService#removeContentProviderExternal(String).
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY"
+ android:label="@string/permlab_accessContentProvidersExternally"
+ android:description="@string/permdesc_accessContentProvidersExternally"
+ android:protectionLevel="signature" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png
index c2e4b783c31e..cb08eed75658 100644
--- a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png
+++ b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png
index 51b839fd5572..ea065c36e742 100644
--- a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png
+++ b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png
new file mode 100644
index 000000000000..d3ba98c3ebc2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png
new file mode 100644
index 000000000000..153c6ad1f6cf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png
new file mode 100644
index 000000000000..ec6bc54d77e2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png
new file mode 100644
index 000000000000..9fd4f33bbf78
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png
Binary files differ
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 19398cf3bebd..840323e238ad 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -975,9 +975,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kon nie aan Wi-Fikoppel nie"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" het \'n swak internetverbinding."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Begin Wi-Fi Direct. Dit sal die Wi-Fi-kliënt/warmkol afskakel."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kon nie Wi-Fi Direct begin nie."</string>
<string name="accept" msgid="1645267259272829559">"Aanvaar"</string>
<string name="decline" msgid="2112225451706137894">"Weier"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Uitnodiging gestuur"</string>
@@ -986,8 +983,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Aan:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Voer die vereiste PIN in:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direk is aan"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Raak vir instellings"</string>
<string name="select_character" msgid="3365550120617701745">"Voeg karakter in"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Onbekend program"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Stuur SMS-boodskappe"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 8f7b70f10c7b..905b90c44288 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"ወደ Wi-Fi ለማያያዝ አልተቻለም"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ደካማ የበይነመረብ ግንኙነት ኣለው።"</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi ቀጥታ"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"የWi-Fi በቀጥታ ጀምር።ይህ የWi-Fi ደንበኛ /ድረስ ነጥብ ያጠፋል።"</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"በቀጥታ Wi-Fi ማስጀመር አልተቻለም።"</string>
<string name="accept" msgid="1645267259272829559">"ተቀበል"</string>
<string name="decline" msgid="2112225451706137894">"ውድቅ አድርግ"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"ግብዣ ተልኳል"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"ለ፦"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"የሚፈለገውን ፒን ተይብ፦"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ፒን፦"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"የWi-Fi ቀጥታ በርቷል"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"ለቅንብሮች ንካ"</string>
<string name="select_character" msgid="3365550120617701745">"ቁምፊ አስገባ"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"ያልታወቀ መተግበሪያ"</string>
<string name="sms_control_title" msgid="7296612781128917719">"የSMS መልዕክቶች መበላክ ላይ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index c9de7de2cc31..a7c37e733cf4 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"تعذر الاتصال بـ Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" لديها اتصال إنترنت رديء."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"اتصال Wi-Fi مباشر"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"ابدأ Wi-Fi Direct. يؤدي هذا إلى إيقاف عميل/نقطة اتصال Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"تعذر بدء Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"قبول"</string>
<string name="decline" msgid="2112225451706137894">"رفض"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"تم إرسال الدعوة"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"إلى:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"اكتب رقم التعريف الشخصي المطلوب:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"رقم التعريف الشخصي:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"تم تشغيل اتصال Wi-Fi المباشر"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"المس للحصول على الإعدادات"</string>
<string name="select_character" msgid="3365550120617701745">"إدراج حرف"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"تطبيق غير معروف"</string>
<string name="sms_control_title" msgid="7296612781128917719">"إرسال رسائل قصيرة SMS"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index adb13388b67f..4a226a157f2d 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Немагчыма падключыцца да Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" дрэннае падключэнне да Інтэрнэту."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Пачаць работу Wi-Fi Direct. Гэта адключыць кліента або кропку доступу Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Немагчыма запусціць Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Прыняць"</string>
<string name="decline" msgid="2112225451706137894">"Адхіліць"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Запрашэнне адпраўлена"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Каму:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Увядзіце патрэбны PIN-код:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct уключаны"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Дакраніцеся, каб наладзіць"</string>
<string name="select_character" msgid="3365550120617701745">"Уставіць сімвал"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Невядомае прыкладанне"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Адпраўка SMS"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 0a2213325a52..f311a28d8682 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Не можа да се свърже с Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" има лоша връзка с интернет."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Стартиране на Wi-Fi Direct. Това ще изключи клиентската програма/точката за достъп до Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct не можа да се стартира."</string>
<string name="accept" msgid="1645267259272829559">"Приемам"</string>
<string name="decline" msgid="2112225451706137894">"Отхвърлям"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Поканата е изпратена"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"До:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Въведете задължителния ПИН:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ПИН:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct е включено"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Докоснете за настройки"</string>
<string name="select_character" msgid="3365550120617701745">"Вмъкване на знак"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Неизвестно приложение"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Изпращане на SMS съобщения"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 460fcb7f504a..95dae9d62b05 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"No s\'ha pogut connectar a la Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" té una mala connexió a Internet."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Inicia Wi-Fi Direct. Això desactivarà el client/la zona Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"No s\'ha pogut iniciar Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Accepta"</string>
<string name="decline" msgid="2112225451706137894">"Rebutja"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"S\'ha enviat la invitació"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Per a:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introdueix el PIN sol·licitat:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct està activat"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Toca per accedir a la configuració"</string>
<string name="select_character" msgid="3365550120617701745">"Insereix un caràcter"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicació desconeguda"</string>
<string name="sms_control_title" msgid="7296612781128917719">"S\'estan enviant missatges SMS"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 545d957d6907..c01526d71b99 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Připojení k síti Wi-Fi se nezdařilo"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" má pomalé připojení k internetu."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Přímé připojení sítě Wi-Fi"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Spustit přímé připojení sítě Wi-Fi. Tato možnost vypne provoz sítě Wi-Fi v režimu klient/hotspot."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Přímé připojení sítě Wi-Fi se nepodařilo spustit."</string>
<string name="accept" msgid="1645267259272829559">"Přijmout"</string>
<string name="decline" msgid="2112225451706137894">"Odmítnout"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pozvánka odeslána."</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Komu:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Zadejte požadovaný kód PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Přímé připojení sítě Wi-Fi je zapnuto"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Nastavení otevřete dotykem"</string>
<string name="select_character" msgid="3365550120617701745">"Vkládání znaků"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznámá aplikace"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Odesílání zpráv SMS"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 65d7cdce5f13..4904a994cb7a 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kunne ikke oprette forbindelse til Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" har en dårlig internetforbindelse."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Start Wi-Fi Direct. Dette slår Wi-Fi-klient/hotspot fra."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct kunne ikke startes."</string>
<string name="accept" msgid="1645267259272829559">"Accepter"</string>
<string name="decline" msgid="2112225451706137894">"Afvis"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitationen er sendt"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Til:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Skriv den påkrævede pinkode:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Pinkode:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct er slået til"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tryk for indstillinger"</string>
<string name="select_character" msgid="3365550120617701745">"Indsæt tegn"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Ukendt app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Sender sms-beskeder"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index dbe81373bcfc..20ef4d7f7aef 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Es konnte keine WLAN-Verbindung hergestellt werden."</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" hat eine schlechte Internetverbindung."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct-Betrieb starten. Hierdurch wird der WLAN-Client-/-Hotspot-Betrieb deaktiviert."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Starten von Wi-Fi Direct nicht möglich"</string>
<string name="accept" msgid="1645267259272829559">"Akzeptieren"</string>
<string name="decline" msgid="2112225451706137894">"Ablehnen"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Einladung gesendet"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"An:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Geben Sie die erforderliche PIN ein:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct ist aktiviert."</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Zum Aufrufen der Einstellungen berühren"</string>
<string name="select_character" msgid="3365550120617701745">"Zeichen einfügen"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Unbekannte App"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Kurznachrichten werden gesendet"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 154ca55f578a..200f7024ef8e 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -215,10 +215,8 @@
<string name="permdesc_reorderTasks" msgid="4175137612205663399">"Επιτρέπει στην εφαρμογή τη μετακίνηση εργασιών στο προσκήνιο και στο φόντο. Τυχόν κακόβουλες εφαρμογές μπορούν να προωθηθούν στο προσκήνιο χωρίς να μπορείτε να τις ελέγξετε."</string>
<string name="permlab_removeTasks" msgid="6821513401870377403">"διακοπή εκτέλεσης εφαρμογών"</string>
<string name="permdesc_removeTasks" msgid="1394714352062635493">"Επιτρέπει στην εφαρμογή την κατάργηση ενεργειών και την απομάκρυνση των εφαρμογών τους. Τυχόν κακόβουλες εφαρμογές ενδέχεται να διαταράξουν τη λειτουργία άλλων εφαρμογών."</string>
- <!-- no translation found for permlab_setScreenCompatibility (6975387118861842061) -->
- <skip />
- <!-- no translation found for permdesc_setScreenCompatibility (692043618693917374) -->
- <skip />
+ <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"ρύθμιση συμβατότητας οθόνης"</string>
+ <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Επιτρέπει στην εφαρμογή να ελέγξει τη λειτουργία συμβατότητας της οθόνης με άλλες εφαρμογές. Οι κακόβουλες εφαρμογές μπορεί να επηρεάσουν τη συμπεριφορά άλλων εφαρμογών."</string>
<string name="permlab_setDebugApp" msgid="3022107198686584052">"ενεργοποίηση εντοπισμού σφαλμάτων εφαρμογής"</string>
<string name="permdesc_setDebugApp" msgid="4474512416299013256">"Επιτρέπει στην εφαρμογή να ενεργοποιήσει τον εντοπισμό σφαλμάτων για μια άλλη εφαρμογή. Τυχόν κακόβουλες εφαρμογές ενδέχεται να χρησιμοποιήσουν αυτήν τη δυνατότητα για τον τερματισμό άλλων εφαρμογών."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"αλλαγή των ρυθμίσεων του UI"</string>
@@ -975,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Δεν είναι δυνατή η σύνδεση στο Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" έχει κακή σύνδεση στο Διαδίκτυο."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Ξεκινήστε τη λειτουργία Wi-Fi Direct. Θα απενεργοποιηθεί η λειτουργία πελάτη/φορητού σημείου πρόσβασης Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Δεν ήταν δυνατή η εκκίνηση του Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Αποδοχή"</string>
<string name="decline" msgid="2112225451706137894">"Απόρριψη"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Η πρόσκληση στάλθηκε"</string>
@@ -986,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Προς:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Πληκτρολογήστε τον απαιτούμενο κωδικό PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Το Wi-Fi Direct έχει ενεργοποιηθεί"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Αγγίξτε για ρυθμίσεις"</string>
<string name="select_character" msgid="3365550120617701745">"Εισαγωγή χαρακτήρα"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Άγνωστη εφαρμογή"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Αποστολή μηνυμάτων SMS"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 0429c8b7f3f6..285994297419 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Couldn\'t connect to Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" has a poor Internet connection."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Start Wi-Fi Direct. This will turn off Wi-Fi client/hotspot."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Couldn\'t start Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Accept"</string>
<string name="decline" msgid="2112225451706137894">"Decline"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitation sent"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"To:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Type the required PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct is on"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Touch for settings"</string>
<string name="select_character" msgid="3365550120617701745">"Insert character"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Unknown app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Sending SMS messages"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index ad3e949899a8..dbc96e7cc32f 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -215,8 +215,8 @@
<string name="permdesc_reorderTasks" msgid="4175137612205663399">"Permite que la aplicación mueva tareas al primero o segundo plano. Las aplicaciones maliciosas pueden forzar su paso al primer plano sin que tú las controles."</string>
<string name="permlab_removeTasks" msgid="6821513401870377403">"detener las aplicaciones en ejecución"</string>
<string name="permdesc_removeTasks" msgid="1394714352062635493">"Permite que la aplicación elimine tareas y cierre sus aplicaciones. Las aplicaciones malintencionadas pueden usar este permiso para interferir en el comportamiento de otras aplicaciones."</string>
- <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"Configurar el modo de la compatibilidad de otras pantalla"</string>
- <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Permite a la aplicación controlar el modo de la compatibilidad de las pantallas de otras aplicaciones. Las aplicaciones malintencionadas pueden interrumpir el funcionamiento de otras aplicaciones."</string>
+ <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"Definir compatibilidad de pantalla"</string>
+ <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Permite a la aplicación controlar el modo de compatibilidad de las pantallas de otras aplicaciones. Las aplicaciones malintencionadas pueden interrumpir el funcionamiento de otras aplicaciones."</string>
<string name="permlab_setDebugApp" msgid="3022107198686584052">"activar depuración de aplicación"</string>
<string name="permdesc_setDebugApp" msgid="4474512416299013256">"Permite que la aplicación active la depuración de otra aplicación. Las aplicaciones malintencionadas pueden usar este permiso para interrumpir la ejecución de otras aplicaciones."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"cambiar tu configuración de la interfaz de usuario"</string>
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"No se pudo conectar a la red Wi-Fi."</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tiene una mala conexión a Internet."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar Wi-Fi Direct. Se desactivará el funcionamiento de la zona o del cliente Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"No se pudo iniciar Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Aceptar"</string>
<string name="decline" msgid="2112225451706137894">"Rechazar"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Se envió la invitación."</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Escribe el PIN solicitado:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Se activó Wi-Fi Direct."</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tocar para ajustar los parámetros de configuración"</string>
<string name="select_character" msgid="3365550120617701745">"Insertar caracteres"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicación desconocida"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Enviando mensajes SMS"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 1bd13bd2b320..d3c55a61bb7f 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"No se ha podido establecer conexión con la red Wi-Fi."</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tiene una conexión inestable a Internet."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar Wi-Fi Direct. Se desactivará el funcionamiento de la zona o del cliente Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"No se ha podido iniciar Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Aceptar"</string>
<string name="decline" msgid="2112225451706137894">"Rechazar"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitación enviada"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Escribe el PIN solicitado:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct activado"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Toca para acceder a Ajustes"</string>
<string name="select_character" msgid="3365550120617701745">"Insertar carácter"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicación desconocida"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Enviando mensajes SMS..."</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 2f42a70d72e9..c3a61693826d 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Ei saanud WiFi-ga ühendust"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" on halb Interneti-ühendus."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"WiFi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Käivitage WiFi otseühendus. See lülitab välja WiFi kliendi/leviala."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"WiFi otseühenduse käivitamine ebaõnnestus."</string>
<string name="accept" msgid="1645267259272829559">"Nõustu"</string>
<string name="decline" msgid="2112225451706137894">"Keeldu"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Kutse on saadetud"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Saaja:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Sisestage nõutav PIN-kood:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kood:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"WiFi Direct on sees"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Puuted seadete jaoks"</string>
<string name="select_character" msgid="3365550120617701745">"Sisesta tähemärk"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Tundmatu rakendus"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS-sõnumite saatmine"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index fd7a77190b7e..87eee68d0385 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"اتصال به Wi-Fi ممکن نیست"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" اتصال اینترنتی ضعیفی دارد."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct را شروع کنید. این کار نقطه اتصال/سرویس گیرنده Wi-Fi را غیرفعال خواهد کرد."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct شروع نشد."</string>
<string name="accept" msgid="1645267259272829559">"پذیرش"</string>
<string name="decline" msgid="2112225451706137894">"عدم پذیرش"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"دعوت‌نامه ارسال شد"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"به:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"پین لازم را تایپ کنید:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"پین:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct روشن است"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"لمس کردن برای تنظیمات"</string>
<string name="select_character" msgid="3365550120617701745">"درج نویسه"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"برنامه ناشناخته"</string>
<string name="sms_control_title" msgid="7296612781128917719">"ارسال پیامک ها"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 9cf18ecc6237..5ab71583e236 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wifi-yhteyden muodostaminen epäonnistui"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" : huono internetyhteys."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Suora wifi-yhteys"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Käynnistä suora wifi-yhteys. Wifi-asiakas/-hotspot poistetaan käytöstä."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Suoran wifi-yhteyden käynnistäminen epäonnistui."</string>
<string name="accept" msgid="1645267259272829559">"Hyväksy"</string>
<string name="decline" msgid="2112225451706137894">"Hylkää"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Kutsu lähetetty."</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Kohde:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Kirjoita pyydetty PIN-koodi:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-koodi:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct on käytössä"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tarkastele asetuksia koskettamalla"</string>
<string name="select_character" msgid="3365550120617701745">"Lisää merkki"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Tuntematon sovellus"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Tekstiviestien lähettäminen"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 33b0f45bd664..606735043a20 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Impossible de se connecter au Wi-Fi."</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" dispose d\'une mauvaise connexion Internet."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Lancer le Wi-Fi Direct. Cela désactive le fonctionnement du Wi-Fi client ou via un point d\'accès."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Impossible d\'activer le Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Accepter"</string>
<string name="decline" msgid="2112225451706137894">"Refuser"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitation envoyée"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"À :"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Saisissez le code PIN requis :"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Code PIN :"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct activé"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Appuyez pour accéder aux paramètres."</string>
<string name="select_character" msgid="3365550120617701745">"Insérer un caractère"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Application inconnue"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Envoi de messages SMS"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 91ac5a370f31..1aaedbd3d0ab 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wi-Fi से कनेक्‍ट नहीं हो सका"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" के पास एक कमज़ोर इंटरनेट कनेक्‍शन है."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi प्रत्यक्ष"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi डायरेक्ट प्रारंभ करें. इससे Wi-Fi क्‍लाइंट/हॉटस्पॉट कार्यवाही बंद हो जाएगी."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi डायरेक्ट प्रारंभ नहीं किया जा सका."</string>
<string name="accept" msgid="1645267259272829559">"स्वीकार करें"</string>
<string name="decline" msgid="2112225451706137894">"अस्वीकार करें"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"आमंत्रण भेजा गया"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"प्रति:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"आवश्‍यक पिन लिखें:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"पिन:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi प्रत्यक्ष चालू है"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"सेटिंग के लिए स्‍पर्श करें"</string>
<string name="select_character" msgid="3365550120617701745">"वर्ण सम्‍मिलित करें"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"अज्ञात एप्‍लिकेशन"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS संदेश भेज रहा है"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index a60dcfaddff7..07759844531a 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Ne može se spojiti na Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ima lošu internetsku vezu."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Izravni Wi-Fi"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Pokreni izravan rad s Wi-Fi mrežom. To će isključiti rad s Wi-Fi klijentom/žarišnom točkom."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Pokretanje izravne Wi-Fi veze nije moguće."</string>
<string name="accept" msgid="1645267259272829559">"Prihvaćam"</string>
<string name="decline" msgid="2112225451706137894">"Odbaci"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pozivnica je poslana"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Prima:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Upišite potreban PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct uključen"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Dodirnite za postavke"</string>
<string name="select_character" msgid="3365550120617701745">"Umetni znak"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Nepoznata aplikacija"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Slanje SMS poruka"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 6b789ea5da70..e125ba8d51b3 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nem sikerült csatlakozni a Wi-Fi hálózathoz"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" rossz internetkapcsolattal rendelkezik."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct elindítása. A Wi-Fi kliens/hotspot ettől leáll."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nem sikerült elindítani a Wi-Fi Direct kapcsolatot."</string>
<string name="accept" msgid="1645267259272829559">"Elfogadás"</string>
<string name="decline" msgid="2112225451706137894">"Elutasítás"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Meghívó elküldve"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Címzett:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Adja meg a szükséges PIN kódot:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN kód:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"A Wi-Fi Direct be van kapcsolva"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"A beállításokhoz érintse meg"</string>
<string name="select_character" msgid="3365550120617701745">"Karakter beszúrása"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Ismeretlen alkalmazás"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS-ek küldése"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 6ed5c0e4b600..72fcb507f652 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Tidak dapat tersambung ke Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" memiliki sambungan internet yang buruk."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Langsung"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Memulai Wi-Fi Langsung. Opsi ini akan mematikan hotspot/klien Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Tidak dapat memulai Wi-Fi Langsung."</string>
<string name="accept" msgid="1645267259272829559">"Terima"</string>
<string name="decline" msgid="2112225451706137894">"Tolak"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Undangan terkirim"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Kepada:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ketik PIN yang diminta:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Langsung aktif"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Sentuh untuk setelan"</string>
<string name="select_character" msgid="3365550120617701745">"Sisipkan huruf"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Apl tak dikenal"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Mengirim pesan SMS"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 8af1f5eee701..ec9457b5bef5 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Impossibile connettersi alla rete Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ha una connessione Internet debole."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Avvia Wi-Fi Direct. Verrà disattivato il client/hotspot Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Avvio di Wi-Fi Direct non riuscito."</string>
<string name="accept" msgid="1645267259272829559">"Accetto"</string>
<string name="decline" msgid="2112225451706137894">"Rifiuto"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invito inviato"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"A:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Inserisci il PIN richiesto:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct è attivo"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tocca per le impostazioni"</string>
<string name="select_character" msgid="3365550120617701745">"Inserisci carattere"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Applicazione sconosciuta"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Invio SMS"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 5d5b9daed4d2..5fc15c720ac9 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"אין אפשרות להתחבר ל-Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" אינו מחובר היטב לאינטרנט."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi ישיר"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"הפעל Wi-Fi ישיר. פעולה זו תכבה את הלקוח/הנקודה החמה של ה-Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"לא ניתן להפעיל Wi-Fi ישיר"</string>
<string name="accept" msgid="1645267259272829559">"קבל"</string>
<string name="decline" msgid="2112225451706137894">"דחה"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"ההזמנה נשלחה"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"אל:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"הקלד את קוד ה-PIN הנדרש."</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi ישיר מופעל"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"גע עבור הגדרות"</string>
<string name="select_character" msgid="3365550120617701745">"הוסף תו"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"יישום לא ידוע"</string>
<string name="sms_control_title" msgid="7296612781128917719">"שולח הודעות SMS"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index cadf93565596..63b8c73f7a3c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wi-Fiに接続できませんでした"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" はインターネット接続に問題があります。"</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Directを開始します。これによりWi-Fiクライアント/アクセスポイントがOFFになります。"</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Directを開始できませんでした。"</string>
<string name="accept" msgid="1645267259272829559">"同意する"</string>
<string name="decline" msgid="2112225451706137894">"同意しない"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"招待状を送信しました"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"To:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"必要なPINを入力してください:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi DirectはONです"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"設定を表示するにはタップしてください"</string>
<string name="select_character" msgid="3365550120617701745">"文字を挿入"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"不明なアプリ"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMSメッセージの送信中"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 031e9f5b7f8c..f97cbdcd6497 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wi-Fi에 연결할 수 없습니다"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" 인터넷 연결 상태가 좋지 않습니다."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct 작업을 시작합니다. 이 작업을 하면 Wi-Fi 클라이언트/핫스팟 작업이 중지됩니다."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct를 시작하지 못했습니다."</string>
<string name="accept" msgid="1645267259272829559">"동의"</string>
<string name="decline" msgid="2112225451706137894">"거부"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"초대장을 보냈습니다."</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"받는사람:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"필수 PIN 입력:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct 켜짐"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"설정으로 이동하려면 터치하세요."</string>
<string name="select_character" msgid="3365550120617701745">"문자 삽입"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"알 수 없는 앱"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS 메시지를 보내는 중"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 22830ac6a146..a1694afb6e26 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nepavyko prisijungti prie „Wi-Fi“"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" turi prastą interneto ryšį."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Tiesioginis „Wi-Fi“ ryšys"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Paleiskite „Wi-Fi Direct“. Bus išjungta „Wi-Fi“ programa / viešosios interneto prieigos taškas."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nepavyko paleisti „Wi-Fi Direct“."</string>
<string name="accept" msgid="1645267259272829559">"Sutikti"</string>
<string name="decline" msgid="2112225451706137894">"Atmesti"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pakvietimas išsiųstas"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Skirta:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Įveskite reikiamą PIN kodą:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN kodas:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"„Wi-Fi Direct“ įjungta"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Jei norite peržiūrėti nustatymus, palieskite"</string>
<string name="select_character" msgid="3365550120617701745">"Įterpti simbolį"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Nežinoma programa"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS pranešimų siuntimas"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index f716cb99f65c..aeec70b31b14 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nevarēja izveidot savienojumu ar Wi-Fi."</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ir slikts interneta savienojums."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Palaist programmu Wi-Fi Direct. Tādējādi tiks izslēgta Wi-Fi klienta/tīklāja darbība."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nevarēja palaist programmu Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Piekrist"</string>
<string name="decline" msgid="2112225451706137894">"Noraidīt"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Ielūgums ir nosūtīts."</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Kam:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ierakstiet pieprasīto PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct ir ieslēgts"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Pieskarieties, lai piekļūtu iestatījumiem."</string>
<string name="select_character" msgid="3365550120617701745">"Ievietojiet rakstzīmi"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Nezināma lietotne"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Īsziņu sūtīšana"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 6aa9e4cafd29..0610e88c6b9f 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Tidak boleh menyambung kepada Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" mempunyai sambungan internet yang kurang baik."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Langsung"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Mulakan Wi-Fi Langsung. Hal ini akan mematikan pengendalian klien/liputan Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Tidak dapat memulakan Wi-Fi Langsung."</string>
<string name="accept" msgid="1645267259272829559">"Terima"</string>
<string name="decline" msgid="2112225451706137894">"Tolak"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Jemputan dihantar"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Kepada:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Taipkan PIN yang diperlukan:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct dihidupkan"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Sentuh untuk tetapan"</string>
<string name="select_character" msgid="3365550120617701745">"Masukkan aksara"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Apl tidak diketahui"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Menghantar mesej SMS"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b1b3cbef1cbb..e82718ef3aa5 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kan ikke koble til Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" har en dårlig Internett-tilkobling."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Start Wi-Fi Direct. Dette deaktiverer Wi-Fi-klienten/-sonen."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kunne ikke starte Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Godta"</string>
<string name="decline" msgid="2112225451706137894">"Avslå"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitasjonen er sendt"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Til:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Skriv inn påkrevd PIN-kode:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct er slått på"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Berør for å se innstillinger"</string>
<string name="select_character" msgid="3365550120617701745">"Sett inn tegn"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Ukjent app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Sender SMS-meldinger"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 94094455845d..ef4dd6a67591 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kan geen verbinding maken met Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" heeft een slechte internetverbinding."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wifi Direct starten. Hierdoor wordt de wifi-client/hotspot uitgeschakeld."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kan Wifi Direct niet starten."</string>
<string name="accept" msgid="1645267259272829559">"Accepteren"</string>
<string name="decline" msgid="2112225451706137894">"Weigeren"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Uitnodiging verzonden"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Naar:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Voer de gewenste pincode in:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Pincode"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct is actief"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Aanraken voor instellingen"</string>
<string name="select_character" msgid="3365550120617701745">"Teken invoegen"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Onbekende app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS-berichten verzenden"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 8564dc793658..f231d897d9c5 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nie można połączyć się z siecią Wi-Fi."</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ma powolne połączenie internetowe."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Uruchom Wi-Fi Direct. Spowoduje to wyłączenie klienta lub punktu dostępu Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nie można uruchomić Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Akceptuj"</string>
<string name="decline" msgid="2112225451706137894">"Odrzuć"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Wysłano zaproszenie"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Do:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Wpisz wymagany kod PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Kod PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct włączone"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Dotknij, aby zmienić ustawienia"</string>
<string name="select_character" msgid="3365550120617701745">"Wstaw znak"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Nieznana aplikacja"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Wysyłanie wiadomości SMS"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index a162aa086f3c..f3f0600904cf 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Não foi possível ligar a Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tem uma ligação à internet fraca."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar o Wi-Fi Direct. Esta opção desativará o cliente/zona Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Não foi possível iniciar o Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Aceitar"</string>
<string name="decline" msgid="2112225451706137894">"Recusar"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Convite enviado"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introduza o PIN solicitado:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"O Wi-Fi Direct está ativado"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Toque para aceder às definições"</string>
<string name="select_character" msgid="3365550120617701745">"Introduzir carácter"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicação desconhecida"</string>
<string name="sms_control_title" msgid="7296612781128917719">"A enviar mensagens SMS"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 5c16708eec86..b49c36b61e20 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Não foi possível se conectar a redes Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tem uma conexão de baixa qualidade com a Internet."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar o Wi-Fi Direct. Isso desativará o ponto de acesso/cliente Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Não foi possível iniciar o Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Aceitar"</string>
<string name="decline" msgid="2112225451706137894">"Recusar"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Convite enviado"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Digite o PIN obrigatório:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct ativado"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tocar para acessar configurações"</string>
<string name="select_character" msgid="3365550120617701745">"Inserir caractere"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicativo desconhecido"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Enviando mensagens SMS"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index 57daee71770e..1447c0e085d2 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -1418,12 +1418,6 @@
<skip />
<!-- no translation found for wifi_watchdog_network_disabled_detailed (5548780776418332675) -->
<skip />
- <!-- no translation found for wifi_p2p_dialog_title (97611782659324517) -->
- <skip />
- <!-- no translation found for wifi_p2p_turnon_message (2909250942299627244) -->
- <skip />
- <!-- no translation found for wifi_p2p_failed_message (3763669677935623084) -->
- <skip />
<!-- no translation found for accept (1645267259272829559) -->
<skip />
<!-- no translation found for decline (2112225451706137894) -->
@@ -1440,10 +1434,6 @@
<skip />
<!-- no translation found for wifi_p2p_show_pin_message (8530563323880921094) -->
<skip />
- <!-- no translation found for wifi_p2p_enabled_notification_title (2068321881673734886) -->
- <skip />
- <!-- no translation found for wifi_p2p_enabled_notification_message (1638949953993894335) -->
- <skip />
<string name="select_character" msgid="3365550120617701745">"Inserir in caracter"</string>
<!-- no translation found for sms_control_default_app_name (3058577482636640465) -->
<skip />
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index fe3687fbfd79..f241e8e91139 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nu se poate conecta la Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" are o conexiune la internet slabă."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Porniţi Wi-Fi Direct. Acest lucru va dezactiva clientul/hotspotul Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct nu a putut porni."</string>
<string name="accept" msgid="1645267259272829559">"Acceptaţi"</string>
<string name="decline" msgid="2112225451706137894">"Refuzaţi"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitaţia a fost trimisă."</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Către:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introduceţi codul PIN necesar:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Cod PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct este activat"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Atingeţi pentru setări"</string>
<string name="select_character" msgid="3365550120617701745">"Introduceţi caracterul"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicaţie necunoscută"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Se trimit mesaje SMS"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 55468a07c6b2..809da8324edc 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Не удалось подключиться к сети Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" – плохое интернет-соединение."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Начать соединение через Wi-Fi Direct. Модуль Wi-Fi будет отключен."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Не удалось запустить Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Принять"</string>
<string name="decline" msgid="2112225451706137894">"Отклонить"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Приглашение отправлено"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Кому:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Введите PIN-код:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct включен"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Нажмите, чтобы открыть настройки"</string>
<string name="select_character" msgid="3365550120617701745">"Введите символ"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Неизвестное приложение"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Отправка SMS-сообщений"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 7d1aa7838e75..7d75949ee008 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nepodarilo sa pripojiť k sieti Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" má nekvalitné internetové pripojenie."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Priame pripojenie Wi-Fi"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Spustiť priame pripojenie siete Wi-Fi. Táto možnosť vypne sieť Wi-Fi v režime klient alebo hotspot."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Priame pripojenie siete Wi-Fi sa nepodarilo spustiť"</string>
<string name="accept" msgid="1645267259272829559">"Prijať"</string>
<string name="decline" msgid="2112225451706137894">"Odmietnuť"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pozvánka bola odoslaná"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Komu:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Zadajte požadovaný kód PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Priame pripojenie siete Wi-Fi je zapnuté"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Nastavenia otvoríte dotykom"</string>
<string name="select_character" msgid="3365550120617701745">"Vkladanie znakov"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznáma aplikácia"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Odosielanie správ SMS"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index dfedb9af8372..e152a2f7b817 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Z omrežjem Wi-Fi se ni mogoče povezati"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ima slabo internetno povezavo."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Zaženite Wi-Fi Direct. S tem boste izklopili odjemalca/dostopno točko Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct ni bilo mogoče zagnati."</string>
<string name="accept" msgid="1645267259272829559">"Sprejmi"</string>
<string name="decline" msgid="2112225451706137894">"Zavrni"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Povabilo je poslano"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Za:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Vnesite zahtevano kodo PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct je vklopljen"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Dotaknite se za nastavitve"</string>
<string name="select_character" msgid="3365550120617701745">"Vstavljanje znaka"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznan program"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Pošiljanje sporočil SMS"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 2a2104fc1631..7f1dbcddc0a0 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Није било могуће повезати са Wi-Fi мрежом"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" има лошу интернет везу."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Покрените Wi-Fi Direct. Тиме ћете искључити клијента/хотспот за Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Није могуће покренути Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Прихвати"</string>
<string name="decline" msgid="2112225451706137894">"Одбиј"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Позивница је послата"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Коме:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Унесите потребни PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct је укључен"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Додирните за подешавања"</string>
<string name="select_character" msgid="3365550120617701745">"Уметање знака"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Непозната апликација"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Слање SMS порука"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 0067a12f4b09..10ecffc82728 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Det gick inte att ansluta till Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" har en dålig Internetanslutning."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi direkt"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Starta direkt Wi-Fi-användning. Detta inaktiverar Wi-Fi-användning med klient/trådlös surfzon."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Det gick inte att starta Wi-Fi direkt."</string>
<string name="accept" msgid="1645267259272829559">"Godkänn"</string>
<string name="decline" msgid="2112225451706137894">"Avvisa"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Inbjudan har skickats"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Till:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ange den obligatoriska PIN-koden:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kod:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct är aktiverat"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tryck om du vill visa inställningar"</string>
<string name="select_character" msgid="3365550120617701745">"Infoga tecken"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Okänd app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Skickar SMS"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 369853c34966..d245685571d4 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Haikuweza kuunganisha kwa Mtandao-Hewa"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ina muunganisho duni wa Mtandao."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Mtandao hewa Moja kwa moja"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Anzisha Wi-Fi Moja kwa Moja. Hii itazima mteja/mtandao-hewa wa Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Haikuweza kuanzisha Wi-Fi Moja kwa Moja."</string>
<string name="accept" msgid="1645267259272829559">"Kubali"</string>
<string name="decline" msgid="2112225451706137894">"Kataa"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Mwaliko umetumwa"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Kwa:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Charaza PIN inayohitajika:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi ya Moja kwa Moja imewashwa"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Gusa kwa ajili ya mipangilio"</string>
<string name="select_character" msgid="3365550120617701745">"Ingiza kibambo"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Programu isiyojulikana"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Inatuma ujumbe wa SMS"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index debe568bc8c3..97f1c70cb863 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"ไม่สามารถเชื่อมต่อ Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" มีสัญญาณอินเทอร์เน็ตไม่ดี"</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"เริ่มการทำงาน WiFi Direct ซึ่งจะเป็นการปิดการทำงาน WiFi ไคลเอ็นต์/ฮอตสปอต"</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"ไม่สามารถเริ่ม WiFi Direct ได้"</string>
<string name="accept" msgid="1645267259272829559">"ยอมรับ"</string>
<string name="decline" msgid="2112225451706137894">"ปฏิเสธ"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"ส่งข้อความเชิญแล้ว"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"ถึง:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"พิมพ์ PIN ที่ต้องการ:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"เปิด Wi-Fi Direct อยู่"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"แตะเพื่อตั้งค่า"</string>
<string name="select_character" msgid="3365550120617701745">"ใส่อักขระ"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"แอปพลิเคชันที่ไม่รู้จัก"</string>
<string name="sms_control_title" msgid="7296612781128917719">"กำลังส่งข้อความ SMS"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index e74e309c3b1e..f59cde4360f4 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Hindi makakonekta sa Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ay mayroong mahinang koneksyon sa Internet."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Simulan ang Wi-Fi Direct. I-o-off nito ang client/hotspot ng Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Hindi masimulan ang Wi-Fi Direct"</string>
<string name="accept" msgid="1645267259272829559">"Tanggapin"</string>
<string name="decline" msgid="2112225451706137894">"Tanggihan"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Naipadala ang imbitasyon"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Kay:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"I-type ang kinakailangang PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Ang Wi-Fi Direct ay naka-on"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Pindutin para sa mga setting"</string>
<string name="select_character" msgid="3365550120617701745">"Magpasok ng character"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Hindi kilalang app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Nagpapadala ng mga SMS na mensahe"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8d52c8c8fc06..bcaa9a8989a8 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kablosuz bağlantısı kurulamadı"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" İnternet bağlantısı zayıf."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Kablosuz Doğrudan Bağlantı"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Kablosuz Doğrudan Bağlantıyı başlat. Bu işlem, Kablosuz istemci/hotspot kullanımını kapatacak."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kablosuz Doğrudan bağlantı başlatılamadı."</string>
<string name="accept" msgid="1645267259272829559">"Kabul et"</string>
<string name="decline" msgid="2112225451706137894">"Reddet"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Davetiye gönderildi"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Alıcı:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Gerekli PIN\'i yazın:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Kablosuz Doğrudan özelliği açık"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Ayarlar için dokunun"</string>
<string name="select_character" msgid="3365550120617701745">"Karakter ekle"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Bilinmeyen uygulama"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS mesajları gönderiliyor"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 9d049cf05cf1..6288aa16cd99 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Не вдалося під’єднатися до мережі Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" має погане з’єднання з Інтернетом."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Запустити Wi-Fi Direct. Це вимкне з’єднання Wi-Fi клієнт/точка доступу."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Не вдалося запустити Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Прийняти"</string>
<string name="decline" msgid="2112225451706137894">"Відхилити"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Запрошення надіслано"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Кому:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Введіть потрібний PIN-код:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct увімкнено"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Торкніться, щоб побачити налаштування"</string>
<string name="select_character" msgid="3365550120617701745">"Вставл-ня символу"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Невідома програма"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Надсил. SMS повідомлень"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 81666068ae8e..ba81838dbf6a 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Không thể kết nối với Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" có kết nối Internet không tốt."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Khởi động Wi-Fi Direct. Việc này sẽ tắt hoạt động của ứng dụng khách/điểm phát sóng Wi-Fi."</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Không thể khởi động Wi-Fi Direct."</string>
<string name="accept" msgid="1645267259272829559">"Đồng ý"</string>
<string name="decline" msgid="2112225451706137894">"Từ chối"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Đã gửi thư mời"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Người nhận:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Nhập PIN bắt buộc:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Mã PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct được bật"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Chạm để cài đặt"</string>
<string name="select_character" msgid="3365550120617701745">"Chèn ký tự"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"Ứng dụng không xác định"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Đang gửi tin nhắn SMS"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index a9f431c54206..cbfc222f737d 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"无法连接到 Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" 互联网连接状况不佳。"</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"启动 Wi-Fi Direct。此操作将会关闭 Wi-Fi 客户端/热点。"</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"无法启动 Wi-Fi Direct。"</string>
<string name="accept" msgid="1645267259272829559">"接受"</string>
<string name="decline" msgid="2112225451706137894">"拒绝"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"邀请已发送"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"收件人:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"键入所需的 PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"已启用 Wi-Fi Direct"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"通过触摸进行设置"</string>
<string name="select_character" msgid="3365550120617701745">"插入字符"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"未知应用程序"</string>
<string name="sms_control_title" msgid="7296612781128917719">"正在发送短信"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 5aee830e55da..4d6042c713f9 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"無法連線至 Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" 的網際網路連線狀況不佳。"</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"啟動 WiFi Direct 作業,這會關閉 WiFi 用戶端/無線基地台作業。"</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"無法啟動 WiFi Direct。"</string>
<string name="accept" msgid="1645267259272829559">"接受"</string>
<string name="decline" msgid="2112225451706137894">"拒絕"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"邀請函已傳送"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"收件者:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"請輸入必要的 PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct 已開啟"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"輕觸即可設定"</string>
<string name="select_character" msgid="3365550120617701745">"插入字元"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"不明的應用程式"</string>
<string name="sms_control_title" msgid="7296612781128917719">"傳送 SMS 簡訊"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 36da63de7b51..c90fe7665e55 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -215,7 +215,7 @@
<string name="permdesc_reorderTasks" msgid="4175137612205663399">"Ivumela insiza ukuthi ihambise izenzo ziye ngaphambili kanye nasemumva. Izinsiza ezinobungozi zingaziphoqelela ukuth iziye phambili ngaphandle kokulawula kwakho."</string>
<string name="permlab_removeTasks" msgid="6821513401870377403">"misa izinsiza ezisebenzayo"</string>
<string name="permdesc_removeTasks" msgid="1394714352062635493">"Vumela ukuthi insiza isuse okumele kwenziwe ibulale nezinsiza zakho. Izinsiza eziwubungozi zingaphazamisa ukusebenza kwezinye izinsiza."</string>
- <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"misa ukuhambelana kwesikrini"</string>
+ <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"setha ukuhambelana kwesikrini"</string>
<string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Ivumela uhlelo lokusebenza ukulawula imodi yokuhambelana kwesikrini kwezinye izinhlelo zokusebenza. Izinhlelo zokusebenza ezinonya zingase zephule ukuziphatha kwezinye izinhlelo zokusebenza."</string>
<string name="permlab_setDebugApp" msgid="3022107198686584052">"vumela insiza ilungise inkinga"</string>
<string name="permdesc_setDebugApp" msgid="4474512416299013256">"Ivumela insiza ukuthi ivule uhlelo lokulungisa lwenye insiza. Izinsiza ezinobungozi zingasebenzisa lokhu ukubulala ezinye izinsiza."</string>
@@ -973,9 +973,6 @@
<skip />
<string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Ayikwazanga ukuxhuma kwi-Wi-Fi"</string>
<string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" inoxhumano oluphansi lwe-inthanethi."</string>
- <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"I-WiFi Eqondile"</string>
- <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Qala ukusebenza kwe-WiFi Okuqondile. Lokhu kuzocima ikhasimende le-WiFi/Ukusebenza okwe-hotspot"</string>
- <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Yehlulekile ukuqala i-Wi-Fi Ngqo"</string>
<string name="accept" msgid="1645267259272829559">"Yamukela"</string>
<string name="decline" msgid="2112225451706137894">"Nqaba"</string>
<string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Isimemo sithunyelwe"</string>
@@ -984,8 +981,6 @@
<string name="wifi_p2p_to_message" msgid="248968974522044099">"Ku:"</string>
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Faka i-PIN edingekayo:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
- <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"I-Wi-Fi Direct ivulekile"</string>
- <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Thinta ukuze uthole izilungiselelo"</string>
<string name="select_character" msgid="3365550120617701745">"Faka uhlamvu"</string>
<string name="sms_control_default_app_name" msgid="3058577482636640465">"insiza engaziwa"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Ithumela imiyalezo ye-SMS"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3a7225ed1daf..1eab01aa7e58 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -246,6 +246,13 @@
<item>4</item>
</integer-array>
+ <!-- If the DUN connection for this CDMA device supports more than just DUN -->
+ <!-- traffic you should list them here. -->
+ <!-- If this device is not CDMA this is ignored. If this list is empty on -->
+ <!-- a DUN-requiring CDMA device, the DUN APN will just support just DUN. -->
+ <string-array translatable="false" name="config_cdma_dun_supported_types">
+ </string-array>
+
<!-- String containing the apn value for tethering. May be overriden by secure settings
TETHER_DUN_APN. Value is a comma separated series of strings:
"name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4e55b4f36e51..f347a4e3f3cf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -218,7 +218,6 @@
<java-symbol type="bool" name="config_allowActionMenuItemTextWithIcon" />
<java-symbol type="bool" name="config_bluetooth_adapter_quick_switch" />
<java-symbol type="bool" name="config_bluetooth_sco_off_call" />
- <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
<java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
<java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
<java-symbol type="bool" name="config_enable_puk_unlock_screen" />
@@ -864,6 +863,7 @@
<java-symbol type="array" name="special_locale_codes" />
<java-symbol type="array" name="special_locale_names" />
<java-symbol type="array" name="config_masterVolumeRamp" />
+ <java-symbol type="array" name="config_cdma_dun_supported_types" />
<java-symbol type="drawable" name="default_wallpaper" />
<java-symbol type="drawable" name="ic_suggestions_add" />
@@ -1084,7 +1084,6 @@
<java-symbol type="style" name="ActiveWallpaperSettings" />
<java-symbol type="style" name="Animation.InputMethodFancy" />
<java-symbol type="style" name="Animation.Wallpaper" />
- <java-symbol type="style" name="Animation.RecentApplications" />
<java-symbol type="style" name="Animation.ZoomButtons" />
<java-symbol type="style" name="PreviewWallpaperSettings" />
<java-symbol type="style" name="TextAppearance.SlidingTabActive" />
@@ -1440,6 +1439,7 @@
<java-symbol type="anim" name="push_down_out" />
<java-symbol type="anim" name="push_up_in" />
<java-symbol type="anim" name="push_up_out" />
+ <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
<java-symbol type="dimen" name="status_bar_icon_size" />
<java-symbol type="dimen" name="system_bar_icon_size" />
<java-symbol type="drawable" name="list_selector_pressed_holo_dark" />
@@ -1477,6 +1477,7 @@
<java-symbol type="string" name="usb_storage_stop_notification_title" />
<java-symbol type="string" name="usb_storage_stop_title" />
<java-symbol type="string" name="usb_storage_title" />
+ <java-symbol type="style" name="Animation.RecentApplications" />
<!-- ImfTest -->
<java-symbol type="layout" name="auto_complete_list" />
@@ -3498,4 +3499,10 @@
<public type="attr" name="textDirection"/>
+ <public type="attr" name="paddingStart"/>
+ <public type="attr" name="paddingEnd"/>
+
+ <public type="attr" name="layout_marginStart"/>
+ <public type="attr" name="layout_marginEnd"/>
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c95ddddef719..3c1f50d11b6d 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2248,6 +2248,14 @@
<!-- Description of an application permission which allows the application access serial ports via the SerialManager. [CHAR LIMIT=NONE] -->
<string name="permdesc_serialPort">Allows the holder to access serial ports using the SerialManager API.</string>
+ <!-- Title of an application permission which allows the holder to access content
+ providers from outside an ApplicationThread. [CHAR LIMIT=40] -->
+ <string name="permlab_accessContentProvidersExternally">access content providers externally</string>
+ <!-- Description of an application permission which allows the holder to access
+ content providers from outside an ApplicationThread. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_accessContentProvidersExternally">Allows the holder to access content
+ providers from the shell. Should never be needed for normal apps.</string>
+
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
<string name="save_password_message">Do you want the browser to remember this password?</string>
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
@@ -2708,6 +2716,12 @@
<!-- Do not translate. Default access point SSID used for tethering -->
<string name="wifi_tether_configure_ssid_default" translatable="false">AndroidAP</string>
+ <string name="wifi_p2p_dialog_title">Wi-Fi Direct</string>
+ <string name="wifi_p2p_turnon_message">Start Wi-Fi Direct. This will turn off Wi-Fi client/hotspot.</string>
+ <string name="wifi_p2p_failed_message">Couldn\'t start Wi-Fi Direct.</string>
+ <string name="wifi_p2p_enabled_notification_title">Wi-Fi Direct is on</string>
+ <string name="wifi_p2p_enabled_notification_message">Touch for settings</string>
+
<string name="accept">Accept</string>
<string name="decline">Decline</string>
<string name="wifi_p2p_invitation_sent_title">Invitation sent</string>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
index 9819c54aad6d..9c1922ff71e7 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
@@ -46,8 +46,7 @@ public class ConnectivityManagerTestRunner extends InstrumentationTestRunner {
// create a new test suite
suite.setName("ConnectivityManagerWifiOnlyFunctionalTests");
String[] methodNames = {"testConnectToWifi", "testConnectToWifWithKnownAP",
- "testDisconnectWifi", "testDataConnectionOverAMWithWifi",
- "testDataConnectionWithWifiToAMToWifi", "testWifiStateChange"};
+ "testDisconnectWifi", "testWifiStateChange"};
Class<ConnectivityManagerMobileTest> testClass = ConnectivityManagerMobileTest.class;
for (String method: methodNames) {
suite.addTest(TestSuite.createTest(testClass, method));
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index b81f7744cf37..88f3f348e76b 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \
$(call all-java-files-under, EnabledTestApp/src)
LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib mockwebserver guava
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib mockwebserver guava littlemock
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCoreTests
diff --git a/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java b/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java
index 4d65588532ec..9015a6f5d7e4 100644
--- a/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java
+++ b/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java
@@ -16,6 +16,8 @@
package android.net.http;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
import java.io.File;
import java.net.CacheRequest;
import java.net.CacheResponse;
@@ -30,6 +32,7 @@ import junit.framework.TestCase;
public final class HttpResponseCacheTest extends TestCase {
private File cacheDir;
+ private MockWebServer server = new MockWebServer();
@Override public void setUp() throws Exception {
super.setUp();
@@ -39,6 +42,7 @@ public final class HttpResponseCacheTest extends TestCase {
@Override protected void tearDown() throws Exception {
ResponseCache.setDefault(null);
+ server.shutdown();
super.tearDown();
}
@@ -100,4 +104,32 @@ public final class HttpResponseCacheTest extends TestCase {
cache.delete();
assertNull(ResponseCache.getDefault());
}
+
+ /**
+ * Make sure that statistics tracking are wired all the way through the
+ * wrapper class. http://code.google.com/p/android/issues/detail?id=25418
+ */
+ public void testStatisticsTracking() throws Exception {
+ HttpResponseCache cache = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024);
+
+ server.enqueue(new MockResponse()
+ .addHeader("Cache-Control: max-age=60")
+ .setBody("A"));
+ server.play();
+
+ URLConnection c1 = server.getUrl("/").openConnection();
+ assertEquals('A', c1.getInputStream().read());
+ assertEquals(1, cache.getRequestCount());
+ assertEquals(1, cache.getNetworkCount());
+ assertEquals(0, cache.getHitCount());
+
+ URLConnection c2 = server.getUrl("/").openConnection();
+ assertEquals('A', c2.getInputStream().read());
+
+ URLConnection c3 = server.getUrl("/").openConnection();
+ assertEquals('A', c3.getInputStream().read());
+ assertEquals(3, cache.getRequestCount());
+ assertEquals(1, cache.getNetworkCount());
+ assertEquals(2, cache.getHitCount());
+ }
}
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocus.java b/core/tests/coretests/src/android/widget/focus/RequestFocus.java
index af9ee170abe4..21d762a55061 100644
--- a/core/tests/coretests/src/android/widget/focus/RequestFocus.java
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocus.java
@@ -21,9 +21,7 @@ import com.android.frameworks.coretests.R;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
-import android.widget.LinearLayout;
import android.widget.Button;
-import android.view.View;
/**
* Exercises cases where elements of the UI are requestFocus()ed.
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
index a78b0c932c4b..f2eba2335808 100644
--- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
@@ -16,21 +16,28 @@
package android.widget.focus;
-import android.widget.focus.RequestFocus;
-import com.android.frameworks.coretests.R;
+import static com.google.testing.littlemock.LittleMock.inOrder;
+import static com.google.testing.littlemock.LittleMock.mock;
import android.os.Handler;
-import android.test.ActivityInstrumentationTestCase;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.Button;
import android.util.AndroidRuntimeException;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
+import android.widget.Button;
+
+import com.android.frameworks.coretests.R;
+import com.google.testing.littlemock.LittleMock.InOrder;
/**
* {@link RequestFocusTest} is set up to exercise cases where the views that
* have focus become invisible or GONE.
*/
-public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFocus> {
+public class RequestFocusTest extends ActivityInstrumentationTestCase2<RequestFocus> {
private Button mTopLeftButton;
private Button mBottomLeftButton;
@@ -39,7 +46,7 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc
private Handler mHandler;
public RequestFocusTest() {
- super("com.android.frameworks.coretests", RequestFocus.class);
+ super(RequestFocus.class);
}
@Override
@@ -94,4 +101,90 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc
e.getClass().getName());
}
}
+
+ /**
+ * This tests checks the case in which the first focusable View clears focus.
+ * In such a case the framework tries to give the focus to another View starting
+ * from the top. Hence, the framework will try to give focus to the view that
+ * wants to clear its focus.
+ *
+ * @throws Exception If an error occurs.
+ */
+ @UiThreadTest
+ public void testOnFocusChangeCallbackOrderWhenClearingFocusOfFirstFocusable()
+ throws Exception {
+ // Get the first focusable.
+ Button clearingFocusButton = mTopLeftButton;
+ Button gainingFocusButton = mTopLeftButton;
+
+ // Make sure that the clearing focus View is the first focusable.
+ View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null,
+ View.FOCUS_FORWARD);
+ assertSame("The clearing focus button is the first focusable.",
+ clearingFocusButton, focusCandidate);
+ assertSame("The gaining focus button is the first focusable.",
+ gainingFocusButton, focusCandidate);
+
+ // Focus the clearing focus button.
+ clearingFocusButton.requestFocus();
+ assertTrue(clearingFocusButton.hasFocus());
+
+ // Register the invocation order checker.
+ CombinedListeners mock = mock(CombinedListeners.class);
+ clearingFocusButton.setOnFocusChangeListener(mock);
+ gainingFocusButton.setOnFocusChangeListener(mock);
+ clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(mock);
+
+ // Try to clear focus.
+ clearingFocusButton.clearFocus();
+
+ // Check that no callback was invoked since focus did not move.
+ InOrder inOrder = inOrder(mock);
+ inOrder.verify(mock).onFocusChange(clearingFocusButton, false);
+ inOrder.verify(mock).onGlobalFocusChanged(clearingFocusButton, gainingFocusButton);
+ inOrder.verify(mock).onFocusChange(gainingFocusButton, true);
+ }
+
+ public interface CombinedListeners extends OnFocusChangeListener, OnGlobalFocusChangeListener {}
+
+ /**
+ * This tests check whether the on focus change callbacks are invoked in
+ * the proper order when a View loses focus and the framework gives it to
+ * the fist focusable one.
+ *
+ * @throws Exception
+ */
+ @UiThreadTest
+ public void testOnFocusChangeCallbackOrderWhenClearingFocusOfNotFirstFocusable()
+ throws Exception {
+ Button clearingFocusButton = mTopRightButton;
+ Button gainingFocusButton = mTopLeftButton;
+
+ // Make sure that the clearing focus View is not the first focusable.
+ View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null,
+ View.FOCUS_FORWARD);
+ assertNotSame("The clearing focus button is not the first focusable.",
+ clearingFocusButton, focusCandidate);
+ assertSame("The gaining focus button is the first focusable.",
+ gainingFocusButton, focusCandidate);
+
+ // Focus the clearing focus button.
+ clearingFocusButton.requestFocus();
+ assertTrue(clearingFocusButton.hasFocus());
+
+ // Register the invocation order checker.
+ CombinedListeners mock = mock(CombinedListeners.class);
+ clearingFocusButton.setOnFocusChangeListener(mock);
+ gainingFocusButton.setOnFocusChangeListener(mock);
+ clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(mock);
+
+ // Try to clear focus.
+ clearingFocusButton.clearFocus();
+
+ // Check that no callback was invoked since focus did not move.
+ InOrder inOrder = inOrder(mock);
+ inOrder.verify(mock).onFocusChange(clearingFocusButton, false);
+ inOrder.verify(mock).onGlobalFocusChanged(clearingFocusButton, gainingFocusButton);
+ inOrder.verify(mock).onFocusChange(gainingFocusButton, true);
+ }
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6cd07a31ccc5..8be1db2bb1f6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -158,6 +158,7 @@
<assign-permission name="android.permission.BACKUP" uid="shell" />
<assign-permission name="android.permission.FORCE_STOP_PACKAGES" uid="shell" />
<assign-permission name="android.permission.STOP_APP_SWITCHES" uid="shell" />
+ <assign-permission name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" uid="shell" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
<assign-permission name="android.permission.ACCESS_DRM" uid="media" />
diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd
index d390ec1474d1..d22dca1fee41 100644
--- a/docs/html/guide/developing/device.jd
+++ b/docs/html/guide/developing/device.jd
@@ -51,19 +51,13 @@ would on the emulator. Before you can start, there are just a few things to do:<
<ol>
<li>Declare your application as "debuggable" in your Android Manifest.
- <p>In Eclipse, you can do this from the <b>Application</b> tab when viewing the Manifest
- (on the right side, set <b>Debuggable</b> to <em>true</em>). Otherwise, in the
-<code>AndroidManifest.xml</code>
- file, add <code>android:debuggable="true"</code> to the <code>&lt;application></code>
-element.</p>
- </li>
- <li>Set up your device to allow installation of non-Market applications. <p>On
-the device, go to <strong>Settings > Applications</strong> and enable
-
-<strong>Unknown sources</strong> (on an Android 4.0 device, the setting is
-located in <strong>Settings > Security</strong>).</p>
-
- </li>
+ <p>When using Eclipse, you can skip this step, because running your app directly from
+the Eclipse IDE automatically enables debugging.</p>
+ <p>In the <code>AndroidManifest.xml</code> file, add <code>android:debuggable="true"</code> to
+the <code>&lt;application></code> element.</p>
+ <p class="note"><strong>Note:</strong> If you manually enable debugging in the manifest
+ file, be sure to disable it before you build for release (your published application
+should usually <em>not</em> be debuggable).</p></li>
<li>Turn on "USB Debugging" on your device.
<p>On the device, go to <strong>Settings > Applications > Development</strong>
and enable <strong>USB debugging</strong>
@@ -72,13 +66,10 @@ located in <strong>Settings > Developer options</strong>).</p>
</li>
<li>Set up your system to detect your device.
<ul>
- <li>If you're developing on Windows, you need to install a USB driver
- for adb. If you're using an Android Developer Phone (ADP), Nexus One, or Nexus S,
- see the <a href="{@docRoot}sdk/win-usb.html">Google Windows USB
- Driver</a>. Otherwise, you can find a link to the appropriate OEM driver in the
- <a href="{@docRoot}sdk/oem-usb.html">OEM USB Drivers</a> document.</li>
+ <li>If you're developing on Windows, you need to install a USB driver for adb. For an
+installation guide and links to OEM drivers, see the <a href="{@docRoot}sdk/oem-usb.html">OEM USB
+Drivers</a> document.</li>
<li>If you're developing on Mac OS X, it just works. Skip this step.</li>
-
<li>If you're developing on Ubuntu Linux, you need to add a
<code>udev</code> rules file that contains a USB configuration for each type of device
you want to use for development. In the rules file, each device manufacturer
@@ -114,7 +105,7 @@ rules</a>.</p>
</li>
</ol>
-<p>You can verify that your device is connected by executing <code>adb
+<p>When plugged in over USB, can verify that your device is connected by executing <code>adb
devices</code> from your SDK {@code platform-tools/} directory. If connected,
you'll see the device name listed as a "device."</p>
diff --git a/docs/html/index.jd b/docs/html/index.jd
index b9d67584aa74..431a7d26482c 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -154,7 +154,7 @@ href="{@docRoot}resources/dashboard/platform-versions.html">Learn more &raquo;</
+ "href='https://plus.google.com/108967384991768947849'>+Android Developers</a>. "
+ "We'll use it to host Hangouts for developers, talk about the latest releases, "
+ "development and design tips, and much more.</p>"
-+ "<div style='margin:.7em 0 0 -1.2em'><g:plus href='https://plus.google.com/108967384991768947849' "
++ "<div style='margin:.7em 0 0 0'><g:plus href='https://plus.google.com/108967384991768947849' "
+ "size=\"smallbadge\" width=\"275\"></g:plus></div>"
},
diff --git a/docs/html/sdk/oem-usb.jd b/docs/html/sdk/oem-usb.jd
index b81be71dc079..f98257df0027 100644
--- a/docs/html/sdk/oem-usb.jd
+++ b/docs/html/sdk/oem-usb.jd
@@ -3,9 +3,21 @@ page.title=OEM USB Drivers
<div id="qv-wrapper">
<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#InstallingDriver">Installing a USB Driver</a>
+ <ol>
+ <li><a href="#Win7">Windows 7</a></li>
+ <li><a href="#WinXp">Windows XP</a></li>
+ <li><a href="#WinVista">Windows Vista</a></li>
+ </ol>
+ </li>
+ <li><a href="#Drivers">OEM Drivers</a></li>
+ </ol>
+
<h2>See also</h2>
<ol>
- <li><a href="{@docRoot}guide/developing/device.html">Developing on a Device</a></li>
+ <li><a href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a></li>
<li><a href="{@docRoot}sdk/win-usb.html">Google USB Driver</a></li>
</ol>
</div>
@@ -18,8 +30,185 @@ where you can download the appropriate USB driver for your device. However, this
not exhaustive for all available Android-powered devices.</p>
<p>If you're developing on Mac OS X or Linux, then you probably don't need to install a USB driver.
-Refer to <a href="{@docRoot}guide/developing/device.html#setting-up">Setting up a Device</a> to
-start development with a device.</p>
+To start developing with your device, read <a
+href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a>.</p>
+
+<p class="note"><strong>Note:</strong> If your device is one of the Android Developer Phones
+(purchased from the Android Market publisher site), a Nexus One, or a Nexus S, then you need
+the <a href="{@docRoot}sdk/win-usb.html">Google USB Driver</a>, instead of an OEM driver. The Galaxy
+Nexus driver, however, is distributed by <a
+href="http://www.samsung.com/us/support/downloads/verizon-wireless/SCH-I515MSAVZW">Samsung</a>
+(listed as model SCH-I515).</p>
+
+
+<h2 id="InstallingDriver">Installing a USB Driver</h2>
+
+<p>First, find the appropriate driver for your device from the <a href="#Drivers">OEM drivers</a>
+table below.</p>
+
+<p>Once you've downloaded your USB driver, follow the instructions below to install or upgrade the
+driver, based on your version of Windows and whether you're installing for the first time
+or upgrading an existing driver.</p>
+
+<p class="note"><strong>Tip:</strong> When you finish the USB driver installation,
+see <a
+href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a> for
+other important information about using an Android-powered device for
+development.</p>
+
+<ol class="nolist">
+ <li><a href="#Win7">Windows 7</a></li>
+ <li><a href="#WinXp">Windows XP</a></li>
+ <li><a href="#WinVista">Windows Vista</a></li>
+</ol>
+
+
+<p class="caution"><strong>Caution:</strong>
+You may make changes to <code>android_winusb.inf</code> file found inside
+<code>usb_driver\</code> (for example, to add support for other devices),
+however, this will lead to security warnings when you install or upgrade the
+driver. Making any other changes to the driver files may break the installation
+process.</p>
+
+
+<h3 id="Win7">Windows 7</h3>
+
+
+<p>To install the Android USB driver on Windows 7 for the first time:</p>
+<ol>
+ <li>Connect your Android-powered device to your computer's USB port.</li>
+ <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer,
+ and select <strong>Manage</strong>.</li>
+ <li>Select <strong>Devices</strong> in the left pane.</li>
+ <li>Locate and expand <em>Other device</em> in the right pane.</li>
+ <li>Right-click the device name (such as <em>Nexus S</em>) and select <strong>Update
+ Driver Software</strong>.
+ This will launch the Hardware Update Wizard.</li>
+ <li>Select <strong>Browse my computer for driver software</strong> and click
+ <strong>Next</strong>.</li>
+ <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
+Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
+ <li>Click <strong>Next</strong> to install the driver.</li>
+</ol>
+
+<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows 7 with the new
+driver:</p>
+
+<ol>
+ <li>Connect your Android-powered device to your computer's USB port.</li>
+ <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer,
+ and select <strong>Manage</strong>.</li>
+ <li>Select <strong>Device Manager</strong> in the left pane of the Computer Management
+ window.</li>
+ <li>Locate and expand <em>Android Phone</em> in the right pane.</li>
+ <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update
+ Driver</strong>.
+ This will launch the Hardware Update Wizard.</li>
+ <li>Select <strong>Install from a list or specific location</strong> and click
+ <strong>Next</strong>.</li>
+ <li>Select <strong>Search for the best driver in these locations</strong>; un-check
+<strong>Search removable media</strong>; and check <strong>Include this location in the
+search</strong>.</li>
+ <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
+Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
+ <li>Click <strong>Next</strong> to upgrade the driver.</li>
+</ol>
+
+
+
+
+
+<h3 id="WinXp">Windows XP</h3>
+
+<p>To install the Android USB driver on Windows XP for the first time:</p>
+
+<ol>
+ <li>Connect your Android-powered device to your computer's USB port. Windows
+ will detect the device and launch the Hardware Update Wizard.</li>
+ <li>Select <strong>Install from a list or specific location</strong> and click
+ <strong>Next</strong>.</li>
+ <li>Select <strong>Search for the best driver in these locations</strong>; un-check
+<strong>Search
+ removable media</strong>; and check <strong>Include
+this location in the search</strong>.</li>
+ <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
+Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
+ <li>Click <strong>Next</strong> to install the driver.</li>
+</ol>
+
+<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows XP with the new
+driver:</p>
+
+<ol>
+ <li>Connect your Android-powered device to your computer's USB port.</li>
+ <li>Right-click on <em>My Computer</em> from your desktop or Windows Explorer,
+ and select <strong>Manage</strong>.</li>
+ <li>Select <strong>Device Manager</strong> in the left pane.</li>
+ <li>Locate and expand <em>Android Phone</em> in the right pane.</li>
+ <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update
+ Driver</strong>.
+ This will launch the Hardware Update Wizard.</li>
+ <li>Select <strong>Install from a list or specific location</strong> and click
+ <strong>Next</strong>.</li>
+ <li>Select <strong>Search for the best driver in these locations</strong>; un-check <strong>Search
+ removable media</strong>; and check <strong>Include
+this location in the search</strong>.</li>
+ <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
+Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
+ <li>Click <strong>Next</strong> to upgrade the driver.</li>
+</ol>
+
+
+
+<h3 id="WinVista">Windows Vista</h3>
+
+<p>To install the Android USB driver on Windows Vista for the first time:</p>
+
+<ol>
+ <li>Connect your Android-powered device to your computer's USB port. Windows
+ will detect the device and launch the Found New Hardware wizard.</li>
+ <li>Select <strong>Locate and install driver software</strong>.</li>
+ <li>Select <strong>Don't search online</strong>.</li>
+ <li>Select <strong>I don't have the disk. Show me other options</strong>.</li>
+ <li>Select <strong>Browse my computer for driver software</strong>.</li>
+ <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
+Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.) As long as you specified the
+exact location of the
+ installation package, you may leave <strong>Include subfolders</strong> checked or
+ unchecked&mdash;it doesn't matter.</li>
+ <li>Click <strong>Next</strong>. Vista may prompt you to confirm the privilege elevation
+ required for driver installation. Confirm it.</li>
+ <li>When Vista asks if you'd like to install the Google ADB Interface device,
+ click <strong>Install</strong> to install the driver.</li>
+</ol>
+
+<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows Vista with the new
+driver:</p>
+
+<ol>
+ <li>Connect your Android-powered device to your computer's USB port.</li>
+ <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer,
+ and select <strong>Manage</strong>.</li>
+ <li>Select <strong>Device Manager</strong> in the left pane.</li>
+ <li>Locate and expand <em>ADB Interface</em> in the right pane.</li>
+ <li>Right-click on <em>HTC Dream Composite ADB Interface</em>, and select <strong>Update
+ Driver Software</strong>.</li>
+ <li>When Vista starts updating the driver, a prompt will ask how you want to
+ search for the driver
+ software. Select <strong>Browse my computer for driver software</strong>.</li>
+ <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
+Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.) As long as you specified the
+exact location of the
+ installation package, you may leave <strong>Include subfolders</strong> checked or
+ unchecked&mdash;it doesn't matter.</li>
+ <li>Click <strong>Next</strong>. Vista might prompt you to confirm the privilege elevation
+ required for driver installation. Confirm it.</li>
+ <li>When Vista asks if you'd like to install the Google ADB Interface device,
+ click <strong>Install</strong> to upgrade the driver.</li>
+</ol>
+
+
+<h2 id="Drivers">OEM Drivers</h2>
<p class="note"><strong>Note:</strong> If your device is one of the Android Developer Phones
(purchased from the Android Market publisher site), a Nexus One, or a Nexus S, then you need
@@ -28,10 +217,7 @@ Nexus driver, however, is distributed by <a
href="http://www.samsung.com/us/support/downloads/verizon-wireless/SCH-I515MSAVZW">Samsung</a>
(listed as model SCH-I515).</p>
-<p>For instructions about how to install the driver on Windows, follow the guide for <a
- href="{@docRoot}sdk/win-usb.html#InstallingDriver">Installing the USB Driver</a>.</p>
-<p class="table-caption"><strong>Table 1.</strong> Links to OEM USB drivers</p>
<table><tr>
<th>OEM</th>
<th>Driver URL</th></tr>
@@ -92,7 +278,7 @@ href="http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index">http
</tr>
<tr><td>KT Tech</td> <td><a
-href="http://www.kttech.co.kr/cscenter/download05.asp">http://www.kttech.co.kr/cscenter/download05.asp</a> for EV-S100(Take)</td>
+href="http://www.kttech.co.kr/cscenter/download05.asp">http://www.kttech.co.kr/cscenter/download05.asp</a> for EV-S100 (Take)</td>
</tr>
<tr>
<td>
diff --git a/docs/html/sdk/win-usb.jd b/docs/html/sdk/win-usb.jd
index 2d1435b8d4b5..2bd031ebb8b3 100644
--- a/docs/html/sdk/win-usb.jd
+++ b/docs/html/sdk/win-usb.jd
@@ -7,19 +7,12 @@ page.title=Google USB Driver
<ol>
<li><a href="#notes">Revisions</a></li>
<li><a href="#WinUsbDriver">Downloading the Google USB Driver</a></li>
- <li><a href="#InstallingDriver">Installing the USB Driver</a>
- <ol>
- <li><a href="#Win7">Windows 7</a></li>
- <li><a href="#WinXp">Windows XP</a></li>
- <li><a href="#WinVista">Windows Vista</a></li>
- </ol>
- </li>
</ol>
<h2>See also</h2>
<ol>
- <li><a href="{@docRoot}guide/developing/device.html">Developing on a Device</a></li>
+ <li><a href="{@docRoot}sdk/oem-usb.html#InstallingDriver">Installing a USB Driver</a></li>
+ <li><a href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a></li>
<li><a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a></li>
- <li><a href="{@docRoot}sdk/oem-usb.html">OEM USB Drivers</a></li>
</ol>
</div>
</div>
@@ -43,9 +36,9 @@ href="http://www.samsung.com/us/support/downloads/verizon-wireless/SCH-I515MSAVZ
(listed as model SCH-I515).</p>
<p class="note"><strong>Note:</strong>
-If you're developing on Mac OS X or Linux, then you do not need to install a USB driver. Refer to <a
-href="{@docRoot}guide/developing/device.html#setting-up">Setting up a Device</a> to start
-development with a device.</p>
+If you're developing on Mac OS X or Linux, then you do not need to install a USB driver. To start
+developing with your device, also read <a href="{@docRoot}guide/developing/device.html">Using
+Hardware Devices</a>.</p>
<p>The sections below provide instructions on how to download and install the Google USB Driver
for Windows. </p>
@@ -170,174 +163,10 @@ included with the <a href="{@docRoot}sdk/index.html">Android SDK</a>:</p>
<ol>
<li>Launch the SDK and AVD Manager by double-clicking <code>SDK Manager.exe</code>,
at the root of your SDK directory.</li>
- <li>Expand the <em>Third party Add-ons</em> and <em>Google Inc. add-ons</em>.</li>
- <li>Check <strong>Google Usb Driver package</strong> and click <strong>Install selected</strong>.</li>
+ <li>Expand <em>Extras</em>.</li>
+ <li>Check <strong>Google USB Driver package</strong> and click <strong>Install</strong>.</li>
<li>Proceed to install the package. When done, the driver files are
downloaded into the <code>&lt;sdk&gt;\extras\google\usb_driver\</code> directory.</li>
</ol>
-
-
-<h2 id="InstallingDriver">Installing the USB Driver</h2>
-
-<p>Once you've downloaded your USB driver, follow the instructions below to install or upgrade the
-driver, based on your version of Windows and whether you're installing for the first time
-or upgrading an existing driver.</p>
-
-<p class="note"><strong>Tip:</strong> When you finish the USB driver installation,
-see <a
-href="{@docRoot}guide/developing/device.html">Developing on a Device</a> for
-other important information about using an Android-powered device for
-development.</p>
-
-<ol class="nolist">
- <li><a href="#Win7">Windows 7</a></li>
- <li><a href="#WinXp">Windows XP</a></li>
- <li><a href="#WinVista">Windows Vista</a></li>
-</ol>
-
-
-<p class="caution"><strong>Caution:</strong>
-You may make changes to <code>android_winusb.inf</code> file found inside
-<code>usb_driver\</code> (for example, to add support for other devices),
-however, this will lead to security warnings when you install or upgrade the
-driver. Making any other changes to the driver files may break the installation
-process.</p>
-
-
-<h3 id="Win7">Windows 7</h3>
-
-
-<p>To install the Android USB driver on Windows 7 for the first time:</p>
-<ol>
- <li>Connect your Android-powered device to your computer's USB port.</li>
- <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer,
- and select <strong>Manage</strong>.</li>
- <li>Select <strong>Devices</strong> in the left pane.</li>
- <li>Locate and expand <em>Other device</em> in the right pane.</li>
- <li>Right-click the device name (such as <em>Nexus S</em>) and select <strong>Update
- Driver Software</strong>.
- This will launch the Hardware Update Wizard.</li>
- <li>Select <strong>Browse my computer for driver software</strong> and click
- <strong>Next</strong>.</li>
- <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
-Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
- <li>Click <strong>Next</strong> to install the driver.</li>
-</ol>
-
-<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows 7 with the new
-driver:</p>
-
-<ol>
- <li>Connect your Android-powered device to your computer's USB port.</li>
- <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer,
- and select <strong>Manage</strong>.</li>
- <li>Select <strong>Device Manager</strong> in the left pane of the Computer Management
- window.</li>
- <li>Locate and expand <em>Android Phone</em> in the right pane.</li>
- <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update
- Driver</strong>.
- This will launch the Hardware Update Wizard.</li>
- <li>Select <strong>Install from a list or specific location</strong> and click
- <strong>Next</strong>.</li>
- <li>Select <strong>Search for the best driver in these locations</strong>; un-check
-<strong>Search removable media</strong>; and check <strong>Include this location in the
-search</strong>.</li>
- <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
-Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
- <li>Click <strong>Next</strong> to upgrade the driver.</li>
-</ol>
-
-
-
-
-
-<h3 id="WinXp">Windows XP</h3>
-
-<p>To install the Android USB driver on Windows XP for the first time:</p>
-
-<ol>
- <li>Connect your Android-powered device to your computer's USB port. Windows
- will detect the device and launch the Hardware Update Wizard.</li>
- <li>Select <strong>Install from a list or specific location</strong> and click
- <strong>Next</strong>.</li>
- <li>Select <strong>Search for the best driver in these locations</strong>; un-check
-<strong>Search
- removable media</strong>; and check <strong>Include
-this location in the search</strong>.</li>
- <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
-Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
- <li>Click <strong>Next</strong> to install the driver.</li>
-</ol>
-
-<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows XP with the new
-driver:</p>
-
-<ol>
- <li>Connect your Android-powered device to your computer's USB port.</li>
- <li>Right-click on <em>My Computer</em> from your desktop or Windows Explorer,
- and select <strong>Manage</strong>.</li>
- <li>Select <strong>Device Manager</strong> in the left pane.</li>
- <li>Locate and expand <em>Android Phone</em> in the right pane.</li>
- <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update
- Driver</strong>.
- This will launch the Hardware Update Wizard.</li>
- <li>Select <strong>Install from a list or specific location</strong> and click
- <strong>Next</strong>.</li>
- <li>Select <strong>Search for the best driver in these locations</strong>; un-check <strong>Search
- removable media</strong>; and check <strong>Include
-this location in the search</strong>.</li>
- <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
-Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.)</li>
- <li>Click <strong>Next</strong> to upgrade the driver.</li>
-</ol>
-
-
-
-<h3 id="WinVista">Windows Vista</h3>
-
-<p>To install the Android USB driver on Windows Vista for the first time:</p>
-
-<ol>
- <li>Connect your Android-powered device to your computer's USB port. Windows
- will detect the device and launch the Found New Hardware wizard.</li>
- <li>Select <strong>Locate and install driver software</strong>.</li>
- <li>Select <strong>Don't search online</strong>.</li>
- <li>Select <strong>I don't have the disk. Show me other options</strong>.</li>
- <li>Select <strong>Browse my computer for driver software</strong>.</li>
- <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
-Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.) As long as you specified the
-exact location of the
- installation package, you may leave <strong>Include subfolders</strong> checked or
- unchecked&mdash;it doesn't matter.</li>
- <li>Click <strong>Next</strong>. Vista may prompt you to confirm the privilege elevation
- required for driver installation. Confirm it.</li>
- <li>When Vista asks if you'd like to install the Google ADB Interface device,
- click <strong>Install</strong> to install the driver.</li>
-</ol>
-
-<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows Vista with the new
-driver:</p>
-
-<ol>
- <li>Connect your Android-powered device to your computer's USB port.</li>
- <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer,
- and select <strong>Manage</strong>.</li>
- <li>Select <strong>Device Manager</strong> in the left pane.</li>
- <li>Locate and expand <em>ADB Interface</em> in the right pane.</li>
- <li>Right-click on <em>HTC Dream Composite ADB Interface</em>, and select <strong>Update
- Driver Software</strong>.</li>
- <li>When Vista starts updating the driver, a prompt will ask how you want to
- search for the driver
- software. Select <strong>Browse my computer for driver software</strong>.</li>
- <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB
-Driver is located in {@code &lt;sdk&gt;\extras\google\usb_driver\}.) As long as you specified the
-exact location of the
- installation package, you may leave <strong>Include subfolders</strong> checked or
- unchecked&mdash;it doesn't matter.</li>
- <li>Click <strong>Next</strong>. Vista might prompt you to confirm the privilege elevation
- required for driver installation. Confirm it.</li>
- <li>When Vista asks if you'd like to install the Google ADB Interface device,
- click <strong>Install</strong> to upgrade the driver.</li>
-</ol>
-
+<p>For installation information, read <a href="{@docRoot}sdk/oem-usb.html#InstallingDriver">Installing a USB Driver</a>.</p>
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 44401c83c1e8..37a270e9c10d 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -102,7 +102,7 @@ public class Allocation extends BaseObj {
public static final int USAGE_SCRIPT = 0x0001;
/**
- * GRAPHICS_TEXTURE The allcation will be used as a texture
+ * GRAPHICS_TEXTURE The allocation will be used as a texture
* source by one or more graphics programs.
*
*/
@@ -124,34 +124,34 @@ public class Allocation extends BaseObj {
public static final int USAGE_GRAPHICS_CONSTANTS = 0x0008;
/**
- * USAGE_GRAPHICS_RENDER_TARGET The allcation will be used as a
+ * USAGE_GRAPHICS_RENDER_TARGET The allocation will be used as a
* target for offscreen rendering
*
*/
public static final int USAGE_GRAPHICS_RENDER_TARGET = 0x0010;
/**
- * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT The allocation will be
- * used with a SurfaceTexture object. This usage will cause the
- * allocation to be created read only.
+ * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE The allocation
+ * will be used as a SurfaceTexture graphics consumer. This
+ * usage may only be used with USAGE_GRAPHICS_TEXTURE.
*
* @hide
*/
public static final int USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE = 0x0020;
/**
- * USAGE_IO_INPUT The allocation will be
- * used with a SurfaceTexture object. This usage will cause the
- * allocation to be created read only.
+ * USAGE_IO_INPUT The allocation will be used as SurfaceTexture
+ * consumer. This usage will cause the allocation to be created
+ * read only.
*
* @hide
*/
public static final int USAGE_IO_INPUT = 0x0040;
/**
- * USAGE_IO_OUTPUT The allocation will be
- * used with a SurfaceTexture object. This usage will cause the
- * allocation to be created write only.
+ * USAGE_IO_OUTPUT The allocation will be used as a
+ * SurfaceTexture producer. The dimensions and format of the
+ * SurfaceTexture will be forced to those of the allocation.
*
* @hide
*/
@@ -323,6 +323,37 @@ public class Allocation extends BaseObj {
mRS.nAllocationSyncAll(getIDSafe(), srcLocation);
}
+ /**
+ * Send a buffer to the output stream. The contents of the
+ * Allocation will be undefined after this operation.
+ *
+ * @hide
+ *
+ */
+ public void ioSendOutput() {
+ if ((mUsage & USAGE_IO_OUTPUT) == 0) {
+ throw new RSIllegalArgumentException(
+ "Can only send buffer if IO_OUTPUT usage specified.");
+ }
+ mRS.validate();
+ mRS.nAllocationIoSend(getID());
+ }
+
+ /**
+ * Receive the latest input into the Allocation.
+ *
+ * @hide
+ *
+ */
+ public void ioGetInput() {
+ if ((mUsage & USAGE_IO_INPUT) == 0) {
+ throw new RSIllegalArgumentException(
+ "Can only send buffer if IO_OUTPUT usage specified.");
+ }
+ mRS.validate();
+ mRS.nAllocationIoReceive(getID());
+ }
+
public void copyFrom(BaseObj[] d) {
mRS.validate();
validateIsObject();
@@ -887,17 +918,37 @@ public class Allocation extends BaseObj {
updateCacheInfo(mType);
}
- /*
+ /**
+ * Resize a 2D allocation. The contents of the allocation are
+ * preserved. If new elements are allocated objects are created
+ * with null contents and the new region is otherwise undefined.
+ *
+ * If the new region is smaller the references of any objects
+ * outside the new region will be released.
+ *
+ * A new type will be created with the new dimension.
+ *
+ * @hide
+ * @param dimX The new size of the allocation.
+ * @param dimY The new size of the allocation.
+ */
public void resize(int dimX, int dimY) {
- if ((mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
- throw new RSIllegalStateException("Resize only support for 2D allocations at this time.");
+ if ((mType.getZ() > 0) || mType.hasFaces() || mType.hasMipmaps()) {
+ throw new RSInvalidStateException(
+ "Resize only support for 2D allocations at this time.");
}
if (mType.getY() == 0) {
- throw new RSIllegalStateException("Resize only support for 2D allocations at this time.");
+ throw new RSInvalidStateException(
+ "Resize only support for 2D allocations at this time.");
}
mRS.nAllocationResize2D(getID(), dimX, dimY);
+ mRS.finish(); // Necessary because resize is fifoed and update is async.
+
+ int typeID = mRS.nAllocationGetType(getID());
+ mType = new Type(typeID, mRS);
+ mType.updateFromNative();
+ updateCacheInfo(mType);
}
- */
@@ -1090,6 +1141,18 @@ public class Allocation extends BaseObj {
}
+ /**
+ * @hide
+ */
+ public void setSurfaceTexture(SurfaceTexture sur) {
+ if ((mUsage & USAGE_IO_OUTPUT) == 0) {
+ throw new RSInvalidStateException("Allocation is not USAGE_IO_OUTPUT.");
+ }
+
+ mRS.validate();
+ mRS.nAllocationSetSurfaceTexture(getID(), sur);
+ }
+
/**
* Creates a non-mipmapped renderscript allocation to use as a
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index f6a02817a573..3d4951f534ce 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2008-2012 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.
@@ -832,10 +832,12 @@ public class Element extends BaseObj {
/**
* Create a custom vector element of the specified DataType and vector size.
- * DataKind will be set to USER.
+ * DataKind will be set to USER. Only primitive types (FLOAT_32, FLOAT_64,
+ * SIGNED_8, SIGNED_16, SIGNED_32, SIGNED_64, UNSIGNED_8, UNSIGNED_16,
+ * UNSIGNED_32, UNSIGNED_64, BOOLEAN) are supported.
*
* @param rs The context associated with the new Element.
- * @param dt The DataType for the new element.
+ * @param dt The DataType for the new Element.
* @param size Vector size for the new Element. Range 2-4 inclusive
* supported.
*
@@ -845,10 +847,31 @@ public class Element extends BaseObj {
if (size < 2 || size > 4) {
throw new RSIllegalArgumentException("Vector size out of range 2-4.");
}
- DataKind dk = DataKind.USER;
- boolean norm = false;
- int id = rs.nElementCreate(dt.mID, dk.mID, norm, size);
- return new Element(id, rs, dt, dk, norm, size);
+
+ switch (dt) {
+ // Support only primitive integer/float/boolean types as vectors.
+ case FLOAT_32:
+ case FLOAT_64:
+ case SIGNED_8:
+ case SIGNED_16:
+ case SIGNED_32:
+ case SIGNED_64:
+ case UNSIGNED_8:
+ case UNSIGNED_16:
+ case UNSIGNED_32:
+ case UNSIGNED_64:
+ case BOOLEAN: {
+ DataKind dk = DataKind.USER;
+ boolean norm = false;
+ int id = rs.nElementCreate(dt.mID, dk.mID, norm, size);
+ return new Element(id, rs, dt, dk, norm, size);
+ }
+
+ default: {
+ throw new RSIllegalArgumentException("Cannot create vector of " +
+ "non-primitive type.");
+ }
+ }
}
/**
@@ -933,10 +956,10 @@ public class Element extends BaseObj {
// Ignore mKind because it is allowed to be different (user vs. pixel).
// We also ignore mNormalized because it can be different. The mType
- // field must be non-null since we require name equivalence for
- // user-created Elements.
+ // field must not be NONE since we require name equivalence for
+ // all user-created Elements.
return ((mSize == e.mSize) &&
- (mType != null) &&
+ (mType != DataType.NONE) &&
(mType == e.mType) &&
(mVectorSize == e.mVectorSize));
}
diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java
index 3f769eecdd97..4d60ac860621 100644
--- a/graphics/java/android/renderscript/Program.java
+++ b/graphics/java/android/renderscript/Program.java
@@ -69,6 +69,7 @@ public class Program extends BaseObj {
Element mOutputs[];
Type mConstants[];
TextureType mTextures[];
+ String mTextureNames[];
int mTextureCount;
String mShader;
@@ -111,6 +112,16 @@ public class Program extends BaseObj {
}
/**
+ * @hide
+ */
+ public String getTextureName(int slot) {
+ if ((slot < 0) || (slot >= mTextureCount)) {
+ throw new IllegalArgumentException("Slot ID out of range.");
+ }
+ return mTextureNames[slot];
+ }
+
+ /**
* Binds a constant buffer to be used as uniform inputs to the
* program
*
@@ -180,6 +191,7 @@ public class Program extends BaseObj {
Type mConstants[];
Type mTextures[];
TextureType mTextureTypes[];
+ String mTextureNames[];
int mInputCount;
int mOutputCount;
int mConstantCount;
@@ -197,6 +209,7 @@ public class Program extends BaseObj {
mConstantCount = 0;
mTextureCount = 0;
mTextureTypes = new TextureType[MAX_TEXTURE];
+ mTextureNames = new String[MAX_TEXTURE];
}
/**
@@ -300,10 +313,28 @@ public class Program extends BaseObj {
* @return self
*/
public BaseProgramBuilder addTexture(TextureType texType) throws IllegalArgumentException {
+ addTexture(texType, "Tex" + mTextureCount);
+ return this;
+ }
+
+ /**
+ * @hide
+ * Adds a texture input to the Program
+ *
+ * @param texType describes that the texture to append it (2D,
+ * Cubemap, etc.)
+ * @param texName what the texture should be called in the
+ * shader
+ * @return self
+ */
+ public BaseProgramBuilder addTexture(TextureType texType, String texName)
+ throws IllegalArgumentException {
if(mTextureCount >= MAX_TEXTURE) {
throw new IllegalArgumentException("Max texture count exceeded.");
}
- mTextureTypes[mTextureCount ++] = texType;
+ mTextureTypes[mTextureCount] = texType;
+ mTextureNames[mTextureCount] = texName;
+ mTextureCount ++;
return this;
}
@@ -317,6 +348,8 @@ public class Program extends BaseObj {
p.mTextureCount = mTextureCount;
p.mTextures = new TextureType[mTextureCount];
System.arraycopy(mTextureTypes, 0, p.mTextures, 0, mTextureCount);
+ p.mTextureNames = new String[mTextureCount];
+ System.arraycopy(mTextureNames, 0, p.mTextureNames, 0, mTextureCount);
}
}
diff --git a/graphics/java/android/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java
index 21bace82f74f..ebc15e5070b5 100644
--- a/graphics/java/android/renderscript/ProgramFragment.java
+++ b/graphics/java/android/renderscript/ProgramFragment.java
@@ -59,6 +59,7 @@ public class ProgramFragment extends Program {
public ProgramFragment create() {
mRS.validate();
int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2];
+ String[] texNames = new String[mTextureCount];
int idx = 0;
for (int i=0; i < mInputCount; i++) {
@@ -76,9 +77,10 @@ public class ProgramFragment extends Program {
for (int i=0; i < mTextureCount; i++) {
tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID;
tmp[idx++] = mTextureTypes[i].mID;
+ texNames[i] = mTextureNames[i];
}
- int id = mRS.nProgramFragmentCreate(mShader, tmp);
+ int id = mRS.nProgramFragmentCreate(mShader, texNames, tmp);
ProgramFragment pf = new ProgramFragment(id, mRS);
initProgram(pf);
return pf;
diff --git a/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java b/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java
index 0ab73c1b222b..cd31db3a919b 100644
--- a/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java
+++ b/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java
@@ -47,6 +47,7 @@ public class ProgramFragmentFixedFunction extends ProgramFragment {
public ProgramFragmentFixedFunction create() {
mRS.validate();
int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2];
+ String[] texNames = new String[mTextureCount];
int idx = 0;
for (int i=0; i < mInputCount; i++) {
@@ -64,9 +65,10 @@ public class ProgramFragmentFixedFunction extends ProgramFragment {
for (int i=0; i < mTextureCount; i++) {
tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID;
tmp[idx++] = mTextureTypes[i].mID;
+ texNames[i] = mTextureNames[i];
}
- int id = mRS.nProgramFragmentCreate(mShader, tmp);
+ int id = mRS.nProgramFragmentCreate(mShader, texNames, tmp);
ProgramFragmentFixedFunction pf = new ProgramFragmentFixedFunction(id, mRS);
initProgram(pf);
return pf;
diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index b3c1bd9ee37a..a6cd15b731ea 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -116,6 +116,7 @@ public class ProgramVertex extends Program {
public ProgramVertex create() {
mRS.validate();
int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2];
+ String[] texNames = new String[mTextureCount];
int idx = 0;
for (int i=0; i < mInputCount; i++) {
@@ -133,9 +134,10 @@ public class ProgramVertex extends Program {
for (int i=0; i < mTextureCount; i++) {
tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID;
tmp[idx++] = mTextureTypes[i].mID;
+ texNames[i] = mTextureNames[i];
}
- int id = mRS.nProgramVertexCreate(mShader, tmp);
+ int id = mRS.nProgramVertexCreate(mShader, texNames, tmp);
ProgramVertex pv = new ProgramVertex(id, mRS);
initProgram(pv);
return pv;
diff --git a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
index 740d6a5a9566..9a43943da360 100644
--- a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -70,6 +70,7 @@ public class ProgramVertexFixedFunction extends ProgramVertex {
public ProgramVertexFixedFunction create() {
mRS.validate();
int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2];
+ String[] texNames = new String[mTextureCount];
int idx = 0;
for (int i=0; i < mInputCount; i++) {
@@ -87,9 +88,10 @@ public class ProgramVertexFixedFunction extends ProgramVertex {
for (int i=0; i < mTextureCount; i++) {
tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID;
tmp[idx++] = mTextureTypes[i].mID;
+ texNames[i] = mTextureNames[i];
}
- int id = mRS.nProgramVertexCreate(mShader, tmp);
+ int id = mRS.nProgramVertexCreate(mShader, texNames, tmp);
ProgramVertexFixedFunction pv = new ProgramVertexFixedFunction(id, mRS);
initProgram(pv);
return pv;
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index d3c801f2f730..95175135cf3a 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -274,6 +274,22 @@ public class RenderScript {
validate();
return rsnAllocationGetSurfaceTextureID(mContext, alloc);
}
+ native void rsnAllocationSetSurfaceTexture(int con, int alloc, SurfaceTexture sur);
+ synchronized void nAllocationSetSurfaceTexture(int alloc, SurfaceTexture sur) {
+ validate();
+ rsnAllocationSetSurfaceTexture(mContext, alloc, sur);
+ }
+ native void rsnAllocationIoSend(int con, int alloc);
+ synchronized void nAllocationIoSend(int alloc) {
+ validate();
+ rsnAllocationIoSend(mContext, alloc);
+ }
+ native void rsnAllocationIoReceive(int con, int alloc);
+ synchronized void nAllocationIoReceive(int alloc) {
+ validate();
+ rsnAllocationIoReceive(mContext, alloc);
+ }
+
native void rsnAllocationGenerateMipmaps(int con, int alloc);
synchronized void nAllocationGenerateMipmaps(int alloc) {
@@ -553,15 +569,15 @@ public class RenderScript {
validate();
rsnProgramBindSampler(mContext, vpf, slot, s);
}
- native int rsnProgramFragmentCreate(int con, String shader, int[] params);
- synchronized int nProgramFragmentCreate(String shader, int[] params) {
+ native int rsnProgramFragmentCreate(int con, String shader, String[] texNames, int[] params);
+ synchronized int nProgramFragmentCreate(String shader, String[] texNames, int[] params) {
validate();
- return rsnProgramFragmentCreate(mContext, shader, params);
+ return rsnProgramFragmentCreate(mContext, shader, texNames, params);
}
- native int rsnProgramVertexCreate(int con, String shader, int[] params);
- synchronized int nProgramVertexCreate(String shader, int[] params) {
+ native int rsnProgramVertexCreate(int con, String shader, String[] texNames, int[] params);
+ synchronized int nProgramVertexCreate(String shader, String[] texNames, int[] params) {
validate();
- return rsnProgramVertexCreate(mContext, shader, params);
+ return rsnProgramVertexCreate(mContext, shader, texNames, params);
}
native int rsnMeshCreate(int con, int[] vtx, int[] idx, int[] prim);
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 94f19b34661d..2d8c416d8054 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -54,13 +54,11 @@ using namespace android;
class AutoJavaStringToUTF8 {
public:
- AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str)
- {
+ AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str) {
fCStr = env->GetStringUTFChars(str, NULL);
fLength = env->GetStringUTFLength(str);
}
- ~AutoJavaStringToUTF8()
- {
+ ~AutoJavaStringToUTF8() {
fEnv->ReleaseStringUTFChars(fJStr, fCStr);
}
const char* c_str() const { return fCStr; }
@@ -73,6 +71,42 @@ private:
jsize fLength;
};
+class AutoJavaStringArrayToUTF8 {
+public:
+ AutoJavaStringArrayToUTF8(JNIEnv* env, jobjectArray strings, jsize stringsLength)
+ : mEnv(env), mStrings(strings), mStringsLength(stringsLength) {
+ mCStrings = NULL;
+ mSizeArray = NULL;
+ if (stringsLength > 0) {
+ mCStrings = (const char **)calloc(stringsLength, sizeof(char *));
+ mSizeArray = (size_t*)calloc(stringsLength, sizeof(size_t));
+ for (jsize ct = 0; ct < stringsLength; ct ++) {
+ jstring s = (jstring)mEnv->GetObjectArrayElement(mStrings, ct);
+ mCStrings[ct] = mEnv->GetStringUTFChars(s, NULL);
+ mSizeArray[ct] = mEnv->GetStringUTFLength(s);
+ }
+ }
+ }
+ ~AutoJavaStringArrayToUTF8() {
+ for (jsize ct=0; ct < mStringsLength; ct++) {
+ jstring s = (jstring)mEnv->GetObjectArrayElement(mStrings, ct);
+ mEnv->ReleaseStringUTFChars(s, mCStrings[ct]);
+ }
+ free(mCStrings);
+ free(mSizeArray);
+ }
+ const char **c_str() const { return mCStrings; }
+ size_t *c_str_len() const { return mSizeArray; }
+ jsize length() const { return mStringsLength; }
+
+private:
+ JNIEnv *mEnv;
+ jobjectArray mStrings;
+ const char **mCStrings;
+ size_t *mSizeArray;
+ jsize mStringsLength;
+};
+
// ---------------------------------------------------------------------------
static jfieldID gContextId = 0;
@@ -322,33 +356,27 @@ nElementCreate(JNIEnv *_env, jobject _this, RsContext con, jint type, jint kind,
}
static jint
-nElementCreate2(JNIEnv *_env, jobject _this, RsContext con, jintArray _ids, jobjectArray _names, jintArray _arraySizes)
+nElementCreate2(JNIEnv *_env, jobject _this, RsContext con,
+ jintArray _ids, jobjectArray _names, jintArray _arraySizes)
{
int fieldCount = _env->GetArrayLength(_ids);
LOG_API("nElementCreate2, con(%p)", con);
jint *ids = _env->GetIntArrayElements(_ids, NULL);
jint *arraySizes = _env->GetIntArrayElements(_arraySizes, NULL);
- const char ** nameArray = (const char **)calloc(fieldCount, sizeof(char *));
- size_t* sizeArray = (size_t*)calloc(fieldCount, sizeof(size_t));
- for (int ct=0; ct < fieldCount; ct++) {
- jstring s = (jstring)_env->GetObjectArrayElement(_names, ct);
- nameArray[ct] = _env->GetStringUTFChars(s, NULL);
- sizeArray[ct] = _env->GetStringUTFLength(s);
- }
+ AutoJavaStringArrayToUTF8 names(_env, _names, fieldCount);
+
+ const char **nameArray = names.c_str();
+ size_t *sizeArray = names.c_str_len();
+
jint id = (jint)rsElementCreate2(con,
(RsElement *)ids, fieldCount,
nameArray, fieldCount * sizeof(size_t), sizeArray,
(const uint32_t *)arraySizes, fieldCount);
- for (int ct=0; ct < fieldCount; ct++) {
- jstring s = (jstring)_env->GetObjectArrayElement(_names, ct);
- _env->ReleaseStringUTFChars(s, nameArray[ct]);
- }
+
_env->ReleaseIntArrayElements(_ids, ids, JNI_ABORT);
_env->ReleaseIntArrayElements(_arraySizes, arraySizes, JNI_ABORT);
- free(nameArray);
- free(sizeArray);
return (jint)id;
}
@@ -451,6 +479,37 @@ nAllocationGetSurfaceTextureID(JNIEnv *_env, jobject _this, RsContext con, jint
}
static void
+nAllocationSetSurfaceTexture(JNIEnv *_env, jobject _this, RsContext con,
+ RsAllocation alloc, jobject sur)
+{
+ LOG_API("nAllocationSetSurfaceTexture, con(%p), alloc(%p), surface(%p)",
+ con, alloc, (Surface *)sur);
+
+ sp<ANativeWindow> window;
+ if (sur != 0) {
+ sp<SurfaceTexture> st = SurfaceTexture_getSurfaceTexture(_env, sur);
+ window = new SurfaceTextureClient(st);
+ }
+
+ rsAllocationSetSurface(con, alloc, window.get());
+}
+
+static void
+nAllocationIoSend(JNIEnv *_env, jobject _this, RsContext con, RsAllocation alloc)
+{
+ LOG_API("nAllocationIoSend, con(%p), alloc(%p)", con, alloc);
+ rsAllocationIoSend(con, alloc);
+}
+
+static void
+nAllocationIoReceive(JNIEnv *_env, jobject _this, RsContext con, RsAllocation alloc)
+{
+ LOG_API("nAllocationIoReceive, con(%p), alloc(%p)", con, alloc);
+ rsAllocationIoReceive(con, alloc);
+}
+
+
+static void
nAllocationGenerateMipmaps(JNIEnv *_env, jobject _this, RsContext con, jint alloc)
{
LOG_API("nAllocationGenerateMipmaps, con(%p), a(%p)", con, (RsAllocation)alloc);
@@ -1033,15 +1092,24 @@ nProgramBindSampler(JNIEnv *_env, jobject _this, RsContext con, jint vpf, jint s
// ---------------------------------------------------------------------------
static jint
-nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
+nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader,
+ jobjectArray texNames, jintArray params)
{
AutoJavaStringToUTF8 shaderUTF(_env, shader);
jint *paramPtr = _env->GetIntArrayElements(params, NULL);
jint paramLen = _env->GetArrayLength(params);
+ int texCount = _env->GetArrayLength(texNames);
+ AutoJavaStringArrayToUTF8 names(_env, texNames, texCount);
+ const char ** nameArray = names.c_str();
+ size_t* sizeArray = names.c_str_len();
+
LOG_API("nProgramFragmentCreate, con(%p), paramLen(%i)", con, paramLen);
- jint ret = (jint)rsProgramFragmentCreate(con, shaderUTF.c_str(), shaderUTF.length(), (uint32_t *)paramPtr, paramLen);
+ jint ret = (jint)rsProgramFragmentCreate(con, shaderUTF.c_str(), shaderUTF.length(),
+ nameArray, texCount, sizeArray,
+ (uint32_t *)paramPtr, paramLen);
+
_env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
return ret;
}
@@ -1050,7 +1118,8 @@ nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shade
// ---------------------------------------------------------------------------
static jint
-nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
+nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader,
+ jobjectArray texNames, jintArray params)
{
AutoJavaStringToUTF8 shaderUTF(_env, shader);
jint *paramPtr = _env->GetIntArrayElements(params, NULL);
@@ -1058,7 +1127,15 @@ nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader,
LOG_API("nProgramVertexCreate, con(%p), paramLen(%i)", con, paramLen);
- jint ret = (jint)rsProgramVertexCreate(con, shaderUTF.c_str(), shaderUTF.length(), (uint32_t *)paramPtr, paramLen);
+ int texCount = _env->GetArrayLength(texNames);
+ AutoJavaStringArrayToUTF8 names(_env, texNames, texCount);
+ const char ** nameArray = names.c_str();
+ size_t* sizeArray = names.c_str_len();
+
+ jint ret = (jint)rsProgramVertexCreate(con, shaderUTF.c_str(), shaderUTF.length(),
+ nameArray, texCount, sizeArray,
+ (uint32_t *)paramPtr, paramLen);
+
_env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
return ret;
}
@@ -1277,6 +1354,9 @@ static JNINativeMethod methods[] = {
{"rsnAllocationSyncAll", "(III)V", (void*)nAllocationSyncAll },
{"rsnAllocationGetSurfaceTextureID", "(II)I", (void*)nAllocationGetSurfaceTextureID },
+{"rsnAllocationSetSurfaceTexture", "(IILandroid/graphics/SurfaceTexture;)V", (void*)nAllocationSetSurfaceTexture },
+{"rsnAllocationIoSend", "(II)V", (void*)nAllocationIoSend },
+{"rsnAllocationIoReceive", "(II)V", (void*)nAllocationIoReceive },
{"rsnAllocationData1D", "(IIIII[II)V", (void*)nAllocationData1D_i },
{"rsnAllocationData1D", "(IIIII[SI)V", (void*)nAllocationData1D_s },
{"rsnAllocationData1D", "(IIIII[BI)V", (void*)nAllocationData1D_b },
@@ -1317,9 +1397,9 @@ static JNINativeMethod methods[] = {
{"rsnProgramBindTexture", "(IIII)V", (void*)nProgramBindTexture },
{"rsnProgramBindSampler", "(IIII)V", (void*)nProgramBindSampler },
-{"rsnProgramFragmentCreate", "(ILjava/lang/String;[I)I", (void*)nProgramFragmentCreate },
+{"rsnProgramFragmentCreate", "(ILjava/lang/String;[Ljava/lang/String;[I)I", (void*)nProgramFragmentCreate },
{"rsnProgramRasterCreate", "(IZI)I", (void*)nProgramRasterCreate },
-{"rsnProgramVertexCreate", "(ILjava/lang/String;[I)I", (void*)nProgramVertexCreate },
+{"rsnProgramVertexCreate", "(ILjava/lang/String;[Ljava/lang/String;[I)I", (void*)nProgramVertexCreate },
{"rsnContextBindRootScript", "(II)V", (void*)nContextBindRootScript },
{"rsnContextBindProgramStore", "(II)V", (void*)nContextBindProgramStore },
diff --git a/include/common_time/ICommonClock.h b/include/common_time/ICommonClock.h
new file mode 100644
index 000000000000..d7073f1337d9
--- /dev/null
+++ b/include/common_time/ICommonClock.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2011 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_ICOMMONCLOCK_H
+#define ANDROID_ICOMMONCLOCK_H
+
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class ICommonClockListener : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonClockListener);
+
+ virtual void onTimelineChanged(uint64_t timelineID) = 0;
+};
+
+class BnCommonClockListener : public BnInterface<ICommonClockListener> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+class ICommonClock : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonClock);
+
+ // Name of the ICommonClock service registered with the service manager.
+ static const String16 kServiceName;
+
+ // a reserved invalid timeline ID
+ static const uint64_t kInvalidTimelineID;
+
+ // a reserved invalid error estimate
+ static const int32_t kErrorEstimateUnknown;
+
+ enum State {
+ // the device just came up and is trying to discover the master
+ STATE_INITIAL,
+
+ // the device is a client of a master
+ STATE_CLIENT,
+
+ // the device is acting as master
+ STATE_MASTER,
+
+ // the device has lost contact with its master and needs to participate
+ // in the election of a new master
+ STATE_RONIN,
+
+ // the device is waiting for announcement of the newly elected master
+ STATE_WAIT_FOR_ELECTION,
+ };
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) = 0;
+ virtual status_t commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) = 0;
+ virtual status_t localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) = 0;
+ virtual status_t getCommonTime(int64_t* commonTime) = 0;
+ virtual status_t getCommonFreq(uint64_t* freq) = 0;
+ virtual status_t getLocalTime(int64_t* localTime) = 0;
+ virtual status_t getLocalFreq(uint64_t* freq) = 0;
+ virtual status_t getEstimatedError(int32_t* estimate) = 0;
+ virtual status_t getTimelineID(uint64_t* id) = 0;
+ virtual status_t getState(State* state) = 0;
+ virtual status_t getMasterAddr(struct sockaddr_storage* addr) = 0;
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener) = 0;
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener) = 0;
+
+ // Simple helper to make it easier to connect to the CommonClock service.
+ static inline sp<ICommonClock> getInstance() {
+ sp<IBinder> binder = defaultServiceManager()->checkService(
+ ICommonClock::kServiceName);
+ sp<ICommonClock> clk = interface_cast<ICommonClock>(binder);
+ return clk;
+ }
+};
+
+class BnCommonClock : public BnInterface<ICommonClock> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_ICOMMONCLOCK_H
diff --git a/include/common_time/ICommonTimeConfig.h b/include/common_time/ICommonTimeConfig.h
new file mode 100644
index 000000000000..497b666de6f8
--- /dev/null
+++ b/include/common_time/ICommonTimeConfig.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 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_ICOMMONTIMECONFIG_H
+#define ANDROID_ICOMMONTIMECONFIG_H
+
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class String16;
+
+class ICommonTimeConfig : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonTimeConfig);
+
+ // Name of the ICommonTimeConfig service registered with the service
+ // manager.
+ static const String16 kServiceName;
+
+ virtual status_t getMasterElectionPriority(uint8_t *priority) = 0;
+ virtual status_t setMasterElectionPriority(uint8_t priority) = 0;
+ virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) = 0;
+ virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr) = 0;
+ virtual status_t getMasterElectionGroupId(uint64_t *id) = 0;
+ virtual status_t setMasterElectionGroupId(uint64_t id) = 0;
+ virtual status_t getInterfaceBinding(String16& ifaceName) = 0;
+ virtual status_t setInterfaceBinding(const String16& ifaceName) = 0;
+ virtual status_t getMasterAnnounceInterval(int *interval) = 0;
+ virtual status_t setMasterAnnounceInterval(int interval) = 0;
+ virtual status_t getClientSyncInterval(int *interval) = 0;
+ virtual status_t setClientSyncInterval(int interval) = 0;
+ virtual status_t getPanicThreshold(int *threshold) = 0;
+ virtual status_t setPanicThreshold(int threshold) = 0;
+ virtual status_t getAutoDisable(bool *autoDisable) = 0;
+ virtual status_t setAutoDisable(bool autoDisable) = 0;
+ virtual status_t forceNetworklessMasterMode() = 0;
+
+ // Simple helper to make it easier to connect to the CommonTimeConfig service.
+ static inline sp<ICommonTimeConfig> getInstance() {
+ sp<IBinder> binder = defaultServiceManager()->checkService(
+ ICommonTimeConfig::kServiceName);
+ sp<ICommonTimeConfig> clk = interface_cast<ICommonTimeConfig>(binder);
+ return clk;
+ }
+};
+
+class BnCommonTimeConfig : public BnInterface<ICommonTimeConfig> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_ICOMMONTIMECONFIG_H
diff --git a/include/common_time/cc_helper.h b/include/common_time/cc_helper.h
new file mode 100644
index 000000000000..8c4d5c063d52
--- /dev/null
+++ b/include/common_time/cc_helper.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2011 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 __CC_HELPER_H__
+#define __CC_HELPER_H__
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+#include <utils/threads.h>
+
+namespace android {
+
+// CCHelper is a simple wrapper class to help with centralizing access to the
+// Common Clock service and implementing lifetime managment, as well as to
+// implement a simple policy of making a basic attempt to reconnect to the
+// common clock service when things go wrong.
+//
+// On platforms which run the native common_time service in auto-disable mode,
+// the service will go into networkless mode whenever it has no active clients.
+// It tracks active clients using registered CommonClockListeners (the callback
+// interface for onTimelineChanged) since this provides a convienent death
+// handler notification for when the service's clients die unexpectedly. This
+// means that users of the common time service should really always have a
+// CommonClockListener, unless they know that the time service is not running in
+// auto disabled mode, or that there is at least one other registered listener
+// active in the system. The CCHelper makes this a little easier by sharing a
+// ref counted ICommonClock interface across all clients and automatically
+// registering and unregistering a listener whenever there are CCHelper
+// instances active in the process.
+class CCHelper {
+ public:
+ CCHelper();
+ ~CCHelper();
+
+ status_t isCommonTimeValid(bool* valid, uint32_t* timelineID);
+ status_t commonTimeToLocalTime(int64_t commonTime, int64_t* localTime);
+ status_t localTimeToCommonTime(int64_t localTime, int64_t* commonTime);
+ status_t getCommonTime(int64_t* commonTime);
+ status_t getCommonFreq(uint64_t* freq);
+ status_t getLocalTime(int64_t* localTime);
+ status_t getLocalFreq(uint64_t* freq);
+
+ private:
+ class CommonClockListener : public BnCommonClockListener {
+ public:
+ void onTimelineChanged(uint64_t timelineID);
+ };
+
+ static bool verifyClock_l();
+
+ static Mutex lock_;
+ static sp<ICommonClock> common_clock_;
+ static sp<ICommonClockListener> common_clock_listener_;
+ static uint32_t ref_count_;
+};
+
+
+} // namespace android
+#endif // __CC_HELPER_H__
diff --git a/include/common_time/local_clock.h b/include/common_time/local_clock.h
new file mode 100644
index 000000000000..845d1c21614d
--- /dev/null
+++ b/include/common_time/local_clock.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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 __LOCAL_CLOCK_H__
+#define __LOCAL_CLOCK_H__
+
+#include <stdint.h>
+
+#include <hardware/local_time_hal.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class LocalClock {
+ public:
+ LocalClock();
+
+ bool initCheck();
+
+ int64_t getLocalTime();
+ uint64_t getLocalFreq();
+ status_t setLocalSlew(int16_t rate);
+ int32_t getDebugLog(struct local_time_debug_event* records,
+ int max_records);
+
+ private:
+ static Mutex dev_lock_;
+ static local_time_hw_device_t* dev_;
+};
+
+} // namespace android
+#endif // __LOCAL_CLOCK_H__
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index ac7f6cf22c10..9f2bd3a15cf4 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -446,7 +446,7 @@ public:
*/
status_t dump(int fd, const Vector<String16>& args) const;
-private:
+protected:
/* copying audio tracks is not allowed */
AudioTrack(const AudioTrack& other);
AudioTrack& operator = (const AudioTrack& other);
@@ -518,10 +518,33 @@ private:
int mAuxEffectId;
mutable Mutex mLock;
status_t mRestoreStatus;
+ bool mIsTimed;
int mPreviousPriority; // before start()
int mPreviousSchedulingGroup;
};
+class TimedAudioTrack : public AudioTrack
+{
+public:
+ TimedAudioTrack();
+
+ /* allocate a shared memory buffer that can be passed to queueTimedBuffer */
+ status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer);
+
+ /* queue a buffer obtained via allocateTimedBuffer for playback at the
+ given timestamp. PTS units a microseconds on the media time timeline.
+ The media time transform (set with setMediaTimeTransform) set by the
+ audio producer will handle converting from media time to local time
+ (perhaps going through the common time timeline in the case of
+ synchronized multiroom audio case) */
+ status_t queueTimedBuffer(const sp<IMemory>& buffer, int64_t pts);
+
+ /* define a transform between media time and either common time or
+ local time */
+ enum TargetTimeline {LOCAL_TIME, COMMON_TIME};
+ status_t setMediaTimeTransform(const LinearTransform& xform,
+ TargetTimeline target);
+};
}; // namespace android
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index 433ce7cf52f1..7a2ada032fb3 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -55,6 +55,7 @@ public:
uint32_t flags,
const sp<IMemory>& sharedBuffer,
audio_io_handle_t output,
+ bool isTimed,
int *sessionId,
status_t *status) = 0;
diff --git a/include/media/IAudioTrack.h b/include/media/IAudioTrack.h
index e4772a17edec..77f3e213c875 100644
--- a/include/media/IAudioTrack.h
+++ b/include/media/IAudioTrack.h
@@ -24,7 +24,7 @@
#include <utils/Errors.h>
#include <binder/IInterface.h>
#include <binder/IMemory.h>
-
+#include <utils/LinearTransform.h>
namespace android {
@@ -71,6 +71,23 @@ public:
*/
virtual status_t attachAuxEffect(int effectId) = 0;
+
+ /* Allocate a shared memory buffer suitable for holding timed audio
+ samples */
+ virtual status_t allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer) = 0;
+
+ /* Queue a buffer obtained via allocateTimedBuffer for playback at the given
+ timestamp */
+ virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) = 0;
+
+ /* Define the linear transform that will be applied to the timestamps
+ given to queueTimedBuffer (which are expressed in media time).
+ Target specifies whether this transform converts media time to local time
+ or Tungsten time. The values for target are defined in AudioTrack.h */
+ virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+ int target) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 77c82b27ba1b..23a3e4905a63 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -46,6 +46,9 @@ enum player_type {
// The shared library with the test player is passed passed as an
// argument to the 'test:' url in the setDataSource call.
TEST_PLAYER = 5,
+
+ AAH_RX_PLAYER = 100,
+ AAH_TX_PLAYER = 101,
};
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index db6388ac14c8..0fe7bd88dad1 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -124,7 +124,7 @@ public final class KeyChain {
public static final String EXTRA_SENDER = "sender";
/**
- * Action to bring up the CertInstaller
+ * Action to bring up the CertInstaller.
*/
private static final String ACTION_INSTALL = "android.credentials.INSTALL";
@@ -167,6 +167,22 @@ public final class KeyChain {
// Compatible with old android.security.Credentials.PKCS12
public static final String EXTRA_PKCS12 = "PKCS12";
+
+ /**
+ * @hide TODO This is temporary and will be removed
+ * Broadcast Action: Indicates the trusted storage has changed. Sent when
+ * one of this happens:
+ *
+ * <ul>
+ * <li>a new CA is added,
+ * <li>an existing CA is removed or disabled,
+ * <li>a disabled CA is enabled,
+ * <li>trusted storage is reset (all user certs are cleared),
+ * <li>when permission to access a private key is changed.
+ * </ul>
+ */
+ public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED";
+
/**
* Returns an {@code Intent} that can be used for credential
* installation. The intent may be used without any extras, in
diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk
new file mode 100644
index 000000000000..526f17b44a5c
--- /dev/null
+++ b/libs/common_time/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libcommon_time_client
+# (binder marshalers for ICommonClock as well as common clock and local clock
+# helper code)
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libcommon_time_client
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := cc_helper.cpp \
+ local_clock.cpp \
+ ICommonClock.cpp \
+ ICommonTimeConfig.cpp \
+ utils.cpp
+LOCAL_SHARED_LIBRARIES := libbinder \
+ libhardware \
+ libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/common_time/ICommonClock.cpp b/libs/common_time/ICommonClock.cpp
new file mode 100644
index 000000000000..28b43acff16a
--- /dev/null
+++ b/libs/common_time/ICommonClock.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+#include <linux/socket.h>
+
+#include <common_time/ICommonClock.h>
+#include <binder/Parcel.h>
+
+#include "utils.h"
+
+namespace android {
+
+/***** ICommonClock *****/
+
+enum {
+ IS_COMMON_TIME_VALID = IBinder::FIRST_CALL_TRANSACTION,
+ COMMON_TIME_TO_LOCAL_TIME,
+ LOCAL_TIME_TO_COMMON_TIME,
+ GET_COMMON_TIME,
+ GET_COMMON_FREQ,
+ GET_LOCAL_TIME,
+ GET_LOCAL_FREQ,
+ GET_ESTIMATED_ERROR,
+ GET_TIMELINE_ID,
+ GET_STATE,
+ GET_MASTER_ADDRESS,
+ REGISTER_LISTENER,
+ UNREGISTER_LISTENER,
+};
+
+const String16 ICommonClock::kServiceName("common_time.clock");
+const uint64_t ICommonClock::kInvalidTimelineID = 0;
+const int32_t ICommonClock::kErrorEstimateUnknown = 0x7FFFFFFF;
+
+class BpCommonClock : public BpInterface<ICommonClock>
+{
+ public:
+ BpCommonClock(const sp<IBinder>& impl)
+ : BpInterface<ICommonClock>(impl) {}
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(IS_COMMON_TIME_VALID,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *valid = reply.readInt32();
+ *timelineID = reply.readInt32();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeInt64(commonTime);
+ status_t status = remote()->transact(COMMON_TIME_TO_LOCAL_TIME,
+ data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *localTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeInt64(localTime);
+ status_t status = remote()->transact(LOCAL_TIME_TO_COMMON_TIME,
+ data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *commonTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getCommonTime(int64_t* commonTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_COMMON_TIME, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *commonTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getCommonFreq(uint64_t* freq) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_COMMON_FREQ, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *freq = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getLocalTime(int64_t* localTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_LOCAL_TIME, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *localTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getLocalFreq(uint64_t* freq) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_LOCAL_FREQ, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *freq = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getEstimatedError(int32_t* estimate) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_ESTIMATED_ERROR, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *estimate = reply.readInt32();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getTimelineID(uint64_t* id) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_TIMELINE_ID, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *id = static_cast<uint64_t>(reply.readInt64());
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getState(State* state) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_STATE, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *state = static_cast<State>(reply.readInt32());
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getMasterAddr(struct sockaddr_storage* addr) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ADDRESS, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK)
+ deserializeSockaddr(&reply, addr);
+ }
+ return status;
+ }
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+
+ status_t status = remote()->transact(REGISTER_LISTENER, data, &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+ status_t status = remote()->transact(UNREGISTER_LISTENER, data, &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClock, "android.os.ICommonClock");
+
+status_t BnCommonClock::onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags) {
+ switch(code) {
+ case IS_COMMON_TIME_VALID: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ bool valid;
+ uint32_t timelineID;
+ status_t status = isCommonTimeValid(&valid, &timelineID);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(valid);
+ reply->writeInt32(timelineID);
+ }
+ return OK;
+ } break;
+
+ case COMMON_TIME_TO_LOCAL_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t commonTime = data.readInt64();
+ int64_t localTime;
+ status_t status = commonTimeToLocalTime(commonTime, &localTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(localTime);
+ }
+ return OK;
+ } break;
+
+ case LOCAL_TIME_TO_COMMON_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t localTime = data.readInt64();
+ int64_t commonTime;
+ status_t status = localTimeToCommonTime(localTime, &commonTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(commonTime);
+ }
+ return OK;
+ } break;
+
+ case GET_COMMON_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t commonTime;
+ status_t status = getCommonTime(&commonTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(commonTime);
+ }
+ return OK;
+ } break;
+
+ case GET_COMMON_FREQ: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t freq;
+ status_t status = getCommonFreq(&freq);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(freq);
+ }
+ return OK;
+ } break;
+
+ case GET_LOCAL_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t localTime;
+ status_t status = getLocalTime(&localTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(localTime);
+ }
+ return OK;
+ } break;
+
+ case GET_LOCAL_FREQ: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t freq;
+ status_t status = getLocalFreq(&freq);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(freq);
+ }
+ return OK;
+ } break;
+
+ case GET_ESTIMATED_ERROR: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int32_t error;
+ status_t status = getEstimatedError(&error);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(error);
+ }
+ return OK;
+ } break;
+
+ case GET_TIMELINE_ID: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t id;
+ status_t status = getTimelineID(&id);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(static_cast<int64_t>(id));
+ }
+ return OK;
+ } break;
+
+ case GET_STATE: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ State state;
+ status_t status = getState(&state);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(static_cast<int32_t>(state));
+ }
+ return OK;
+ } break;
+
+ case GET_MASTER_ADDRESS: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ struct sockaddr_storage addr;
+ status_t status = getMasterAddr(&addr);
+
+ if ((status == OK) && !canSerializeSockaddr(&addr)) {
+ status = UNKNOWN_ERROR;
+ }
+
+ reply->writeInt32(status);
+
+ if (status == OK) {
+ serializeSockaddr(reply, &addr);
+ }
+
+ return OK;
+ } break;
+
+ case REGISTER_LISTENER: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ sp<ICommonClockListener> listener =
+ interface_cast<ICommonClockListener>(data.readStrongBinder());
+ status_t status = registerListener(listener);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case UNREGISTER_LISTENER: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ sp<ICommonClockListener> listener =
+ interface_cast<ICommonClockListener>(data.readStrongBinder());
+ status_t status = unregisterListener(listener);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+/***** ICommonClockListener *****/
+
+enum {
+ ON_TIMELINE_CHANGED = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpCommonClockListener : public BpInterface<ICommonClockListener>
+{
+ public:
+ BpCommonClockListener(const sp<IBinder>& impl)
+ : BpInterface<ICommonClockListener>(impl) {}
+
+ virtual void onTimelineChanged(uint64_t timelineID) {
+ Parcel data, reply;
+ data.writeInterfaceToken(
+ ICommonClockListener::getInterfaceDescriptor());
+ data.writeInt64(timelineID);
+ remote()->transact(ON_TIMELINE_CHANGED, data, &reply);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClockListener,
+ "android.os.ICommonClockListener");
+
+status_t BnCommonClockListener::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch(code) {
+ case ON_TIMELINE_CHANGED: {
+ CHECK_INTERFACE(ICommonClockListener, data, reply);
+ uint32_t timelineID = data.readInt64();
+ onTimelineChanged(timelineID);
+ return NO_ERROR;
+ } break;
+ }
+
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/libs/common_time/ICommonTimeConfig.cpp b/libs/common_time/ICommonTimeConfig.cpp
new file mode 100644
index 000000000000..8eb37cb8064a
--- /dev/null
+++ b/libs/common_time/ICommonTimeConfig.cpp
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+#include <linux/socket.h>
+
+#include <common_time/ICommonTimeConfig.h>
+#include <binder/Parcel.h>
+
+#include "utils.h"
+
+namespace android {
+
+/***** ICommonTimeConfig *****/
+
+enum {
+ GET_MASTER_ELECTION_PRIORITY = IBinder::FIRST_CALL_TRANSACTION,
+ SET_MASTER_ELECTION_PRIORITY,
+ GET_MASTER_ELECTION_ENDPOINT,
+ SET_MASTER_ELECTION_ENDPOINT,
+ GET_MASTER_ELECTION_GROUP_ID,
+ SET_MASTER_ELECTION_GROUP_ID,
+ GET_INTERFACE_BINDING,
+ SET_INTERFACE_BINDING,
+ GET_MASTER_ANNOUNCE_INTERVAL,
+ SET_MASTER_ANNOUNCE_INTERVAL,
+ GET_CLIENT_SYNC_INTERVAL,
+ SET_CLIENT_SYNC_INTERVAL,
+ GET_PANIC_THRESHOLD,
+ SET_PANIC_THRESHOLD,
+ GET_AUTO_DISABLE,
+ SET_AUTO_DISABLE,
+ FORCE_NETWORKLESS_MASTER_MODE,
+};
+
+const String16 ICommonTimeConfig::kServiceName("common_time.config");
+
+class BpCommonTimeConfig : public BpInterface<ICommonTimeConfig>
+{
+ public:
+ BpCommonTimeConfig(const sp<IBinder>& impl)
+ : BpInterface<ICommonTimeConfig>(impl) {}
+
+ virtual status_t getMasterElectionPriority(uint8_t *priority) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ELECTION_PRIORITY,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *priority = static_cast<uint8_t>(reply.readInt32());
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterElectionPriority(uint8_t priority) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(static_cast<int32_t>(priority));
+ status_t status = remote()->transact(SET_MASTER_ELECTION_PRIORITY,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ELECTION_ENDPOINT,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ deserializeSockaddr(&reply, addr);
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterElectionEndpoint(
+ const struct sockaddr_storage *addr) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ if (!canSerializeSockaddr(addr))
+ return BAD_VALUE;
+ if (NULL == addr) {
+ data.writeInt32(0);
+ } else {
+ data.writeInt32(1);
+ serializeSockaddr(&data, addr);
+ }
+ status_t status = remote()->transact(SET_MASTER_ELECTION_ENDPOINT,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getMasterElectionGroupId(uint64_t *id) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ELECTION_GROUP_ID,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *id = static_cast<uint64_t>(reply.readInt64());
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterElectionGroupId(uint64_t id) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt64(id);
+ status_t status = remote()->transact(SET_MASTER_ELECTION_GROUP_ID,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getInterfaceBinding(String16& ifaceName) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_INTERFACE_BINDING,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ ifaceName = reply.readString16();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setInterfaceBinding(const String16& ifaceName) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeString16(ifaceName);
+ status_t status = remote()->transact(SET_INTERFACE_BINDING,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getMasterAnnounceInterval(int *interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ANNOUNCE_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *interval = reply.readInt32();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterAnnounceInterval(int interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(interval);
+ status_t status = remote()->transact(SET_MASTER_ANNOUNCE_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getClientSyncInterval(int *interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_CLIENT_SYNC_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *interval = reply.readInt32();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setClientSyncInterval(int interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(interval);
+ status_t status = remote()->transact(SET_CLIENT_SYNC_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getPanicThreshold(int *threshold) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_PANIC_THRESHOLD,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *threshold = reply.readInt32();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setPanicThreshold(int threshold) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(threshold);
+ status_t status = remote()->transact(SET_PANIC_THRESHOLD,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getAutoDisable(bool *autoDisable) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_AUTO_DISABLE,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *autoDisable = (0 != reply.readInt32());
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setAutoDisable(bool autoDisable) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(autoDisable ? 1 : 0);
+ status_t status = remote()->transact(SET_AUTO_DISABLE,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t forceNetworklessMasterMode() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(FORCE_NETWORKLESS_MASTER_MODE,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonTimeConfig, "android.os.ICommonTimeConfig");
+
+status_t BnCommonTimeConfig::onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags) {
+ switch(code) {
+ case GET_MASTER_ELECTION_PRIORITY: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint8_t priority;
+ status_t status = getMasterElectionPriority(&priority);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(static_cast<int32_t>(priority));
+ }
+ return OK;
+ } break;
+
+ case SET_MASTER_ELECTION_PRIORITY: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint8_t priority = static_cast<uint8_t>(data.readInt32());
+ status_t status = setMasterElectionPriority(priority);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_MASTER_ELECTION_ENDPOINT: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ struct sockaddr_storage addr;
+ status_t status = getMasterElectionEndpoint(&addr);
+
+ if ((status == OK) && !canSerializeSockaddr(&addr)) {
+ status = UNKNOWN_ERROR;
+ }
+
+ reply->writeInt32(status);
+
+ if (status == OK) {
+ serializeSockaddr(reply, &addr);
+ }
+
+ return OK;
+ } break;
+
+ case SET_MASTER_ELECTION_ENDPOINT: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ struct sockaddr_storage addr;
+ int hasAddr = data.readInt32();
+
+ status_t status;
+ if (hasAddr) {
+ deserializeSockaddr(&data, &addr);
+ status = setMasterElectionEndpoint(&addr);
+ } else {
+ status = setMasterElectionEndpoint(&addr);
+ }
+
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_MASTER_ELECTION_GROUP_ID: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint64_t id;
+ status_t status = getMasterElectionGroupId(&id);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(id);
+ }
+ return OK;
+ } break;
+
+ case SET_MASTER_ELECTION_GROUP_ID: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint64_t id = static_cast<uint64_t>(data.readInt64());
+ status_t status = setMasterElectionGroupId(id);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_INTERFACE_BINDING: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ String16 ret;
+ status_t status = getInterfaceBinding(ret);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeString16(ret);
+ }
+ return OK;
+ } break;
+
+ case SET_INTERFACE_BINDING: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ String16 ifaceName;
+ ifaceName = data.readString16();
+ status_t status = setInterfaceBinding(ifaceName);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_MASTER_ANNOUNCE_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval;
+ status_t status = getMasterAnnounceInterval(&interval);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(interval);
+ }
+ return OK;
+ } break;
+
+ case SET_MASTER_ANNOUNCE_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval = data.readInt32();
+ status_t status = setMasterAnnounceInterval(interval);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_CLIENT_SYNC_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval;
+ status_t status = getClientSyncInterval(&interval);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(interval);
+ }
+ return OK;
+ } break;
+
+ case SET_CLIENT_SYNC_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval = data.readInt32();
+ status_t status = setClientSyncInterval(interval);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_PANIC_THRESHOLD: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int threshold;
+ status_t status = getPanicThreshold(&threshold);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(threshold);
+ }
+ return OK;
+ } break;
+
+ case SET_PANIC_THRESHOLD: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int threshold = data.readInt32();
+ status_t status = setPanicThreshold(threshold);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_AUTO_DISABLE: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ bool autoDisable;
+ status_t status = getAutoDisable(&autoDisable);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(autoDisable ? 1 : 0);
+ }
+ return OK;
+ } break;
+
+ case SET_AUTO_DISABLE: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ bool autoDisable = (0 != data.readInt32());
+ status_t status = setAutoDisable(autoDisable);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case FORCE_NETWORKLESS_MASTER_MODE: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ status_t status = forceNetworklessMasterMode();
+ reply->writeInt32(status);
+ return OK;
+ } break;
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
+
diff --git a/libs/common_time/cc_helper.cpp b/libs/common_time/cc_helper.cpp
new file mode 100644
index 000000000000..8d8556cb7f54
--- /dev/null
+++ b/libs/common_time/cc_helper.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <stdint.h>
+
+#include <common_time/cc_helper.h>
+#include <common_time/ICommonClock.h>
+#include <utils/threads.h>
+
+namespace android {
+
+Mutex CCHelper::lock_;
+sp<ICommonClock> CCHelper::common_clock_;
+sp<ICommonClockListener> CCHelper::common_clock_listener_;
+uint32_t CCHelper::ref_count_ = 0;
+
+bool CCHelper::verifyClock_l() {
+ bool ret = false;
+
+ if (common_clock_ == NULL) {
+ common_clock_ = ICommonClock::getInstance();
+ if (common_clock_ == NULL)
+ goto bailout;
+ }
+
+ if (ref_count_ > 0) {
+ if (common_clock_listener_ == NULL) {
+ common_clock_listener_ = new CommonClockListener();
+ if (common_clock_listener_ == NULL)
+ goto bailout;
+
+ if (OK != common_clock_->registerListener(common_clock_listener_))
+ goto bailout;
+ }
+ }
+
+ ret = true;
+
+bailout:
+ if (!ret) {
+ common_clock_listener_ = NULL;
+ common_clock_ = NULL;
+ }
+ return ret;
+}
+
+CCHelper::CCHelper() {
+ Mutex::Autolock lock(&lock_);
+ ref_count_++;
+ verifyClock_l();
+}
+
+CCHelper::~CCHelper() {
+ Mutex::Autolock lock(&lock_);
+
+ assert(ref_count_ > 0);
+ ref_count_--;
+
+ // If we were the last CCHelper instance in the system, and we had
+ // previously register a listener, unregister it now so that the common time
+ // service has the chance to go into auto-disabled mode.
+ if (!ref_count_ &&
+ (common_clock_ != NULL) &&
+ (common_clock_listener_ != NULL)) {
+ common_clock_->unregisterListener(common_clock_listener_);
+ common_clock_listener_ = NULL;
+ }
+}
+
+void CCHelper::CommonClockListener::onTimelineChanged(uint64_t timelineID) {
+ // do nothing; listener is only really used as a token so the server can
+ // find out when clients die.
+}
+
+// Helper methods which attempts to make calls to the common time binder
+// service. If the first attempt fails with DEAD_OBJECT, the helpers will
+// attempt to make a connection to the service again (assuming that the process
+// hosting the service had crashed and the client proxy we are holding is dead)
+// If the second attempt fails, or no connection can be made, the we let the
+// error propagate up the stack and let the caller deal with the situation as
+// best they can.
+#define CCHELPER_METHOD(decl, call) \
+ status_t CCHelper::decl { \
+ Mutex::Autolock lock(&lock_); \
+ \
+ if (!verifyClock_l()) \
+ return DEAD_OBJECT; \
+ \
+ status_t status = common_clock_->call; \
+ if (DEAD_OBJECT == status) { \
+ if (!verifyClock_l()) \
+ return DEAD_OBJECT; \
+ status = common_clock_->call; \
+ } \
+ \
+ return status; \
+ }
+
+#define VERIFY_CLOCK()
+
+CCHELPER_METHOD(isCommonTimeValid(bool* valid, uint32_t* timelineID),
+ isCommonTimeValid(valid, timelineID))
+CCHELPER_METHOD(commonTimeToLocalTime(int64_t commonTime, int64_t* localTime),
+ commonTimeToLocalTime(commonTime, localTime))
+CCHELPER_METHOD(localTimeToCommonTime(int64_t localTime, int64_t* commonTime),
+ localTimeToCommonTime(localTime, commonTime))
+CCHELPER_METHOD(getCommonTime(int64_t* commonTime),
+ getCommonTime(commonTime))
+CCHELPER_METHOD(getCommonFreq(uint64_t* freq),
+ getCommonFreq(freq))
+CCHELPER_METHOD(getLocalTime(int64_t* localTime),
+ getLocalTime(localTime))
+CCHELPER_METHOD(getLocalFreq(uint64_t* freq),
+ getLocalFreq(freq))
+
+} // namespace android
diff --git a/libs/common_time/local_clock.cpp b/libs/common_time/local_clock.cpp
new file mode 100644
index 000000000000..a7c61fc76aa5
--- /dev/null
+++ b/libs/common_time/local_clock.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 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 "common_time"
+#include <utils/Log.h>
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <common_time/local_clock.h>
+#include <hardware/hardware.h>
+#include <hardware/local_time_hal.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+Mutex LocalClock::dev_lock_;
+local_time_hw_device_t* LocalClock::dev_ = NULL;
+
+LocalClock::LocalClock() {
+ int res;
+ const hw_module_t* mod;
+
+ AutoMutex lock(&dev_lock_);
+
+ if (dev_ != NULL)
+ return;
+
+ res = hw_get_module_by_class(LOCAL_TIME_HARDWARE_MODULE_ID, NULL, &mod);
+ if (res) {
+ ALOGE("Failed to open local time HAL module (res = %d)", res);
+ } else {
+ res = local_time_hw_device_open(mod, &dev_);
+ if (res) {
+ ALOGE("Failed to open local time HAL device (res = %d)", res);
+ dev_ = NULL;
+ }
+ }
+}
+
+bool LocalClock::initCheck() {
+ return (NULL != dev_);
+}
+
+int64_t LocalClock::getLocalTime() {
+ assert(NULL != dev_);
+ assert(NULL != dev_->get_local_time);
+
+ return dev_->get_local_time(dev_);
+}
+
+uint64_t LocalClock::getLocalFreq() {
+ assert(NULL != dev_);
+ assert(NULL != dev_->get_local_freq);
+
+ return dev_->get_local_freq(dev_);
+}
+
+status_t LocalClock::setLocalSlew(int16_t rate) {
+ assert(NULL != dev_);
+
+ if (!dev_->set_local_slew)
+ return INVALID_OPERATION;
+
+ return static_cast<status_t>(dev_->set_local_slew(dev_, rate));
+}
+
+int32_t LocalClock::getDebugLog(struct local_time_debug_event* records,
+ int max_records) {
+ assert(NULL != dev_);
+
+ if (!dev_->get_debug_log)
+ return INVALID_OPERATION;
+
+ return dev_->get_debug_log(dev_, records, max_records);
+}
+
+} // namespace android
diff --git a/libs/common_time/utils.cpp b/libs/common_time/utils.cpp
new file mode 100644
index 000000000000..65391711b0bd
--- /dev/null
+++ b/libs/common_time/utils.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <arpa/inet.h>
+#include <linux/socket.h>
+
+#include <binder/Parcel.h>
+
+namespace android {
+
+bool canSerializeSockaddr(const struct sockaddr_storage* addr) {
+ switch (addr->ss_family) {
+ case AF_INET:
+ case AF_INET6:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr) {
+ switch (addr->ss_family) {
+ case AF_INET: {
+ const struct sockaddr_in* s =
+ reinterpret_cast<const struct sockaddr_in*>(addr);
+ p->writeInt32(AF_INET);
+ p->writeInt32(ntohl(s->sin_addr.s_addr));
+ p->writeInt32(static_cast<int32_t>(ntohs(s->sin_port)));
+ } break;
+
+ case AF_INET6: {
+ const struct sockaddr_in6* s =
+ reinterpret_cast<const struct sockaddr_in6*>(addr);
+ const int32_t* a =
+ reinterpret_cast<const int32_t*>(s->sin6_addr.s6_addr);
+ p->writeInt32(AF_INET6);
+ p->writeInt32(ntohl(a[0]));
+ p->writeInt32(ntohl(a[1]));
+ p->writeInt32(ntohl(a[2]));
+ p->writeInt32(ntohl(a[3]));
+ p->writeInt32(static_cast<int32_t>(ntohs(s->sin6_port)));
+ p->writeInt32(ntohl(s->sin6_flowinfo));
+ p->writeInt32(ntohl(s->sin6_scope_id));
+ } break;
+ }
+}
+
+void deserializeSockaddr(const Parcel* p, struct sockaddr_storage* addr) {
+ memset(addr, 0, sizeof(addr));
+
+ addr->ss_family = p->readInt32();
+ switch(addr->ss_family) {
+ case AF_INET: {
+ struct sockaddr_in* s =
+ reinterpret_cast<struct sockaddr_in*>(addr);
+ s->sin_addr.s_addr = htonl(p->readInt32());
+ s->sin_port = htons(static_cast<uint16_t>(p->readInt32()));
+ } break;
+
+ case AF_INET6: {
+ struct sockaddr_in6* s =
+ reinterpret_cast<struct sockaddr_in6*>(addr);
+ int32_t* a = reinterpret_cast<int32_t*>(s->sin6_addr.s6_addr);
+
+ a[0] = htonl(p->readInt32());
+ a[1] = htonl(p->readInt32());
+ a[2] = htonl(p->readInt32());
+ a[3] = htonl(p->readInt32());
+ s->sin6_port = htons(static_cast<uint16_t>(p->readInt32()));
+ s->sin6_flowinfo = htonl(p->readInt32());
+ s->sin6_scope_id = htonl(p->readInt32());
+ } break;
+ }
+}
+
+} // namespace android
diff --git a/libs/common_time/utils.h b/libs/common_time/utils.h
new file mode 100644
index 000000000000..ce79d0d74cec
--- /dev/null
+++ b/libs/common_time/utils.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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_LIBCOMMONCLOCK_UTILS_H
+#define ANDROID_LIBCOMMONCLOCK_UTILS_H
+
+#include <linux/socket.h>
+
+#include <binder/Parcel.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+extern bool canSerializeSockaddr(const struct sockaddr_storage* addr);
+extern void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr);
+extern status_t deserializeSockaddr(const Parcel* p,
+ struct sockaddr_storage* addr);
+
+}; // namespace android
+
+#endif // ANDROID_LIBCOMMONCLOCK_UTILS_H
diff --git a/libs/rs/driver/rsdAllocation.cpp b/libs/rs/driver/rsdAllocation.cpp
index ea921923e745..fb93d822e8a6 100644
--- a/libs/rs/driver/rsdAllocation.cpp
+++ b/libs/rs/driver/rsdAllocation.cpp
@@ -23,6 +23,11 @@
#include "rsAllocation.h"
+#include "system/window.h"
+#include "hardware/gralloc.h"
+#include "ui/Rect.h"
+#include "ui/GraphicBufferMapper.h"
+
#include <GLES/gl.h>
#include <GLES2/gl2.h>
#include <GLES/glext.h>
@@ -220,7 +225,8 @@ bool rsdAllocationInit(const Context *rsc, Allocation *alloc, bool forceZero) {
}
void * ptr = alloc->mHal.state.usrPtr;
- if (!ptr) {
+ if (alloc->mHal.state.usageFlags & RS_ALLOCATION_USAGE_IO_OUTPUT) {
+ } else {
ptr = malloc(alloc->mHal.state.type->getSizeBytes());
if (!ptr) {
free(drv);
@@ -248,7 +254,7 @@ bool rsdAllocationInit(const Context *rsc, Allocation *alloc, bool forceZero) {
alloc->mHal.drvState.mallocPtr = ptr;
drv->mallocPtr = (uint8_t *)ptr;
alloc->mHal.drv = drv;
- if (forceZero) {
+ if (forceZero && ptr) {
memset(ptr, 0, alloc->mHal.state.type->getSizeBytes());
}
@@ -386,9 +392,96 @@ int32_t rsdAllocationInitSurfaceTexture(const Context *rsc, const Allocation *al
return drv->textureID;
}
+static bool IoGetBuffer(const Context *rsc, Allocation *alloc, ANativeWindow *nw) {
+ DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
+
+ int32_t r = nw->dequeueBuffer(nw, &drv->wndBuffer);
+ if (r) {
+ rsc->setError(RS_ERROR_DRIVER, "Error getting next IO output buffer.");
+ return false;
+ }
+
+ // This lock is implicitly released by the queue buffer in IoSend
+ r = nw->lockBuffer(nw, drv->wndBuffer);
+ if (r) {
+ rsc->setError(RS_ERROR_DRIVER, "Error locking next IO output buffer.");
+ return false;
+ }
+
+ // Must lock the whole surface
+ GraphicBufferMapper &mapper = GraphicBufferMapper::get();
+ Rect bounds(drv->wndBuffer->width, drv->wndBuffer->height);
+
+ void *dst = NULL;
+ mapper.lock(drv->wndBuffer->handle,
+ GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_OFTEN,
+ bounds, &dst);
+ alloc->mHal.drvState.mallocPtr = dst;
+ return true;
+}
+
+void rsdAllocationSetSurfaceTexture(const Context *rsc, Allocation *alloc, ANativeWindow *nw) {
+ DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
+
+ //ALOGE("rsdAllocationSetSurfaceTexture %p %p", alloc, nw);
+
+ // Cleanup old surface if there is one.
+ if (alloc->mHal.state.wndSurface) {
+ ANativeWindow *old = alloc->mHal.state.wndSurface;
+ GraphicBufferMapper &mapper = GraphicBufferMapper::get();
+ mapper.unlock(drv->wndBuffer->handle);
+ old->queueBuffer(old, drv->wndBuffer);
+ }
+
+ if (nw != NULL) {
+ int32_t r;
+ r = native_window_set_usage(nw, GRALLOC_USAGE_SW_READ_RARELY |
+ GRALLOC_USAGE_SW_WRITE_OFTEN);
+ if (r) {
+ rsc->setError(RS_ERROR_DRIVER, "Error setting IO output buffer usage.");
+ return;
+ }
+
+ r = native_window_set_buffers_dimensions(nw, alloc->mHal.state.dimensionX,
+ alloc->mHal.state.dimensionY);
+ if (r) {
+ rsc->setError(RS_ERROR_DRIVER, "Error setting IO output buffer dimensions.");
+ return;
+ }
+
+ r = native_window_set_buffer_count(nw, 3);
+ if (r) {
+ rsc->setError(RS_ERROR_DRIVER, "Error setting IO output buffer count.");
+ return;
+ }
+
+ IoGetBuffer(rsc, alloc, nw);
+ }
+}
+
+void rsdAllocationIoSend(const Context *rsc, Allocation *alloc) {
+ DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
+ ANativeWindow *nw = alloc->mHal.state.wndSurface;
+
+ GraphicBufferMapper &mapper = GraphicBufferMapper::get();
+ mapper.unlock(drv->wndBuffer->handle);
+ int32_t r = nw->queueBuffer(nw, drv->wndBuffer);
+ if (r) {
+ rsc->setError(RS_ERROR_DRIVER, "Error sending IO output buffer.");
+ return;
+ }
+
+ IoGetBuffer(rsc, alloc, nw);
+}
+
+void rsdAllocationIoReceive(const Context *rsc, Allocation *alloc) {
+ ALOGE("not implemented");
+}
+
+
void rsdAllocationData1D(const Context *rsc, const Allocation *alloc,
uint32_t xoff, uint32_t lod, uint32_t count,
- const void *data, uint32_t sizeBytes) {
+ const void *data, size_t sizeBytes) {
DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
const uint32_t eSize = alloc->mHal.state.type->getElementSizeBytes();
@@ -407,7 +500,7 @@ void rsdAllocationData1D(const Context *rsc, const Allocation *alloc,
void rsdAllocationData2D(const Context *rsc, const Allocation *alloc,
uint32_t xoff, uint32_t yoff, uint32_t lod, RsAllocationCubemapFace face,
- uint32_t w, uint32_t h, const void *data, uint32_t sizeBytes) {
+ uint32_t w, uint32_t h, const void *data, size_t sizeBytes) {
DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
uint32_t eSize = alloc->mHal.state.elementSizeBytes;
diff --git a/libs/rs/driver/rsdAllocation.h b/libs/rs/driver/rsdAllocation.h
index 230804b53ae8..e3a5126383d0 100644
--- a/libs/rs/driver/rsdAllocation.h
+++ b/libs/rs/driver/rsdAllocation.h
@@ -24,6 +24,7 @@
#include <GLES2/gl2.h>
class RsdFrameBufferObj;
+struct ANativeWindowBuffer;
struct DrvAllocation {
// Is this a legal structure to be used as a texture source.
@@ -47,6 +48,7 @@ struct DrvAllocation {
bool uploadDeferred;
RsdFrameBufferObj * readBackFBO;
+ ANativeWindowBuffer *wndBuffer;
};
GLenum rsdTypeToGLType(RsDataType t);
@@ -69,6 +71,12 @@ void rsdAllocationMarkDirty(const android::renderscript::Context *rsc,
const android::renderscript::Allocation *alloc);
int32_t rsdAllocationInitSurfaceTexture(const android::renderscript::Context *rsc,
const android::renderscript::Allocation *alloc);
+void rsdAllocationSetSurfaceTexture(const android::renderscript::Context *rsc,
+ android::renderscript::Allocation *alloc, ANativeWindow *nw);
+void rsdAllocationIoSend(const android::renderscript::Context *rsc,
+ android::renderscript::Allocation *alloc);
+void rsdAllocationIoReceive(const android::renderscript::Context *rsc,
+ android::renderscript::Allocation *alloc);
void rsdAllocationData1D(const android::renderscript::Context *rsc,
const android::renderscript::Allocation *alloc,
diff --git a/libs/rs/driver/rsdCore.cpp b/libs/rs/driver/rsdCore.cpp
index e01195509a87..bf2b62a69cdb 100644
--- a/libs/rs/driver/rsdCore.cpp
+++ b/libs/rs/driver/rsdCore.cpp
@@ -74,6 +74,9 @@ static RsdHalFunctions FunctionTable = {
rsdAllocationSyncAll,
rsdAllocationMarkDirty,
rsdAllocationInitSurfaceTexture,
+ rsdAllocationSetSurfaceTexture,
+ rsdAllocationIoSend,
+ rsdAllocationIoReceive,
rsdAllocationData1D,
rsdAllocationData2D,
rsdAllocationData3D,
diff --git a/libs/rs/driver/rsdProgram.cpp b/libs/rs/driver/rsdProgram.cpp
index 54484dfced3a..fa4cb0f4a70f 100644
--- a/libs/rs/driver/rsdProgram.cpp
+++ b/libs/rs/driver/rsdProgram.cpp
@@ -34,8 +34,11 @@ using namespace android;
using namespace android::renderscript;
bool rsdProgramVertexInit(const Context *rsc, const ProgramVertex *pv,
- const char* shader, uint32_t shaderLen) {
- RsdShader *drv = new RsdShader(pv, GL_VERTEX_SHADER, shader, shaderLen);
+ const char* shader, size_t shaderLen,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength) {
+ RsdShader *drv = new RsdShader(pv, GL_VERTEX_SHADER, shader, shaderLen,
+ textureNames, textureNamesCount, textureNamesLength);
pv->mHal.drv = drv;
return drv->createShader();
@@ -78,8 +81,11 @@ void rsdProgramVertexDestroy(const Context *rsc, const ProgramVertex *pv) {
}
bool rsdProgramFragmentInit(const Context *rsc, const ProgramFragment *pf,
- const char* shader, uint32_t shaderLen) {
- RsdShader *drv = new RsdShader(pf, GL_FRAGMENT_SHADER, shader, shaderLen);
+ const char* shader, size_t shaderLen,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength) {
+ RsdShader *drv = new RsdShader(pf, GL_FRAGMENT_SHADER, shader, shaderLen,
+ textureNames, textureNamesCount, textureNamesLength);
pf->mHal.drv = drv;
return drv->createShader();
diff --git a/libs/rs/driver/rsdProgramFragment.h b/libs/rs/driver/rsdProgramFragment.h
index 366cb40d2962..b03a9fe65ac1 100644
--- a/libs/rs/driver/rsdProgramFragment.h
+++ b/libs/rs/driver/rsdProgramFragment.h
@@ -22,7 +22,9 @@
bool rsdProgramFragmentInit(const android::renderscript::Context *rsc,
const android::renderscript::ProgramFragment *,
- const char* shader, uint32_t shaderLen);
+ const char* shader, size_t shaderLen,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength);
void rsdProgramFragmentSetActive(const android::renderscript::Context *rsc,
const android::renderscript::ProgramFragment *);
void rsdProgramFragmentDestroy(const android::renderscript::Context *rsc,
diff --git a/libs/rs/driver/rsdProgramVertex.h b/libs/rs/driver/rsdProgramVertex.h
index e99857298165..f917a4123747 100644
--- a/libs/rs/driver/rsdProgramVertex.h
+++ b/libs/rs/driver/rsdProgramVertex.h
@@ -21,7 +21,9 @@
bool rsdProgramVertexInit(const android::renderscript::Context *rsc,
const android::renderscript::ProgramVertex *,
- const char* shader, uint32_t shaderLen);
+ const char* shader, size_t shaderLen,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength);
void rsdProgramVertexSetActive(const android::renderscript::Context *rsc,
const android::renderscript::ProgramVertex *);
void rsdProgramVertexDestroy(const android::renderscript::Context *rsc,
diff --git a/libs/rs/driver/rsdShader.cpp b/libs/rs/driver/rsdShader.cpp
index 3bca794ea9d6..1e73b95b6cd6 100644
--- a/libs/rs/driver/rsdShader.cpp
+++ b/libs/rs/driver/rsdShader.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-2012 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.
@@ -30,14 +30,16 @@ using namespace android;
using namespace android::renderscript;
RsdShader::RsdShader(const Program *p, uint32_t type,
- const char * shaderText, uint32_t shaderLength) {
-
+ const char * shaderText, size_t shaderLength,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength) {
mUserShader.setTo(shaderText, shaderLength);
mRSProgram = p;
mType = type;
initMemberVars();
initAttribAndUniformArray();
- init();
+ init(textureNames, textureNamesCount, textureNamesLength);
+ createTexturesString(textureNames, textureNamesCount, textureNamesLength);
}
RsdShader::~RsdShader() {
@@ -65,25 +67,26 @@ void RsdShader::initMemberVars() {
mIsValid = false;
}
-void RsdShader::init() {
+void RsdShader::init(const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength) {
uint32_t attribCount = 0;
uint32_t uniformCount = 0;
for (uint32_t ct=0; ct < mRSProgram->mHal.state.inputElementsCount; ct++) {
- initAddUserElement(mRSProgram->mHal.state.inputElements[ct], mAttribNames, NULL, &attribCount, RS_SHADER_ATTR);
+ initAddUserElement(mRSProgram->mHal.state.inputElements[ct], mAttribNames,
+ NULL, &attribCount, RS_SHADER_ATTR);
}
for (uint32_t ct=0; ct < mRSProgram->mHal.state.constantsCount; ct++) {
- initAddUserElement(mRSProgram->mHal.state.constantTypes[ct]->getElement(), mUniformNames, mUniformArraySizes, &uniformCount, RS_SHADER_UNI);
+ initAddUserElement(mRSProgram->mHal.state.constantTypes[ct]->getElement(),
+ mUniformNames, mUniformArraySizes, &uniformCount, RS_SHADER_UNI);
}
mTextureUniformIndexStart = uniformCount;
- char buf[256];
for (uint32_t ct=0; ct < mRSProgram->mHal.state.texturesCount; ct++) {
- snprintf(buf, sizeof(buf), "UNI_Tex%i", ct);
- mUniformNames[uniformCount].setTo(buf);
+ mUniformNames[uniformCount].setTo("UNI_");
+ mUniformNames[uniformCount].append(textureNames[ct], textureNamesLength[ct]);
mUniformArraySizes[uniformCount] = 1;
uniformCount++;
}
-
}
String8 RsdShader::getGLSLInputString() const {
@@ -135,22 +138,25 @@ void RsdShader::appendAttributes() {
}
}
-void RsdShader::appendTextures() {
- char buf[256];
- for (uint32_t ct=0; ct < mRSProgram->mHal.state.texturesCount; ct++) {
+void RsdShader::createTexturesString(const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength) {
+ mShaderTextures.setTo("");
+ for (uint32_t ct = 0; ct < mRSProgram->mHal.state.texturesCount; ct ++) {
if (mRSProgram->mHal.state.textureTargets[ct] == RS_TEXTURE_2D) {
Allocation *a = mRSProgram->mHal.state.textures[ct];
if (a && a->mHal.state.surfaceTextureID) {
- snprintf(buf, sizeof(buf), "uniform samplerExternalOES UNI_Tex%i;\n", ct);
+ mShaderTextures.append("uniform samplerExternalOES UNI_");
} else {
- snprintf(buf, sizeof(buf), "uniform sampler2D UNI_Tex%i;\n", ct);
+ mShaderTextures.append("uniform sampler2D UNI_");
}
mTextureTargets[ct] = GL_TEXTURE_2D;
} else {
- snprintf(buf, sizeof(buf), "uniform samplerCube UNI_Tex%i;\n", ct);
+ mShaderTextures.append("uniform samplerCube UNI_");
mTextureTargets[ct] = GL_TEXTURE_CUBE_MAP;
}
- mShader.append(buf);
+
+ mShaderTextures.append(textureNames[ct], textureNamesLength[ct]);
+ mShaderTextures.append(";\n");
}
}
@@ -161,7 +167,7 @@ bool RsdShader::createShader() {
}
appendUserConstants();
appendAttributes();
- appendTextures();
+ mShader.append(mShaderTextures);
mShader.append(mUserShader);
@@ -418,7 +424,8 @@ void RsdShader::setupTextures(const Context *rsc, RsdShaderCache *sc) {
DrvAllocation *drvTex = (DrvAllocation *)mRSProgram->mHal.state.textures[ct]->mHal.drv;
if (drvTex->glTarget != GL_TEXTURE_2D && drvTex->glTarget != GL_TEXTURE_CUBE_MAP) {
- ALOGE("Attempting to bind unknown texture to shader id %u, texture unit %u", (uint)this, ct);
+ ALOGE("Attempting to bind unknown texture to shader id %u, texture unit %u",
+ (uint)this, ct);
rsc->setError(RS_ERROR_BAD_SHADER, "Non-texture allocation bound to a shader");
}
RSD_CALL_GL(glBindTexture, drvTex->glTarget, drvTex->textureID);
diff --git a/libs/rs/driver/rsdShader.h b/libs/rs/driver/rsdShader.h
index 3f0d6eae909e..e32145fad548 100644
--- a/libs/rs/driver/rsdShader.h
+++ b/libs/rs/driver/rsdShader.h
@@ -39,7 +39,9 @@ class RsdShader {
public:
RsdShader(const android::renderscript::Program *p, uint32_t type,
- const char * shaderText, uint32_t shaderLength);
+ const char * shaderText, uint32_t shaderLength,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength);
virtual ~RsdShader();
bool createShader();
@@ -67,19 +69,27 @@ protected:
// Applies to vertex and fragment shaders only
void appendUserConstants();
- void setupUserConstants(const android::renderscript::Context *rsc, RsdShaderCache *sc, bool isFragment);
- void initAddUserElement(const android::renderscript::Element *e, android::String8 *names, uint32_t *arrayLengths, uint32_t *count, const char *prefix);
+ void setupUserConstants(const android::renderscript::Context *rsc,
+ RsdShaderCache *sc, bool isFragment);
+ void initAddUserElement(const android::renderscript::Element *e,
+ android::String8 *names, uint32_t *arrayLengths,
+ uint32_t *count, const char *prefix);
void setupTextures(const android::renderscript::Context *rsc, RsdShaderCache *sc);
- void setupSampler(const android::renderscript::Context *rsc, const android::renderscript::Sampler *s, const android::renderscript::Allocation *tex);
+ void setupSampler(const android::renderscript::Context *rsc,
+ const android::renderscript::Sampler *s,
+ const android::renderscript::Allocation *tex);
void appendAttributes();
void appendTextures();
+ void createTexturesString(const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength);
void initAttribAndUniformArray();
mutable bool mDirty;
android::String8 mShader;
android::String8 mUserShader;
+ android::String8 mShaderTextures;
uint32_t mShaderID;
uint32_t mType;
@@ -93,10 +103,14 @@ protected:
int32_t mTextureUniformIndexStart;
- void logUniform(const android::renderscript::Element *field, const float *fd, uint32_t arraySize );
- void setUniform(const android::renderscript::Context *rsc, const android::renderscript::Element *field, const float *fd, int32_t slot, uint32_t arraySize );
+ void logUniform(const android::renderscript::Element *field,
+ const float *fd, uint32_t arraySize);
+ void setUniform(const android::renderscript::Context *rsc,
+ const android::renderscript::Element *field,
+ const float *fd, int32_t slot, uint32_t arraySize );
void initMemberVars();
- void init();
+ void init(const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength);
};
#endif //ANDROID_RSD_SHADER_H
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 6759bc7bb271..cf4a3918771c 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -69,168 +69,183 @@ AllocationGetSurfaceTextureID {
ret int32_t
}
+AllocationSetSurface {
+ param RsAllocation alloc
+ param RsNativeWindow sur
+ sync
+ }
+
+AllocationIoSend {
+ param RsAllocation alloc
+ }
+
+AllocationIoReceive {
+ param RsAllocation alloc
+ }
+
+
ContextFinish {
- sync
- }
+ sync
+ }
ContextBindRootScript {
- param RsScript sampler
- }
+ param RsScript sampler
+ }
ContextBindProgramStore {
- param RsProgramStore pgm
- }
+ param RsProgramStore pgm
+ }
ContextBindProgramFragment {
- param RsProgramFragment pgm
- }
+ param RsProgramFragment pgm
+ }
ContextBindProgramVertex {
- param RsProgramVertex pgm
- }
+ param RsProgramVertex pgm
+ }
ContextBindProgramRaster {
- param RsProgramRaster pgm
- }
+ param RsProgramRaster pgm
+ }
ContextBindFont {
- param RsFont pgm
- }
+ param RsFont pgm
+ }
ContextPause {
- }
+ }
ContextResume {
- }
+ }
ContextSetSurface {
- param uint32_t width
- param uint32_t height
- param RsNativeWindow sur
+ param uint32_t width
+ param uint32_t height
+ param RsNativeWindow sur
sync
- }
+ }
ContextDump {
- param int32_t bits
+ param int32_t bits
}
ContextSetPriority {
- param int32_t priority
- }
+ param int32_t priority
+ }
ContextDestroyWorker {
sync
}
AssignName {
- param RsObjectBase obj
- param const char *name
- }
+ param RsObjectBase obj
+ param const char *name
+ }
ObjDestroy {
- param RsAsyncVoidPtr objPtr
- }
+ param RsAsyncVoidPtr objPtr
+ }
ElementCreate {
direct
- param RsDataType mType
- param RsDataKind mKind
- param bool mNormalized
- param uint32_t mVectorSize
- ret RsElement
- }
+ param RsDataType mType
+ param RsDataKind mKind
+ param bool mNormalized
+ param uint32_t mVectorSize
+ ret RsElement
+ }
ElementCreate2 {
direct
- param const RsElement * elements
- param const char ** names
- param const uint32_t * arraySize
- ret RsElement
- }
+ param const RsElement * elements
+ param const char ** names
+ param const uint32_t * arraySize
+ ret RsElement
+ }
AllocationCopyToBitmap {
- param RsAllocation alloc
- param void * data
- }
+ param RsAllocation alloc
+ param void * data
+ }
Allocation1DData {
- param RsAllocation va
- param uint32_t xoff
- param uint32_t lod
- param uint32_t count
- param const void *data
- }
+ param RsAllocation va
+ param uint32_t xoff
+ param uint32_t lod
+ param uint32_t count
+ param const void *data
+ }
Allocation1DElementData {
- param RsAllocation va
- param uint32_t x
- param uint32_t lod
- param const void *data
- param size_t comp_offset
- }
+ param RsAllocation va
+ param uint32_t x
+ param uint32_t lod
+ param const void *data
+ param size_t comp_offset
+ }
Allocation2DData {
- param RsAllocation va
- param uint32_t xoff
- param uint32_t yoff
- param uint32_t lod
- param RsAllocationCubemapFace face
- param uint32_t w
- param uint32_t h
- param const void *data
- }
+ param RsAllocation va
+ param uint32_t xoff
+ param uint32_t yoff
+ param uint32_t lod
+ param RsAllocationCubemapFace face
+ param uint32_t w
+ param uint32_t h
+ param const void *data
+ }
Allocation2DElementData {
- param RsAllocation va
- param uint32_t x
- param uint32_t y
- param uint32_t lod
- param RsAllocationCubemapFace face
- param const void *data
- param size_t element_offset
- }
+ param RsAllocation va
+ param uint32_t x
+ param uint32_t y
+ param uint32_t lod
+ param RsAllocationCubemapFace face
+ param const void *data
+ param size_t element_offset
+ }
AllocationGenerateMipmaps {
- param RsAllocation va
+ param RsAllocation va
}
AllocationRead {
- param RsAllocation va
- param void * data
- }
+ param RsAllocation va
+ param void * data
+ }
AllocationSyncAll {
- param RsAllocation va
- param RsAllocationUsageType src
+ param RsAllocation va
+ param RsAllocationUsageType src
}
AllocationResize1D {
- param RsAllocation va
- param uint32_t dimX
- }
+ param RsAllocation va
+ param uint32_t dimX
+ }
AllocationResize2D {
- param RsAllocation va
- param uint32_t dimX
- param uint32_t dimY
- }
+ param RsAllocation va
+ param uint32_t dimX
+ param uint32_t dimY
+ }
AllocationCopy2DRange {
- param RsAllocation dest
- param uint32_t destXoff
- param uint32_t destYoff
- param uint32_t destMip
- param uint32_t destFace
- param uint32_t width
- param uint32_t height
- param RsAllocation src
- param uint32_t srcXoff
- param uint32_t srcYoff
- param uint32_t srcMip
- param uint32_t srcFace
- }
+ param RsAllocation dest
+ param uint32_t destXoff
+ param uint32_t destYoff
+ param uint32_t destMip
+ param uint32_t destFace
+ param uint32_t width
+ param uint32_t height
+ param RsAllocation src
+ param uint32_t srcXoff
+ param uint32_t srcYoff
+ param uint32_t srcMip
+ param uint32_t srcFace
+ }
SamplerCreate {
direct
@@ -244,26 +259,26 @@ SamplerCreate {
}
ScriptBindAllocation {
- param RsScript vtm
- param RsAllocation va
- param uint32_t slot
- }
+ param RsScript vtm
+ param RsAllocation va
+ param uint32_t slot
+ }
ScriptSetTimeZone {
- param RsScript s
- param const char * timeZone
- }
+ param RsScript s
+ param const char * timeZone
+ }
ScriptInvoke {
- param RsScript s
- param uint32_t slot
- }
+ param RsScript s
+ param uint32_t slot
+ }
ScriptInvokeV {
- param RsScript s
- param uint32_t slot
- param const void * data
- }
+ param RsScript s
+ param uint32_t slot
+ param const void * data
+ }
ScriptForEach {
param RsScript s
@@ -274,125 +289,127 @@ ScriptForEach {
}
ScriptSetVarI {
- param RsScript s
- param uint32_t slot
- param int value
- }
+ param RsScript s
+ param uint32_t slot
+ param int value
+ }
ScriptSetVarObj {
- param RsScript s
- param uint32_t slot
- param RsObjectBase value
- }
+ param RsScript s
+ param uint32_t slot
+ param RsObjectBase value
+ }
ScriptSetVarJ {
- param RsScript s
- param uint32_t slot
- param int64_t value
- }
+ param RsScript s
+ param uint32_t slot
+ param int64_t value
+ }
ScriptSetVarF {
- param RsScript s
- param uint32_t slot
- param float value
- }
+ param RsScript s
+ param uint32_t slot
+ param float value
+ }
ScriptSetVarD {
- param RsScript s
- param uint32_t slot
- param double value
- }
+ param RsScript s
+ param uint32_t slot
+ param double value
+ }
ScriptSetVarV {
- param RsScript s
- param uint32_t slot
- param const void * data
- }
+ param RsScript s
+ param uint32_t slot
+ param const void * data
+ }
ScriptCCreate {
param const char * resName
param const char * cacheDir
- param const char * text
- ret RsScript
- }
+ param const char * text
+ ret RsScript
+ }
ProgramStoreCreate {
- direct
- param bool colorMaskR
- param bool colorMaskG
- param bool colorMaskB
- param bool colorMaskA
+ direct
+ param bool colorMaskR
+ param bool colorMaskG
+ param bool colorMaskB
+ param bool colorMaskA
param bool depthMask
param bool ditherEnable
- param RsBlendSrcFunc srcFunc
- param RsBlendDstFunc destFunc
+ param RsBlendSrcFunc srcFunc
+ param RsBlendDstFunc destFunc
param RsDepthFunc depthFunc
- ret RsProgramStore
- }
+ ret RsProgramStore
+ }
ProgramRasterCreate {
- direct
- param bool pointSprite
- param RsCullMode cull
- ret RsProgramRaster
+ direct
+ param bool pointSprite
+ param RsCullMode cull
+ ret RsProgramRaster
}
ProgramBindConstants {
- param RsProgram vp
- param uint32_t slot
- param RsAllocation constants
- }
+ param RsProgram vp
+ param uint32_t slot
+ param RsAllocation constants
+ }
ProgramBindTexture {
- param RsProgramFragment pf
- param uint32_t slot
- param RsAllocation a
- }
+ param RsProgramFragment pf
+ param uint32_t slot
+ param RsAllocation a
+ }
ProgramBindSampler {
- param RsProgramFragment pf
- param uint32_t slot
- param RsSampler s
- }
+ param RsProgramFragment pf
+ param uint32_t slot
+ param RsSampler s
+ }
ProgramFragmentCreate {
- direct
- param const char * shaderText
- param const uint32_t * params
- ret RsProgramFragment
- }
+ direct
+ param const char * shaderText
+ param const char ** textureNames
+ param const uint32_t * params
+ ret RsProgramFragment
+ }
ProgramVertexCreate {
- direct
- param const char * shaderText
- param const uint32_t * params
- ret RsProgramVertex
- }
+ direct
+ param const char * shaderText
+ param const char ** textureNames
+ param const uint32_t * params
+ ret RsProgramVertex
+ }
FontCreateFromFile {
- param const char *name
- param float fontSize
- param uint32_t dpi
- ret RsFont
- }
+ param const char *name
+ param float fontSize
+ param uint32_t dpi
+ ret RsFont
+ }
FontCreateFromMemory {
- param const char *name
- param float fontSize
- param uint32_t dpi
- param const void *data
- ret RsFont
- }
+ param const char *name
+ param float fontSize
+ param uint32_t dpi
+ param const void *data
+ ret RsFont
+ }
MeshCreate {
- param RsAllocation *vtx
- param RsAllocation *idx
- param uint32_t *primType
- ret RsMesh
- }
+ param RsAllocation *vtx
+ param RsAllocation *idx
+ param uint32_t *primType
+ ret RsMesh
+ }
PathCreate {
param RsPathPrimitive pp
@@ -402,4 +419,3 @@ PathCreate {
param float quality
ret RsPath
}
-
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 02c680939de3..83c88fdffacf 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -17,6 +17,7 @@
#include "rsContext.h"
#include "rs_hal.h"
+#include "system/window.h"
using namespace android;
using namespace android::renderscript;
@@ -44,6 +45,7 @@ Allocation * Allocation::createAllocation(Context *rsc, const Type *type, uint32
delete a;
return NULL;
}
+
return a;
}
@@ -420,6 +422,28 @@ int32_t Allocation::getSurfaceTextureID(const Context *rsc) {
return id;
}
+void Allocation::setSurface(const Context *rsc, RsNativeWindow sur) {
+ ANativeWindow *nw = (ANativeWindow *)sur;
+ ANativeWindow *old = mHal.state.wndSurface;
+ if (nw) {
+ nw->incStrong(NULL);
+ }
+ rsc->mHal.funcs.allocation.setSurfaceTexture(rsc, this, nw);
+ mHal.state.wndSurface = nw;
+ if (old) {
+ old->decStrong(NULL);
+ }
+}
+
+void Allocation::ioSend(const Context *rsc) {
+ rsc->mHal.funcs.allocation.ioSend(rsc, this);
+}
+
+void Allocation::ioReceive(const Context *rsc) {
+ rsc->mHal.funcs.allocation.ioReceive(rsc, this);
+}
+
+
/////////////////
//
@@ -670,6 +694,21 @@ int32_t rsi_AllocationGetSurfaceTextureID(Context *rsc, RsAllocation valloc) {
return alloc->getSurfaceTextureID(rsc);
}
+void rsi_AllocationSetSurface(Context *rsc, RsAllocation valloc, RsNativeWindow sur) {
+ Allocation *alloc = static_cast<Allocation *>(valloc);
+ alloc->setSurface(rsc, sur);
+}
+
+void rsi_AllocationIoSend(Context *rsc, RsAllocation valloc) {
+ Allocation *alloc = static_cast<Allocation *>(valloc);
+ alloc->ioSend(rsc);
+}
+
+void rsi_AllocationIoReceive(Context *rsc, RsAllocation valloc) {
+ Allocation *alloc = static_cast<Allocation *>(valloc);
+ alloc->ioReceive(rsc);
+}
+
}
}
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index 58a582b75866..58a6fcacf6ac 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -19,6 +19,8 @@
#include "rsType.h"
+struct ANativeWindow;
+
// ---------------------------------------------------------------------------
namespace android {
namespace renderscript {
@@ -57,6 +59,7 @@ public:
bool hasReferences;
void * usrPtr;
int32_t surfaceTextureID;
+ ANativeWindow *wndSurface;
};
State state;
@@ -127,6 +130,9 @@ public:
}
int32_t getSurfaceTextureID(const Context *rsc);
+ void setSurface(const Context *rsc, RsNativeWindow sur);
+ void ioSend(const Context *rsc);
+ void ioReceive(const Context *rsc);
protected:
Vector<const Program *> mToDirtyList;
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 4f21b3b0140d..c4276cfe8261 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -490,8 +490,14 @@ void FontState::initRenderState() {
shaderString.append(" gl_FragColor = col;\n");
shaderString.append("}\n");
- ObjectBaseRef<const Element> colorElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4);
- ObjectBaseRef<const Element> gammaElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 1);
+ const char *textureNames[] = { "Tex0" };
+ const size_t textureNamesLengths[] = { 4 };
+ size_t numTextures = sizeof(textureNamesLengths)/sizeof(*textureNamesLengths);
+
+ ObjectBaseRef<const Element> colorElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32,
+ RS_KIND_USER, false, 4);
+ ObjectBaseRef<const Element> gammaElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32,
+ RS_KIND_USER, false, 1);
Element::Builder builder;
builder.add(colorElem.get(), "Color", 1);
builder.add(gammaElem.get(), "Gamma", 1);
@@ -506,14 +512,17 @@ void FontState::initRenderState() {
tmp[3] = RS_TEXTURE_2D;
mFontShaderFConstant.set(Allocation::createAllocation(mRSC, inputType.get(),
- RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS));
- ProgramFragment *pf = new ProgramFragment(mRSC, shaderString.string(),
- shaderString.length(), tmp, 4);
+ RS_ALLOCATION_USAGE_SCRIPT |
+ RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS));
+ ProgramFragment *pf = new ProgramFragment(mRSC, shaderString.string(), shaderString.length(),
+ textureNames, numTextures, textureNamesLengths,
+ tmp, 4);
mFontShaderF.set(pf);
mFontShaderF->bindAllocation(mRSC, mFontShaderFConstant.get(), 0);
mFontSampler.set(Sampler::getSampler(mRSC, RS_SAMPLER_NEAREST, RS_SAMPLER_NEAREST,
- RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP).get());
+ RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP,
+ RS_SAMPLER_CLAMP).get());
mFontShaderF->bindSampler(mRSC, 0, mFontSampler.get());
mFontProgramStore.set(ProgramStore::getProgramStore(mRSC, true, true, true, true,
@@ -525,10 +534,12 @@ void FontState::initRenderState() {
}
void FontState::initTextTexture() {
- ObjectBaseRef<const Element> alphaElem = Element::createRef(mRSC, RS_TYPE_UNSIGNED_8, RS_KIND_PIXEL_A, true, 1);
+ ObjectBaseRef<const Element> alphaElem = Element::createRef(mRSC, RS_TYPE_UNSIGNED_8,
+ RS_KIND_PIXEL_A, true, 1);
// We will allocate a texture to initially hold 32 character bitmaps
- ObjectBaseRef<Type> texType = Type::getTypeRef(mRSC, alphaElem.get(), 1024, 256, 0, false, false);
+ ObjectBaseRef<Type> texType = Type::getTypeRef(mRSC, alphaElem.get(),
+ 1024, 256, 0, false, false);
Allocation *cacheAlloc = Allocation::createAllocation(mRSC, texType.get(),
RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE);
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 8061515e24fa..7114f29d0509 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -20,8 +20,8 @@
using namespace android;
using namespace android::renderscript;
-Program::Program(Context *rsc, const char * shaderText, uint32_t shaderLength,
- const uint32_t * params, uint32_t paramLength)
+Program::Program(Context *rsc, const char * shaderText, size_t shaderLength,
+ const uint32_t * params, size_t paramLength)
: ProgramBase(rsc) {
initMemberVars();
diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h
index 06fc3eca71ab..d03293066803 100644
--- a/libs/rs/rsProgram.h
+++ b/libs/rs/rsProgram.h
@@ -58,8 +58,8 @@ public:
};
Hal mHal;
- Program(Context *, const char * shaderText, uint32_t shaderLength,
- const uint32_t * params, uint32_t paramLength);
+ Program(Context *, const char * shaderText, size_t shaderLength,
+ const uint32_t * params, size_t paramLength);
virtual ~Program();
virtual bool freeChildren();
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 4e73ca63e90b..bebde1e0cb53 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -20,16 +20,18 @@
using namespace android;
using namespace android::renderscript;
-ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText,
- uint32_t shaderLength, const uint32_t * params,
- uint32_t paramLength)
+ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText, size_t shaderLength,
+ const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength,
+
+ const uint32_t * params, size_t paramLength)
: Program(rsc, shaderText, shaderLength, params, paramLength) {
mConstantColor[0] = 1.f;
mConstantColor[1] = 1.f;
mConstantColor[2] = 1.f;
mConstantColor[3] = 1.f;
- mRSC->mHal.funcs.fragment.init(mRSC, this, mUserShader.string(), mUserShader.length());
+ mRSC->mHal.funcs.fragment.init(mRSC, this, mUserShader.string(), mUserShader.length(),
+ textureNames, textureNamesCount, textureNamesLength);
}
ProgramFragment::~ProgramFragment() {
@@ -110,8 +112,8 @@ void ProgramFragmentState::init(Context *rsc) {
Allocation *constAlloc = Allocation::createAllocation(rsc, inputType.get(),
RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS);
- ProgramFragment *pf = new ProgramFragment(rsc, shaderString.string(),
- shaderString.length(), tmp, 2);
+ ProgramFragment *pf = new ProgramFragment(rsc, shaderString.string(), shaderString.length(),
+ NULL, 0, NULL, tmp, 2);
pf->bindAllocation(rsc, constAlloc, 0);
pf->setConstantColor(rsc, 1.0f, 1.0f, 1.0f, 1.0f);
@@ -127,9 +129,14 @@ namespace android {
namespace renderscript {
RsProgramFragment rsi_ProgramFragmentCreate(Context *rsc, const char * shaderText,
- size_t shaderLength, const uint32_t * params,
- size_t paramLength) {
- ProgramFragment *pf = new ProgramFragment(rsc, shaderText, shaderLength, params, paramLength);
+ size_t shaderLength,
+ const char** textureNames,
+ size_t textureNamesCount,
+ const size_t *textureNamesLength,
+ const uint32_t * params, size_t paramLength) {
+ ProgramFragment *pf = new ProgramFragment(rsc, shaderText, shaderLength,
+ textureNames, textureNamesCount, textureNamesLength,
+ params, paramLength);
pf->incUserRef();
//ALOGE("rsi_ProgramFragmentCreate %p", pf);
return pf;
diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h
index d6e20cd192b9..4eb28e7db171 100644
--- a/libs/rs/rsProgramFragment.h
+++ b/libs/rs/rsProgramFragment.h
@@ -27,9 +27,9 @@ class ProgramFragmentState;
class ProgramFragment : public Program {
public:
- ProgramFragment(Context *rsc, const char * shaderText,
- uint32_t shaderLength, const uint32_t * params,
- uint32_t paramLength);
+ ProgramFragment(Context *rsc, const char * shaderText, size_t shaderLength,
+ const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength,
+ const uint32_t * params, size_t paramLength);
virtual ~ProgramFragment();
virtual void setup(Context *, ProgramFragmentState *);
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index 871caacd6f66..c8a53eab2012 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -21,11 +21,13 @@ using namespace android;
using namespace android::renderscript;
-ProgramVertex::ProgramVertex(Context *rsc, const char * shaderText,
- uint32_t shaderLength, const uint32_t * params,
- uint32_t paramLength)
+ProgramVertex::ProgramVertex(Context *rsc, const char * shaderText, size_t shaderLength,
+ const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength,
+
+ const uint32_t * params, size_t paramLength)
: Program(rsc, shaderText, shaderLength, params, paramLength) {
- mRSC->mHal.funcs.vertex.init(mRSC, this, mUserShader.string(), mUserShader.length());
+ mRSC->mHal.funcs.vertex.init(mRSC, this, mUserShader.string(), mUserShader.length(),
+ textureNames, textureNamesCount, textureNamesLength);
}
ProgramVertex::~ProgramVertex() {
@@ -191,8 +193,8 @@ void ProgramVertexState::init(Context *rsc) {
tmp[2] = RS_PROGRAM_PARAM_INPUT;
tmp[3] = (uint32_t)attrElem.get();
- ProgramVertex *pv = new ProgramVertex(rsc, shaderString.string(),
- shaderString.length(), tmp, 4);
+ ProgramVertex *pv = new ProgramVertex(rsc, shaderString.string(), shaderString.length(),
+ NULL, 0, NULL, tmp, 4);
Allocation *alloc = Allocation::createAllocation(rsc, inputType.get(),
RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS);
pv->bindAllocation(rsc, alloc, 0);
@@ -229,10 +231,13 @@ void ProgramVertexState::deinit(Context *rsc) {
namespace android {
namespace renderscript {
-RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, const char * shaderText,
- size_t shaderLength, const uint32_t * params,
- size_t paramLength) {
- ProgramVertex *pv = new ProgramVertex(rsc, shaderText, shaderLength, params, paramLength);
+RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, const char * shaderText, size_t shaderLength,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength,
+ const uint32_t * params, size_t paramLength) {
+ ProgramVertex *pv = new ProgramVertex(rsc, shaderText, shaderLength,
+ textureNames, textureNamesCount, textureNamesLength,
+ params, paramLength);
pv->incUserRef();
return pv;
}
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index 5cfdd8be355d..67c2a88840f6 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -27,8 +27,9 @@ class ProgramVertexState;
class ProgramVertex : public Program {
public:
- ProgramVertex(Context *,const char * shaderText, uint32_t shaderLength,
- const uint32_t * params, uint32_t paramLength);
+ ProgramVertex(Context *,const char * shaderText, size_t shaderLength,
+ const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength,
+ const uint32_t * params, size_t paramLength);
virtual ~ProgramVertex();
virtual void setup(Context *rsc, ProgramVertexState *state);
diff --git a/libs/rs/rs_hal.h b/libs/rs/rs_hal.h
index 1e222e1664cf..120112819d8c 100644
--- a/libs/rs/rs_hal.h
+++ b/libs/rs/rs_hal.h
@@ -19,6 +19,8 @@
#include <RenderScriptDefines.h>
+struct ANativeWindow;
+
namespace android {
namespace renderscript {
@@ -115,19 +117,23 @@ typedef struct {
bool zeroNew);
void (*syncAll)(const Context *rsc, const Allocation *alloc, RsAllocationUsageType src);
void (*markDirty)(const Context *rsc, const Allocation *alloc);
+
int32_t (*initSurfaceTexture)(const Context *rsc, const Allocation *alloc);
+ void (*setSurfaceTexture)(const Context *rsc, Allocation *alloc, ANativeWindow *sur);
+ void (*ioSend)(const Context *rsc, Allocation *alloc);
+ void (*ioReceive)(const Context *rsc, Allocation *alloc);
void (*data1D)(const Context *rsc, const Allocation *alloc,
uint32_t xoff, uint32_t lod, uint32_t count,
- const void *data, uint32_t sizeBytes);
+ const void *data, size_t sizeBytes);
void (*data2D)(const Context *rsc, const Allocation *alloc,
uint32_t xoff, uint32_t yoff, uint32_t lod,
RsAllocationCubemapFace face, uint32_t w, uint32_t h,
- const void *data, uint32_t sizeBytes);
+ const void *data, size_t sizeBytes);
void (*data3D)(const Context *rsc, const Allocation *alloc,
uint32_t xoff, uint32_t yoff, uint32_t zoff,
uint32_t lod, RsAllocationCubemapFace face,
- uint32_t w, uint32_t h, uint32_t d, const void *data, uint32_t sizeBytes);
+ uint32_t w, uint32_t h, uint32_t d, const void *data, size_t sizeBytes);
// Allocation to allocation copies
void (*allocData1D)(const Context *rsc,
@@ -151,9 +157,9 @@ typedef struct {
uint32_t srcLod, RsAllocationCubemapFace srcFace);
void (*elementData1D)(const Context *rsc, const Allocation *alloc, uint32_t x,
- const void *data, uint32_t elementOff, uint32_t sizeBytes);
+ const void *data, uint32_t elementOff, size_t sizeBytes);
void (*elementData2D)(const Context *rsc, const Allocation *alloc, uint32_t x, uint32_t y,
- const void *data, uint32_t elementOff, uint32_t sizeBytes);
+ const void *data, uint32_t elementOff, size_t sizeBytes);
} allocation;
@@ -172,14 +178,18 @@ typedef struct {
struct {
bool (*init)(const Context *rsc, const ProgramVertex *pv,
- const char* shader, uint32_t shaderLen);
+ const char* shader, size_t shaderLen,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength);
void (*setActive)(const Context *rsc, const ProgramVertex *pv);
void (*destroy)(const Context *rsc, const ProgramVertex *pv);
} vertex;
struct {
bool (*init)(const Context *rsc, const ProgramFragment *pf,
- const char* shader, uint32_t shaderLen);
+ const char* shader, size_t shaderLen,
+ const char** textureNames, size_t textureNamesCount,
+ const size_t *textureNamesLength);
void (*setActive)(const Context *rsc, const ProgramFragment *pf);
void (*destroy)(const Context *rsc, const ProgramFragment *pf);
} fragment;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index eae03beae6fc..1c7f57750d02 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -591,7 +591,7 @@ public class AudioService extends IAudioService.Stub {
// Post a persist volume msg
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
- SENDMSG_REPLACE,
+ SENDMSG_QUEUE,
PERSIST_LAST_AUDIBLE,
device,
s,
@@ -606,7 +606,7 @@ public class AudioService extends IAudioService.Stub {
// to persist). Do not change volume if stream is muted.
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
- SENDMSG_NOOP,
+ SENDMSG_QUEUE,
device,
0,
streamState,
@@ -746,7 +746,7 @@ public class AudioService extends IAudioService.Stub {
// Post a persist volume msg
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
- SENDMSG_REPLACE,
+ SENDMSG_QUEUE,
PERSIST_LAST_AUDIBLE,
device,
streamState,
@@ -758,7 +758,7 @@ public class AudioService extends IAudioService.Stub {
// to persist).
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
- SENDMSG_NOOP,
+ SENDMSG_QUEUE,
device,
0,
streamState,
@@ -2208,7 +2208,7 @@ public class AudioService extends IAudioService.Stub {
}
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
- SENDMSG_NOOP,
+ SENDMSG_QUEUE,
0,
0,
VolumeStreamState.this, 0);
@@ -2252,7 +2252,7 @@ public class AudioService extends IAudioService.Stub {
}
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
- SENDMSG_NOOP,
+ SENDMSG_QUEUE,
0,
0,
VolumeStreamState.this, 0);
@@ -2350,7 +2350,7 @@ public class AudioService extends IAudioService.Stub {
// Post a persist volume msg
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
- SENDMSG_REPLACE,
+ SENDMSG_QUEUE,
PERSIST_CURRENT|PERSIST_LAST_AUDIBLE,
device,
streamState,
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index bcf7b89b48a5..91d0addde5f8 100755
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -84,6 +84,7 @@ public class Visualizer {
// to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp
private static final int NATIVE_EVENT_PCM_CAPTURE = 0;
private static final int NATIVE_EVENT_FFT_CAPTURE = 1;
+ private static final int NATIVE_EVENT_SERVER_DIED = 2;
// Error codes:
/**
@@ -147,6 +148,10 @@ public class Visualizer {
* PCM and FFT capture listener registered by client
*/
private OnDataCaptureListener mCaptureListener = null;
+ /**
+ * Server Died listener registered by client
+ */
+ private OnServerDiedListener mServerDiedListener = null;
// accessed by native methods
private int mNativeVisualizer;
@@ -396,6 +401,9 @@ public class Visualizer {
public interface OnDataCaptureListener {
/**
* Method called when a new waveform capture is available.
+ * <p>Data in the waveform buffer is valid only within the scope of the callback.
+ * Applications which needs access to the waveform data after returning from the callback
+ * should make a copy of the data instead of holding a reference.
* @param visualizer Visualizer object on which the listener is registered.
* @param waveform array of bytes containing the waveform representation.
* @param samplingRate sampling rate of the audio visualized.
@@ -404,6 +412,9 @@ public class Visualizer {
/**
* Method called when a new frequency capture is available.
+ * <p>Data in the fft buffer is valid only within the scope of the callback.
+ * Applications which needs access to the fft data after returning from the callback
+ * should make a copy of the data instead of holding a reference.
* @param visualizer Visualizer object on which the listener is registered.
* @param fft array of bytes containing the frequency representation.
* @param samplingRate sampling rate of the audio visualized.
@@ -452,6 +463,43 @@ public class Visualizer {
}
/**
+ * @hide
+ *
+ * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that
+ * the connection to the native media server has been broken and that the Visualizer object will
+ * need to be released and re-created.
+ * The client application can implement this interface and register the listener with the
+ * {@link #setServerDiedListener(OnServerDiedListener)} method.
+ */
+ public interface OnServerDiedListener {
+ /**
+ * @hide
+ *
+ * Method called when the native media server has died.
+ * <p>If the native media server encounters a fatal error and needs to restart, the binder
+ * connection from the {@link #Visualizer} to the media server will be broken. Data capture
+ * callbacks will stop happening, and client initiated calls to the {@link #Visualizer}
+ * instance will fail with the error code {@link #DEAD_OBJECT}. To restore functionality,
+ * clients should {@link #release()} their old visualizer and create a new instance.
+ */
+ void onServerDied();
+ }
+
+ /**
+ * @hide
+ *
+ * Registers an OnServerDiedListener interface.
+ * <p>Call this method with a null listener to stop receiving server death notifications.
+ * @return {@link #SUCCESS} in case of success,
+ */
+ public int setServerDiedListener(OnServerDiedListener listener) {
+ synchronized (mListenerLock) {
+ mServerDiedListener = listener;
+ }
+ return SUCCESS;
+ }
+
+ /**
* Helper class to handle the forwarding of native events to the appropriate listeners
*/
private class NativeEventHandler extends Handler
@@ -463,11 +511,7 @@ public class Visualizer {
mVisualizer = v;
}
- @Override
- public void handleMessage(Message msg) {
- if (mVisualizer == null) {
- return;
- }
+ private void handleCaptureMessage(Message msg) {
OnDataCaptureListener l = null;
synchronized (mListenerLock) {
l = mVisualizer.mCaptureListener;
@@ -476,6 +520,7 @@ public class Visualizer {
if (l != null) {
byte[] data = (byte[])msg.obj;
int samplingRate = msg.arg1;
+
switch(msg.what) {
case NATIVE_EVENT_PCM_CAPTURE:
l.onWaveFormDataCapture(mVisualizer, data, samplingRate);
@@ -484,11 +529,41 @@ public class Visualizer {
l.onFftDataCapture(mVisualizer, data, samplingRate);
break;
default:
- Log.e(TAG,"Unknown native event: "+msg.what);
+ Log.e(TAG,"Unknown native event in handleCaptureMessge: "+msg.what);
break;
}
}
}
+
+ private void handleServerDiedMessage(Message msg) {
+ OnServerDiedListener l = null;
+ synchronized (mListenerLock) {
+ l = mVisualizer.mServerDiedListener;
+ }
+
+ if (l != null)
+ l.onServerDied();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mVisualizer == null) {
+ return;
+ }
+
+ switch(msg.what) {
+ case NATIVE_EVENT_PCM_CAPTURE:
+ case NATIVE_EVENT_FFT_CAPTURE:
+ handleCaptureMessage(msg);
+ break;
+ case NATIVE_EVENT_SERVER_DIED:
+ handleServerDiedMessage(msg);
+ break;
+ default:
+ Log.e(TAG,"Unknown native event: "+msg.what);
+ break;
+ }
+ }
}
//---------------------------------------------------------
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index ecd4d076aaed..f015afbaf209 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -23,6 +23,7 @@
#include <nativehelper/jni.h>
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
+#include <utils/threads.h>
#include "media/Visualizer.h"
using namespace android;
@@ -38,6 +39,7 @@ using namespace android;
#define NATIVE_EVENT_PCM_CAPTURE 0
#define NATIVE_EVENT_FFT_CAPTURE 1
+#define NATIVE_EVENT_SERVER_DIED 2
// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/audiofx/Visualizer";
@@ -54,6 +56,43 @@ static fields_t fields;
struct visualizer_callback_cookie {
jclass visualizer_class; // Visualizer class
jobject visualizer_ref; // Visualizer object instance
+
+ // Lazily allocated arrays used to hold callback data provided to java
+ // applications. These arrays are allocated during the first callback and
+ // reallocated when the size of the callback data changes. Allocating on
+ // demand and saving the arrays means that applications cannot safely hold a
+ // reference to the provided data (they need to make a copy if they want to
+ // hold onto outside of the callback scope), but it avoids GC thrash caused
+ // by constantly allocating and releasing arrays to hold callback data.
+ Mutex callback_data_lock;
+ jbyteArray waveform_data;
+ jbyteArray fft_data;
+
+ visualizer_callback_cookie() {
+ waveform_data = NULL;
+ fft_data = NULL;
+ }
+
+ ~visualizer_callback_cookie() {
+ cleanupBuffers();
+ }
+
+ void cleanupBuffers() {
+ AutoMutex lock(&callback_data_lock);
+ if (waveform_data || fft_data) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ if (waveform_data) {
+ env->DeleteGlobalRef(waveform_data);
+ waveform_data = NULL;
+ }
+
+ if (fft_data) {
+ env->DeleteGlobalRef(fft_data);
+ fft_data = NULL;
+ }
+ }
+ }
};
// ----------------------------------------------------------------------------
@@ -66,7 +105,6 @@ class visualizerJniStorage {
~visualizerJniStorage() {
}
-
};
@@ -93,6 +131,26 @@ static jint translateError(int code) {
// ----------------------------------------------------------------------------
+static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
+ if (NULL != *array) {
+ uint32_t len = env->GetArrayLength(*array);
+ if (len == size)
+ return;
+
+ env->DeleteGlobalRef(*array);
+ *array = NULL;
+ }
+
+ jbyteArray localRef = env->NewByteArray(size);
+ if (NULL != localRef) {
+ // Promote to global ref.
+ *array = (jbyteArray)env->NewGlobalRef(localRef);
+
+ // Release our (now pointless) local ref.
+ env->DeleteLocalRef(localRef);
+ }
+}
+
static void captureCallback(void* user,
uint32_t waveformSize,
uint8_t *waveform,
@@ -106,6 +164,7 @@ static void captureCallback(void* user,
visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user;
JNIEnv *env = AndroidRuntime::getJNIEnv();
+ AutoMutex lock(&callbackInfo->callback_data_lock);
ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
callbackInfo,
@@ -118,7 +177,11 @@ static void captureCallback(void* user,
}
if (waveformSize != 0 && waveform != NULL) {
- jbyteArray jArray = env->NewByteArray(waveformSize);
+ jbyteArray jArray;
+
+ ensureArraySize(env, &callbackInfo->waveform_data, waveformSize);
+ jArray = callbackInfo->waveform_data;
+
if (jArray != NULL) {
jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
memcpy(nArray, waveform, waveformSize);
@@ -131,12 +194,15 @@ static void captureCallback(void* user,
samplingrate,
0,
jArray);
- env->DeleteLocalRef(jArray);
}
}
if (fftSize != 0 && fft != NULL) {
- jbyteArray jArray = env->NewByteArray(fftSize);
+ jbyteArray jArray;
+
+ ensureArraySize(env, &callbackInfo->fft_data, fftSize);
+ jArray = callbackInfo->fft_data;
+
if (jArray != NULL) {
jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
memcpy(nArray, fft, fftSize);
@@ -149,7 +215,6 @@ static void captureCallback(void* user,
samplingrate,
0,
jArray);
- env->DeleteLocalRef(jArray);
}
}
@@ -220,6 +285,23 @@ android_media_visualizer_native_init(JNIEnv *env)
}
+static void android_media_visualizer_effect_callback(int32_t event,
+ void *user,
+ void *info) {
+ if ((event == AudioEffect::EVENT_ERROR) &&
+ (*((status_t*)info) == DEAD_OBJECT)) {
+ visualizerJniStorage* lpJniStorage = (visualizerJniStorage*)user;
+ visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData;
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ env->CallStaticVoidMethod(
+ callbackInfo->visualizer_class,
+ fields.midPostNativeEvent,
+ callbackInfo->visualizer_ref,
+ NATIVE_EVENT_SERVER_DIED,
+ 0, 0, 0);
+ }
+}
static jint
android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
@@ -255,8 +337,8 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th
// create the native Visualizer object
lpVisualizer = new Visualizer(0,
- NULL,
- NULL,
+ android_media_visualizer_effect_callback,
+ lpJniStorage,
sessionId);
if (lpVisualizer == NULL) {
ALOGE("Error creating Visualizer");
@@ -345,7 +427,17 @@ android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean e
return VISUALIZER_ERROR_NO_INIT;
}
- return translateError(lpVisualizer->setEnabled(enabled));
+ jint retVal = translateError(lpVisualizer->setEnabled(enabled));
+
+ if (!enabled) {
+ visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField(
+ thiz, fields.fidJniData);
+
+ if (NULL != lpJniStorage)
+ lpJniStorage->mCallbackData.cleanupBuffers();
+ }
+
+ return retVal;
}
static jboolean
diff --git a/media/libaah_rtp/Android.mk b/media/libaah_rtp/Android.mk
new file mode 100644
index 000000000000..54fd9ec10ebd
--- /dev/null
+++ b/media/libaah_rtp/Android.mk
@@ -0,0 +1,40 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libaah_rtp
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaah_rtp
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ aah_decoder_pump.cpp \
+ aah_rx_player.cpp \
+ aah_rx_player_core.cpp \
+ aah_rx_player_ring_buffer.cpp \
+ aah_rx_player_substream.cpp \
+ aah_tx_packet.cpp \
+ aah_tx_player.cpp \
+ aah_tx_sender.cpp \
+ pipe_event.cpp
+
+LOCAL_C_INCLUDES := \
+ frameworks/base/include \
+ frameworks/base/include/media/stagefright/openmax \
+ frameworks/base/media \
+ frameworks/base/media/libstagefright
+
+LOCAL_SHARED_LIBRARIES := \
+ libcommon_time_client \
+ libbinder \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libutils
+
+LOCAL_LDLIBS := \
+ -lpthread
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/media/libaah_rtp/aah_decoder_pump.cpp b/media/libaah_rtp/aah_decoder_pump.cpp
new file mode 100644
index 000000000000..72fe43baf5d9
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.cpp
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <poll.h>
+#include <pthread.h>
+
+#include <common_time/cc_helper.h>
+#include <media/AudioSystem.h>
+#include <media/AudioTrack.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+#include <utils/Timers.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+
+namespace android {
+
+static const long long kLongDecodeErrorThreshold = 1000000ll;
+static const uint32_t kMaxLongErrorsBeforeFatal = 3;
+static const uint32_t kMaxErrorsBeforeFatal = 60;
+
+AAH_DecoderPump::AAH_DecoderPump(OMXClient& omx)
+ : omx_(omx)
+ , thread_status_(OK)
+ , renderer_(NULL)
+ , last_queued_pts_valid_(false)
+ , last_queued_pts_(0)
+ , last_ts_transform_valid_(false)
+ , last_volume_(0xFF) {
+ thread_ = new ThreadWrapper(this);
+}
+
+AAH_DecoderPump::~AAH_DecoderPump() {
+ shutdown();
+}
+
+status_t AAH_DecoderPump::initCheck() {
+ if (thread_ == NULL) {
+ ALOGE("Failed to allocate thread");
+ return NO_MEMORY;
+ }
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::queueForDecode(MediaBuffer* buf) {
+ if (NULL == buf) {
+ return BAD_VALUE;
+ }
+
+ if (OK != thread_status_) {
+ return thread_status_;
+ }
+
+ { // Explicit scope for AutoMutex pattern.
+ AutoMutex lock(&thread_lock_);
+ in_queue_.push_back(buf);
+ }
+
+ thread_cond_.signal();
+
+ return OK;
+}
+
+void AAH_DecoderPump::queueToRenderer(MediaBuffer* decoded_sample) {
+ Mutex::Autolock lock(&render_lock_);
+ sp<MetaData> meta;
+ int64_t ts;
+ status_t res;
+
+ // Fetch the metadata and make sure the sample has a timestamp. We
+ // cannot render samples which are missing PTSs.
+ meta = decoded_sample->meta_data();
+ if ((meta == NULL) || (!meta->findInt64(kKeyTime, &ts))) {
+ ALOGV("Decoded sample missing timestamp, cannot render.");
+ CHECK(false);
+ } else {
+ // If we currently are not holding on to a renderer, go ahead and
+ // make one now.
+ if (NULL == renderer_) {
+ renderer_ = new TimedAudioTrack();
+ if (NULL != renderer_) {
+ int frameCount;
+ AudioTrack::getMinFrameCount(&frameCount,
+ AUDIO_STREAM_DEFAULT,
+ static_cast<int>(format_sample_rate_));
+ int ch_format = (format_channels_ == 1)
+ ? AUDIO_CHANNEL_OUT_MONO
+ : AUDIO_CHANNEL_OUT_STEREO;
+
+ res = renderer_->set(AUDIO_STREAM_DEFAULT,
+ format_sample_rate_,
+ AUDIO_FORMAT_PCM_16_BIT,
+ ch_format,
+ frameCount);
+ if (res != OK) {
+ ALOGE("Failed to setup audio renderer. (res = %d)", res);
+ delete renderer_;
+ renderer_ = NULL;
+ } else {
+ CHECK(last_ts_transform_valid_);
+
+ res = renderer_->setMediaTimeTransform(
+ last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+ if (res != NO_ERROR) {
+ ALOGE("Failed to set media time transform on AudioTrack"
+ " (res = %d)", res);
+ delete renderer_;
+ renderer_ = NULL;
+ } else {
+ float volume = static_cast<float>(last_volume_)
+ / 255.0f;
+ if (renderer_->setVolume(volume, volume) != OK) {
+ ALOGW("%s: setVolume failed", __FUNCTION__);
+ }
+
+ renderer_->start();
+ }
+ }
+ } else {
+ ALOGE("Failed to allocate AudioTrack to use as a renderer.");
+ }
+ }
+
+ if (NULL != renderer_) {
+ uint8_t* decoded_data =
+ reinterpret_cast<uint8_t*>(decoded_sample->data());
+ uint32_t decoded_amt = decoded_sample->range_length();
+ decoded_data += decoded_sample->range_offset();
+
+ sp<IMemory> pcm_payload;
+ res = renderer_->allocateTimedBuffer(decoded_amt, &pcm_payload);
+ if (res != OK) {
+ ALOGE("Failed to allocate %d byte audio track buffer."
+ " (res = %d)", decoded_amt, res);
+ } else {
+ memcpy(pcm_payload->pointer(), decoded_data, decoded_amt);
+
+ res = renderer_->queueTimedBuffer(pcm_payload, ts);
+ if (res != OK) {
+ ALOGE("Failed to queue %d byte audio track buffer with media"
+ " PTS %lld. (res = %d)", decoded_amt, ts, res);
+ } else {
+ last_queued_pts_valid_ = true;
+ last_queued_pts_ = ts;
+ }
+ }
+
+ } else {
+ ALOGE("No renderer, dropping audio payload.");
+ }
+ }
+}
+
+void AAH_DecoderPump::stopAndCleanupRenderer() {
+ if (NULL == renderer_) {
+ return;
+ }
+
+ renderer_->stop();
+ delete renderer_;
+ renderer_ = NULL;
+}
+
+void AAH_DecoderPump::setRenderTSTransform(const LinearTransform& trans) {
+ Mutex::Autolock lock(&render_lock_);
+
+ if (last_ts_transform_valid_ && !memcmp(&trans,
+ &last_ts_transform_,
+ sizeof(trans))) {
+ return;
+ }
+
+ last_ts_transform_ = trans;
+ last_ts_transform_valid_ = true;
+
+ if (NULL != renderer_) {
+ status_t res = renderer_->setMediaTimeTransform(
+ last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+ if (res != NO_ERROR) {
+ ALOGE("Failed to set media time transform on AudioTrack"
+ " (res = %d)", res);
+ }
+ }
+}
+
+void AAH_DecoderPump::setRenderVolume(uint8_t volume) {
+ Mutex::Autolock lock(&render_lock_);
+
+ if (volume == last_volume_) {
+ return;
+ }
+
+ last_volume_ = volume;
+ if (renderer_ != NULL) {
+ float volume = static_cast<float>(last_volume_) / 255.0f;
+ if (renderer_->setVolume(volume, volume) != OK) {
+ ALOGW("%s: setVolume failed", __FUNCTION__);
+ }
+ }
+}
+
+// isAboutToUnderflow is something of a hack used to figure out when it might be
+// time to give up on trying to fill in a gap in the RTP sequence and simply
+// move on with a discontinuity. If we had perfect knowledge of when we were
+// going to underflow, it would not be a hack, but unfortunately we do not.
+// Right now, we just take the PTS of the last sample queued, and check to see
+// if its presentation time is within kAboutToUnderflowThreshold from now. If
+// it is, then we say that we are about to underflow. This decision is based on
+// two (possibly invalid) assumptions.
+//
+// 1) The transmitter is leading the clock by more than
+// kAboutToUnderflowThreshold.
+// 2) The delta between the PTS of the last sample queued and the next sample
+// is less than the transmitter's clock lead amount.
+//
+// Right now, the default transmitter lead time is 1 second, which is a pretty
+// large number and greater than the 50mSec that kAboutToUnderflowThreshold is
+// currently set to. This should satisfy assumption #1 for now, but changes to
+// the transmitter clock lead time could effect this.
+//
+// For non-sparse streams with a homogeneous sample rate (the vast majority of
+// streams in the world), the delta between any two adjacent PTSs will always be
+// the homogeneous sample period. It is very uncommon to see a sample period
+// greater than the 1 second clock lead we are currently using, and you
+// certainly will not see it in an MP3 file which should satisfy assumption #2.
+// Sparse audio streams (where no audio is transmitted for long periods of
+// silence) and extremely low framerate video stream (like an MPEG-2 slideshow
+// or the video stream for a pay TV audio channel) are examples of streams which
+// might violate assumption #2.
+bool AAH_DecoderPump::isAboutToUnderflow(int64_t threshold) {
+ Mutex::Autolock lock(&render_lock_);
+
+ // If we have never queued anything to the decoder, we really don't know if
+ // we are going to underflow or not.
+ if (!last_queued_pts_valid_ || !last_ts_transform_valid_) {
+ return false;
+ }
+
+ // Don't have access to Common Time? If so, then things are Very Bad
+ // elsewhere in the system; it pretty much does not matter what we do here.
+ // Since we cannot really tell if we are about to underflow or not, its
+ // probably best to assume that we are not and proceed accordingly.
+ int64_t tt_now;
+ if (OK != cc_helper_.getCommonTime(&tt_now)) {
+ return false;
+ }
+
+ // Transform from media time to common time.
+ int64_t last_queued_pts_tt;
+ if (!last_ts_transform_.doForwardTransform(last_queued_pts_,
+ &last_queued_pts_tt)) {
+ return false;
+ }
+
+ // Check to see if we are underflowing.
+ return ((tt_now + threshold - last_queued_pts_tt) > 0);
+}
+
+void* AAH_DecoderPump::workThread() {
+ // No need to lock when accessing decoder_ from the thread. The
+ // implementation of init and shutdown ensure that other threads never touch
+ // decoder_ while the work thread is running.
+ CHECK(decoder_ != NULL);
+ CHECK(format_ != NULL);
+
+ // Start the decoder and note its result code. If something goes horribly
+ // wrong, callers of queueForDecode and getOutput will be able to detect
+ // that the thread encountered a fatal error and shut down by examining
+ // thread_status_.
+ thread_status_ = decoder_->start(format_.get());
+ if (OK != thread_status_) {
+ ALOGE("AAH_DecoderPump's work thread failed to start decoder (res = %d)",
+ thread_status_);
+ return NULL;
+ }
+
+ DurationTimer decode_timer;
+ uint32_t consecutive_long_errors = 0;
+ uint32_t consecutive_errors = 0;
+
+ while (!thread_->exitPending()) {
+ status_t res;
+ MediaBuffer* bufOut = NULL;
+
+ decode_timer.start();
+ res = decoder_->read(&bufOut);
+ decode_timer.stop();
+
+ if (res == INFO_FORMAT_CHANGED) {
+ // Format has changed. Destroy our current renderer so that a new
+ // one can be created during queueToRenderer with the proper format.
+ //
+ // TODO : In order to transition seamlessly, we should change this
+ // to put the old renderer in a queue to play out completely before
+ // we destroy it. We can still create a new renderer, the timed
+ // nature of the renderer should ensure a seamless splice.
+ stopAndCleanupRenderer();
+ res = OK;
+ }
+
+ // Try to be a little nuanced in our handling of actual decode errors.
+ // Errors could happen because of minor stream corruption or because of
+ // transient resource limitations. In these cases, we would rather drop
+ // a little bit of output and ride out the unpleasantness then throw up
+ // our hands and abort everything.
+ //
+ // OTOH - When things are really bad (like we have a non-transient
+ // resource or bookkeeping issue, or the stream being fed to us is just
+ // complete and total garbage) we really want to terminate playback and
+ // raise an error condition all the way up to the application level so
+ // they can deal with it.
+ //
+ // Unfortunately, the error codes returned by the decoder can be a
+ // little non-specific. For example, if an OMXCodec times out
+ // attempting to obtain an output buffer, the error we get back is a
+ // generic -1. Try to distinguish between this resource timeout error
+ // and ES corruption error by timing how long the decode operation
+ // takes. Maintain accounting for both errors and "long errors". If we
+ // get more than a certain number consecutive errors of either type,
+ // consider it fatal and shutdown (which will cause the error to
+ // propagate all of the way up to the application level). The threshold
+ // for "long errors" is deliberately much lower than that of normal
+ // decode errors, both because of how long they take to happen and
+ // because they generally indicate resource limitation errors which are
+ // unlikely to go away in pathologically bad cases (in contrast to
+ // stream corruption errors which might happen 20 times in a row and
+ // then be suddenly OK again)
+ if (res != OK) {
+ consecutive_errors++;
+ if (decode_timer.durationUsecs() >= kLongDecodeErrorThreshold)
+ consecutive_long_errors++;
+
+ CHECK(NULL == bufOut);
+
+ ALOGW("%s: Failed to decode data (res = %d)",
+ __PRETTY_FUNCTION__, res);
+
+ if ((consecutive_errors >= kMaxErrorsBeforeFatal) ||
+ (consecutive_long_errors >= kMaxLongErrorsBeforeFatal)) {
+ ALOGE("%s: Maximum decode error threshold has been reached."
+ " There have been %d consecutive decode errors, and %d"
+ " consecutive decode operations which resulted in errors"
+ " and took more than %lld uSec to process. The last"
+ " decode operation took %lld uSec.",
+ __PRETTY_FUNCTION__,
+ consecutive_errors, consecutive_long_errors,
+ kLongDecodeErrorThreshold, decode_timer.durationUsecs());
+ thread_status_ = res;
+ break;
+ }
+
+ continue;
+ }
+
+ if (NULL == bufOut) {
+ ALOGW("%s: Successful decode, but no buffer produced",
+ __PRETTY_FUNCTION__);
+ continue;
+ }
+
+ // Successful decode (with actual output produced). Clear the error
+ // counters.
+ consecutive_errors = 0;
+ consecutive_long_errors = 0;
+
+ queueToRenderer(bufOut);
+ bufOut->release();
+ }
+
+ decoder_->stop();
+ stopAndCleanupRenderer();
+
+ return NULL;
+}
+
+status_t AAH_DecoderPump::init(const sp<MetaData>& params) {
+ Mutex::Autolock lock(&init_lock_);
+
+ if (decoder_ != NULL) {
+ // already inited
+ return OK;
+ }
+
+ if (params == NULL) {
+ return BAD_VALUE;
+ }
+
+ if (!params->findInt32(kKeyChannelCount, &format_channels_)) {
+ return BAD_VALUE;
+ }
+
+ if (!params->findInt32(kKeySampleRate, &format_sample_rate_)) {
+ return BAD_VALUE;
+ }
+
+ CHECK(OK == thread_status_);
+ CHECK(decoder_ == NULL);
+
+ status_t ret_val = UNKNOWN_ERROR;
+
+ // Cache the format and attempt to create the decoder.
+ format_ = params;
+ decoder_ = OMXCodec::Create(
+ omx_.interface(), // IOMX Handle
+ format_, // Metadata for substream (indicates codec)
+ false, // Make a decoder, not an encoder
+ sp<MediaSource>(this)); // We will be the source for this codec.
+
+ if (decoder_ == NULL) {
+ ALOGE("Failed to allocate decoder in %s", __PRETTY_FUNCTION__);
+ goto bailout;
+ }
+
+ // Fire up the pump thread. It will take care of starting and stopping the
+ // decoder.
+ ret_val = thread_->run("aah_decode_pump", ANDROID_PRIORITY_AUDIO);
+ if (OK != ret_val) {
+ ALOGE("Failed to start work thread in %s (res = %d)",
+ __PRETTY_FUNCTION__, ret_val);
+ goto bailout;
+ }
+
+bailout:
+ if (OK != ret_val) {
+ decoder_ = NULL;
+ format_ = NULL;
+ }
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::shutdown() {
+ Mutex::Autolock lock(&init_lock_);
+ return shutdown_l();
+}
+
+status_t AAH_DecoderPump::shutdown_l() {
+ thread_->requestExit();
+ thread_cond_.signal();
+ thread_->requestExitAndWait();
+
+ for (MBQueue::iterator iter = in_queue_.begin();
+ iter != in_queue_.end();
+ ++iter) {
+ (*iter)->release();
+ }
+ in_queue_.clear();
+
+ last_queued_pts_valid_ = false;
+ last_ts_transform_valid_ = false;
+ last_volume_ = 0xFF;
+ thread_status_ = OK;
+
+ decoder_ = NULL;
+ format_ = NULL;
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::read(MediaBuffer **buffer,
+ const ReadOptions *options) {
+ if (!buffer) {
+ return BAD_VALUE;
+ }
+
+ *buffer = NULL;
+
+ // While its not time to shut down, and we have no data to process, wait.
+ AutoMutex lock(&thread_lock_);
+ while (!thread_->exitPending() && in_queue_.empty())
+ thread_cond_.wait(thread_lock_);
+
+ // At this point, if its not time to shutdown then we must have something to
+ // process. Go ahead and pop the front of the queue for processing.
+ if (!thread_->exitPending()) {
+ CHECK(!in_queue_.empty());
+
+ *buffer = *(in_queue_.begin());
+ in_queue_.erase(in_queue_.begin());
+ }
+
+ // If we managed to get a buffer, then everything must be OK. If not, then
+ // we must be shutting down.
+ return (NULL == *buffer) ? INVALID_OPERATION : OK;
+}
+
+AAH_DecoderPump::ThreadWrapper::ThreadWrapper(AAH_DecoderPump* owner)
+ : Thread(false /* canCallJava*/ )
+ , owner_(owner) {
+}
+
+bool AAH_DecoderPump::ThreadWrapper::threadLoop() {
+ CHECK(NULL != owner_);
+ owner_->workThread();
+ return false;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_decoder_pump.h b/media/libaah_rtp/aah_decoder_pump.h
new file mode 100644
index 000000000000..f5a6529396be
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 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 __DECODER_PUMP_H__
+#define __DECODER_PUMP_H__
+
+#include <pthread.h>
+
+#include <common_time/cc_helper.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class MetaData;
+class OMXClient;
+class TimedAudioTrack;
+
+class AAH_DecoderPump : public MediaSource {
+ public:
+ explicit AAH_DecoderPump(OMXClient& omx);
+ status_t initCheck();
+
+ status_t queueForDecode(MediaBuffer* buf);
+
+ status_t init(const sp<MetaData>& params);
+ status_t shutdown();
+
+ void setRenderTSTransform(const LinearTransform& trans);
+ void setRenderVolume(uint8_t volume);
+ bool isAboutToUnderflow(int64_t threshold);
+ bool getStatus() const { return thread_status_; }
+
+ // MediaSource methods
+ virtual status_t start(MetaData *params) { return OK; }
+ virtual sp<MetaData> getFormat() { return format_; }
+ virtual status_t stop() { return OK; }
+ virtual status_t read(MediaBuffer **buffer,
+ const ReadOptions *options);
+
+ protected:
+ virtual ~AAH_DecoderPump();
+
+ private:
+ class ThreadWrapper : public Thread {
+ public:
+ friend class AAH_DecoderPump;
+ explicit ThreadWrapper(AAH_DecoderPump* owner);
+
+ private:
+ virtual bool threadLoop();
+ AAH_DecoderPump* owner_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+ };
+
+ void* workThread();
+ virtual status_t shutdown_l();
+ void queueToRenderer(MediaBuffer* decoded_sample);
+ void stopAndCleanupRenderer();
+
+ sp<MetaData> format_;
+ int32_t format_channels_;
+ int32_t format_sample_rate_;
+
+ sp<MediaSource> decoder_;
+ OMXClient& omx_;
+ Mutex init_lock_;
+
+ sp<ThreadWrapper> thread_;
+ Condition thread_cond_;
+ Mutex thread_lock_;
+ status_t thread_status_;
+
+ Mutex render_lock_;
+ TimedAudioTrack* renderer_;
+ bool last_queued_pts_valid_;
+ int64_t last_queued_pts_;
+ bool last_ts_transform_valid_;
+ LinearTransform last_ts_transform_;
+ uint8_t last_volume_;
+ CCHelper cc_helper_;
+
+ // protected by the thread_lock_
+ typedef List<MediaBuffer*> MBQueue;
+ MBQueue in_queue_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_DecoderPump);
+};
+
+} // namespace android
+#endif // __DECODER_PUMP_H__
diff --git a/media/libaah_rtp/aah_rx_player.cpp b/media/libaah_rtp/aah_rx_player.cpp
new file mode 100644
index 000000000000..9dd79fd2dd83
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <binder/IServiceManager.h>
+#include <media/MediaPlayerInterface.h>
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRTPRingBufferSize = 1 << 10;
+
+sp<MediaPlayerBase> createAAH_RXPlayer() {
+ sp<MediaPlayerBase> ret = new AAH_RXPlayer();
+ return ret;
+}
+
+AAH_RXPlayer::AAH_RXPlayer()
+ : ring_buffer_(kRTPRingBufferSize)
+ , substreams_(NULL) {
+ thread_wrapper_ = new ThreadWrapper(*this);
+
+ is_playing_ = false;
+ multicast_joined_ = false;
+ transmitter_known_ = false;
+ current_epoch_known_ = false;
+ data_source_set_ = false;
+ sock_fd_ = -1;
+
+ substreams_.setCapacity(4);
+
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+ memset(&transmitter_addr_, 0, sizeof(transmitter_addr_));
+
+ fetchAudioFlinger();
+}
+
+AAH_RXPlayer::~AAH_RXPlayer() {
+ reset_l();
+ CHECK(substreams_.size() == 0);
+ omx_.disconnect();
+}
+
+status_t AAH_RXPlayer::initCheck() {
+ if (thread_wrapper_ == NULL) {
+ ALOGE("Failed to allocate thread wrapper!");
+ return NO_MEMORY;
+ }
+
+ if (!ring_buffer_.initCheck()) {
+ ALOGE("Failed to allocate reassembly ring buffer!");
+ return NO_MEMORY;
+ }
+
+ // Check for the presense of the common time service by attempting to query
+ // for CommonTime's frequency. If we get an error back, we cannot talk to
+ // the service at all and should abort now.
+ status_t res;
+ uint64_t freq;
+ res = cc_helper_.getCommonFreq(&freq);
+ if (OK != res) {
+ ALOGE("Failed to connect to common time service!");
+ return res;
+ }
+
+ return omx_.connect();
+}
+
+status_t AAH_RXPlayer::setDataSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ AutoMutex api_lock(&api_lock_);
+ uint32_t a, b, c, d;
+ uint16_t port;
+
+ if (data_source_set_) {
+ return INVALID_OPERATION;
+ }
+
+ if (NULL == url) {
+ return BAD_VALUE;
+ }
+
+ if (5 != sscanf(url, "%*[^:/]://%u.%u.%u.%u:%hu", &a, &b, &c, &d, &port)) {
+ ALOGE("Failed to parse URL \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ if ((a > 255) || (b > 255) || (c > 255) || (d > 255) || (port == 0)) {
+ ALOGE("Bad multicast address \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ ALOGI("setDataSource :: %u.%u.%u.%u:%hu", a, b, c, d, port);
+
+ a = (a << 24) | (b << 16) | (c << 8) | d;
+
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+ listen_addr_.sin_family = AF_INET;
+ listen_addr_.sin_port = htons(port);
+ listen_addr_.sin_addr.s_addr = htonl(a);
+ data_source_set_ = true;
+
+ return OK;
+}
+
+status_t AAH_RXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_RXPlayer::setVideoSurface(const sp<Surface>& surface) {
+ return OK;
+}
+
+status_t AAH_RXPlayer::setVideoSurfaceTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ return OK;
+}
+
+status_t AAH_RXPlayer::prepare() {
+ return OK;
+}
+
+status_t AAH_RXPlayer::prepareAsync() {
+ sendEvent(MEDIA_PREPARED);
+ return OK;
+}
+
+status_t AAH_RXPlayer::start() {
+ AutoMutex api_lock(&api_lock_);
+
+ if (is_playing_) {
+ return OK;
+ }
+
+ status_t res = startWorkThread();
+ is_playing_ = (res == OK);
+ return res;
+}
+
+status_t AAH_RXPlayer::stop() {
+ return pause();
+}
+
+status_t AAH_RXPlayer::pause() {
+ AutoMutex api_lock(&api_lock_);
+ stopWorkThread();
+ CHECK(sock_fd_ < 0);
+ is_playing_ = false;
+ return OK;
+}
+
+bool AAH_RXPlayer::isPlaying() {
+ AutoMutex api_lock(&api_lock_);
+ return is_playing_;
+}
+
+status_t AAH_RXPlayer::seekTo(int msec) {
+ sendEvent(MEDIA_SEEK_COMPLETE);
+ return OK;
+}
+
+status_t AAH_RXPlayer::getCurrentPosition(int *msec) {
+ if (NULL != msec) {
+ *msec = 0;
+ }
+ return OK;
+}
+
+status_t AAH_RXPlayer::getDuration(int *msec) {
+ if (NULL != msec) {
+ *msec = 1;
+ }
+ return OK;
+}
+
+status_t AAH_RXPlayer::reset() {
+ AutoMutex api_lock(&api_lock_);
+ reset_l();
+ return OK;
+}
+
+void AAH_RXPlayer::reset_l() {
+ stopWorkThread();
+ CHECK(sock_fd_ < 0);
+ CHECK(!multicast_joined_);
+ is_playing_ = false;
+ data_source_set_ = false;
+ transmitter_known_ = false;
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+}
+
+status_t AAH_RXPlayer::setLooping(int loop) {
+ return OK;
+}
+
+player_type AAH_RXPlayer::playerType() {
+ return AAH_RX_PLAYER;
+}
+
+status_t AAH_RXPlayer::setParameter(int key, const Parcel &request) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::getParameter(int key, Parcel *reply) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::invoke(const Parcel& request, Parcel *reply) {
+ if (!reply) {
+ return BAD_VALUE;
+ }
+
+ int32_t magic;
+ status_t err = request.readInt32(&magic);
+ if (err != OK) {
+ reply->writeInt32(err);
+ return OK;
+ }
+
+ if (magic != 0x12345) {
+ reply->writeInt32(BAD_VALUE);
+ return OK;
+ }
+
+ int32_t methodID;
+ err = request.readInt32(&methodID);
+ if (err != OK) {
+ reply->writeInt32(err);
+ return OK;
+ }
+
+ switch (methodID) {
+ // Get Volume
+ case INVOKE_GET_MASTER_VOLUME: {
+ if (audio_flinger_ != NULL) {
+ reply->writeInt32(OK);
+ reply->writeFloat(audio_flinger_->masterVolume());
+ } else {
+ reply->writeInt32(UNKNOWN_ERROR);
+ }
+ } break;
+
+ // Set Volume
+ case INVOKE_SET_MASTER_VOLUME: {
+ float targetVol = request.readFloat();
+ reply->writeInt32(audio_flinger_->setMasterVolume(targetVol));
+ } break;
+
+ default: return BAD_VALUE;
+ }
+
+ return OK;
+}
+
+void AAH_RXPlayer::fetchAudioFlinger() {
+ if (audio_flinger_ == NULL) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder;
+ binder = sm->getService(String16("media.audio_flinger"));
+
+ if (binder == NULL) {
+ ALOGW("AAH_RXPlayer failed to fetch handle to audio flinger."
+ " Master volume control will not be possible.");
+ }
+
+ audio_flinger_ = interface_cast<IAudioFlinger>(binder);
+ }
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player.h b/media/libaah_rtp/aah_rx_player.h
new file mode 100644
index 000000000000..7a1b6e3a7efa
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.h
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2011 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 __AAH_RX_PLAYER_H__
+#define __AAH_RX_PLAYER_H__
+
+#include <common_time/cc_helper.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <netinet/in.h>
+#include <utils/KeyedVector.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+#include "pipe_event.h"
+
+namespace android {
+
+class AAH_RXPlayer : public MediaPlayerInterface {
+ public:
+ AAH_RXPlayer();
+
+ virtual status_t initCheck();
+ virtual status_t setDataSource(const char *url,
+ const KeyedVector<String8, String8>*
+ headers);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setVideoSurface(const sp<Surface>& surface);
+ virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+ surfaceTexture);
+ virtual status_t prepare();
+ virtual status_t prepareAsync();
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t pause();
+ virtual bool isPlaying();
+ virtual status_t seekTo(int msec);
+ virtual status_t getCurrentPosition(int *msec);
+ virtual status_t getDuration(int *msec);
+ virtual status_t reset();
+ virtual status_t setLooping(int loop);
+ virtual player_type playerType();
+ virtual status_t setParameter(int key, const Parcel &request);
+ virtual status_t getParameter(int key, Parcel *reply);
+ virtual status_t invoke(const Parcel& request, Parcel *reply);
+
+ protected:
+ virtual ~AAH_RXPlayer();
+
+ private:
+ class ThreadWrapper : public Thread {
+ public:
+ friend class AAH_RXPlayer;
+ explicit ThreadWrapper(AAH_RXPlayer& player)
+ : Thread(false /* canCallJava */ )
+ , player_(player) { }
+
+ virtual bool threadLoop() { return player_.threadLoop(); }
+
+ private:
+ AAH_RXPlayer& player_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+ };
+
+#pragma pack(push, 1)
+ // PacketBuffers are structures used by the RX ring buffer. The ring buffer
+ // is a ring of pointers to PacketBuffer structures which act as variable
+ // length byte arrays and hold the contents of received UDP packets. Rather
+ // than make this a structure which hold a length and a pointer to another
+ // allocated structure (which would require two allocations), this struct
+ // uses a structure overlay pattern where allocation for the byte array
+ // consists of allocating (arrayLen + sizeof(ssize_t)) bytes of data from
+ // whatever pool/heap the packet buffer pulls from, and then overlaying the
+ // packed PacketBuffer structure on top of the allocation. The one-byte
+ // array at the end of the structure serves as an offset to the the data
+ // portion of the allocation; packet buffers are never allocated on the
+ // stack or using the new operator. Instead, the static allocate-byte-array
+ // and destroy methods handle the allocate and overlay pattern. They also
+ // allow for a potential future optimization where instead of just
+ // allocating blocks from the process global heap and overlaying, the
+ // allocator is replaced with a different implementation (private heap,
+ // free-list, circular buffer, etc) which reduces potential heap
+ // fragmentation issues which might arise from the frequent allocation and
+ // destruction of the received UDP traffic.
+ struct PacketBuffer {
+ ssize_t length_;
+ uint8_t data_[1];
+
+ // TODO : consider changing this to be some form of ring buffer or free
+ // pool system instead of just using the heap in order to avoid heap
+ // fragmentation.
+ static PacketBuffer* allocate(ssize_t length);
+ static void destroy(PacketBuffer* pb);
+
+ private:
+ // Force people to use allocate/destroy instead of new/delete.
+ PacketBuffer() { }
+ ~PacketBuffer() { }
+ };
+
+ struct RetransRequest {
+ uint32_t magic_;
+ uint32_t mcast_ip_;
+ uint16_t mcast_port_;
+ uint16_t start_seq_;
+ uint16_t end_seq_;
+ };
+#pragma pack(pop)
+
+ enum GapStatus {
+ kGS_NoGap = 0,
+ kGS_NormalGap,
+ kGS_FastStartGap,
+ };
+
+ struct SeqNoGap {
+ uint16_t start_seq_;
+ uint16_t end_seq_;
+ };
+
+ class RXRingBuffer {
+ public:
+ explicit RXRingBuffer(uint32_t capacity);
+ ~RXRingBuffer();
+
+ bool initCheck() const { return (ring_ != NULL); }
+ void reset();
+
+ // Push a packet buffer with a given sequence number into the ring
+ // buffer. pushBuffer will always consume the buffer pushed to it,
+ // either destroying it because it was a duplicate or overflow, or
+ // holding on to it in the ring. Callers should not hold any references
+ // to PacketBuffers after they have been pushed to the ring. Returns
+ // false in the case of a serious error (such as ring overflow).
+ // Callers should consider resetting the pipeline entirely in the event
+ // of a serious error.
+ bool pushBuffer(PacketBuffer* buf, uint16_t seq);
+
+ // Fetch the next buffer in the RTP sequence. Returns NULL if there is
+ // no buffer to fetch. If a non-NULL PacketBuffer is returned,
+ // is_discon will be set to indicate whether or not this PacketBuffer is
+ // discontiuous with any previously returned packet buffers. Packet
+ // buffers returned by fetchBuffer are the caller's responsibility; they
+ // must be certain to destroy the buffers when they are done.
+ PacketBuffer* fetchBuffer(bool* is_discon);
+
+ // Returns true and fills out the gap structure if the read pointer of
+ // the ring buffer is currently pointing to a gap which would stall a
+ // fetchBuffer operation. Returns false if the read pointer is not
+ // pointing to a gap in the sequence currently.
+ GapStatus fetchCurrentGap(SeqNoGap* gap);
+
+ // Causes the read pointer to skip over any portion of a gap indicated
+ // by nak. If nak is NULL, any gap currently blocking the read pointer
+ // will be completely skipped. If any portion of a gap is skipped, the
+ // next successful read from fetch buffer will indicate a discontinuity.
+ void processNAK(const SeqNoGap* nak = NULL);
+
+ // Compute the number of milliseconds until the inactivity timer for
+ // this RTP stream. Returns -1 if there is no active timeout, or 0 if
+ // the system has already timed out.
+ int computeInactivityTimeout();
+
+ private:
+ Mutex lock_;
+ PacketBuffer** ring_;
+ uint32_t capacity_;
+ uint32_t rd_;
+ uint32_t wr_;
+
+ uint16_t rd_seq_;
+ bool rd_seq_known_;
+ bool waiting_for_fast_start_;
+ bool fetched_first_packet_;
+
+ uint64_t rtp_activity_timeout_;
+ bool rtp_activity_timeout_valid_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(RXRingBuffer);
+ };
+
+ class Substream : public virtual RefBase {
+ public:
+ Substream(uint32_t ssrc, OMXClient& omx);
+
+ void cleanupBufferInProgress();
+ void shutdown();
+ void processPayloadStart(uint8_t* buf,
+ uint32_t amt,
+ int32_t ts_lower);
+ void processPayloadCont (uint8_t* buf,
+ uint32_t amt);
+ void processTSTransform(const LinearTransform& trans);
+
+ bool isAboutToUnderflow();
+ uint32_t getSSRC() const { return ssrc_; }
+ uint16_t getProgramID() const { return (ssrc_ >> 5) & 0x1F; }
+ status_t getStatus() const { return status_; }
+
+ protected:
+ virtual ~Substream() {
+ shutdown();
+ }
+
+ private:
+ void cleanupDecoder();
+ bool shouldAbort(const char* log_tag);
+ void processCompletedBuffer();
+ bool setupSubstreamType(uint8_t substream_type,
+ uint8_t codec_type);
+
+ uint32_t ssrc_;
+ bool waiting_for_rap_;
+ status_t status_;
+
+ bool substream_details_known_;
+ uint8_t substream_type_;
+ uint8_t codec_type_;
+ sp<MetaData> substream_meta_;
+
+ MediaBuffer* buffer_in_progress_;
+ uint32_t expected_buffer_size_;
+ uint32_t buffer_filled_;
+
+ sp<AAH_DecoderPump> decoder_;
+
+ static int64_t kAboutToUnderflowThreshold;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Substream);
+ };
+
+ typedef DefaultKeyedVector< uint32_t, sp<Substream> > SubstreamVec;
+
+ status_t startWorkThread();
+ void stopWorkThread();
+ virtual bool threadLoop();
+ bool setupSocket();
+ void cleanupSocket();
+ void resetPipeline();
+ void reset_l();
+ bool processRX(PacketBuffer* pb);
+ void processRingBuffer();
+ void processCommandPacket(PacketBuffer* pb);
+ bool processGaps();
+ int computeNextGapRetransmitTimeout();
+ void fetchAudioFlinger();
+
+ PipeEvent wakeup_work_thread_evt_;
+ sp<ThreadWrapper> thread_wrapper_;
+ Mutex api_lock_;
+ bool is_playing_;
+ bool data_source_set_;
+
+ struct sockaddr_in listen_addr_;
+ int sock_fd_;
+ bool multicast_joined_;
+
+ struct sockaddr_in transmitter_addr_;
+ bool transmitter_known_;
+
+ uint32_t current_epoch_;
+ bool current_epoch_known_;
+
+ SeqNoGap current_gap_;
+ GapStatus current_gap_status_;
+ uint64_t next_retrans_req_time_;
+
+ RXRingBuffer ring_buffer_;
+ SubstreamVec substreams_;
+ OMXClient omx_;
+ CCHelper cc_helper_;
+
+ // Connection to audio flinger used to hack a path to setMasterVolume.
+ sp<IAudioFlinger> audio_flinger_;
+
+ static const uint32_t kRTPRingBufferSize;
+ static const uint32_t kRetransRequestMagic;
+ static const uint32_t kFastStartRequestMagic;
+ static const uint32_t kRetransNAKMagic;
+ static const uint32_t kGapRerequestTimeoutUSec;
+ static const uint32_t kFastStartTimeoutUSec;
+ static const uint32_t kRTPActivityTimeoutUSec;
+
+ static const uint32_t INVOKE_GET_MASTER_VOLUME = 3;
+ static const uint32_t INVOKE_SET_MASTER_VOLUME = 4;
+
+ static uint64_t monotonicUSecNow();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_RXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_RX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_rx_player_core.cpp b/media/libaah_rtp/aah_rx_player_core.cpp
new file mode 100644
index 000000000000..d2b33866cdeb
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_core.cpp
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <utils/misc.h>
+
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRetransRequestMagic =
+ FOURCC('T','r','e','q');
+const uint32_t AAH_RXPlayer::kRetransNAKMagic =
+ FOURCC('T','n','a','k');
+const uint32_t AAH_RXPlayer::kFastStartRequestMagic =
+ FOURCC('T','f','s','t');
+const uint32_t AAH_RXPlayer::kGapRerequestTimeoutUSec = 75000;
+const uint32_t AAH_RXPlayer::kFastStartTimeoutUSec = 800000;
+const uint32_t AAH_RXPlayer::kRTPActivityTimeoutUSec = 10000000;
+
+static inline int16_t fetchInt16(uint8_t* data) {
+ return static_cast<int16_t>(U16_AT(data));
+}
+
+static inline int32_t fetchInt32(uint8_t* data) {
+ return static_cast<int32_t>(U32_AT(data));
+}
+
+static inline int64_t fetchInt64(uint8_t* data) {
+ return static_cast<int64_t>(U64_AT(data));
+}
+
+uint64_t AAH_RXPlayer::monotonicUSecNow() {
+ struct timespec now;
+ int res = clock_gettime(CLOCK_MONOTONIC, &now);
+ CHECK(res >= 0);
+
+ uint64_t ret = static_cast<uint64_t>(now.tv_sec) * 1000000;
+ ret += now.tv_nsec / 1000;
+
+ return ret;
+}
+
+status_t AAH_RXPlayer::startWorkThread() {
+ status_t res;
+ stopWorkThread();
+ res = thread_wrapper_->run("TRX_Player", PRIORITY_AUDIO);
+
+ if (res != OK) {
+ ALOGE("Failed to start work thread (res = %d)", res);
+ }
+
+ return res;
+}
+
+void AAH_RXPlayer::stopWorkThread() {
+ thread_wrapper_->requestExit(); // set the exit pending flag
+ wakeup_work_thread_evt_.setEvent();
+
+ status_t res;
+ res = thread_wrapper_->requestExitAndWait(); // block until thread exit.
+ if (res != OK) {
+ ALOGE("Failed to stop work thread (res = %d)", res);
+ }
+
+ wakeup_work_thread_evt_.clearPendingEvents();
+}
+
+void AAH_RXPlayer::cleanupSocket() {
+ if (sock_fd_ >= 0) {
+ if (multicast_joined_) {
+ int res;
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = listen_addr_.sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ res = setsockopt(sock_fd_,
+ IPPROTO_IP,
+ IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (res < 0) {
+ ALOGW("Failed to leave multicast group. (%d, %d)", res, errno);
+ }
+ multicast_joined_ = false;
+ }
+
+ close(sock_fd_);
+ sock_fd_ = -1;
+ }
+
+ resetPipeline();
+}
+
+void AAH_RXPlayer::resetPipeline() {
+ ring_buffer_.reset();
+
+ // Explicitly shudown all of the active substreams, then call clear out the
+ // collection. Failure to clear out a substream can result in its decoder
+ // holding a reference to itself and therefor not going away when the
+ // collection is cleared.
+ for (size_t i = 0; i < substreams_.size(); ++i)
+ substreams_.valueAt(i)->shutdown();
+
+ substreams_.clear();
+
+ current_gap_status_ = kGS_NoGap;
+}
+
+bool AAH_RXPlayer::setupSocket() {
+ long flags;
+ int res, buf_size;
+ socklen_t opt_size;
+
+ cleanupSocket();
+ CHECK(sock_fd_ < 0);
+
+ // Make the socket
+ sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock_fd_ < 0) {
+ ALOGE("Failed to create listen socket (errno %d)", errno);
+ goto bailout;
+ }
+
+ // Set non-blocking operation
+ flags = fcntl(sock_fd_, F_GETFL);
+ res = fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK);
+ if (res < 0) {
+ ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+ sock_fd_, errno);
+ goto bailout;
+ }
+
+ // Bind to our port
+ struct sockaddr_in bind_addr;
+ memset(&bind_addr, 0, sizeof(bind_addr));
+ bind_addr.sin_family = AF_INET;
+ bind_addr.sin_addr.s_addr = INADDR_ANY;
+ bind_addr.sin_port = listen_addr_.sin_port;
+ res = bind(sock_fd_,
+ reinterpret_cast<const sockaddr*>(&bind_addr),
+ sizeof(bind_addr));
+ if (res < 0) {
+ uint32_t a = ntohl(bind_addr.sin_addr.s_addr);
+ uint16_t p = ntohs(bind_addr.sin_port);
+ ALOGE("Failed to bind socket (%d) to %d.%d.%d.%d:%hd. (errno %d)",
+ sock_fd_,
+ (a >> 24) & 0xFF,
+ (a >> 16) & 0xFF,
+ (a >> 8) & 0xFF,
+ (a ) & 0xFF,
+ p,
+ errno);
+
+ goto bailout;
+ }
+
+ buf_size = 1 << 16; // 64k
+ res = setsockopt(sock_fd_,
+ SOL_SOCKET, SO_RCVBUF,
+ &buf_size, sizeof(buf_size));
+ if (res < 0) {
+ ALOGW("Failed to increase socket buffer size to %d. (errno %d)",
+ buf_size, errno);
+ }
+
+ buf_size = 0;
+ opt_size = sizeof(buf_size);
+ res = getsockopt(sock_fd_,
+ SOL_SOCKET, SO_RCVBUF,
+ &buf_size, &opt_size);
+ if (res < 0) {
+ ALOGW("Failed to fetch socket buffer size. (errno %d)", errno);
+ } else {
+ ALOGI("RX socket buffer size is now %d bytes", buf_size);
+ }
+
+ if (listen_addr_.sin_addr.s_addr) {
+ // Join the multicast group and we should be good to go.
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = listen_addr_.sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ res = setsockopt(sock_fd_,
+ IPPROTO_IP,
+ IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (res < 0) {
+ ALOGE("Failed to join multicast group. (errno %d)", errno);
+ goto bailout;
+ }
+ multicast_joined_ = true;
+ }
+
+ return true;
+
+bailout:
+ cleanupSocket();
+ return false;
+}
+
+bool AAH_RXPlayer::threadLoop() {
+ struct pollfd poll_fds[2];
+ bool process_more_right_now = false;
+
+ if (!setupSocket()) {
+ sendEvent(MEDIA_ERROR);
+ goto bailout;
+ }
+
+ while (!thread_wrapper_->exitPending()) {
+ // Step 1: Wait until there is something to do.
+ int gap_timeout = computeNextGapRetransmitTimeout();
+ int ring_timeout = ring_buffer_.computeInactivityTimeout();
+ int timeout = -1;
+
+ if (!ring_timeout) {
+ ALOGW("RTP inactivity timeout reached, resetting pipeline.");
+ resetPipeline();
+ timeout = gap_timeout;
+ } else {
+ if (gap_timeout < 0) {
+ timeout = ring_timeout;
+ } else if (ring_timeout < 0) {
+ timeout = gap_timeout;
+ } else {
+ timeout = (gap_timeout < ring_timeout) ? gap_timeout
+ : ring_timeout;
+ }
+ }
+
+ if ((0 != timeout) && (!process_more_right_now)) {
+ // Set up the events to wait on. Start with the wakeup pipe.
+ memset(&poll_fds, 0, sizeof(poll_fds));
+ poll_fds[0].fd = wakeup_work_thread_evt_.getWakeupHandle();
+ poll_fds[0].events = POLLIN;
+
+ // Add the RX socket.
+ poll_fds[1].fd = sock_fd_;
+ poll_fds[1].events = POLLIN;
+
+ // Wait for something interesing to happen.
+ int poll_res = poll(poll_fds, NELEM(poll_fds), timeout);
+ if (poll_res < 0) {
+ ALOGE("Fatal error (%d,%d) while waiting on events",
+ poll_res, errno);
+ sendEvent(MEDIA_ERROR);
+ goto bailout;
+ }
+ }
+
+ if (thread_wrapper_->exitPending()) {
+ break;
+ }
+
+ wakeup_work_thread_evt_.clearPendingEvents();
+ process_more_right_now = false;
+
+ // Step 2: Do we have data waiting in the socket? If so, drain the
+ // socket moving valid RTP information into the ring buffer to be
+ // processed.
+ if (poll_fds[1].revents) {
+ struct sockaddr_in from;
+ socklen_t from_len;
+
+ ssize_t res = 0;
+ while (!thread_wrapper_->exitPending()) {
+ // Check the size of any pending packet.
+ res = recv(sock_fd_, NULL, 0, MSG_PEEK | MSG_TRUNC);
+
+ // Error?
+ if (res < 0) {
+ // If the error is anything other than would block,
+ // something has gone very wrong.
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) {
+ ALOGE("Fatal socket error during recvfrom (%d, %d)",
+ (int)res, errno);
+ goto bailout;
+ }
+
+ // Socket is out of data, just break out of processing and
+ // wait for more.
+ break;
+ }
+
+ // Allocate a payload.
+ PacketBuffer* pb = PacketBuffer::allocate(res);
+ if (NULL == pb) {
+ ALOGE("Fatal error, failed to allocate packet buffer of"
+ " length %u", static_cast<uint32_t>(res));
+ goto bailout;
+ }
+
+ // Fetch the data.
+ from_len = sizeof(from);
+ res = recvfrom(sock_fd_, pb->data_, pb->length_, 0,
+ reinterpret_cast<struct sockaddr*>(&from),
+ &from_len);
+ if (res != pb->length_) {
+ ALOGE("Fatal error, fetched packet length (%d) does not"
+ " match peeked packet length (%u). This should never"
+ " happen. (errno = %d)",
+ static_cast<int>(res),
+ static_cast<uint32_t>(pb->length_),
+ errno);
+ }
+
+ bool drop_packet = false;
+ if (transmitter_known_) {
+ if (from.sin_addr.s_addr !=
+ transmitter_addr_.sin_addr.s_addr) {
+ uint32_t a = ntohl(from.sin_addr.s_addr);
+ uint16_t p = ntohs(from.sin_port);
+ ALOGV("Dropping packet from unknown transmitter"
+ " %u.%u.%u.%u:%hu",
+ ((a >> 24) & 0xFF),
+ ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF),
+ ( a & 0xFF),
+ p);
+
+ drop_packet = true;
+ } else {
+ transmitter_addr_.sin_port = from.sin_port;
+ }
+ } else {
+ memcpy(&transmitter_addr_, &from, sizeof(from));
+ transmitter_known_ = true;
+ }
+
+ if (!drop_packet) {
+ bool serious_error = !processRX(pb);
+
+ if (serious_error) {
+ // Something went "seriously wrong". Currently, the
+ // only trigger for this should be a ring buffer
+ // overflow. The current failsafe behavior for when
+ // something goes seriously wrong is to just reset the
+ // pipeline. The system should behave as if this
+ // AAH_RXPlayer was just set up for the first time.
+ ALOGE("Something just went seriously wrong with the"
+ " pipeline. Resetting.");
+ resetPipeline();
+ }
+ } else {
+ PacketBuffer::destroy(pb);
+ }
+ }
+ }
+
+ // Step 3: Process any data we mave have accumulated in the ring buffer
+ // so far.
+ if (!thread_wrapper_->exitPending()) {
+ processRingBuffer();
+ }
+
+ // Step 4: At this point in time, the ring buffer should either be
+ // empty, or stalled in front of a gap caused by some dropped packets.
+ // Check on the current gap situation and deal with it in an appropriate
+ // fashion. If processGaps returns true, it means that it has given up
+ // on a gap and that we should try to process some more data
+ // immediately.
+ if (!thread_wrapper_->exitPending()) {
+ process_more_right_now = processGaps();
+ }
+
+ // Step 5: Check for fatal errors. If any of our substreams has
+ // encountered a fatal, unrecoverable, error, then propagate the error
+ // up to user level and shut down.
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ status_t status;
+ CHECK(substreams_.valueAt(i) != NULL);
+
+ status = substreams_.valueAt(i)->getStatus();
+ if (OK != status) {
+ ALOGE("Substream index %d has encountered an unrecoverable"
+ " error (%d). Signalling application level and shutting"
+ " down.", i, status);
+ sendEvent(MEDIA_ERROR);
+ goto bailout;
+ }
+ }
+ }
+
+bailout:
+ cleanupSocket();
+ return false;
+}
+
+bool AAH_RXPlayer::processRX(PacketBuffer* pb) {
+ CHECK(NULL != pb);
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+ uint32_t nak_magic;
+ uint16_t seq_no;
+ uint32_t epoch;
+
+ // Every packet either starts with an RTP header which is at least 12 bytes
+ // long or is a retry NAK which is 14 bytes long. If there are fewer than
+ // 12 bytes here, this cannot be a proper RTP packet.
+ if (amt < 12) {
+ ALOGV("Dropping packet, too short to contain RTP header (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto drop_packet;
+ }
+
+ // Check to see if this is the special case of a NAK packet.
+ nak_magic = ntohl(*(reinterpret_cast<uint32_t*>(data)));
+ if (nak_magic == kRetransNAKMagic) {
+ // Looks like a NAK packet; make sure its long enough.
+
+ if (amt < static_cast<ssize_t>(sizeof(RetransRequest))) {
+ ALOGV("Dropping packet, too short to contain NAK payload (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto drop_packet;
+ }
+
+ SeqNoGap gap;
+ RetransRequest* rtr = reinterpret_cast<RetransRequest*>(data);
+ gap.start_seq_ = ntohs(rtr->start_seq_);
+ gap.end_seq_ = ntohs(rtr->end_seq_);
+
+ ALOGV("Process NAK for gap at [%hu, %hu]", gap.start_seq_, gap.end_seq_);
+ ring_buffer_.processNAK(&gap);
+
+ return true;
+ }
+
+ // According to the TRTP spec, version should be 2, padding should be 0,
+ // extension should be 0 and CSRCCnt should be 0. If any of these tests
+ // fail, we chuck the packet.
+ if (data[0] != 0x80) {
+ ALOGV("Dropping packet, bad V/P/X/CSRCCnt field (0x%02x)",
+ data[0]);
+ goto drop_packet;
+ }
+
+ // Check the payload type. For TRTP, it should always be 100.
+ if ((data[1] & 0x7F) != 100) {
+ ALOGV("Dropping packet, bad payload type. (%u)",
+ data[1] & 0x7F);
+ goto drop_packet;
+ }
+
+ // Check whether the transmitter has begun a new epoch.
+ epoch = (U32_AT(data + 8) >> 10) & 0x3FFFFF;
+ if (current_epoch_known_) {
+ if (epoch != current_epoch_) {
+ ALOGV("%s: new epoch %u", __PRETTY_FUNCTION__, epoch);
+ current_epoch_ = epoch;
+ resetPipeline();
+ }
+ } else {
+ current_epoch_ = epoch;
+ current_epoch_known_ = true;
+ }
+
+ // Extract the sequence number and hand the packet off to the ring buffer
+ // for dropped packet detection and later processing.
+ seq_no = U16_AT(data + 2);
+ return ring_buffer_.pushBuffer(pb, seq_no);
+
+drop_packet:
+ PacketBuffer::destroy(pb);
+ return true;
+}
+
+void AAH_RXPlayer::processRingBuffer() {
+ PacketBuffer* pb;
+ bool is_discon;
+ sp<Substream> substream;
+ LinearTransform trans;
+ bool foundTrans = false;
+
+ while (NULL != (pb = ring_buffer_.fetchBuffer(&is_discon))) {
+ if (is_discon) {
+ // Abort all partially assembled payloads.
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ CHECK(substreams_.valueAt(i) != NULL);
+ substreams_.valueAt(i)->cleanupBufferInProgress();
+ }
+ }
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+
+ // Should not have any non-RTP packets in the ring buffer. RTP packets
+ // must be at least 12 bytes long.
+ CHECK(amt >= 12);
+
+ // Extract the marker bit and the SSRC field.
+ bool marker = (data[1] & 0x80) != 0;
+ uint32_t ssrc = U32_AT(data + 8);
+
+ // Is this the start of a new TRTP payload? If so, the marker bit
+ // should be set and there are some things we should be checking for.
+ if (marker) {
+ // TRTP headers need to have at least a byte for version, a byte for
+ // payload type and flags, and 4 bytes for length.
+ if (amt < 18) {
+ ALOGV("Dropping packet, too short to contain TRTP header"
+ " (%u bytes)", static_cast<uint32_t>(amt));
+ goto process_next_packet;
+ }
+
+ // Check the TRTP version and extract the payload type/flags.
+ uint8_t trtp_version = data[12];
+ uint8_t payload_type = (data[13] >> 4) & 0xF;
+ uint8_t trtp_flags = data[13] & 0xF;
+
+ if (1 != trtp_version) {
+ ALOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+ goto process_next_packet;
+ }
+
+ // Is there a timestamp transformation present on this packet? If
+ // so, extract it and pass it to the appropriate substreams.
+ if (trtp_flags & 0x02) {
+ ssize_t offset = 18 + ((trtp_flags & 0x01) ? 4 : 0);
+ if (amt < (offset + 24)) {
+ ALOGV("Dropping packet, too short to contain TRTP Timestamp"
+ " Transformation (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto process_next_packet;
+ }
+
+ trans.a_zero = fetchInt64(data + offset);
+ trans.b_zero = fetchInt64(data + offset + 16);
+ trans.a_to_b_numer = static_cast<int32_t>(
+ fetchInt32 (data + offset + 8));
+ trans.a_to_b_denom = U32_AT(data + offset + 12);
+ foundTrans = true;
+
+ uint32_t program_id = (ssrc >> 5) & 0x1F;
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ sp<Substream> iter = substreams_.valueAt(i);
+ CHECK(iter != NULL);
+
+ if (iter->getProgramID() == program_id) {
+ iter->processTSTransform(trans);
+ }
+ }
+ }
+
+ // Is this a command packet? If so, its not necessarily associate
+ // with one particular substream. Just give it to the command
+ // packet handler and then move on.
+ if (4 == payload_type) {
+ processCommandPacket(pb);
+ goto process_next_packet;
+ }
+ }
+
+ // If we got to here, then we are a normal packet. Find (or allocate)
+ // the substream we belong to and send the packet off to be processed.
+ substream = substreams_.valueFor(ssrc);
+ if (substream == NULL) {
+ substream = new Substream(ssrc, omx_);
+ if (substream == NULL) {
+ ALOGE("Failed to allocate substream for SSRC 0x%08x", ssrc);
+ goto process_next_packet;
+ }
+ substreams_.add(ssrc, substream);
+
+ if (foundTrans) {
+ substream->processTSTransform(trans);
+ }
+ }
+
+ CHECK(substream != NULL);
+
+ if (marker) {
+ // Start of a new TRTP payload for this substream. Extract the
+ // lower 32 bits of the timestamp and hand the buffer to the
+ // substream for processing.
+ uint32_t ts_lower = U32_AT(data + 4);
+ substream->processPayloadStart(data + 12, amt - 12, ts_lower);
+ } else {
+ // Continuation of an existing TRTP payload. Just hand it off to
+ // the substream for processing.
+ substream->processPayloadCont(data + 12, amt - 12);
+ }
+
+process_next_packet:
+ PacketBuffer::destroy(pb);
+ } // end of main processing while loop.
+}
+
+void AAH_RXPlayer::processCommandPacket(PacketBuffer* pb) {
+ CHECK(NULL != pb);
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+
+ // verify that this packet meets the minimum length of a command packet
+ if (amt < 20) {
+ return;
+ }
+
+ uint8_t trtp_version = data[12];
+ uint8_t trtp_flags = data[13] & 0xF;
+
+ if (1 != trtp_version) {
+ ALOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+ return;
+ }
+
+ // calculate the start of the command payload
+ ssize_t offset = 18;
+ if (trtp_flags & 0x01) {
+ // timestamp is present (4 bytes)
+ offset += 4;
+ }
+ if (trtp_flags & 0x02) {
+ // transform is present (24 bytes)
+ offset += 24;
+ }
+
+ // the packet must contain 2 bytes of command payload beyond the TRTP header
+ if (amt < offset + 2) {
+ return;
+ }
+
+ uint16_t command_id = U16_AT(data + offset);
+
+ switch (command_id) {
+ case TRTPControlPacket::kCommandNop:
+ break;
+
+ case TRTPControlPacket::kCommandEOS:
+ case TRTPControlPacket::kCommandFlush: {
+ uint16_t program_id = (U32_AT(data + 8) >> 5) & 0x1F;
+ ALOGI("*** %s flushing program_id=%d",
+ __PRETTY_FUNCTION__, program_id);
+
+ Vector<uint32_t> substreams_to_remove;
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ sp<Substream> iter = substreams_.valueAt(i);
+ if (iter->getProgramID() == program_id) {
+ iter->shutdown();
+ substreams_to_remove.add(iter->getSSRC());
+ }
+ }
+
+ for (size_t i = 0; i < substreams_to_remove.size(); ++i) {
+ substreams_.removeItem(substreams_to_remove[i]);
+ }
+ } break;
+ }
+}
+
+bool AAH_RXPlayer::processGaps() {
+ // Deal with the current gap situation. Specifically...
+ //
+ // 1) If a new gap has shown up, send a retransmit request to the
+ // transmitter.
+ // 2) If a gap we were working on has had a packet in the middle or at
+ // the end filled in, send another retransmit request for the begining
+ // portion of the gap. TRTP was designed for LANs where packet
+ // re-ordering is very unlikely; so see the middle or end of a gap
+ // filled in before the begining is an almost certain indication that
+ // a retransmission packet was also dropped.
+ // 3) If we have been working on a gap for a while and it still has not
+ // been filled in, send another retransmit request.
+ // 4) If the are no more gaps in the ring, clear the current_gap_status_
+ // flag to indicate that all is well again.
+
+ // Start by fetching the active gap status.
+ SeqNoGap gap;
+ bool send_retransmit_request = false;
+ bool ret_val = false;
+ GapStatus gap_status;
+ if (kGS_NoGap != (gap_status = ring_buffer_.fetchCurrentGap(&gap))) {
+ // Note: checking for a change in the end sequence number should cover
+ // moving on to an entirely new gap for case #1 as well as resending the
+ // begining of a gap range for case #2.
+ send_retransmit_request = (kGS_NoGap == current_gap_status_) ||
+ (current_gap_.end_seq_ != gap.end_seq_);
+
+ // If this is the same gap we have been working on, and it has timed
+ // out, then check to see if our substreams are about to underflow. If
+ // so, instead of sending another retransmit request, just give up on
+ // this gap and move on.
+ if (!send_retransmit_request &&
+ (kGS_NoGap != current_gap_status_) &&
+ (0 == computeNextGapRetransmitTimeout())) {
+
+ // If out current gap is the fast-start gap, don't bother to skip it
+ // because substreams look like the are about to underflow.
+ if ((kGS_FastStartGap != gap_status) ||
+ (current_gap_.end_seq_ != gap.end_seq_)) {
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ if (substreams_.valueAt(i)->isAboutToUnderflow()) {
+ ALOGV("About to underflow, giving up on gap [%hu, %hu]",
+ gap.start_seq_, gap.end_seq_);
+ ring_buffer_.processNAK();
+ current_gap_status_ = kGS_NoGap;
+ return true;
+ }
+ }
+ }
+
+ // Looks like no one is about to underflow. Just go ahead and send
+ // the request.
+ send_retransmit_request = true;
+ }
+ } else {
+ current_gap_status_ = kGS_NoGap;
+ }
+
+ if (send_retransmit_request) {
+ // If we have been working on a fast start, and it is still not filled
+ // in, even after the extended retransmit time out, give up and skip it.
+ // The system should fall back into its normal slow-start behavior.
+ if ((kGS_FastStartGap == current_gap_status_) &&
+ (current_gap_.end_seq_ == gap.end_seq_)) {
+ ALOGV("Fast start is taking forever; giving up.");
+ ring_buffer_.processNAK();
+ current_gap_status_ = kGS_NoGap;
+ return true;
+ }
+
+ // Send the request.
+ RetransRequest req;
+ uint32_t magic = (kGS_FastStartGap == gap_status)
+ ? kFastStartRequestMagic
+ : kRetransRequestMagic;
+ req.magic_ = htonl(magic);
+ req.mcast_ip_ = listen_addr_.sin_addr.s_addr;
+ req.mcast_port_ = listen_addr_.sin_port;
+ req.start_seq_ = htons(gap.start_seq_);
+ req.end_seq_ = htons(gap.end_seq_);
+
+ {
+ uint32_t a = ntohl(transmitter_addr_.sin_addr.s_addr);
+ uint16_t p = ntohs(transmitter_addr_.sin_port);
+ ALOGV("Sending to transmitter %u.%u.%u.%u:%hu",
+ ((a >> 24) & 0xFF),
+ ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF),
+ ( a & 0xFF),
+ p);
+ }
+
+ int res = sendto(sock_fd_, &req, sizeof(req), 0,
+ reinterpret_cast<struct sockaddr*>(&transmitter_addr_),
+ sizeof(transmitter_addr_));
+ if (res < 0) {
+ ALOGE("Error when sending retransmit request (%d)", errno);
+ } else {
+ ALOGV("%s request for range [%hu, %hu] sent",
+ (kGS_FastStartGap == gap_status) ? "Fast Start" : "Retransmit",
+ gap.start_seq_, gap.end_seq_);
+ }
+
+ // Update the current gap info.
+ current_gap_ = gap;
+ current_gap_status_ = gap_status;
+ next_retrans_req_time_ = monotonicUSecNow() +
+ ((kGS_FastStartGap == current_gap_status_)
+ ? kFastStartTimeoutUSec
+ : kGapRerequestTimeoutUSec);
+ }
+
+ return false;
+}
+
+// Compute when its time to send the next gap retransmission in milliseconds.
+// Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit
+// right now.
+int AAH_RXPlayer::computeNextGapRetransmitTimeout() {
+ if (kGS_NoGap == current_gap_status_) {
+ return -1;
+ }
+
+ int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow();
+
+ timeout_delta /= 1000;
+ if (timeout_delta <= 0) {
+ return 0;
+ }
+
+ return static_cast<uint32_t>(timeout_delta);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_ring_buffer.cpp b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
new file mode 100644
index 000000000000..0d8b31f5441d
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+AAH_RXPlayer::RXRingBuffer::RXRingBuffer(uint32_t capacity) {
+ capacity_ = capacity;
+ rd_ = wr_ = 0;
+ ring_ = new PacketBuffer*[capacity];
+ memset(ring_, 0, sizeof(PacketBuffer*) * capacity);
+ reset();
+}
+
+AAH_RXPlayer::RXRingBuffer::~RXRingBuffer() {
+ reset();
+ delete[] ring_;
+}
+
+void AAH_RXPlayer::RXRingBuffer::reset() {
+ AutoMutex lock(&lock_);
+
+ if (NULL != ring_) {
+ while (rd_ != wr_) {
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ PacketBuffer::destroy(ring_[rd_]);
+ ring_[rd_] = NULL;
+ }
+ rd_ = (rd_ + 1) % capacity_;
+ }
+ }
+
+ rd_ = wr_ = 0;
+ rd_seq_known_ = false;
+ waiting_for_fast_start_ = true;
+ fetched_first_packet_ = false;
+ rtp_activity_timeout_valid_ = false;
+}
+
+bool AAH_RXPlayer::RXRingBuffer::pushBuffer(PacketBuffer* buf,
+ uint16_t seq) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != buf);
+
+ rtp_activity_timeout_valid_ = true;
+ rtp_activity_timeout_ = monotonicUSecNow() + kRTPActivityTimeoutUSec;
+
+ // If the ring buffer is totally reset (we have never received a single
+ // payload) then we don't know the rd sequence number and this should be
+ // simple. We just store the payload, advance the wr pointer and record the
+ // initial sequence number.
+ if (!rd_seq_known_) {
+ CHECK(rd_ == wr_);
+ CHECK(NULL == ring_[wr_]);
+ CHECK(wr_ < capacity_);
+
+ ring_[wr_] = buf;
+ wr_ = (wr_ + 1) % capacity_;
+ rd_seq_ = seq;
+ rd_seq_known_ = true;
+ return true;
+ }
+
+ // Compute the seqence number of this payload and of the write pointer,
+ // normalized around the read pointer. IOW - transform the payload seq no
+ // and the wr pointer seq no into a space where the rd pointer seq no is
+ // zero. This will define 4 cases we can consider...
+ //
+ // 1) norm_seq == norm_wr_seq
+ // This payload is contiguous with the last. All is good.
+ //
+ // 2) ((norm_seq < norm_wr_seq) && (norm_seq >= norm_rd_seq)
+ // aka ((norm_seq < norm_wr_seq) && (norm_seq >= 0)
+ // This payload is in the past, in the unprocessed region of the ring
+ // buffer. It is probably a retransmit intended to fill in a dropped
+ // payload; it may be a duplicate.
+ //
+ // 3) ((norm_seq - norm_wr_seq) & 0x8000) != 0
+ // This payload is in the past compared to the write pointer (or so very
+ // far in the future that it has wrapped the seq no space), but not in
+ // the unprocessed region of the ring buffer. This could be a duplicate
+ // retransmit; we just drop these payloads unless we are waiting for our
+ // first fast start packet. If we are waiting for fast start, than this
+ // packet is probably the first packet of the fast start retransmission.
+ // If it will fit in the buffer, back up the read pointer to its position
+ // and clear the fast start flag, otherwise just drop it.
+ //
+ // 4) ((norm_seq - norm_wr_seq) & 0x8000) == 0
+ // This payload which is ahead of the next write pointer. This indicates
+ // that we have missed some payloads and need to request a retransmit.
+ // If norm_seq >= (capacity - 1), then the gap is so large that it would
+ // overflow the ring buffer and we should probably start to panic.
+
+ uint16_t norm_wr_seq = ((wr_ + capacity_ - rd_) % capacity_);
+ uint16_t norm_seq = seq - rd_seq_;
+
+ // Check for overflow first.
+ if ((!(norm_seq & 0x8000)) && (norm_seq >= (capacity_ - 1))) {
+ ALOGW("Ring buffer overflow; cap = %u, [rd, wr] = [%hu, %hu], seq = %hu",
+ capacity_, rd_seq_, norm_wr_seq + rd_seq_, seq);
+ PacketBuffer::destroy(buf);
+ return false;
+ }
+
+ // Check for case #1
+ if (norm_seq == norm_wr_seq) {
+ CHECK(wr_ < capacity_);
+ CHECK(NULL == ring_[wr_]);
+
+ ring_[wr_] = buf;
+ wr_ = (wr_ + 1) % capacity_;
+
+ CHECK(wr_ != rd_);
+ return true;
+ }
+
+ // Check case #2
+ uint32_t ring_pos = (rd_ + norm_seq) % capacity_;
+ if ((norm_seq < norm_wr_seq) && (!(norm_seq & 0x8000))) {
+ // Do we already have a payload for this slot? If so, then this looks
+ // like a duplicate retransmit. Just ignore it.
+ if (NULL != ring_[ring_pos]) {
+ ALOGD("RXed duplicate retransmit, seq = %hu", seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ // Looks like we were missing this payload. Go ahead and store it.
+ ring_[ring_pos] = buf;
+ }
+
+ return true;
+ }
+
+ // Check case #3
+ if ((norm_seq - norm_wr_seq) & 0x8000) {
+ if (!waiting_for_fast_start_) {
+ ALOGD("RXed duplicate retransmit from before rd pointer, seq = %hu",
+ seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ // Looks like a fast start fill-in. Go ahead and store it, assuming
+ // that we can fit it in the buffer.
+ uint32_t implied_ring_size = static_cast<uint32_t>(norm_wr_seq)
+ + (rd_seq_ - seq);
+
+ if (implied_ring_size >= (capacity_ - 1)) {
+ ALOGD("RXed what looks like a fast start packet (seq = %hu),"
+ " but packet is too far in the past to fit into the ring"
+ " buffer. Dropping.", seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ ring_pos = (rd_ + capacity_ + seq - rd_seq_) % capacity_;
+ rd_seq_ = seq;
+ rd_ = ring_pos;
+ waiting_for_fast_start_ = false;
+
+ CHECK(ring_pos < capacity_);
+ CHECK(NULL == ring_[ring_pos]);
+ ring_[ring_pos] = buf;
+ }
+
+ }
+ return true;
+ }
+
+ // Must be in case #4 with no overflow. This packet fits in the current
+ // ring buffer, but is discontiuguous. Advance the write pointer leaving a
+ // gap behind.
+ uint32_t gap_len = (ring_pos + capacity_ - wr_) % capacity_;
+ ALOGD("Drop detected; %u packets, seq_range [%hu, %hu]",
+ gap_len,
+ rd_seq_ + norm_wr_seq,
+ rd_seq_ + norm_wr_seq + gap_len - 1);
+
+ CHECK(NULL == ring_[ring_pos]);
+ ring_[ring_pos] = buf;
+ wr_ = (ring_pos + 1) % capacity_;
+ CHECK(wr_ != rd_);
+
+ return true;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::RXRingBuffer::fetchBuffer(bool* is_discon) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != is_discon);
+
+ // If the read seqence number is not known, then this ring buffer has not
+ // received a packet since being reset and there cannot be any packets to
+ // return. If we are still waiting for the first fast start packet to show
+ // up, we don't want to let any buffer be consumed yet because we expect to
+ // see a packet before the initial read sequence number show up shortly.
+ if (!rd_seq_known_ || waiting_for_fast_start_) {
+ *is_discon = false;
+ return NULL;
+ }
+
+ PacketBuffer* ret = NULL;
+ *is_discon = !fetched_first_packet_;
+
+ while ((rd_ != wr_) && (NULL == ret)) {
+ CHECK(rd_ < capacity_);
+
+ // If we hit a gap, stall and do not advance the read pointer. Let the
+ // higher level code deal with requesting retries and/or deciding to
+ // skip the current gap.
+ ret = ring_[rd_];
+ if (NULL == ret) {
+ break;
+ }
+
+ ring_[rd_] = NULL;
+ rd_ = (rd_ + 1) % capacity_;
+ ++rd_seq_;
+ }
+
+ if (NULL != ret) {
+ fetched_first_packet_ = true;
+ }
+
+ return ret;
+}
+
+AAH_RXPlayer::GapStatus
+AAH_RXPlayer::RXRingBuffer::fetchCurrentGap(SeqNoGap* gap) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != gap);
+
+ // If the read seqence number is not known, then this ring buffer has not
+ // received a packet since being reset and there cannot be any gaps.
+ if (!rd_seq_known_) {
+ return kGS_NoGap;
+ }
+
+ // If we are waiting for fast start, then the current gap is a fast start
+ // gap and it includes all packets before the read sequence number.
+ if (waiting_for_fast_start_) {
+ gap->start_seq_ =
+ gap->end_seq_ = rd_seq_ - 1;
+ return kGS_FastStartGap;
+ }
+
+ // If rd == wr, then the buffer is empty and there cannot be any gaps.
+ if (rd_ == wr_) {
+ return kGS_NoGap;
+ }
+
+ // If rd_ is currently pointing at an unprocessed packet, then there is no
+ // current gap.
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ return kGS_NoGap;
+ }
+
+ // Looks like there must be a gap here. The start of the gap is the current
+ // rd sequence number, all we need to do now is determine its length in
+ // order to compute the end sequence number.
+ gap->start_seq_ = rd_seq_;
+ uint16_t end = rd_seq_;
+ uint32_t tmp = (rd_ + 1) % capacity_;
+ while ((tmp != wr_) && (NULL == ring_[tmp])) {
+ ++end;
+ tmp = (tmp + 1) % capacity_;
+ }
+ gap->end_seq_ = end;
+
+ return kGS_NormalGap;
+}
+
+void AAH_RXPlayer::RXRingBuffer::processNAK(const SeqNoGap* nak) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+
+ // If we were waiting for our first fast start fill-in packet, and we
+ // received a NAK, then apparantly we are not getting our fast start. Just
+ // clear the waiting flag and go back to normal behavior.
+ if (waiting_for_fast_start_) {
+ waiting_for_fast_start_ = false;
+ }
+
+ // If we have not received a packet since last reset, or there is no data in
+ // the ring, then there is nothing to skip.
+ if ((!rd_seq_known_) || (rd_ == wr_)) {
+ return;
+ }
+
+ // If rd_ is currently pointing at an unprocessed packet, then there is no
+ // gap to skip.
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ return;
+ }
+
+ // Looks like there must be a gap here. Advance rd until we have passed
+ // over the portion of it indicated by nak (or all of the gap if nak is
+ // NULL). Then reset fetched_first_packet_ so that the next read will show
+ // up as being discontiguous.
+ uint16_t seq_after_gap = (NULL == nak) ? 0 : nak->end_seq_ + 1;
+ while ((rd_ != wr_) &&
+ (NULL == ring_[rd_]) &&
+ ((NULL == nak) || (seq_after_gap != rd_seq_))) {
+ rd_ = (rd_ + 1) % capacity_;
+ ++rd_seq_;
+ }
+ fetched_first_packet_ = false;
+}
+
+int AAH_RXPlayer::RXRingBuffer::computeInactivityTimeout() {
+ AutoMutex lock(&lock_);
+
+ if (!rtp_activity_timeout_valid_) {
+ return -1;
+ }
+
+ uint64_t now = monotonicUSecNow();
+ if (rtp_activity_timeout_ <= now) {
+ return 0;
+ }
+
+ return (rtp_activity_timeout_ - now) / 1000;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::PacketBuffer::allocate(ssize_t length) {
+ if (length <= 0) {
+ return NULL;
+ }
+
+ uint32_t alloc_len = sizeof(PacketBuffer) + length;
+ PacketBuffer* ret = reinterpret_cast<PacketBuffer*>(
+ new uint8_t[alloc_len]);
+
+ if (NULL != ret) {
+ ret->length_ = length;
+ }
+
+ return ret;
+}
+
+void AAH_RXPlayer::PacketBuffer::destroy(PacketBuffer* pb) {
+ uint8_t* kill_me = reinterpret_cast<uint8_t*>(pb);
+ delete[] kill_me;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_substream.cpp b/media/libaah_rtp/aah_rx_player_substream.cpp
new file mode 100644
index 000000000000..1e4c784629a2
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_substream.cpp
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include <include/avc_utils.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+int64_t AAH_RXPlayer::Substream::kAboutToUnderflowThreshold =
+ 50ull * 1000;
+
+AAH_RXPlayer::Substream::Substream(uint32_t ssrc, OMXClient& omx) {
+ ssrc_ = ssrc;
+ substream_details_known_ = false;
+ buffer_in_progress_ = NULL;
+ status_ = OK;
+
+ decoder_ = new AAH_DecoderPump(omx);
+ if (decoder_ == NULL) {
+ ALOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__);
+ }
+ if (OK != decoder_->initCheck()) {
+ ALOGE("%s failed to initialize decoder pump!", __PRETTY_FUNCTION__);
+ }
+
+ // cleanupBufferInProgress will reset most of the internal state variables.
+ // Just need to make sure that buffer_in_progress_ is NULL before calling.
+ cleanupBufferInProgress();
+}
+
+
+void AAH_RXPlayer::Substream::shutdown() {
+ substream_meta_ = NULL;
+ status_ = OK;
+ cleanupBufferInProgress();
+ cleanupDecoder();
+}
+
+void AAH_RXPlayer::Substream::cleanupBufferInProgress() {
+ if (NULL != buffer_in_progress_) {
+ buffer_in_progress_->release();
+ buffer_in_progress_ = NULL;
+ }
+
+ expected_buffer_size_ = 0;
+ buffer_filled_ = 0;
+ waiting_for_rap_ = true;
+}
+
+void AAH_RXPlayer::Substream::cleanupDecoder() {
+ if (decoder_ != NULL) {
+ decoder_->shutdown();
+ }
+}
+
+bool AAH_RXPlayer::Substream::shouldAbort(const char* log_tag) {
+ // If we have already encountered a fatal error, do nothing. We are just
+ // waiting for our owner to shut us down now.
+ if (OK != status_) {
+ ALOGV("Skipping %s, substream has encountered fatal error (%d).",
+ log_tag, status_);
+ return true;
+ }
+
+ return false;
+}
+
+void AAH_RXPlayer::Substream::processPayloadStart(uint8_t* buf,
+ uint32_t amt,
+ int32_t ts_lower) {
+ uint32_t min_length = 6;
+
+ if (shouldAbort(__PRETTY_FUNCTION__)) {
+ return;
+ }
+
+ // Do we have a buffer in progress already? If so, abort the buffer. In
+ // theory, this should never happen. If there were a discontinutity in the
+ // stream, the discon in the seq_nos at the RTP level should have already
+ // triggered a cleanup of the buffer in progress. To see a problem at this
+ // level is an indication either of a bug in the transmitter, or some form
+ // of terrible corruption/tampering on the wire.
+ if (NULL != buffer_in_progress_) {
+ ALOGE("processPayloadStart is aborting payload already in progress.");
+ cleanupBufferInProgress();
+ }
+
+ // Parse enough of the header to know where we stand. Since this is a
+ // payload start, it should begin with a TRTP header which has to be at
+ // least 6 bytes long.
+ if (amt < min_length) {
+ ALOGV("Discarding payload too short to contain TRTP header (len = %u)",
+ amt);
+ return;
+ }
+
+ // Check the TRTP version number.
+ if (0x01 != buf[0]) {
+ ALOGV("Unexpected TRTP version (%u) in header. Expected %u.",
+ buf[0], 1);
+ return;
+ }
+
+ // Extract the substream type field and make sure its one we understand (and
+ // one that does not conflict with any previously received substream type.
+ uint8_t header_type = (buf[1] >> 4) & 0xF;
+ switch (header_type) {
+ case 0x01:
+ // Audio, yay! Just break. We understand audio payloads.
+ break;
+ case 0x02:
+ ALOGV("RXed packet with unhandled TRTP header type (Video).");
+ return;
+ case 0x03:
+ ALOGV("RXed packet with unhandled TRTP header type (Subpicture).");
+ return;
+ case 0x04:
+ ALOGV("RXed packet with unhandled TRTP header type (Control).");
+ return;
+ default:
+ ALOGV("RXed packet with unhandled TRTP header type (%u).",
+ header_type);
+ return;
+ }
+
+ if (substream_details_known_ && (header_type != substream_type_)) {
+ ALOGV("RXed TRTP Payload for SSRC=0x%08x where header type (%u) does not"
+ " match previously received header type (%u)",
+ ssrc_, header_type, substream_type_);
+ return;
+ }
+
+ // Check the flags to see if there is another 32 bits of timestamp present.
+ uint32_t trtp_header_len = 6;
+ bool ts_valid = buf[1] & 0x1;
+ if (ts_valid) {
+ min_length += 4;
+ trtp_header_len += 4;
+ if (amt < min_length) {
+ ALOGV("Discarding payload too short to contain TRTP timestamp"
+ " (len = %u)", amt);
+ return;
+ }
+ }
+
+ // Extract the TRTP length field and sanity check it.
+ uint32_t trtp_len;
+ trtp_len = (static_cast<uint32_t>(buf[2]) << 24) |
+ (static_cast<uint32_t>(buf[3]) << 16) |
+ (static_cast<uint32_t>(buf[4]) << 8) |
+ static_cast<uint32_t>(buf[5]);
+ if (trtp_len < min_length) {
+ ALOGV("TRTP length (%u) is too short to be valid. Must be at least %u"
+ " bytes.", trtp_len, min_length);
+ return;
+ }
+
+ // Extract the rest of the timestamp field if valid.
+ int64_t ts = 0;
+ uint32_t parse_offset = 6;
+ if (ts_valid) {
+ ts = (static_cast<int64_t>(buf[parse_offset ]) << 56) |
+ (static_cast<int64_t>(buf[parse_offset + 1]) << 48) |
+ (static_cast<int64_t>(buf[parse_offset + 2]) << 40) |
+ (static_cast<int64_t>(buf[parse_offset + 3]) << 32);
+ ts |= ts_lower;
+ parse_offset += 4;
+ }
+
+ // Check the flags to see if there is another 24 bytes of timestamp
+ // transformation present.
+ if (buf[1] & 0x2) {
+ min_length += 24;
+ parse_offset += 24;
+ trtp_header_len += 24;
+ if (amt < min_length) {
+ ALOGV("Discarding payload too short to contain TRTP timestamp"
+ " transformation (len = %u)", amt);
+ return;
+ }
+ }
+
+ // TODO : break the parsing into individual parsers for the different
+ // payload types (audio, video, etc).
+ //
+ // At this point in time, we know that this is audio. Go ahead and parse
+ // the basic header, check the codec type, and find the payload portion of
+ // the packet.
+ min_length += 3;
+ if (trtp_len < min_length) {
+ ALOGV("TRTP length (%u) is too short to be a valid audio payload. Must"
+ " be at least %u bytes.", trtp_len, min_length);
+ return;
+ }
+
+ if (amt < min_length) {
+ ALOGV("TRTP porttion of RTP payload (%u bytes) too small to contain"
+ " entire TRTP header. TRTP does not currently support fragmenting"
+ " TRTP headers across RTP payloads", amt);
+ return;
+ }
+
+ uint8_t codec_type = buf[parse_offset ];
+ uint8_t flags = buf[parse_offset + 1];
+ uint8_t volume = buf[parse_offset + 2];
+ parse_offset += 3;
+ trtp_header_len += 3;
+
+ if (!setupSubstreamType(header_type, codec_type)) {
+ return;
+ }
+
+ if (decoder_ != NULL) {
+ decoder_->setRenderVolume(volume);
+ }
+
+ // TODO : move all of the constant flag and offset definitions for TRTP up
+ // into some sort of common header file.
+ if (waiting_for_rap_ && !(flags & 0x08)) {
+ ALOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP.");
+ return;
+ }
+
+ if (flags & 0x10) {
+ ALOGV("Dropping TRTP Audio Payload with aux codec data present (only"
+ " handle MP3 right now, and it has no aux data)");
+ return;
+ }
+
+ // OK - everything left is just payload. Compute the payload size, start
+ // the buffer in progress and pack as much payload as we can into it. If
+ // the payload is finished once we are done, go ahead and send the payload
+ // to the decoder.
+ expected_buffer_size_ = trtp_len - trtp_header_len;
+ if (!expected_buffer_size_) {
+ ALOGV("Dropping TRTP Audio Payload with 0 Access Unit length");
+ return;
+ }
+
+ CHECK(amt >= trtp_header_len);
+ uint32_t todo = amt - trtp_header_len;
+ if (expected_buffer_size_ < todo) {
+ ALOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;"
+ " dropping payload.", todo, expected_buffer_size_);
+ return;
+ }
+
+ buffer_filled_ = 0;
+ buffer_in_progress_ = new MediaBuffer(expected_buffer_size_);
+ if ((NULL == buffer_in_progress_) ||
+ (NULL == buffer_in_progress_->data())) {
+ ALOGV("Failed to allocate MediaBuffer of length %u",
+ expected_buffer_size_);
+ cleanupBufferInProgress();
+ return;
+ }
+
+ sp<MetaData> meta = buffer_in_progress_->meta_data();
+ if (meta == NULL) {
+ ALOGV("Missing metadata structure in allocated MediaBuffer; dropping"
+ " payload");
+ cleanupBufferInProgress();
+ return;
+ }
+
+ // TODO : set this based on the codec type indicated in the TRTP stream.
+ // Right now, we only support MP3, so the choice is obvious.
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+ if (ts_valid) {
+ meta->setInt64(kKeyTime, ts);
+ }
+
+ if (amt > 0) {
+ uint8_t* tgt =
+ reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+ memcpy(tgt + buffer_filled_, buf + trtp_header_len, todo);
+ buffer_filled_ += amt;
+ }
+
+ if (buffer_filled_ >= expected_buffer_size_) {
+ processCompletedBuffer();
+ }
+}
+
+void AAH_RXPlayer::Substream::processPayloadCont(uint8_t* buf,
+ uint32_t amt) {
+ if (shouldAbort(__PRETTY_FUNCTION__)) {
+ return;
+ }
+
+ if (NULL == buffer_in_progress_) {
+ ALOGV("TRTP Receiver skipping payload continuation; no buffer currently"
+ " in progress.");
+ return;
+ }
+
+ CHECK(buffer_filled_ < expected_buffer_size_);
+ uint32_t buffer_left = expected_buffer_size_ - buffer_filled_;
+ if (amt > buffer_left) {
+ ALOGV("Extra data (%u > %u) present in continued TRTP Audio Payload;"
+ " dropping payload.", amt, buffer_left);
+ cleanupBufferInProgress();
+ return;
+ }
+
+ if (amt > 0) {
+ uint8_t* tgt =
+ reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+ memcpy(tgt + buffer_filled_, buf, amt);
+ buffer_filled_ += amt;
+ }
+
+ if (buffer_filled_ >= expected_buffer_size_) {
+ processCompletedBuffer();
+ }
+}
+
+void AAH_RXPlayer::Substream::processCompletedBuffer() {
+ const uint8_t* buffer_data = NULL;
+ int sample_rate;
+ int channel_count;
+ size_t frame_size;
+ status_t res;
+
+ CHECK(NULL != buffer_in_progress_);
+
+ if (decoder_ == NULL) {
+ ALOGV("Dropping complete buffer, no decoder pump allocated");
+ goto bailout;
+ }
+
+ buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data());
+ if (buffer_in_progress_->size() < 4) {
+ ALOGV("MP3 payload too short to contain header, dropping payload.");
+ goto bailout;
+ }
+
+ // Extract the channel count and the sample rate from the MP3 header. The
+ // stagefright MP3 requires that these be delivered before decoing can
+ // begin.
+ if (!GetMPEGAudioFrameSize(U32_AT(buffer_data),
+ &frame_size,
+ &sample_rate,
+ &channel_count,
+ NULL,
+ NULL)) {
+ ALOGV("Failed to parse MP3 header in payload, droping payload.");
+ goto bailout;
+ }
+
+
+ // Make sure that our substream metadata is set up properly. If there has
+ // been a format change, be sure to reset the underlying decoder. In
+ // stagefright, it seems like the only way to do this is to destroy and
+ // recreate the decoder.
+ if (substream_meta_ == NULL) {
+ substream_meta_ = new MetaData();
+
+ if (substream_meta_ == NULL) {
+ ALOGE("Failed to allocate MetaData structure for substream");
+ goto bailout;
+ }
+
+ substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+ substream_meta_->setInt32 (kKeyChannelCount, channel_count);
+ substream_meta_->setInt32 (kKeySampleRate, sample_rate);
+ } else {
+ int32_t prev_sample_rate;
+ int32_t prev_channel_count;
+ substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate);
+ substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count);
+
+ if ((prev_channel_count != channel_count) ||
+ (prev_sample_rate != sample_rate)) {
+ ALOGW("Format change detected, forcing decoder reset.");
+ cleanupDecoder();
+
+ substream_meta_->setInt32(kKeyChannelCount, channel_count);
+ substream_meta_->setInt32(kKeySampleRate, sample_rate);
+ }
+ }
+
+ // If our decoder has not be set up, do so now.
+ res = decoder_->init(substream_meta_);
+ if (OK != res) {
+ ALOGE("Failed to init decoder (res = %d)", res);
+ cleanupDecoder();
+ substream_meta_ = NULL;
+ goto bailout;
+ }
+
+ // Queue the payload for decode.
+ res = decoder_->queueForDecode(buffer_in_progress_);
+
+ if (res != OK) {
+ ALOGD("Failed to queue payload for decode, resetting decoder pump!"
+ " (res = %d)", res);
+ status_ = res;
+ cleanupDecoder();
+ cleanupBufferInProgress();
+ }
+
+ // NULL out buffer_in_progress before calling the cleanup helper.
+ //
+ // MediaBuffers use something of a hybrid ref-counting pattern which prevent
+ // the AAH_DecoderPump's input queue from adding their own reference to the
+ // MediaBuffer. MediaBuffers start life with a reference count of 0, as
+ // well as an observer which starts as NULL. Before being given an
+ // observer, the ref count cannot be allowed to become non-zero as it will
+ // cause calls to release() to assert. Basically, before a MediaBuffer has
+ // an observer, they behave like non-ref counted obects where release()
+ // serves the roll of delete. After a MediaBuffer has an observer, they
+ // become more like ref counted objects where add ref and release can be
+ // used, and when the ref count hits zero, the MediaBuffer is handed off to
+ // the observer.
+ //
+ // Given all of this, when we give the buffer to the decoder pump to wait in
+ // the to-be-processed queue, the decoder cannot add a ref to the buffer as
+ // it would in a traditional ref counting system. Instead it needs to
+ // "steal" the non-existent ref. In the case of queue failure, we need to
+ // make certain to release this non-existent reference so that the buffer is
+ // cleaned up during the cleanupBufferInProgress helper. In the case of a
+ // successful queue operation, we need to make certain that the
+ // cleanupBufferInProgress helper does not release the buffer since it needs
+ // to remain alive in the queue. We acomplish this by NULLing out the
+ // buffer pointer before calling the cleanup helper.
+ buffer_in_progress_ = NULL;
+
+bailout:
+ cleanupBufferInProgress();
+}
+
+
+void AAH_RXPlayer::Substream::processTSTransform(const LinearTransform& trans) {
+ if (decoder_ != NULL) {
+ decoder_->setRenderTSTransform(trans);
+ }
+}
+
+bool AAH_RXPlayer::Substream::isAboutToUnderflow() {
+ if (decoder_ == NULL) {
+ return false;
+ }
+
+ return decoder_->isAboutToUnderflow(kAboutToUnderflowThreshold);
+}
+
+bool AAH_RXPlayer::Substream::setupSubstreamType(uint8_t substream_type,
+ uint8_t codec_type) {
+ // Sanity check the codec type. Right now we only support MP3. Also check
+ // for conflicts with previously delivered codec types.
+ if (substream_details_known_ && (codec_type != codec_type_)) {
+ ALOGV("RXed TRTP Payload for SSRC=0x%08x where codec type (%u) does not"
+ " match previously received codec type (%u)",
+ ssrc_, codec_type, codec_type_);
+ return false;
+ }
+
+ if (codec_type != 0x03) {
+ ALOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported codec"
+ " type (%u)", ssrc_, codec_type);
+ return false;
+ }
+
+ if (!substream_details_known_) {
+ substream_type_ = substream_type;
+ codec_type_ = codec_type;
+ substream_details_known_ = true;
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.cpp b/media/libaah_rtp/aah_tx_packet.cpp
new file mode 100644
index 000000000000..3f6e0e9520d6
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.cpp
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const int TRTPPacket::kRTPHeaderLen;
+const uint32_t TRTPPacket::kTRTPEpochMask;
+
+TRTPPacket::~TRTPPacket() {
+ delete mPacket;
+}
+
+/*** TRTP packet properties ***/
+
+void TRTPPacket::setSeqNumber(uint16_t val) {
+ mSeqNumber = val;
+
+ if (mIsPacked) {
+ const int kTRTPSeqNumberOffset = 2;
+ uint16_t* buf = reinterpret_cast<uint16_t*>(
+ mPacket + kTRTPSeqNumberOffset);
+ *buf = htons(mSeqNumber);
+ }
+}
+
+uint16_t TRTPPacket::getSeqNumber() const {
+ return mSeqNumber;
+}
+
+void TRTPPacket::setPTS(int64_t val) {
+ CHECK(!mIsPacked);
+ mPTS = val;
+ mPTSValid = true;
+}
+
+int64_t TRTPPacket::getPTS() const {
+ return mPTS;
+}
+
+void TRTPPacket::setEpoch(uint32_t val) {
+ mEpoch = val;
+
+ if (mIsPacked) {
+ const int kTRTPEpochOffset = 8;
+ uint32_t* buf = reinterpret_cast<uint32_t*>(
+ mPacket + kTRTPEpochOffset);
+ uint32_t val = ntohl(*buf);
+ val &= ~(kTRTPEpochMask << kTRTPEpochShift);
+ val |= (mEpoch & kTRTPEpochMask) << kTRTPEpochShift;
+ *buf = htonl(val);
+ }
+}
+
+void TRTPPacket::setProgramID(uint16_t val) {
+ CHECK(!mIsPacked);
+ mProgramID = val;
+}
+
+void TRTPPacket::setSubstreamID(uint16_t val) {
+ CHECK(!mIsPacked);
+ mSubstreamID = val;
+}
+
+
+void TRTPPacket::setClockTransform(const LinearTransform& trans) {
+ CHECK(!mIsPacked);
+ mClockTranform = trans;
+ mClockTranformValid = true;
+}
+
+uint8_t* TRTPPacket::getPacket() const {
+ CHECK(mIsPacked);
+ return mPacket;
+}
+
+int TRTPPacket::getPacketLen() const {
+ CHECK(mIsPacked);
+ return mPacketLen;
+}
+
+void TRTPPacket::setExpireTime(nsecs_t val) {
+ CHECK(!mIsPacked);
+ mExpireTime = val;
+}
+
+nsecs_t TRTPPacket::getExpireTime() const {
+ return mExpireTime;
+}
+
+/*** TRTP audio packet properties ***/
+
+void TRTPAudioPacket::setCodecType(TRTPAudioCodecType val) {
+ CHECK(!mIsPacked);
+ mCodecType = val;
+}
+
+void TRTPAudioPacket::setRandomAccessPoint(bool val) {
+ CHECK(!mIsPacked);
+ mRandomAccessPoint = val;
+}
+
+void TRTPAudioPacket::setDropable(bool val) {
+ CHECK(!mIsPacked);
+ mDropable = val;
+}
+
+void TRTPAudioPacket::setDiscontinuity(bool val) {
+ CHECK(!mIsPacked);
+ mDiscontinuity = val;
+}
+
+void TRTPAudioPacket::setEndOfStream(bool val) {
+ CHECK(!mIsPacked);
+ mEndOfStream = val;
+}
+
+void TRTPAudioPacket::setVolume(uint8_t val) {
+ CHECK(!mIsPacked);
+ mVolume = val;
+}
+
+void TRTPAudioPacket::setAccessUnitData(void* data, int len) {
+ CHECK(!mIsPacked);
+ mAccessUnitData = data;
+ mAccessUnitLen = len;
+}
+
+/*** TRTP control packet properties ***/
+
+void TRTPControlPacket::setCommandID(TRTPCommandID val) {
+ CHECK(!mIsPacked);
+ mCommandID = val;
+}
+
+/*** TRTP packet serializers ***/
+
+void TRTPPacket::writeU8(uint8_t*& buf, uint8_t val) {
+ *buf = val;
+ buf++;
+}
+
+void TRTPPacket::writeU16(uint8_t*& buf, uint16_t val) {
+ *reinterpret_cast<uint16_t*>(buf) = htons(val);
+ buf += 2;
+}
+
+void TRTPPacket::writeU32(uint8_t*& buf, uint32_t val) {
+ *reinterpret_cast<uint32_t*>(buf) = htonl(val);
+ buf += 4;
+}
+
+void TRTPPacket::writeU64(uint8_t*& buf, uint64_t val) {
+ buf[0] = static_cast<uint8_t>(val >> 56);
+ buf[1] = static_cast<uint8_t>(val >> 48);
+ buf[2] = static_cast<uint8_t>(val >> 40);
+ buf[3] = static_cast<uint8_t>(val >> 32);
+ buf[4] = static_cast<uint8_t>(val >> 24);
+ buf[5] = static_cast<uint8_t>(val >> 16);
+ buf[6] = static_cast<uint8_t>(val >> 8);
+ buf[7] = static_cast<uint8_t>(val);
+ buf += 8;
+}
+
+void TRTPPacket::writeTRTPHeader(uint8_t*& buf,
+ bool isFirstFragment,
+ int totalPacketLen) {
+ // RTP header
+ writeU8(buf,
+ ((mVersion & 0x03) << 6) |
+ (static_cast<int>(mPadding) << 5) |
+ (static_cast<int>(mExtension) << 4) |
+ (mCsrcCount & 0x0F));
+ writeU8(buf,
+ (static_cast<int>(isFirstFragment) << 7) |
+ (mPayloadType & 0x7F));
+ writeU16(buf, mSeqNumber);
+ if (isFirstFragment && mPTSValid) {
+ writeU32(buf, mPTS & 0xFFFFFFFF);
+ } else {
+ writeU32(buf, 0);
+ }
+ writeU32(buf,
+ ((mEpoch & kTRTPEpochMask) << kTRTPEpochShift) |
+ ((mProgramID & 0x1F) << 5) |
+ (mSubstreamID & 0x1F));
+
+ // TRTP header
+ writeU8(buf, mTRTPVersion);
+ writeU8(buf,
+ ((mTRTPHeaderType & 0x0F) << 4) |
+ (mClockTranformValid ? 0x02 : 0x00) |
+ (mPTSValid ? 0x01 : 0x00));
+ writeU32(buf, totalPacketLen - kRTPHeaderLen);
+ if (mPTSValid) {
+ writeU32(buf, mPTS >> 32);
+ }
+
+ if (mClockTranformValid) {
+ writeU64(buf, mClockTranform.a_zero);
+ writeU32(buf, mClockTranform.a_to_b_numer);
+ writeU32(buf, mClockTranform.a_to_b_denom);
+ writeU64(buf, mClockTranform.b_zero);
+ }
+}
+
+bool TRTPAudioPacket::pack() {
+ if (mIsPacked) {
+ return false;
+ }
+
+ int packetLen = kRTPHeaderLen +
+ mAccessUnitLen +
+ TRTPHeaderLen();
+
+ // TODO : support multiple fragments
+ const int kMaxUDPPayloadLen = 65507;
+ if (packetLen > kMaxUDPPayloadLen) {
+ return false;
+ }
+
+ mPacket = new uint8_t[packetLen];
+ if (!mPacket) {
+ return false;
+ }
+
+ mPacketLen = packetLen;
+
+ uint8_t* cur = mPacket;
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU8(cur, mCodecType);
+ writeU8(cur,
+ (static_cast<int>(mRandomAccessPoint) << 3) |
+ (static_cast<int>(mDropable) << 2) |
+ (static_cast<int>(mDiscontinuity) << 1) |
+ (static_cast<int>(mEndOfStream)));
+ writeU8(cur, mVolume);
+
+ memcpy(cur, mAccessUnitData, mAccessUnitLen);
+
+ mIsPacked = true;
+ return true;
+}
+
+int TRTPPacket::TRTPHeaderLen() const {
+ // 6 bytes for version, payload type, flags and length. An additional 4 if
+ // there are upper timestamp bits present and another 24 if there is a clock
+ // transformation present.
+ return 6 +
+ (mClockTranformValid ? 24 : 0) +
+ (mPTSValid ? 4 : 0);
+}
+
+int TRTPAudioPacket::TRTPHeaderLen() const {
+ // TRTPPacket::TRTPHeaderLen() for the base TRTPHeader. 3 bytes for audio's
+ // codec type, flags and volume field. Another 5 bytes if the codec type is
+ // PCM and we are sending sample rate/channel count. as well as however long
+ // the aux data (if present) is.
+
+ int pcmParamLength;
+ switch(mCodecType) {
+ case kCodecPCMBigEndian:
+ case kCodecPCMLittleEndian:
+ pcmParamLength = 5;
+ break;
+
+ default:
+ pcmParamLength = 0;
+ break;
+ }
+
+
+ // TODO : properly compute aux data length. Currently, nothing
+ // uses aux data, so its length is always 0.
+ int auxDataLength = 0;
+ return TRTPPacket::TRTPHeaderLen() +
+ 3 +
+ auxDataLength +
+ pcmParamLength;
+}
+
+bool TRTPControlPacket::pack() {
+ if (mIsPacked) {
+ return false;
+ }
+
+ // command packets contain a 2-byte command ID
+ int packetLen = kRTPHeaderLen +
+ TRTPHeaderLen() +
+ 2;
+
+ mPacket = new uint8_t[packetLen];
+ if (!mPacket) {
+ return false;
+ }
+
+ mPacketLen = packetLen;
+
+ uint8_t* cur = mPacket;
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU16(cur, mCommandID);
+
+ mIsPacked = true;
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.h b/media/libaah_rtp/aah_tx_packet.h
new file mode 100644
index 000000000000..833803ea1bef
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2011 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 __AAH_TX_PACKET_H__
+#define __AAH_TX_PACKET_H__
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/LinearTransform.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class TRTPPacket : public RefBase {
+ protected:
+ enum TRTPHeaderType {
+ kHeaderTypeAudio = 1,
+ kHeaderTypeVideo = 2,
+ kHeaderTypeSubpicture = 3,
+ kHeaderTypeControl = 4,
+ };
+
+ TRTPPacket(TRTPHeaderType headerType)
+ : mIsPacked(false)
+ , mVersion(2)
+ , mPadding(false)
+ , mExtension(false)
+ , mCsrcCount(0)
+ , mPayloadType(100)
+ , mSeqNumber(0)
+ , mPTSValid(false)
+ , mPTS(0)
+ , mEpoch(0)
+ , mProgramID(0)
+ , mSubstreamID(0)
+ , mClockTranformValid(false)
+ , mTRTPVersion(1)
+ , mTRTPLength(0)
+ , mTRTPHeaderType(headerType)
+ , mPacket(NULL)
+ , mPacketLen(0) { }
+
+ public:
+ virtual ~TRTPPacket();
+
+ void setSeqNumber(uint16_t val);
+ uint16_t getSeqNumber() const;
+
+ void setPTS(int64_t val);
+ int64_t getPTS() const;
+
+ void setEpoch(uint32_t val);
+ void setProgramID(uint16_t val);
+ void setSubstreamID(uint16_t val);
+ void setClockTransform(const LinearTransform& trans);
+
+ uint8_t* getPacket() const;
+ int getPacketLen() const;
+
+ void setExpireTime(nsecs_t val);
+ nsecs_t getExpireTime() const;
+
+ virtual bool pack() = 0;
+
+ // mask for the number of bits in a TRTP epoch
+ static const uint32_t kTRTPEpochMask = (1 << 22) - 1;
+ static const int kTRTPEpochShift = 10;
+
+ protected:
+ static const int kRTPHeaderLen = 12;
+ virtual int TRTPHeaderLen() const;
+
+ void writeTRTPHeader(uint8_t*& buf,
+ bool isFirstFragment,
+ int totalPacketLen);
+
+ void writeU8(uint8_t*& buf, uint8_t val);
+ void writeU16(uint8_t*& buf, uint16_t val);
+ void writeU32(uint8_t*& buf, uint32_t val);
+ void writeU64(uint8_t*& buf, uint64_t val);
+
+ bool mIsPacked;
+
+ uint8_t mVersion;
+ bool mPadding;
+ bool mExtension;
+ uint8_t mCsrcCount;
+ uint8_t mPayloadType;
+ uint16_t mSeqNumber;
+ bool mPTSValid;
+ int64_t mPTS;
+ uint32_t mEpoch;
+ uint16_t mProgramID;
+ uint16_t mSubstreamID;
+ LinearTransform mClockTranform;
+ bool mClockTranformValid;
+ uint8_t mTRTPVersion;
+ uint32_t mTRTPLength;
+ TRTPHeaderType mTRTPHeaderType;
+
+ uint8_t* mPacket;
+ int mPacketLen;
+
+ nsecs_t mExpireTime;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TRTPPacket);
+};
+
+class TRTPAudioPacket : public TRTPPacket {
+ public:
+ TRTPAudioPacket()
+ : TRTPPacket(kHeaderTypeAudio)
+ , mCodecType(kCodecInvalid)
+ , mRandomAccessPoint(false)
+ , mDropable(false)
+ , mDiscontinuity(false)
+ , mEndOfStream(false)
+ , mVolume(0)
+ , mAccessUnitData(NULL) { }
+
+ enum TRTPAudioCodecType {
+ kCodecInvalid = 0,
+ kCodecPCMBigEndian = 1,
+ kCodecPCMLittleEndian = 2,
+ kCodecMPEG1Audio = 3,
+ };
+
+ void setCodecType(TRTPAudioCodecType val);
+ void setRandomAccessPoint(bool val);
+ void setDropable(bool val);
+ void setDiscontinuity(bool val);
+ void setEndOfStream(bool val);
+ void setVolume(uint8_t val);
+ void setAccessUnitData(void* data, int len);
+
+ virtual bool pack();
+
+ protected:
+ virtual int TRTPHeaderLen() const;
+
+ private:
+ TRTPAudioCodecType mCodecType;
+ bool mRandomAccessPoint;
+ bool mDropable;
+ bool mDiscontinuity;
+ bool mEndOfStream;
+ uint8_t mVolume;
+ void* mAccessUnitData;
+ int mAccessUnitLen;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TRTPAudioPacket);
+};
+
+class TRTPControlPacket : public TRTPPacket {
+ public:
+ TRTPControlPacket()
+ : TRTPPacket(kHeaderTypeControl)
+ , mCommandID(kCommandNop) {}
+
+ enum TRTPCommandID {
+ kCommandNop = 1,
+ kCommandFlush = 2,
+ kCommandEOS = 3,
+ };
+
+ void setCommandID(TRTPCommandID val);
+
+ virtual bool pack();
+
+ private:
+ TRTPCommandID mCommandID;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TRTPControlPacket);
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp
new file mode 100644
index 000000000000..a79a9891ef03
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.cpp
@@ -0,0 +1,1139 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+#include <utils/Log.h>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include <netdb.h>
+#include <netinet/ip.h>
+
+#include <common_time/cc_helper.h>
+#include <media/IMediaPlayer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/Timers.h>
+
+#include "aah_tx_packet.h"
+#include "aah_tx_player.h"
+
+namespace android {
+
+static int64_t kLowWaterMarkUs = 2000000ll; // 2secs
+static int64_t kHighWaterMarkUs = 10000000ll; // 10secs
+static const size_t kLowWaterMarkBytes = 40000;
+static const size_t kHighWaterMarkBytes = 200000;
+
+// When we start up, how much lead time should we put on the first access unit?
+static const int64_t kAAHStartupLeadTimeUs = 300000LL;
+
+// How much time do we attempt to lead the clock by in steady state?
+static const int64_t kAAHBufferTimeUs = 1000000LL;
+
+// how long do we keep data in our retransmit buffer after sending it.
+const int64_t AAH_TXPlayer::kAAHRetryKeepAroundTimeNs =
+ kAAHBufferTimeUs * 1100;
+
+sp<MediaPlayerBase> createAAH_TXPlayer() {
+ sp<MediaPlayerBase> ret = new AAH_TXPlayer();
+ return ret;
+}
+
+template <typename T> static T clamp(T val, T min, T max) {
+ if (val < min) {
+ return min;
+ } else if (val > max) {
+ return max;
+ } else {
+ return val;
+ }
+}
+
+struct AAH_TXEvent : public TimedEventQueue::Event {
+ AAH_TXEvent(AAH_TXPlayer *player,
+ void (AAH_TXPlayer::*method)()) : mPlayer(player)
+ , mMethod(method) {}
+
+ protected:
+ virtual ~AAH_TXEvent() {}
+
+ virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
+ (mPlayer->*mMethod)();
+ }
+
+ private:
+ AAH_TXPlayer *mPlayer;
+ void (AAH_TXPlayer::*mMethod)();
+
+ AAH_TXEvent(const AAH_TXEvent &);
+ AAH_TXEvent& operator=(const AAH_TXEvent &);
+};
+
+AAH_TXPlayer::AAH_TXPlayer()
+ : mQueueStarted(false)
+ , mFlags(0)
+ , mExtractorFlags(0) {
+ DataSource::RegisterDefaultSniffers();
+
+ mBufferingEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onBufferingUpdate);
+ mBufferingEventPending = false;
+
+ mPumpAudioEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onPumpAudio);
+ mPumpAudioEventPending = false;
+
+ reset_l();
+}
+
+AAH_TXPlayer::~AAH_TXPlayer() {
+ if (mQueueStarted) {
+ mQueue.stop();
+ }
+
+ reset_l();
+}
+
+void AAH_TXPlayer::cancelPlayerEvents(bool keepBufferingGoing) {
+ if (!keepBufferingGoing) {
+ mQueue.cancelEvent(mBufferingEvent->eventID());
+ mBufferingEventPending = false;
+
+ mQueue.cancelEvent(mPumpAudioEvent->eventID());
+ mPumpAudioEventPending = false;
+ }
+}
+
+status_t AAH_TXPlayer::initCheck() {
+ // Check for the presense of the common time service by attempting to query
+ // for CommonTime's frequency. If we get an error back, we cannot talk to
+ // the service at all and should abort now.
+ status_t res;
+ uint64_t freq;
+ res = mCCHelper.getCommonFreq(&freq);
+ if (OK != res) {
+ ALOGE("Failed to connect to common time service! (res %d)", res);
+ return res;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ Mutex::Autolock autoLock(mLock);
+ return setDataSource_l(url, headers);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ reset_l();
+
+ // the URL must consist of "aahTX://" followed by the real URL of
+ // the data source
+ const char *kAAHPrefix = "aahTX://";
+ if (strncasecmp(url, kAAHPrefix, strlen(kAAHPrefix))) {
+ return INVALID_OPERATION;
+ }
+
+ mUri.setTo(url + strlen(kAAHPrefix));
+
+ if (headers) {
+ mUriHeaders = *headers;
+
+ ssize_t index = mUriHeaders.indexOfKey(String8("x-hide-urls-from-log"));
+ if (index >= 0) {
+ // Browser is in "incognito" mode, suppress logging URLs.
+
+ // This isn't something that should be passed to the server.
+ mUriHeaders.removeItemsAt(index);
+
+ mFlags |= INCOGNITO;
+ }
+ }
+
+ // The URL may optionally contain a "#" character followed by a Skyjam
+ // cookie. Ideally the cookie header should just be passed in the headers
+ // argument, but the Java API for supplying headers is apparently not yet
+ // exposed in the SDK used by application developers.
+ const char kSkyjamCookieDelimiter = '#';
+ char* skyjamCookie = strrchr(mUri.string(), kSkyjamCookieDelimiter);
+ if (skyjamCookie) {
+ skyjamCookie++;
+ mUriHeaders.add(String8("Cookie"), String8(skyjamCookie));
+ mUri = String8(mUri.string(), skyjamCookie - mUri.string());
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ Mutex::Autolock autoLock(mLock);
+
+ reset_l();
+
+ sp<DataSource> dataSource = new FileSource(dup(fd), offset, length);
+
+ status_t err = dataSource->initCheck();
+
+ if (err != OK) {
+ return err;
+ }
+
+ mFileSource = dataSource;
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+ if (extractor == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setVideoSurface(const sp<Surface>& surface) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVideoSurfaceTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::prepare() {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::prepareAsync() {
+ Mutex::Autolock autoLock(mLock);
+
+ return prepareAsync_l();
+}
+
+status_t AAH_TXPlayer::prepareAsync_l() {
+ if (mFlags & PREPARING) {
+ return UNKNOWN_ERROR; // async prepare already pending
+ }
+
+ mAAH_Sender = AAH_TXSender::GetInstance();
+ if (mAAH_Sender == NULL) {
+ return NO_MEMORY;
+ }
+
+ if (!mQueueStarted) {
+ mQueue.start();
+ mQueueStarted = true;
+ }
+
+ mFlags |= PREPARING;
+ mAsyncPrepareEvent = new AAH_TXEvent(
+ this, &AAH_TXPlayer::onPrepareAsyncEvent);
+
+ mQueue.postEvent(mAsyncPrepareEvent);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::finishSetDataSource_l() {
+ sp<DataSource> dataSource;
+
+ if (!strncasecmp("http://", mUri.string(), 7) ||
+ !strncasecmp("https://", mUri.string(), 8)) {
+
+ mConnectingDataSource = HTTPBase::Create(
+ (mFlags & INCOGNITO)
+ ? HTTPBase::kFlagIncognito
+ : 0);
+
+ mLock.unlock();
+ status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
+ mLock.lock();
+
+ if (err != OK) {
+ mConnectingDataSource.clear();
+
+ ALOGI("mConnectingDataSource->connect() returned %d", err);
+ return err;
+ }
+
+ mCachedSource = new NuCachedSource2(mConnectingDataSource);
+ mConnectingDataSource.clear();
+
+ dataSource = mCachedSource;
+
+ // We're going to prefill the cache before trying to instantiate
+ // the extractor below, as the latter is an operation that otherwise
+ // could block on the datasource for a significant amount of time.
+ // During that time we'd be unable to abort the preparation phase
+ // without this prefill.
+
+ mLock.unlock();
+
+ for (;;) {
+ status_t finalStatus;
+ size_t cachedDataRemaining =
+ mCachedSource->approxDataRemaining(&finalStatus);
+
+ if (finalStatus != OK ||
+ cachedDataRemaining >= kHighWaterMarkBytes ||
+ (mFlags & PREPARE_CANCELLED)) {
+ break;
+ }
+
+ usleep(200000);
+ }
+
+ mLock.lock();
+
+ if (mFlags & PREPARE_CANCELLED) {
+ ALOGI("Prepare cancelled while waiting for initial cache fill.");
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
+ }
+
+ if (dataSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+ if (extractor == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
+ // Attempt to approximate overall stream bitrate by summing all
+ // tracks' individual bitrates, if not all of them advertise bitrate,
+ // we have to fail.
+
+ int64_t totalBitRate = 0;
+
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ int32_t bitrate;
+ if (!meta->findInt32(kKeyBitRate, &bitrate)) {
+ totalBitRate = -1;
+ break;
+ }
+
+ totalBitRate += bitrate;
+ }
+
+ mBitrate = totalBitRate;
+
+ ALOGV("mBitrate = %lld bits/sec", mBitrate);
+
+ bool haveAudio = false;
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ if (!strncasecmp(mime, "audio/", 6)) {
+ mAudioSource = extractor->getTrack(i);
+ CHECK(mAudioSource != NULL);
+ haveAudio = true;
+ break;
+ }
+ }
+
+ if (!haveAudio) {
+ return UNKNOWN_ERROR;
+ }
+
+ mExtractorFlags = extractor->flags();
+
+ return OK;
+}
+
+void AAH_TXPlayer::abortPrepare(status_t err) {
+ CHECK(err != OK);
+
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
+
+ mPrepareResult = err;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mPreparedCondition.broadcast();
+}
+
+void AAH_TXPlayer::onPrepareAsyncEvent() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mFlags & PREPARE_CANCELLED) {
+ ALOGI("prepare was cancelled before doing anything");
+ abortPrepare(UNKNOWN_ERROR);
+ return;
+ }
+
+ if (mUri.size() > 0) {
+ status_t err = finishSetDataSource_l();
+
+ if (err != OK) {
+ abortPrepare(err);
+ return;
+ }
+ }
+
+ mAudioSource->getFormat()->findInt64(kKeyDuration, &mDurationUs);
+
+ status_t err = mAudioSource->start();
+ if (err != OK) {
+ ALOGI("failed to start audio source, err=%d", err);
+ abortPrepare(err);
+ return;
+ }
+
+ mFlags |= PREPARING_CONNECTED;
+
+ if (mCachedSource != NULL) {
+ postBufferingEvent_l();
+ } else {
+ finishAsyncPrepare_l();
+ }
+}
+
+void AAH_TXPlayer::finishAsyncPrepare_l() {
+ notifyListener_l(MEDIA_PREPARED);
+
+ mPrepareResult = OK;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mFlags |= PREPARED;
+ mPreparedCondition.broadcast();
+}
+
+status_t AAH_TXPlayer::start() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return play_l();
+}
+
+status_t AAH_TXPlayer::play_l() {
+ if (mFlags & PLAYING) {
+ return OK;
+ }
+
+ if (!(mFlags & PREPARED)) {
+ return INVALID_OPERATION;
+ }
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return INVALID_OPERATION;
+ }
+ if (!mEndpointRegistered) {
+ mProgramID = mAAH_Sender->registerEndpoint(mEndpoint);
+ mEndpointRegistered = true;
+ }
+ }
+
+ mFlags |= PLAYING;
+
+ updateClockTransform_l(false);
+
+ postPumpAudioEvent_l(-1);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::stop() {
+ status_t ret = pause();
+ sendEOS_l();
+ return ret;
+}
+
+status_t AAH_TXPlayer::pause() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return pause_l();
+}
+
+status_t AAH_TXPlayer::pause_l(bool doClockUpdate) {
+ if (!(mFlags & PLAYING)) {
+ return OK;
+ }
+
+ cancelPlayerEvents(true /* keepBufferingGoing */);
+
+ mFlags &= ~PLAYING;
+
+ if (doClockUpdate) {
+ updateClockTransform_l(true);
+ }
+
+ return OK;
+}
+
+void AAH_TXPlayer::updateClockTransform_l(bool pause) {
+ // record the new pause status so that onPumpAudio knows what rate to apply
+ // when it initializes the transform
+ mPlayRateIsPaused = pause;
+
+ // if we haven't yet established a valid clock transform, then we can't
+ // do anything here
+ if (!mCurrentClockTransformValid) {
+ return;
+ }
+
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+ ALOGE("updateClockTransform_l get common time failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // convert the current common time to media time using the old
+ // transform
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(
+ commonTimeNow, &mediaTimeNow)) {
+ ALOGE("updateClockTransform_l reverse transform failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // calculate a new transform that preserves the old transform's
+ // result for the current time
+ mCurrentClockTransform.a_zero = mediaTimeNow;
+ mCurrentClockTransform.b_zero = commonTimeNow;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = pause ? 0 : 1;
+
+ // send a packet announcing the new transform
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setCommandID(TRTPControlPacket::kCommandNop);
+ queuePacketToSender_l(packet);
+}
+
+void AAH_TXPlayer::sendEOS_l() {
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandEOS);
+ queuePacketToSender_l(packet);
+}
+
+bool AAH_TXPlayer::isPlaying() {
+ return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN);
+}
+
+status_t AAH_TXPlayer::seekTo(int msec) {
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ Mutex::Autolock autoLock(mLock);
+ return seekTo_l(static_cast<int64_t>(msec) * 1000);
+ }
+
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ return OK;
+}
+
+status_t AAH_TXPlayer::seekTo_l(int64_t timeUs) {
+ mIsSeeking = true;
+ mSeekTimeUs = timeUs;
+
+ mCurrentClockTransformValid = false;
+ mLastQueuedMediaTimePTSValid = false;
+
+ // send a flush command packet
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandFlush);
+ queuePacketToSender_l(packet);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getCurrentPosition(int *msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ int position;
+
+ if (mIsSeeking) {
+ position = mSeekTimeUs / 1000;
+ } else if (mCurrentClockTransformValid) {
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+ ALOGE("getCurrentPosition get common time failed");
+ return INVALID_OPERATION;
+ }
+
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(commonTimeNow,
+ &mediaTimeNow)) {
+ ALOGE("getCurrentPosition reverse transform failed");
+ return INVALID_OPERATION;
+ }
+
+ position = static_cast<int>(mediaTimeNow / 1000);
+ } else {
+ position = 0;
+ }
+
+ int duration;
+ if (getDuration_l(&duration) == OK) {
+ *msec = clamp(position, 0, duration);
+ } else {
+ *msec = (position >= 0) ? position : 0;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getDuration(int* msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ return getDuration_l(msec);
+}
+
+status_t AAH_TXPlayer::getDuration_l(int* msec) {
+ if (mDurationUs < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ *msec = (mDurationUs + 500) / 1000;
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::reset() {
+ Mutex::Autolock autoLock(mLock);
+ reset_l();
+ return OK;
+}
+
+void AAH_TXPlayer::reset_l() {
+ if (mFlags & PREPARING) {
+ mFlags |= PREPARE_CANCELLED;
+ if (mConnectingDataSource != NULL) {
+ ALOGI("interrupting the connection process");
+ mConnectingDataSource->disconnect();
+ }
+
+ if (mFlags & PREPARING_CONNECTED) {
+ // We are basically done preparing, we're just buffering
+ // enough data to start playback, we can safely interrupt that.
+ finishAsyncPrepare_l();
+ }
+ }
+
+ while (mFlags & PREPARING) {
+ mPreparedCondition.wait(mLock);
+ }
+
+ cancelPlayerEvents();
+
+ sendEOS_l();
+
+ mCachedSource.clear();
+
+ if (mAudioSource != NULL) {
+ mAudioSource->stop();
+ }
+ mAudioSource.clear();
+
+ mFlags = 0;
+ mExtractorFlags = 0;
+
+ mDurationUs = -1;
+ mIsSeeking = false;
+ mSeekTimeUs = 0;
+
+ mUri.setTo("");
+ mUriHeaders.clear();
+
+ mFileSource.clear();
+
+ mBitrate = -1;
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (mAAH_Sender != NULL && mEndpointRegistered) {
+ mAAH_Sender->unregisterEndpoint(mEndpoint);
+ }
+ mEndpointRegistered = false;
+ mEndpointValid = false;
+ }
+
+ mProgramID = 0;
+
+ mAAH_Sender.clear();
+ mLastQueuedMediaTimePTSValid = false;
+ mCurrentClockTransformValid = false;
+ mPlayRateIsPaused = false;
+
+ mTRTPVolume = 255;
+}
+
+status_t AAH_TXPlayer::setLooping(int loop) {
+ return OK;
+}
+
+player_type AAH_TXPlayer::playerType() {
+ return AAH_TX_PLAYER;
+}
+
+status_t AAH_TXPlayer::setParameter(int key, const Parcel &request) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::getParameter(int key, Parcel *reply) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::invoke(const Parcel& request, Parcel *reply) {
+ if (!reply) {
+ return BAD_VALUE;
+ }
+
+ int32_t methodID;
+ status_t err = request.readInt32(&methodID);
+ if (err != android::OK) {
+ return err;
+ }
+
+ switch (methodID) {
+ case kInvokeSetAAHDstIPPort:
+ case kInvokeSetAAHConfigBlob: {
+ if (mEndpointValid) {
+ return INVALID_OPERATION;
+ }
+
+ String8 addr;
+ uint16_t port;
+
+ if (methodID == kInvokeSetAAHDstIPPort) {
+ addr = String8(request.readString16());
+
+ int32_t port32;
+ err = request.readInt32(&port32);
+ if (err != android::OK) {
+ return err;
+ }
+ port = static_cast<uint16_t>(port32);
+ } else {
+ String8 blob(request.readString16());
+
+ char addr_buf[101];
+ if (sscanf(blob.string(), "V1:%100s %" SCNu16,
+ addr_buf, &port) != 2) {
+ return BAD_VALUE;
+ }
+ if (addr.setTo(addr_buf) != OK) {
+ return NO_MEMORY;
+ }
+ }
+
+ struct hostent* ent = gethostbyname(addr.string());
+ if (ent == NULL) {
+ return ERROR_UNKNOWN_HOST;
+ }
+ if (!(ent->h_addrtype == AF_INET && ent->h_length == 4)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mEndpointLock);
+ mEndpoint = AAH_TXSender::Endpoint(
+ reinterpret_cast<struct in_addr*>(ent->h_addr)->s_addr,
+ port);
+ mEndpointValid = true;
+ return OK;
+ };
+
+ default:
+ return INVALID_OPERATION;
+ }
+}
+
+status_t AAH_TXPlayer::getMetadata(const media::Metadata::Filter& ids,
+ Parcel* records) {
+ using media::Metadata;
+
+ Metadata metadata(records);
+
+ metadata.appendBool(Metadata::kPauseAvailable, true);
+ metadata.appendBool(Metadata::kSeekBackwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekForwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekAvailable, false);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVolume(float leftVolume, float rightVolume) {
+ if (leftVolume != rightVolume) {
+ ALOGE("%s does not support per channel volume: %f, %f",
+ __PRETTY_FUNCTION__, leftVolume, rightVolume);
+ }
+
+ float volume = clamp(leftVolume, 0.0f, 1.0f);
+
+ Mutex::Autolock lock(mLock);
+ mTRTPVolume = static_cast<uint8_t>((leftVolume * 255.0) + 0.5);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setAudioStreamType(audio_stream_type_t streamType) {
+ return OK;
+}
+
+void AAH_TXPlayer::notifyListener_l(int msg, int ext1, int ext2) {
+ sendEvent(msg, ext1, ext2);
+}
+
+bool AAH_TXPlayer::getBitrate_l(int64_t *bitrate) {
+ off64_t size;
+ if (mDurationUs >= 0 &&
+ mCachedSource != NULL &&
+ mCachedSource->getSize(&size) == OK) {
+ *bitrate = size * 8000000ll / mDurationUs; // in bits/sec
+ return true;
+ }
+
+ if (mBitrate >= 0) {
+ *bitrate = mBitrate;
+ return true;
+ }
+
+ *bitrate = 0;
+
+ return false;
+}
+
+// Returns true iff cached duration is available/applicable.
+bool AAH_TXPlayer::getCachedDuration_l(int64_t *durationUs, bool *eos) {
+ int64_t bitrate;
+
+ if (mCachedSource != NULL && getBitrate_l(&bitrate)) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ *durationUs = cachedDataRemaining * 8000000ll / bitrate;
+ *eos = (finalStatus != OK);
+ return true;
+ }
+
+ return false;
+}
+
+void AAH_TXPlayer::ensureCacheIsFetching_l() {
+ if (mCachedSource != NULL) {
+ mCachedSource->resumeFetchingIfNecessary();
+ }
+}
+
+void AAH_TXPlayer::postBufferingEvent_l() {
+ if (mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = true;
+ mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
+}
+
+void AAH_TXPlayer::postPumpAudioEvent_l(int64_t delayUs) {
+ if (mPumpAudioEventPending) {
+ return;
+ }
+ mPumpAudioEventPending = true;
+ mQueue.postEventWithDelay(mPumpAudioEvent, delayUs < 0 ? 10000 : delayUs);
+}
+
+void AAH_TXPlayer::onBufferingUpdate() {
+ Mutex::Autolock autoLock(mLock);
+ if (!mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = false;
+
+ if (mCachedSource != NULL) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ bool eos = (finalStatus != OK);
+
+ if (eos) {
+ if (finalStatus == ERROR_END_OF_STREAM) {
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ }
+ if (mFlags & PREPARING) {
+ ALOGV("cache has reached EOS, prepare is done.");
+ finishAsyncPrepare_l();
+ }
+ } else {
+ int64_t bitrate;
+ if (getBitrate_l(&bitrate)) {
+ size_t cachedSize = mCachedSource->cachedSize();
+ int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate;
+
+ int percentage = (100.0 * (double) cachedDurationUs)
+ / mDurationUs;
+ if (percentage > 100) {
+ percentage = 100;
+ }
+
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage);
+ } else {
+ // We don't know the bitrate of the stream, use absolute size
+ // limits to maintain the cache.
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDataRemaining < kLowWaterMarkBytes)) {
+ ALOGI("cache is running low (< %d) , pausing.",
+ kLowWaterMarkBytes);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDataRemaining > kHighWaterMarkBytes) {
+ if (mFlags & CACHE_UNDERRUN) {
+ ALOGI("cache has filled up (> %d), resuming.",
+ kHighWaterMarkBytes);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ ALOGV("cache has filled up (> %d), prepare is done",
+ kHighWaterMarkBytes);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+ }
+ }
+
+ int64_t cachedDurationUs;
+ bool eos;
+ if (getCachedDuration_l(&cachedDurationUs, &eos)) {
+ ALOGV("cachedDurationUs = %.2f secs, eos=%d",
+ cachedDurationUs / 1E6, eos);
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDurationUs < kLowWaterMarkUs)) {
+ ALOGI("cache is running low (%.2f secs) , pausing.",
+ cachedDurationUs / 1E6);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDurationUs > kHighWaterMarkUs) {
+ if (mFlags & CACHE_UNDERRUN) {
+ ALOGI("cache has filled up (%.2f secs), resuming.",
+ cachedDurationUs / 1E6);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ ALOGV("cache has filled up (%.2f secs), prepare is done",
+ cachedDurationUs / 1E6);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+
+ postBufferingEvent_l();
+}
+
+void AAH_TXPlayer::onPumpAudio() {
+ while (true) {
+ Mutex::Autolock autoLock(mLock);
+ // If this flag is clear, its because someone has externally canceled
+ // this pump operation (probably because we a resetting/shutting down).
+ // Get out immediately, do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Start by checking if there is still work to be doing. If we have
+ // never queued a payload (so we don't know what the last queued PTS is)
+ // or we have never established a MediaTime->CommonTime transformation,
+ // then we have work to do (one time through this loop should establish
+ // both). Otherwise, we want to keep a fixed amt of presentation time
+ // worth of data buffered. If we cannot get common time (service is
+ // unavailable, or common time is undefined)) then we don't have a lot
+ // of good options here. For now, signal an error up to the app level
+ // and shut down the transmission pump.
+ int64_t commonTimeNow;
+ if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+ // Failed to get common time; either the service is down or common
+ // time is not synced. Raise an error and shutdown the player.
+ ALOGE("*** Cannot pump audio, unable to fetch common time."
+ " Shutting down.");
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+
+ if (mCurrentClockTransformValid && mLastQueuedMediaTimePTSValid) {
+ int64_t mediaTimeNow;
+ bool conversionResult = mCurrentClockTransform.doReverseTransform(
+ commonTimeNow,
+ &mediaTimeNow);
+ CHECK(conversionResult);
+
+ if ((mediaTimeNow +
+ kAAHBufferTimeUs -
+ mLastQueuedMediaTimePTS) <= 0) {
+ break;
+ }
+ }
+
+ MediaSource::ReadOptions options;
+ if (mIsSeeking) {
+ options.setSeekTo(mSeekTimeUs);
+ }
+
+ MediaBuffer* mediaBuffer;
+ status_t err = mAudioSource->read(&mediaBuffer, &options);
+ if (err != NO_ERROR) {
+ if (err == ERROR_END_OF_STREAM) {
+ ALOGI("*** %s reached end of stream", __PRETTY_FUNCTION__);
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+ pause_l(false);
+ sendEOS_l();
+ } else {
+ ALOGE("*** %s read failed err=%d", __PRETTY_FUNCTION__, err);
+ }
+ return;
+ }
+
+ if (mIsSeeking) {
+ mIsSeeking = false;
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ }
+
+ uint8_t* data = (static_cast<uint8_t*>(mediaBuffer->data()) +
+ mediaBuffer->range_offset());
+ ALOGV("*** %s got media buffer data=[%02hhx %02hhx %02hhx %02hhx]"
+ " offset=%d length=%d", __PRETTY_FUNCTION__,
+ data[0], data[1], data[2], data[3],
+ mediaBuffer->range_offset(), mediaBuffer->range_length());
+
+ int64_t mediaTimeUs;
+ CHECK(mediaBuffer->meta_data()->findInt64(kKeyTime, &mediaTimeUs));
+ ALOGV("*** timeUs=%lld", mediaTimeUs);
+
+ if (!mCurrentClockTransformValid) {
+ if (OK == mCCHelper.getCommonTime(&commonTimeNow)) {
+ mCurrentClockTransform.a_zero = mediaTimeUs;
+ mCurrentClockTransform.b_zero = commonTimeNow +
+ kAAHStartupLeadTimeUs;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = mPlayRateIsPaused ? 0 : 1;
+ mCurrentClockTransformValid = true;
+ } else {
+ // Failed to get common time; either the service is down or
+ // common time is not synced. Raise an error and shutdown the
+ // player.
+ ALOGE("*** Cannot begin transmission, unable to fetch common"
+ " time. Dropping sample with pts=%lld", mediaTimeUs);
+ notifyListener_l(MEDIA_ERROR,
+ MEDIA_ERROR_UNKNOWN,
+ UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+ }
+
+ ALOGV("*** transmitting packet with pts=%lld", mediaTimeUs);
+
+ sp<TRTPAudioPacket> packet = new TRTPAudioPacket();
+ packet->setPTS(mediaTimeUs);
+ packet->setSubstreamID(1);
+
+ packet->setCodecType(TRTPAudioPacket::kCodecMPEG1Audio);
+ packet->setVolume(mTRTPVolume);
+ // TODO : introduce a throttle for this so we can control the
+ // frequency with which transforms get sent.
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setAccessUnitData(data, mediaBuffer->range_length());
+ packet->setRandomAccessPoint(true);
+
+ queuePacketToSender_l(packet);
+ mediaBuffer->release();
+
+ mLastQueuedMediaTimePTSValid = true;
+ mLastQueuedMediaTimePTS = mediaTimeUs;
+ }
+
+ { // Explicit scope for the autolock pattern.
+ Mutex::Autolock autoLock(mLock);
+
+ // If someone externally has cleared this flag, its because we should be
+ // shutting down. Do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Looks like no one canceled us explicitly. Clear our flag and post a
+ // new event to ourselves.
+ mPumpAudioEventPending = false;
+ postPumpAudioEvent_l(10000);
+ }
+}
+
+void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) {
+ if (mAAH_Sender == NULL) {
+ return;
+ }
+
+ sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket,
+ mAAH_Sender->handlerID());
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return;
+ }
+
+ message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr);
+ message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port);
+ }
+
+ packet->setProgramID(mProgramID);
+ packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet);
+
+ message->post();
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_player.h b/media/libaah_rtp/aah_tx_player.h
new file mode 100644
index 000000000000..64cf5dc11a03
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2011 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 __AAH_TX_PLAYER_H__
+#define __AAH_TX_PLAYER_H__
+
+#include <common_time/cc_helper.h>
+#include <libstagefright/include/HTTPBase.h>
+#include <libstagefright/include/NuCachedSource2.h>
+#include <libstagefright/include/TimedEventQueue.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+#include "aah_tx_sender.h"
+
+namespace android {
+
+class AAH_TXPlayer : public MediaPlayerHWInterface {
+ public:
+ AAH_TXPlayer();
+
+ virtual status_t initCheck();
+ virtual status_t setDataSource(const char *url,
+ const KeyedVector<String8, String8>*
+ headers);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setVideoSurface(const sp<Surface>& surface);
+ virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+ surfaceTexture);
+ virtual status_t prepare();
+ virtual status_t prepareAsync();
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t pause();
+ virtual bool isPlaying();
+ virtual status_t seekTo(int msec);
+ virtual status_t getCurrentPosition(int *msec);
+ virtual status_t getDuration(int *msec);
+ virtual status_t reset();
+ virtual status_t setLooping(int loop);
+ virtual player_type playerType();
+ virtual status_t setParameter(int key, const Parcel &request);
+ virtual status_t getParameter(int key, Parcel *reply);
+ virtual status_t invoke(const Parcel& request, Parcel *reply);
+ virtual status_t getMetadata(const media::Metadata::Filter& ids,
+ Parcel* records);
+ virtual status_t setVolume(float leftVolume, float rightVolume);
+ virtual status_t setAudioStreamType(audio_stream_type_t streamType);
+
+ // invoke method IDs
+ enum {
+ // set the IP address and port of the A@H receiver
+ kInvokeSetAAHDstIPPort = 1,
+
+ // set the destination IP address and port (and perhaps any additional
+ // parameters added in the future) packaged in one string
+ kInvokeSetAAHConfigBlob,
+ };
+
+ static const int64_t kAAHRetryKeepAroundTimeNs;
+
+ protected:
+ virtual ~AAH_TXPlayer();
+
+ private:
+ friend struct AwesomeEvent;
+
+ enum {
+ PLAYING = 1,
+ PREPARING = 8,
+ PREPARED = 16,
+ PREPARE_CANCELLED = 64,
+ CACHE_UNDERRUN = 128,
+
+ // We are basically done preparing but are currently buffering
+ // sufficient data to begin playback and finish the preparation
+ // phase for good.
+ PREPARING_CONNECTED = 2048,
+
+ INCOGNITO = 32768,
+ };
+
+ status_t setDataSource_l(const char *url,
+ const KeyedVector<String8, String8> *headers);
+ status_t setDataSource_l(const sp<MediaExtractor>& extractor);
+ status_t finishSetDataSource_l();
+ status_t prepareAsync_l();
+ void onPrepareAsyncEvent();
+ void finishAsyncPrepare_l();
+ void abortPrepare(status_t err);
+ status_t play_l();
+ status_t pause_l(bool doClockUpdate = true);
+ status_t seekTo_l(int64_t timeUs);
+ void updateClockTransform_l(bool pause);
+ void sendEOS_l();
+ void cancelPlayerEvents(bool keepBufferingGoing = false);
+ void reset_l();
+ void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0);
+ bool getBitrate_l(int64_t* bitrate);
+ status_t getDuration_l(int* msec);
+ bool getCachedDuration_l(int64_t* durationUs, bool* eos);
+ void ensureCacheIsFetching_l();
+ void postBufferingEvent_l();
+ void postPumpAudioEvent_l(int64_t delayUs);
+ void onBufferingUpdate();
+ void onPumpAudio();
+ void queuePacketToSender_l(const sp<TRTPPacket>& packet);
+
+ Mutex mLock;
+
+ TimedEventQueue mQueue;
+ bool mQueueStarted;
+
+ sp<TimedEventQueue::Event> mBufferingEvent;
+ bool mBufferingEventPending;
+
+ uint32_t mFlags;
+ uint32_t mExtractorFlags;
+
+ String8 mUri;
+ KeyedVector<String8, String8> mUriHeaders;
+
+ sp<DataSource> mFileSource;
+
+ sp<TimedEventQueue::Event> mAsyncPrepareEvent;
+ Condition mPreparedCondition;
+ status_t mPrepareResult;
+
+ bool mIsSeeking;
+ int64_t mSeekTimeUs;
+
+ sp<TimedEventQueue::Event> mPumpAudioEvent;
+ bool mPumpAudioEventPending;
+
+ sp<HTTPBase> mConnectingDataSource;
+ sp<NuCachedSource2> mCachedSource;
+
+ sp<MediaSource> mAudioSource;
+ int64_t mDurationUs;
+ int64_t mBitrate;
+
+ sp<AAH_TXSender> mAAH_Sender;
+ LinearTransform mCurrentClockTransform;
+ bool mCurrentClockTransformValid;
+ int64_t mLastQueuedMediaTimePTS;
+ bool mLastQueuedMediaTimePTSValid;
+ bool mPlayRateIsPaused;
+ CCHelper mCCHelper;
+
+ Mutex mEndpointLock;
+ AAH_TXSender::Endpoint mEndpoint;
+ bool mEndpointValid;
+ bool mEndpointRegistered;
+ uint16_t mProgramID;
+ uint8_t mTRTPVolume;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_TXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_sender.cpp b/media/libaah_rtp/aah_tx_sender.cpp
new file mode 100644
index 000000000000..d991ea76afe2
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.cpp
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+#include <media/stagefright/foundation/ADebug.h>
+
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <media/stagefright/foundation/AMessage.h>
+#include <utils/misc.h>
+
+#include "aah_tx_player.h"
+#include "aah_tx_sender.h"
+
+namespace android {
+
+const char* AAH_TXSender::kSendPacketIPAddr = "ipaddr";
+const char* AAH_TXSender::kSendPacketPort = "port";
+const char* AAH_TXSender::kSendPacketTRTPPacket = "trtp";
+
+const int AAH_TXSender::kRetryTrimIntervalUs = 100000;
+const int AAH_TXSender::kHeartbeatIntervalUs = 1000000;
+const int AAH_TXSender::kRetryBufferCapacity = 100;
+const nsecs_t AAH_TXSender::kHeartbeatTimeout = 600ull * 1000000000ull;
+
+Mutex AAH_TXSender::sLock;
+wp<AAH_TXSender> AAH_TXSender::sInstance;
+uint32_t AAH_TXSender::sNextEpoch;
+bool AAH_TXSender::sNextEpochValid = false;
+
+AAH_TXSender::AAH_TXSender() : mSocket(-1) {
+ mLastSentPacketTime = systemTime();
+}
+
+sp<AAH_TXSender> AAH_TXSender::GetInstance() {
+ Mutex::Autolock autoLock(sLock);
+
+ sp<AAH_TXSender> sender = sInstance.promote();
+
+ if (sender == NULL) {
+ sender = new AAH_TXSender();
+ if (sender == NULL) {
+ return NULL;
+ }
+
+ sender->mLooper = new ALooper();
+ if (sender->mLooper == NULL) {
+ return NULL;
+ }
+
+ sender->mReflector = new AHandlerReflector<AAH_TXSender>(sender.get());
+ if (sender->mReflector == NULL) {
+ return NULL;
+ }
+
+ sender->mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sender->mSocket == -1) {
+ ALOGW("%s unable to create socket", __PRETTY_FUNCTION__);
+ return NULL;
+ }
+
+ struct sockaddr_in bind_addr;
+ memset(&bind_addr, 0, sizeof(bind_addr));
+ bind_addr.sin_family = AF_INET;
+ if (bind(sender->mSocket,
+ reinterpret_cast<const sockaddr*>(&bind_addr),
+ sizeof(bind_addr)) < 0) {
+ ALOGW("%s unable to bind socket (errno %d)",
+ __PRETTY_FUNCTION__, errno);
+ return NULL;
+ }
+
+ sender->mRetryReceiver = new RetryReceiver(sender.get());
+ if (sender->mRetryReceiver == NULL) {
+ return NULL;
+ }
+
+ sender->mLooper->setName("AAH_TXSender");
+ sender->mLooper->registerHandler(sender->mReflector);
+ sender->mLooper->start(false, false, PRIORITY_AUDIO);
+
+ if (sender->mRetryReceiver->run("AAH_TXSenderRetry", PRIORITY_AUDIO)
+ != OK) {
+ ALOGW("%s unable to start retry thread", __PRETTY_FUNCTION__);
+ return NULL;
+ }
+
+ sInstance = sender;
+ }
+
+ return sender;
+}
+
+AAH_TXSender::~AAH_TXSender() {
+ mLooper->stop();
+ mLooper->unregisterHandler(mReflector->id());
+
+ if (mRetryReceiver != NULL) {
+ mRetryReceiver->requestExit();
+ mRetryReceiver->mWakeupEvent.setEvent();
+ if (mRetryReceiver->requestExitAndWait() != OK) {
+ ALOGW("%s shutdown of retry receiver failed", __PRETTY_FUNCTION__);
+ }
+ mRetryReceiver->mSender = NULL;
+ mRetryReceiver.clear();
+ }
+
+ if (mSocket != -1) {
+ close(mSocket);
+ }
+}
+
+// Return the next epoch number usable for a newly instantiated endpoint.
+uint32_t AAH_TXSender::getNextEpoch() {
+ Mutex::Autolock autoLock(sLock);
+
+ if (sNextEpochValid) {
+ sNextEpoch = (sNextEpoch + 1) & TRTPPacket::kTRTPEpochMask;
+ } else {
+ sNextEpoch = ns2ms(systemTime()) & TRTPPacket::kTRTPEpochMask;
+ sNextEpochValid = true;
+ }
+
+ return sNextEpoch;
+}
+
+// Notify the sender that a player has started sending to this endpoint.
+// Returns a program ID for use by the calling player.
+uint16_t AAH_TXSender::registerEndpoint(const Endpoint& endpoint) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (eps) {
+ eps->playerRefCount++;
+ } else {
+ eps = new EndpointState(getNextEpoch());
+ mEndpointMap.add(endpoint, eps);
+ }
+
+ // if this is the first registered endpoint, then send a message to start
+ // trimming retry buffers and a message to start sending heartbeats.
+ if (mEndpointMap.size() == 1) {
+ sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+ handlerID());
+ trimMessage->post(kRetryTrimIntervalUs);
+
+ sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+ handlerID());
+ heartbeatMessage->post(kHeartbeatIntervalUs);
+ }
+
+ eps->nextProgramID++;
+ return eps->nextProgramID;
+}
+
+// Notify the sender that a player has ceased sending to this endpoint.
+// An endpoint's state can not be deleted until all of the endpoint's
+// registered players have called unregisterEndpoint.
+void AAH_TXSender::unregisterEndpoint(const Endpoint& endpoint) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (eps) {
+ eps->playerRefCount--;
+ CHECK(eps->playerRefCount >= 0);
+ }
+}
+
+void AAH_TXSender::onMessageReceived(const sp<AMessage>& msg) {
+ switch (msg->what()) {
+ case kWhatSendPacket:
+ onSendPacket(msg);
+ break;
+
+ case kWhatTrimRetryBuffers:
+ trimRetryBuffers();
+ break;
+
+ case kWhatSendHeartbeats:
+ sendHeartbeats();
+ break;
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+void AAH_TXSender::onSendPacket(const sp<AMessage>& msg) {
+ sp<RefBase> obj;
+ CHECK(msg->findObject(kSendPacketTRTPPacket, &obj));
+ sp<TRTPPacket> packet = static_cast<TRTPPacket*>(obj.get());
+
+ uint32_t ipAddr;
+ CHECK(msg->findInt32(kSendPacketIPAddr,
+ reinterpret_cast<int32_t*>(&ipAddr)));
+
+ int32_t port32;
+ CHECK(msg->findInt32(kSendPacketPort, &port32));
+ uint16_t port = port32;
+
+ Mutex::Autolock lock(mEndpointLock);
+ doSendPacket_l(packet, Endpoint(ipAddr, port));
+ mLastSentPacketTime = systemTime();
+}
+
+void AAH_TXSender::doSendPacket_l(const sp<TRTPPacket>& packet,
+ const Endpoint& endpoint) {
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (!eps) {
+ // the endpoint state has disappeared, so the player that sent this
+ // packet must be dead.
+ return;
+ }
+
+ // assign the packet's sequence number
+ packet->setEpoch(eps->epoch);
+ packet->setSeqNumber(eps->trtpSeqNumber++);
+
+ // add the packet to the retry buffer
+ RetryBuffer& retry = eps->retry;
+ retry.push_back(packet);
+
+ // send the packet
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = endpoint.addr;
+ addr.sin_port = htons(endpoint.port);
+
+ ssize_t result = sendto(mSocket,
+ packet->getPacket(),
+ packet->getPacketLen(),
+ 0,
+ (const struct sockaddr *) &addr,
+ sizeof(addr));
+ if (result == -1) {
+ ALOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+}
+
+void AAH_TXSender::trimRetryBuffers() {
+ Mutex::Autolock lock(mEndpointLock);
+
+ nsecs_t localTimeNow = systemTime();
+
+ Vector<Endpoint> endpointsToRemove;
+
+ for (size_t i = 0; i < mEndpointMap.size(); i++) {
+ EndpointState* eps = mEndpointMap.editValueAt(i);
+ RetryBuffer& retry = eps->retry;
+
+ while (!retry.isEmpty()) {
+ if (retry[0]->getExpireTime() < localTimeNow) {
+ retry.pop_front();
+ } else {
+ break;
+ }
+ }
+
+ if (retry.isEmpty() && eps->playerRefCount == 0) {
+ endpointsToRemove.add(mEndpointMap.keyAt(i));
+ }
+ }
+
+ // remove the state for any endpoints that are no longer in use
+ for (size_t i = 0; i < endpointsToRemove.size(); i++) {
+ Endpoint& e = endpointsToRemove.editItemAt(i);
+ ALOGD("*** %s removing endpoint addr=%08x", __PRETTY_FUNCTION__, e.addr);
+ size_t index = mEndpointMap.indexOfKey(e);
+ delete mEndpointMap.valueAt(index);
+ mEndpointMap.removeItemsAt(index);
+ }
+
+ // schedule the next trim
+ if (mEndpointMap.size()) {
+ sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+ handlerID());
+ trimMessage->post(kRetryTrimIntervalUs);
+ }
+}
+
+void AAH_TXSender::sendHeartbeats() {
+ Mutex::Autolock lock(mEndpointLock);
+
+ if (shouldSendHeartbeats_l()) {
+ for (size_t i = 0; i < mEndpointMap.size(); i++) {
+ EndpointState* eps = mEndpointMap.editValueAt(i);
+ const Endpoint& ep = mEndpointMap.keyAt(i);
+
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandNop);
+
+ packet->setExpireTime(systemTime() +
+ AAH_TXPlayer::kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ doSendPacket_l(packet, ep);
+ }
+ }
+
+ // schedule the next heartbeat
+ if (mEndpointMap.size()) {
+ sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+ handlerID());
+ heartbeatMessage->post(kHeartbeatIntervalUs);
+ }
+}
+
+bool AAH_TXSender::shouldSendHeartbeats_l() {
+ // assert(holding endpoint lock)
+ return (systemTime() < (mLastSentPacketTime + kHeartbeatTimeout));
+}
+
+// Receiver
+
+// initial 4-byte ID of a retry request packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryRequestID = 'Treq';
+
+// initial 4-byte ID of a retry NAK packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryNakID = 'Tnak';
+
+// initial 4-byte ID of a fast start request packet
+const uint32_t AAH_TXSender::RetryReceiver::kFastStartRequestID = 'Tfst';
+
+AAH_TXSender::RetryReceiver::RetryReceiver(AAH_TXSender* sender)
+ : Thread(false),
+ mSender(sender) {}
+
+ AAH_TXSender::RetryReceiver::~RetryReceiver() {
+ mWakeupEvent.clearPendingEvents();
+ }
+
+// Returns true if val is within the interval bounded inclusively by
+// start and end. Also handles the case where there is a rollover of the
+// range between start and end.
+template <typename T>
+static inline bool withinIntervalWithRollover(T val, T start, T end) {
+ return ((start <= end && val >= start && val <= end) ||
+ (start > end && (val >= start || val <= end)));
+}
+
+bool AAH_TXSender::RetryReceiver::threadLoop() {
+ struct pollfd pollFds[2];
+ pollFds[0].fd = mSender->mSocket;
+ pollFds[0].events = POLLIN;
+ pollFds[0].revents = 0;
+ pollFds[1].fd = mWakeupEvent.getWakeupHandle();
+ pollFds[1].events = POLLIN;
+ pollFds[1].revents = 0;
+
+ int pollResult = poll(pollFds, NELEM(pollFds), -1);
+ if (pollResult == -1) {
+ ALOGE("%s poll failed", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (exitPending()) {
+ ALOGI("*** %s exiting", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (pollFds[0].revents) {
+ handleRetryRequest();
+ }
+
+ return true;
+}
+
+void AAH_TXSender::RetryReceiver::handleRetryRequest() {
+ ALOGV("*** RX %s start", __PRETTY_FUNCTION__);
+
+ RetryPacket request;
+ struct sockaddr requestSrcAddr;
+ socklen_t requestSrcAddrLen = sizeof(requestSrcAddr);
+
+ ssize_t result = recvfrom(mSender->mSocket, &request, sizeof(request), 0,
+ &requestSrcAddr, &requestSrcAddrLen);
+ if (result == -1) {
+ ALOGE("%s recvfrom failed, errno=%d", __PRETTY_FUNCTION__, errno);
+ return;
+ }
+
+ if (static_cast<size_t>(result) < sizeof(RetryPacket)) {
+ ALOGW("%s short packet received", __PRETTY_FUNCTION__);
+ return;
+ }
+
+ uint32_t host_request_id = ntohl(request.id);
+ if ((host_request_id != kRetryRequestID) &&
+ (host_request_id != kFastStartRequestID)) {
+ ALOGW("%s received retry request with bogus ID (%08x)",
+ __PRETTY_FUNCTION__, host_request_id);
+ return;
+ }
+
+ Endpoint endpoint(request.endpointIP, ntohs(request.endpointPort));
+
+ Mutex::Autolock lock(mSender->mEndpointLock);
+
+ EndpointState* eps = mSender->mEndpointMap.valueFor(endpoint);
+
+ if (eps == NULL || eps->retry.isEmpty()) {
+ // we have no retry buffer or an empty retry buffer for this endpoint,
+ // so NAK the entire request
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ ALOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ return;
+ }
+
+ RetryBuffer& retry = eps->retry;
+
+ uint16_t startSeq = ntohs(request.seqStart);
+ uint16_t endSeq = ntohs(request.seqEnd);
+
+ uint16_t retryFirstSeq = retry[0]->getSeqNumber();
+ uint16_t retryLastSeq = retry[retry.size() - 1]->getSeqNumber();
+
+ // If this is a fast start, then force the start of the retry to match the
+ // start of the retransmit ring buffer (unless the end of the retransmit
+ // ring buffer is already past the point of fast start)
+ if ((host_request_id == kFastStartRequestID) &&
+ !((startSeq - retryFirstSeq) & 0x8000)) {
+ startSeq = retryFirstSeq;
+ }
+
+ int startIndex;
+ if (withinIntervalWithRollover(startSeq, retryFirstSeq, retryLastSeq)) {
+ startIndex = static_cast<uint16_t>(startSeq - retryFirstSeq);
+ } else {
+ startIndex = -1;
+ }
+
+ int endIndex;
+ if (withinIntervalWithRollover(endSeq, retryFirstSeq, retryLastSeq)) {
+ endIndex = static_cast<uint16_t>(endSeq - retryFirstSeq);
+ } else {
+ endIndex = -1;
+ }
+
+ if (startIndex == -1 && endIndex == -1) {
+ // no part of the request range is found in the retry buffer
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ ALOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ return;
+ }
+
+ if (startIndex == -1) {
+ // NAK a subrange at the front of the request range
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ nak.seqEnd = htons(retryFirstSeq - 1);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ ALOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+
+ startIndex = 0;
+ } else if (endIndex == -1) {
+ // NAK a subrange at the back of the request range
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ nak.seqStart = htons(retryLastSeq + 1);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ ALOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+
+ endIndex = retry.size() - 1;
+ }
+
+ // send the retry packets
+ for (int i = startIndex; i <= endIndex; i++) {
+ const sp<TRTPPacket>& replyPacket = retry[i];
+
+ result = sendto(mSender->mSocket,
+ replyPacket->getPacket(),
+ replyPacket->getPacketLen(),
+ 0,
+ &requestSrcAddr,
+ requestSrcAddrLen);
+
+ if (result == -1) {
+ ALOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ }
+}
+
+// Endpoint
+
+AAH_TXSender::Endpoint::Endpoint()
+ : addr(0)
+ , port(0) { }
+
+AAH_TXSender::Endpoint::Endpoint(uint32_t a, uint16_t p)
+ : addr(a)
+ , port(p) {}
+
+bool AAH_TXSender::Endpoint::operator<(const Endpoint& other) const {
+ return ((addr < other.addr) ||
+ (addr == other.addr && port < other.port));
+}
+
+// EndpointState
+
+AAH_TXSender::EndpointState::EndpointState(uint32_t _epoch)
+ : retry(kRetryBufferCapacity)
+ , playerRefCount(1)
+ , trtpSeqNumber(0)
+ , nextProgramID(0)
+ , epoch(_epoch) { }
+
+// CircularBuffer
+
+template <typename T>
+CircularBuffer<T>::CircularBuffer(size_t capacity)
+ : mCapacity(capacity)
+ , mHead(0)
+ , mTail(0)
+ , mFillCount(0) {
+ mBuffer = new T[capacity];
+}
+
+template <typename T>
+CircularBuffer<T>::~CircularBuffer() {
+ delete [] mBuffer;
+}
+
+template <typename T>
+void CircularBuffer<T>::push_back(const T& item) {
+ if (this->isFull()) {
+ this->pop_front();
+ }
+ mBuffer[mHead] = item;
+ mHead = (mHead + 1) % mCapacity;
+ mFillCount++;
+}
+
+template <typename T>
+void CircularBuffer<T>::pop_front() {
+ CHECK(!isEmpty());
+ mBuffer[mTail] = T();
+ mTail = (mTail + 1) % mCapacity;
+ mFillCount--;
+}
+
+template <typename T>
+size_t CircularBuffer<T>::size() const {
+ return mFillCount;
+}
+
+template <typename T>
+bool CircularBuffer<T>::isFull() const {
+ return (mFillCount == mCapacity);
+}
+
+template <typename T>
+bool CircularBuffer<T>::isEmpty() const {
+ return (mFillCount == 0);
+}
+
+template <typename T>
+const T& CircularBuffer<T>::itemAt(size_t index) const {
+ CHECK(index < mFillCount);
+ return mBuffer[(mTail + index) % mCapacity];
+}
+
+template <typename T>
+const T& CircularBuffer<T>::operator[](size_t index) const {
+ return itemAt(index);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_sender.h b/media/libaah_rtp/aah_tx_sender.h
new file mode 100644
index 000000000000..74206c49b28b
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2011 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 __AAH_TX_SENDER_H__
+#define __AAH_TX_SENDER_H__
+
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+
+#include "aah_tx_packet.h"
+#include "pipe_event.h"
+
+namespace android {
+
+template <typename T> class CircularBuffer {
+ public:
+ CircularBuffer(size_t capacity);
+ ~CircularBuffer();
+ void push_back(const T& item);;
+ void pop_front();
+ size_t size() const;
+ bool isFull() const;
+ bool isEmpty() const;
+ const T& itemAt(size_t index) const;
+ const T& operator[](size_t index) const;
+
+ private:
+ T* mBuffer;
+ size_t mCapacity;
+ size_t mHead;
+ size_t mTail;
+ size_t mFillCount;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CircularBuffer);
+};
+
+class AAH_TXSender : public virtual RefBase {
+ public:
+ ~AAH_TXSender();
+
+ static sp<AAH_TXSender> GetInstance();
+
+ ALooper::handler_id handlerID() { return mReflector->id(); }
+
+ // an IP address and port
+ struct Endpoint {
+ Endpoint();
+ Endpoint(uint32_t a, uint16_t p);
+ bool operator<(const Endpoint& other) const;
+
+ uint32_t addr;
+ uint16_t port;
+ };
+
+ uint16_t registerEndpoint(const Endpoint& endpoint);
+ void unregisterEndpoint(const Endpoint& endpoint);
+
+ enum {
+ kWhatSendPacket,
+ kWhatTrimRetryBuffers,
+ kWhatSendHeartbeats,
+ };
+
+ // fields for SendPacket messages
+ static const char* kSendPacketIPAddr;
+ static const char* kSendPacketPort;
+ static const char* kSendPacketTRTPPacket;
+
+ private:
+ AAH_TXSender();
+
+ static Mutex sLock;
+ static wp<AAH_TXSender> sInstance;
+ static uint32_t sNextEpoch;
+ static bool sNextEpochValid;
+
+ static uint32_t getNextEpoch();
+
+ typedef CircularBuffer<sp<TRTPPacket> > RetryBuffer;
+
+ // state maintained on a per-endpoint basis
+ struct EndpointState {
+ EndpointState(uint32_t epoch);
+ RetryBuffer retry;
+ int playerRefCount;
+ uint16_t trtpSeqNumber;
+ uint16_t nextProgramID;
+ uint32_t epoch;
+ };
+
+ friend class AHandlerReflector<AAH_TXSender>;
+ void onMessageReceived(const sp<AMessage>& msg);
+ void onSendPacket(const sp<AMessage>& msg);
+ void doSendPacket_l(const sp<TRTPPacket>& packet,
+ const Endpoint& endpoint);
+ void trimRetryBuffers();
+ void sendHeartbeats();
+ bool shouldSendHeartbeats_l();
+
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<AAH_TXSender> > mReflector;
+
+ int mSocket;
+ nsecs_t mLastSentPacketTime;
+
+ DefaultKeyedVector<Endpoint, EndpointState*> mEndpointMap;
+ Mutex mEndpointLock;
+
+ static const int kRetryTrimIntervalUs;
+ static const int kHeartbeatIntervalUs;
+ static const int kRetryBufferCapacity;
+ static const nsecs_t kHeartbeatTimeout;
+
+ class RetryReceiver : public Thread {
+ private:
+ friend class AAH_TXSender;
+
+ RetryReceiver(AAH_TXSender* sender);
+ virtual ~RetryReceiver();
+ virtual bool threadLoop();
+ void handleRetryRequest();
+
+ static const int kMaxReceiverPacketLen;
+ static const uint32_t kRetryRequestID;
+ static const uint32_t kFastStartRequestID;
+ static const uint32_t kRetryNakID;
+
+ AAH_TXSender* mSender;
+ PipeEvent mWakeupEvent;
+ };
+
+ sp<RetryReceiver> mRetryReceiver;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_TXSender);
+};
+
+struct RetryPacket {
+ uint32_t id;
+ uint32_t endpointIP;
+ uint16_t endpointPort;
+ uint16_t seqStart;
+ uint16_t seqEnd;
+} __attribute__((packed));
+
+} // namespace android
+
+#endif // __AAH_TX_SENDER_H__
diff --git a/media/libaah_rtp/pipe_event.cpp b/media/libaah_rtp/pipe_event.cpp
new file mode 100644
index 000000000000..b8e696080ca9
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 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 "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include "pipe_event.h"
+
+namespace android {
+
+PipeEvent::PipeEvent() {
+ pipe_[0] = -1;
+ pipe_[1] = -1;
+
+ // Create the pipe.
+ if (pipe(pipe_) >= 0) {
+ // Set non-blocking mode on the read side of the pipe so we can
+ // easily drain it whenever we wakeup.
+ fcntl(pipe_[0], F_SETFL, O_NONBLOCK);
+ } else {
+ ALOGE("Failed to create pipe event %d %d %d",
+ pipe_[0], pipe_[1], errno);
+ pipe_[0] = -1;
+ pipe_[1] = -1;
+ }
+}
+
+PipeEvent::~PipeEvent() {
+ if (pipe_[0] >= 0) {
+ close(pipe_[0]);
+ }
+
+ if (pipe_[1] >= 0) {
+ close(pipe_[1]);
+ }
+}
+
+void PipeEvent::clearPendingEvents() {
+ char drain_buffer[16];
+ while (read(pipe_[0], drain_buffer, sizeof(drain_buffer)) > 0) {
+ // No body.
+ }
+}
+
+bool PipeEvent::wait(int timeout) {
+ struct pollfd wait_fd;
+
+ wait_fd.fd = getWakeupHandle();
+ wait_fd.events = POLLIN;
+ wait_fd.revents = 0;
+
+ int res = poll(&wait_fd, 1, timeout);
+
+ if (res < 0) {
+ ALOGE("Wait error in PipeEvent; sleeping to prevent overload!");
+ usleep(1000);
+ }
+
+ return (res > 0);
+}
+
+void PipeEvent::setEvent() {
+ char foo = 'q';
+ write(pipe_[1], &foo, 1);
+}
+
+} // namespace android
+
diff --git a/media/libaah_rtp/pipe_event.h b/media/libaah_rtp/pipe_event.h
new file mode 100644
index 000000000000..e53b0fdb74c0
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 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 __PIPE_EVENT_H__
+#define __PIPE_EVENT_H__
+
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+class PipeEvent {
+ public:
+ PipeEvent();
+ ~PipeEvent();
+
+ bool initCheck() const {
+ return ((pipe_[0] >= 0) && (pipe_[1] >= 0));
+ }
+
+ int getWakeupHandle() const { return pipe_[0]; }
+
+ // block until the event fires; returns true if the event fired and false if
+ // the wait timed out. Timeout is expressed in milliseconds; negative
+ // values mean wait forever.
+ bool wait(int timeout = -1);
+
+ void clearPendingEvents();
+ void setEvent();
+
+ private:
+ int pipe_[2];
+
+ DISALLOW_EVIL_CONSTRUCTORS(PipeEvent);
+};
+
+} // namespace android
+
+#endif // __PIPE_EVENT_H__
diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp
index f9f997fe6a32..19b7e324b32b 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -202,7 +202,7 @@ bool AudioEffect::getEnabled() const
status_t AudioEffect::setEnabled(bool enabled)
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
status_t status = NO_ERROR;
@@ -231,7 +231,7 @@ status_t AudioEffect::command(uint32_t cmdCode,
{
if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) {
ALOGV("command() bad status %d", mStatus);
- return INVALID_OPERATION;
+ return mStatus;
}
if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) {
@@ -263,7 +263,7 @@ status_t AudioEffect::command(uint32_t cmdCode,
status_t AudioEffect::setParameter(effect_param_t *param)
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -281,7 +281,7 @@ status_t AudioEffect::setParameter(effect_param_t *param)
status_t AudioEffect::setParameterDeferred(effect_param_t *param)
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -307,7 +307,7 @@ status_t AudioEffect::setParameterDeferred(effect_param_t *param)
status_t AudioEffect::setParameterCommit()
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
Mutex::Autolock _l(mCblk->lock);
@@ -321,7 +321,7 @@ status_t AudioEffect::setParameterCommit()
status_t AudioEffect::getParameter(effect_param_t *param)
{
if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) {
- return INVALID_OPERATION;
+ return mStatus;
}
if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -341,7 +341,7 @@ status_t AudioEffect::getParameter(effect_param_t *param)
void AudioEffect::binderDied()
{
ALOGW("IEffect died");
- mStatus = NO_INIT;
+ mStatus = DEAD_OBJECT;
if (mCbf != NULL) {
status_t status = DEAD_OBJECT;
mCbf(EVENT_ERROR, mUserData, &status);
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index aead9a1654be..74c97ed932cf 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -80,7 +80,9 @@ status_t AudioTrack::getMinFrameCount(
AudioTrack::AudioTrack()
: mStatus(NO_INIT),
- mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT)
+ mIsTimed(false),
+ mPreviousPriority(ANDROID_PRIORITY_NORMAL),
+ mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT)
{
}
@@ -96,7 +98,9 @@ AudioTrack::AudioTrack(
int notificationFrames,
int sessionId)
: mStatus(NO_INIT),
- mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT)
+ mIsTimed(false),
+ mPreviousPriority(ANDROID_PRIORITY_NORMAL),
+ mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT)
{
mStatus = set(streamType, sampleRate, format, channelMask,
frameCount, flags, cbf, user, notificationFrames,
@@ -134,7 +138,9 @@ AudioTrack::AudioTrack(
int notificationFrames,
int sessionId)
: mStatus(NO_INIT),
- mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT)
+ mIsTimed(false),
+ mPreviousPriority(ANDROID_PRIORITY_NORMAL),
+ mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT)
{
mStatus = set(streamType, sampleRate, format, channelMask,
0, flags, cbf, user, notificationFrames,
@@ -540,6 +546,10 @@ status_t AudioTrack::setSampleRate(int rate)
{
int afSamplingRate;
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) {
return NO_INIT;
}
@@ -553,6 +563,10 @@ status_t AudioTrack::setSampleRate(int rate)
uint32_t AudioTrack::getSampleRate() const
{
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
AutoMutex lock(mLock);
return mCblk->sampleRate;
}
@@ -578,6 +592,10 @@ status_t AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCou
return NO_ERROR;
}
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
if (loopStart >= loopEnd ||
loopEnd - loopStart > cblk->frameCount ||
cblk->server > loopStart) {
@@ -641,6 +659,8 @@ status_t AudioTrack::getPositionUpdatePeriod(uint32_t *updatePeriod) const
status_t AudioTrack::setPosition(uint32_t position)
{
+ if (mIsTimed) return INVALID_OPERATION;
+
AutoMutex lock(mLock);
if (!stopped_l()) return INVALID_OPERATION;
@@ -791,6 +811,7 @@ status_t AudioTrack::createTrack_l(
((uint16_t)flags) << 16,
sharedBuffer,
output,
+ mIsTimed,
&mSessionId,
&status);
@@ -957,6 +978,7 @@ ssize_t AudioTrack::write(const void* buffer, size_t userSize)
{
if (mSharedBuffer != 0) return INVALID_OPERATION;
+ if (mIsTimed) return INVALID_OPERATION;
if (ssize_t(userSize) < 0) {
// Sanity-check: user is most-likely passing an error code, and it would
@@ -1013,6 +1035,59 @@ ssize_t AudioTrack::write(const void* buffer, size_t userSize)
// -------------------------------------------------------------------------
+TimedAudioTrack::TimedAudioTrack() {
+ mIsTimed = true;
+}
+
+status_t TimedAudioTrack::allocateTimedBuffer(size_t size, sp<IMemory>* buffer)
+{
+ status_t result = UNKNOWN_ERROR;
+
+ // If the track is not invalid already, try to allocate a buffer. alloc
+ // fails indicating that the server is dead, flag the track as invalid so
+ // we can attempt to restore in in just a bit.
+ if (!(mCblk->flags & CBLK_INVALID_MSK)) {
+ result = mAudioTrack->allocateTimedBuffer(size, buffer);
+ if (result == DEAD_OBJECT) {
+ android_atomic_or(CBLK_INVALID_ON, &mCblk->flags);
+ }
+ }
+
+ // If the track is invalid at this point, attempt to restore it. and try the
+ // allocation one more time.
+ if (mCblk->flags & CBLK_INVALID_MSK) {
+ mCblk->lock.lock();
+ result = restoreTrack_l(mCblk, false);
+ mCblk->lock.unlock();
+
+ if (result == OK)
+ result = mAudioTrack->allocateTimedBuffer(size, buffer);
+ }
+
+ return result;
+}
+
+status_t TimedAudioTrack::queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts)
+{
+ // restart track if it was disabled by audioflinger due to previous underrun
+ if (mActive && (mCblk->flags & CBLK_DISABLED_MSK)) {
+ android_atomic_and(~CBLK_DISABLED_ON, &mCblk->flags);
+ ALOGW("queueTimedBuffer() track %p disabled, restarting", this);
+ mAudioTrack->start(0);
+ }
+
+ return mAudioTrack->queueTimedBuffer(buffer, pts);
+}
+
+status_t TimedAudioTrack::setMediaTimeTransform(const LinearTransform& xform,
+ TargetTimeline target)
+{
+ return mAudioTrack->setMediaTimeTransform(xform, target);
+}
+
+// -------------------------------------------------------------------------
+
bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
{
Buffer audioBuffer;
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 4507e5db7683..ebadbfa5499c 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -90,6 +90,7 @@ public:
uint32_t flags,
const sp<IMemory>& sharedBuffer,
audio_io_handle_t output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -105,6 +106,7 @@ public:
data.writeInt32(flags);
data.writeStrongBinder(sharedBuffer->asBinder());
data.writeInt32((int32_t) output);
+ data.writeInt32(isTimed);
int lSessionId = 0;
if (sessionId != NULL) {
lSessionId = *sessionId;
@@ -689,11 +691,12 @@ status_t BnAudioFlinger::onTransact(
uint32_t flags = data.readInt32();
sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder());
audio_io_handle_t output = (audio_io_handle_t) data.readInt32();
+ bool isTimed = data.readInt32();
int sessionId = data.readInt32();
status_t status;
sp<IAudioTrack> track = createTrack(pid,
(audio_stream_type_t) streamType, sampleRate, format,
- channelCount, bufferCount, flags, buffer, output, &sessionId, &status);
+ channelCount, bufferCount, flags, buffer, output, isTimed, &sessionId, &status);
reply->writeInt32(sessionId);
reply->writeInt32(status);
reply->writeStrongBinder(track->asBinder());
diff --git a/media/libmedia/IAudioTrack.cpp b/media/libmedia/IAudioTrack.cpp
index a7958debe1ae..28ebbbfc73be 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -35,7 +35,10 @@ enum {
FLUSH,
MUTE,
PAUSE,
- ATTACH_AUX_EFFECT
+ ATTACH_AUX_EFFECT,
+ ALLOCATE_TIMED_BUFFER,
+ QUEUE_TIMED_BUFFER,
+ SET_MEDIA_TIME_TRANSFORM,
};
class BpAudioTrack : public BpInterface<IAudioTrack>
@@ -114,6 +117,52 @@ public:
}
return status;
}
+
+ virtual status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeInt32(size);
+ status_t status = remote()->transact(ALLOCATE_TIMED_BUFFER,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ if (status == NO_ERROR) {
+ *buffer = interface_cast<IMemory>(reply.readStrongBinder());
+ }
+ }
+ return status;
+ }
+
+ virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeStrongBinder(buffer->asBinder());
+ data.writeInt64(pts);
+ status_t status = remote()->transact(QUEUE_TIMED_BUFFER,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ }
+ return status;
+ }
+
+ virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+ int target) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeInt64(xform.a_zero);
+ data.writeInt64(xform.b_zero);
+ data.writeInt32(xform.a_to_b_numer);
+ data.writeInt32(xform.a_to_b_denom);
+ data.writeInt32(target);
+ status_t status = remote()->transact(SET_MEDIA_TIME_TRANSFORM,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ }
+ return status;
+ }
};
IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack");
@@ -159,10 +208,38 @@ status_t BnAudioTrack::onTransact(
reply->writeInt32(attachAuxEffect(data.readInt32()));
return NO_ERROR;
} break;
+ case ALLOCATE_TIMED_BUFFER: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ sp<IMemory> buffer;
+ status_t status = allocateTimedBuffer(data.readInt32(), &buffer);
+ reply->writeInt32(status);
+ if (status == NO_ERROR) {
+ reply->writeStrongBinder(buffer->asBinder());
+ }
+ return NO_ERROR;
+ } break;
+ case QUEUE_TIMED_BUFFER: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ sp<IMemory> buffer = interface_cast<IMemory>(
+ data.readStrongBinder());
+ uint64_t pts = data.readInt64();
+ reply->writeInt32(queueTimedBuffer(buffer, pts));
+ return NO_ERROR;
+ } break;
+ case SET_MEDIA_TIME_TRANSFORM: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ LinearTransform xform;
+ xform.a_zero = data.readInt64();
+ xform.b_zero = data.readInt64();
+ xform.a_to_b_numer = data.readInt32();
+ xform.a_to_b_denom = data.readInt32();
+ int target = data.readInt32();
+ reply->writeInt32(setMediaTimeTransform(xform, target));
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
}; // namespace android
-
diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp
index d469e280f647..5d40cc82af48 100644
--- a/media/libmedia/IEffect.cpp
+++ b/media/libmedia/IEffect.cpp
@@ -83,8 +83,15 @@ public:
size = *pReplySize;
}
data.writeInt32(size);
- remote()->transact(COMMAND, data, &reply);
- status_t status = reply.readInt32();
+
+ status_t status = remote()->transact(COMMAND, data, &reply);
+ if (status != NO_ERROR) {
+ if (pReplySize != NULL)
+ *pReplySize = 0;
+ return status;
+ }
+
+ status = reply.readInt32();
size = reply.readInt32();
if (size != 0 && pReplyData != NULL && pReplySize != NULL) {
reply.read(pReplyData, size);
diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp
index 13b64e9f2917..70f8c0c28e8e 100644
--- a/media/libmedia/Visualizer.cpp
+++ b/media/libmedia/Visualizer.cpp
@@ -168,7 +168,7 @@ status_t Visualizer::getWaveForm(uint8_t *waveform)
uint32_t replySize = mCaptureSize;
status = command(VISUALIZER_CMD_CAPTURE, 0, NULL, &replySize, waveform);
ALOGV("getWaveForm() command returned %d", status);
- if (replySize == 0) {
+ if ((status == NO_ERROR) && (replySize == 0)) {
status = NOT_ENOUGH_DATA;
}
} else {
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index a3e2517b37d2..e521648fde9f 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -29,7 +29,8 @@ LOCAL_SHARED_LIBRARIES := \
libstagefright_omx \
libstagefright_foundation \
libgui \
- libdl
+ libdl \
+ libaah_rtp
LOCAL_STATIC_LIBRARIES := \
libstagefright_nuplayer \
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 4df7f3d654c4..764eddc840a8 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -70,6 +70,11 @@
#include <OMX.h>
+namespace android {
+sp<MediaPlayerBase> createAAH_TXPlayer();
+sp<MediaPlayerBase> createAAH_RXPlayer();
+}
+
namespace {
using android::media::Metadata;
using android::status_t;
@@ -593,6 +598,14 @@ player_type getPlayerType(const char* url)
return NU_PLAYER;
}
+ if (!strncasecmp("aahRX://", url, 8)) {
+ return AAH_RX_PLAYER;
+ }
+
+ if (!strncasecmp("aahTX://", url, 8)) {
+ return AAH_TX_PLAYER;
+ }
+
// use MidiFile for MIDI extensions
int lenURL = strlen(url);
for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
@@ -629,6 +642,14 @@ static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,
ALOGV("Create Test Player stub");
p = new TestPlayerStub();
break;
+ case AAH_RX_PLAYER:
+ ALOGV(" create A@H RX Player");
+ p = createAAH_RXPlayer();
+ break;
+ case AAH_TX_PLAYER:
+ ALOGV(" create A@H TX Player");
+ p = createAAH_TXPlayer();
+ break;
default:
ALOGE("Unknown player type: %d", playerType);
return NULL;
@@ -1031,9 +1052,21 @@ status_t MediaPlayerService::Client::setLooping(int loop)
status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume)
{
ALOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume);
- // TODO: for hardware output, call player instead
- Mutex::Autolock l(mLock);
- if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+
+ // for hardware output, call player instead
+ sp<MediaPlayerBase> p = getPlayer();
+ {
+ Mutex::Autolock l(mLock);
+ if (p != 0 && p->hardwareOutput()) {
+ MediaPlayerHWInterface* hwp =
+ reinterpret_cast<MediaPlayerHWInterface*>(p.get());
+ return hwp->setVolume(leftVolume, rightVolume);
+ } else {
+ if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+ return NO_ERROR;
+ }
+ }
+
return NO_ERROR;
}
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 4a56dcffde22..eec5ce1a0677 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -202,34 +202,6 @@ egl_connection_t* validate_display_config(EGLDisplay dpy, EGLConfig,
// ----------------------------------------------------------------------------
-EGLImageKHR egl_get_image_for_current_context(EGLImageKHR image)
-{
- EGLContext context = egl_tls_t::getContext();
- if (context == EGL_NO_CONTEXT || image == EGL_NO_IMAGE_KHR)
- return EGL_NO_IMAGE_KHR;
-
- egl_context_t const * const c = get_context(context);
- if (c == NULL) // this should never happen, by construction
- return EGL_NO_IMAGE_KHR;
-
- egl_display_t* display = egl_display_t::get(c->dpy);
- if (display == NULL) // this should never happen, by construction
- return EGL_NO_IMAGE_KHR;
-
- ImageRef _i(display, image);
- if (!_i.get())
- return EGL_NO_IMAGE_KHR;
-
- // here we don't validate the context because if it's been marked for
- // termination, this call should still succeed since it's internal to
- // EGL.
-
- egl_image_t const * const i = get_image(image);
- return i->image;
-}
-
-// ----------------------------------------------------------------------------
-
const GLubyte * egl_get_string_for_current_context(GLenum name) {
// NOTE: returning NULL here will fall-back to the default
// implementation.
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index bb2783dc26bc..a5dc832bf6de 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -627,29 +627,6 @@ EGLint eglGetError(void)
return err;
}
-// Note: Similar implementations of these functions also exist in
-// gl2.cpp and gl.cpp, and are used by applications that call the
-// exported entry points directly.
-typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image);
-typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC) (GLenum target, GLeglImageOES image);
-
-static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES_impl = NULL;
-static PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES_impl = NULL;
-
-static void glEGLImageTargetTexture2DOES_wrapper(GLenum target, GLeglImageOES image)
-{
- GLeglImageOES implImage =
- (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image);
- glEGLImageTargetTexture2DOES_impl(target, implImage);
-}
-
-static void glEGLImageTargetRenderbufferStorageOES_wrapper(GLenum target, GLeglImageOES image)
-{
- GLeglImageOES implImage =
- (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image);
- glEGLImageTargetRenderbufferStorageOES_impl(target, implImage);
-}
-
__eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname)
{
// eglGetProcAddress() could be the very first function called
@@ -724,16 +701,6 @@ __eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname)
if (found) {
addr = gExtensionForwarders[slot];
-
- if (!strcmp(procname, "glEGLImageTargetTexture2DOES")) {
- glEGLImageTargetTexture2DOES_impl = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)addr;
- addr = (__eglMustCastToProperFunctionPointerType)glEGLImageTargetTexture2DOES_wrapper;
- }
- if (!strcmp(procname, "glEGLImageTargetRenderbufferStorageOES")) {
- glEGLImageTargetRenderbufferStorageOES_impl = (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC)addr;
- addr = (__eglMustCastToProperFunctionPointerType)glEGLImageTargetRenderbufferStorageOES_wrapper;
- }
-
sGLExtentionMap.add(name, addr);
sGLExtentionSlot++;
}
@@ -1024,57 +991,18 @@ EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target,
egl_display_t const * const dp = validate_display(dpy);
if (!dp) return EGL_NO_IMAGE_KHR;
- if (ctx != EGL_NO_CONTEXT) {
- ContextRef _c(dp, ctx);
- if (!_c.get())
- return setError(EGL_BAD_CONTEXT, EGL_NO_IMAGE_KHR);
- egl_context_t * const c = get_context(ctx);
- // since we have an EGLContext, we know which implementation to use
- EGLImageKHR image = c->cnx->egl.eglCreateImageKHR(
- dp->disp.dpy, c->context, target, buffer, attrib_list);
- if (image == EGL_NO_IMAGE_KHR)
- return image;
-
- egl_image_t* result = new egl_image_t(dpy, ctx);
- result->image = image;
- return (EGLImageKHR)result;
- } else {
- // EGL_NO_CONTEXT is a valid parameter
-
- /* Since we don't have a way to know which implementation to call,
- * we're calling all of them. If at least one of the implementation
- * succeeded, this is a success.
- */
-
- EGLint currentError = eglGetError();
-
- EGLImageKHR implImage = EGL_NO_IMAGE_KHR;
- egl_connection_t* const cnx = &gEGLImpl;
- if (cnx->dso && cnx->egl.eglCreateImageKHR) {
- implImage = cnx->egl.eglCreateImageKHR(
- dp->disp.dpy, ctx, target, buffer, attrib_list);
- }
-
- if (implImage == EGL_NO_IMAGE_KHR) {
- // failure, if there was an error when we entered this function,
- // the error flag must not be updated.
- // Otherwise, the error is whatever happened in the implementation
- // that faulted.
- if (currentError != EGL_SUCCESS) {
- setError(currentError, EGL_NO_IMAGE_KHR);
- }
- return EGL_NO_IMAGE_KHR;
- } else {
- // In case of success, we need to clear all error flags
- // (especially those caused by the implementation that didn't
- // succeed).
- eglGetError();
- }
+ ContextRef _c(dp, ctx);
+ egl_context_t * const c = _c.get();
- egl_image_t* result = new egl_image_t(dpy, ctx);
- result->image = implImage;
- return (EGLImageKHR)result;
+ EGLImageKHR result = EGL_NO_IMAGE_KHR;
+ egl_connection_t* const cnx = &gEGLImpl;
+ if (cnx->dso && cnx->egl.eglCreateImageKHR) {
+ result = cnx->egl.eglCreateImageKHR(
+ dp->disp.dpy,
+ c ? c->context : EGL_NO_CONTEXT,
+ target, buffer, attrib_list);
}
+ return result;
}
EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img)
@@ -1084,27 +1012,10 @@ EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img)
egl_display_t const * const dp = validate_display(dpy);
if (!dp) return EGL_FALSE;
- ImageRef _i(dp, img);
- if (!_i.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
-
- egl_image_t* image = get_image(img);
- bool success = false;
-
egl_connection_t* const cnx = &gEGLImpl;
- if (image->image != EGL_NO_IMAGE_KHR) {
- if (cnx->dso && cnx->egl.eglDestroyImageKHR) {
- if (cnx->egl.eglDestroyImageKHR(
- dp->disp.dpy, image->image)) {
- success = true;
- }
- }
+ if (cnx->dso && cnx->egl.eglDestroyImageKHR) {
+ cnx->egl.eglDestroyImageKHR(dp->disp.dpy, img);
}
-
- if (!success)
- return EGL_FALSE;
-
- _i.terminate();
-
return EGL_TRUE;
}
@@ -1120,21 +1031,12 @@ EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_l
egl_display_t const * const dp = validate_display(dpy);
if (!dp) return EGL_NO_SYNC_KHR;
- EGLContext ctx = eglGetCurrentContext();
- ContextRef _c(dp, ctx);
- if (!_c.get())
- return setError(EGL_BAD_CONTEXT, EGL_NO_SYNC_KHR);
-
- egl_context_t * const c = get_context(ctx);
EGLSyncKHR result = EGL_NO_SYNC_KHR;
- if (c->cnx->egl.eglCreateSyncKHR) {
- EGLSyncKHR sync = c->cnx->egl.eglCreateSyncKHR(
- dp->disp.dpy, type, attrib_list);
- if (sync == EGL_NO_SYNC_KHR)
- return sync;
- result = (egl_sync_t*)new egl_sync_t(dpy, ctx, sync);
+ egl_connection_t* const cnx = &gEGLImpl;
+ if (cnx->dso && cnx->egl.eglCreateSyncKHR) {
+ result = cnx->egl.eglCreateSyncKHR(dp->disp.dpy, type, attrib_list);
}
- return (EGLSyncKHR)result;
+ return result;
}
EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync)
@@ -1144,75 +1046,46 @@ EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync)
egl_display_t const * const dp = validate_display(dpy);
if (!dp) return EGL_FALSE;
- SyncRef _s(dp, sync);
- if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
- egl_sync_t* syncObject = get_sync(sync);
-
- EGLContext ctx = syncObject->context;
- ContextRef _c(dp, ctx);
- if (!_c.get())
- return setError(EGL_BAD_CONTEXT, EGL_FALSE);
-
EGLBoolean result = EGL_FALSE;
- egl_context_t * const c = get_context(ctx);
- if (c->cnx->egl.eglDestroySyncKHR) {
- result = c->cnx->egl.eglDestroySyncKHR(
- dp->disp.dpy, syncObject->sync);
- if (result)
- _s.terminate();
+ egl_connection_t* const cnx = &gEGLImpl;
+ if (cnx->dso && cnx->egl.eglDestroySyncKHR) {
+ result = cnx->egl.eglDestroySyncKHR(dp->disp.dpy, sync);
}
return result;
}
-EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout)
+EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync,
+ EGLint flags, EGLTimeKHR timeout)
{
clearError();
egl_display_t const * const dp = validate_display(dpy);
if (!dp) return EGL_FALSE;
- SyncRef _s(dp, sync);
- if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE);
- egl_sync_t* syncObject = get_sync(sync);
-
- EGLContext ctx = syncObject->context;
- ContextRef _c(dp, ctx);
- if (!_c.get())
- return setError(EGL_BAD_CONTEXT, EGL_FALSE);
-
- egl_context_t * const c = get_context(ctx);
- if (c->cnx->egl.eglClientWaitSyncKHR) {
- return c->cnx->egl.eglClientWaitSyncKHR(
- dp->disp.dpy, syncObject->sync, flags, timeout);
+ EGLBoolean result = EGL_FALSE;
+ egl_connection_t* const cnx = &gEGLImpl;
+ if (cnx->dso && cnx->egl.eglClientWaitSyncKHR) {
+ result = cnx->egl.eglClientWaitSyncKHR(
+ dp->disp.dpy, sync, flags, timeout);
}
-
- return EGL_FALSE;
+ return result;
}
-EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value)
+EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync,
+ EGLint attribute, EGLint *value)
{
clearError();
egl_display_t const * const dp = validate_display(dpy);
if (!dp) return EGL_FALSE;
- SyncRef _s(dp, sync);
- if (!_s.get())
- return setError(EGL_BAD_PARAMETER, EGL_FALSE);
-
- egl_sync_t* syncObject = get_sync(sync);
- EGLContext ctx = syncObject->context;
- ContextRef _c(dp, ctx);
- if (!_c.get())
- return setError(EGL_BAD_CONTEXT, EGL_FALSE);
-
- egl_context_t * const c = get_context(ctx);
- if (c->cnx->egl.eglGetSyncAttribKHR) {
- return c->cnx->egl.eglGetSyncAttribKHR(
- dp->disp.dpy, syncObject->sync, attribute, value);
+ EGLBoolean result = EGL_FALSE;
+ egl_connection_t* const cnx = &gEGLImpl;
+ if (cnx->dso && cnx->egl.eglGetSyncAttribKHR) {
+ result = cnx->egl.eglGetSyncAttribKHR(
+ dp->disp.dpy, sync, attribute, value);
}
-
- return EGL_FALSE;
+ return result;
}
// ----------------------------------------------------------------------------
diff --git a/opengl/libs/EGL/egl_object.h b/opengl/libs/EGL/egl_object.h
index f137badd1190..4d91f5417b0b 100644
--- a/opengl/libs/EGL/egl_object.h
+++ b/opengl/libs/EGL/egl_object.h
@@ -173,40 +173,10 @@ public:
String8 gl_extensions;
};
-class egl_image_t: public egl_object_t {
-protected:
- ~egl_image_t() {}
-public:
- typedef egl_object_t::LocalRef<egl_image_t, EGLImageKHR> Ref;
-
- egl_image_t(EGLDisplay dpy, EGLContext context) :
- egl_object_t(get_display(dpy)),
- dpy(dpy), context(context), image(EGL_NO_IMAGE_KHR) { }
- EGLDisplay dpy;
- EGLContext context;
- EGLImageKHR image;
-};
-
-class egl_sync_t: public egl_object_t {
-protected:
- ~egl_sync_t() {}
-public:
- typedef egl_object_t::LocalRef<egl_sync_t, EGLSyncKHR> Ref;
-
- egl_sync_t(EGLDisplay dpy, EGLContext context, EGLSyncKHR sync) :
- egl_object_t(get_display(dpy)), dpy(dpy), context(context), sync(sync) {
- }
- EGLDisplay dpy;
- EGLContext context;
- EGLSyncKHR sync;
-};
-
// ----------------------------------------------------------------------------
typedef egl_surface_t::Ref SurfaceRef;
typedef egl_context_t::Ref ContextRef;
-typedef egl_image_t::Ref ImageRef;
-typedef egl_sync_t::Ref SyncRef;
// ----------------------------------------------------------------------------
@@ -225,16 +195,6 @@ egl_context_t* get_context(EGLContext context) {
return egl_to_native_cast<egl_context_t>(context);
}
-static inline
-egl_image_t* get_image(EGLImageKHR image) {
- return egl_to_native_cast<egl_image_t>(image);
-}
-
-static inline
-egl_sync_t* get_sync(EGLSyncKHR sync) {
- return egl_to_native_cast<egl_sync_t>(sync);
-}
-
// ----------------------------------------------------------------------------
}; // namespace android
// ----------------------------------------------------------------------------
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index 79aa3cd59eeb..4345c2b89463 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -124,27 +124,3 @@ const GLubyte * glGetString(GLenum name)
}
return ret;
}
-
-/*
- * These GL calls are special because they need to EGL to retrieve some
- * informations before they can execute.
- */
-
-extern "C" void __glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image);
-extern "C" void __glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image);
-
-
-void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image)
-{
- GLeglImageOES implImage =
- (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image);
- __glEGLImageTargetTexture2DOES(target, implImage);
-}
-
-void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image)
-{
- GLeglImageOES implImage =
- (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image);
- __glEGLImageTargetRenderbufferStorageOES(target, implImage);
-}
-
diff --git a/opengl/libs/GLES2/gl2ext_api.in b/opengl/libs/GLES2/gl2ext_api.in
index a8907fd40bbe..c381075b9862 100644
--- a/opengl/libs/GLES2/gl2ext_api.in
+++ b/opengl/libs/GLES2/gl2ext_api.in
@@ -1,7 +1,7 @@
-void API_ENTRY(__glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) {
+void API_ENTRY(glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) {
CALL_GL_API(glEGLImageTargetTexture2DOES, target, image);
}
-void API_ENTRY(__glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) {
+void API_ENTRY(glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) {
CALL_GL_API(glEGLImageTargetRenderbufferStorageOES, target, image);
}
void API_ENTRY(glGetProgramBinaryOES)(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary) {
diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp
index adeaa5b8233d..adcb60d711f2 100644
--- a/opengl/libs/GLES_CM/gl.cpp
+++ b/opengl/libs/GLES_CM/gl.cpp
@@ -179,27 +179,3 @@ const GLubyte * glGetString(GLenum name)
}
return ret;
}
-
-/*
- * These GL calls are special because they need to EGL to retrieve some
- * informations before they can execute.
- */
-
-extern "C" void __glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image);
-extern "C" void __glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image);
-
-
-void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image)
-{
- GLeglImageOES implImage =
- (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image);
- __glEGLImageTargetTexture2DOES(target, implImage);
-}
-
-void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image)
-{
- GLeglImageOES implImage =
- (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image);
- __glEGLImageTargetRenderbufferStorageOES(target, implImage);
-}
-
diff --git a/opengl/libs/GLES_CM/glext_api.in b/opengl/libs/GLES_CM/glext_api.in
index 268a5352bab5..7cd6cb539251 100644
--- a/opengl/libs/GLES_CM/glext_api.in
+++ b/opengl/libs/GLES_CM/glext_api.in
@@ -31,10 +31,10 @@ void API_ENTRY(glDrawTexfOES)(GLfloat x, GLfloat y, GLfloat z, GLfloat width, GL
void API_ENTRY(glDrawTexfvOES)(const GLfloat *coords) {
CALL_GL_API(glDrawTexfvOES, coords);
}
-void API_ENTRY(__glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) {
+void API_ENTRY(glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) {
CALL_GL_API(glEGLImageTargetTexture2DOES, target, image);
}
-void API_ENTRY(__glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) {
+void API_ENTRY(glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) {
CALL_GL_API(glEGLImageTargetRenderbufferStorageOES, target, image);
}
void API_ENTRY(glAlphaFuncxOES)(GLenum func, GLclampx ref) {
diff --git a/opengl/libs/egl_impl.h b/opengl/libs/egl_impl.h
index 8ff51eca4b7a..cb0e908d3140 100644
--- a/opengl/libs/egl_impl.h
+++ b/opengl/libs/egl_impl.h
@@ -30,7 +30,6 @@ namespace android {
// ----------------------------------------------------------------------------
EGLAPI const GLubyte * egl_get_string_for_current_context(GLenum name);
-EGLAPI EGLImageKHR egl_get_image_for_current_context(EGLImageKHR image);
// ----------------------------------------------------------------------------
}; // namespace android
diff --git a/opengl/libs/tools/glapigen b/opengl/libs/tools/glapigen
index 9be40cf67d48..4d8334f90f0c 100755
--- a/opengl/libs/tools/glapigen
+++ b/opengl/libs/tools/glapigen
@@ -37,12 +37,6 @@ while (my $line = <>) {
#printf("%s", $line);
my $prefix = "";
- if ($name eq "glEGLImageTargetTexture2DOES") {
- $prefix = "__";
- }
- if ($name eq "glEGLImageTargetRenderbufferStorageOES") {
- $prefix = "__";
- }
if ($name eq "glGetString") {
$prefix = "__";
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 3beaac998b75..3a8e3fc6fcff 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -148,4 +148,7 @@
<!-- Development settings -->
<bool name="def_stay_on_while_plugged_in">false</bool>
+ <!-- Number of retries for connecting to DHCP.
+ Value here is the same as WifiStateMachine.DEFAULT_MAX_DHCP_RETRIES -->
+ <integer name="def_max_dhcp_retries">9</integer>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 882aa6624a28..330a1899a0b0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1602,6 +1602,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
loadBooleanSetting(stmt, Settings.Secure.NETSTATS_ENABLED,
R.bool.def_netstats_enabled);
+
+ loadIntegerSetting(stmt, Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
+ R.integer.def_max_dhcp_retries);
} finally {
if (stmt != null) stmt.close();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
index f8a45923349b..dc2f0be40542 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
@@ -134,7 +134,9 @@ import android.view.View;
void jumpTo(boolean appearing) {
mContentView.setTranslationY(appearing ? 0 : mPanelHeight);
- mScrimView.getBackground().setAlpha(appearing ? 255 : 0);
+ if (mScrimView.getBackground() != null) {
+ mScrimView.getBackground().setAlpha(appearing ? 255 : 0);
+ }
}
public void setPanelHeight(int h) {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 8706f10128b3..78967203e889 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -239,6 +239,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener
public void show(boolean show, boolean animate) {
if (show) {
+ refreshRecentTasksList(null, true);
mWaitingToShow = true;
mWaitingToShowAnimated = animate;
showIfReady();
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index 157405a2168c..22fa7520ee1a 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -7,6 +7,7 @@ LOCAL_SRC_FILES:= \
AudioMixer.cpp.arm \
AudioResampler.cpp.arm \
AudioPolicyService.cpp \
+ AudioBufferProvider.cpp \
ServiceUtilities.cpp
# AudioResamplerSinc.cpp.arm
# AudioResamplerCubic.cpp.arm
@@ -17,6 +18,7 @@ LOCAL_C_INCLUDES := \
LOCAL_SHARED_LIBRARIES := \
libaudioutils \
+ libcommon_time_client \
libcutils \
libutils \
libbinder \
diff --git a/services/audioflinger/AudioBufferProvider.cpp b/services/audioflinger/AudioBufferProvider.cpp
new file mode 100644
index 000000000000..678fd58b4e47
--- /dev/null
+++ b/services/audioflinger/AudioBufferProvider.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#undef __STRICT_ANSI__
+#define __STDINT_LIMITS
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include "AudioBufferProvider.h"
+
+namespace android {
+
+const int64_t AudioBufferProvider::kInvalidPTS = INT64_MAX;
+
+}; // namespace android
diff --git a/services/audioflinger/AudioBufferProvider.h b/services/audioflinger/AudioBufferProvider.h
index 81c5c3959930..62ad6bd727b8 100644
--- a/services/audioflinger/AudioBufferProvider.h
+++ b/services/audioflinger/AudioBufferProvider.h
@@ -38,8 +38,15 @@ public:
};
virtual ~AudioBufferProvider() {}
-
- virtual status_t getNextBuffer(Buffer* buffer) = 0;
+
+ // value representing an invalid presentation timestamp
+ static const int64_t kInvalidPTS;
+
+ // pts is the local time when the next sample yielded by getNextBuffer
+ // will be rendered.
+ // Pass kInvalidPTS if the PTS is unknown or not applicable.
+ virtual status_t getNextBuffer(Buffer* buffer, int64_t pts) = 0;
+
virtual void releaseBuffer(Buffer* buffer) = 0;
};
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 024868729adb..29d63de1bf3c 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -61,6 +61,9 @@
#include <powermanager/PowerManager.h>
// #define DEBUG_CPU_USAGE 10 // log statistics every n wall clock seconds
+#include <common_time/cc_helper.h>
+#include <common_time/local_clock.h>
+
// ----------------------------------------------------------------------------
@@ -69,7 +72,6 @@ namespace android {
static const char kDeadlockedString[] = "AudioFlinger may be deadlocked\n";
static const char kHardwareLockedString[] = "Hardware lock is taken\n";
-//static const nsecs_t kStandbyTimeInNsecs = seconds(3);
static const float MAX_GAIN = 4096.0f;
static const uint32_t MAX_GAIN_INT = 0x1000;
@@ -99,6 +101,7 @@ static const uint32_t kMinThreadSleepTimeUs = 5000;
// maximum divider applied to the active sleep time in the mixer thread loop
static const uint32_t kMaxThreadSleepTimeShift = 2;
+nsecs_t AudioFlinger::mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;
// ----------------------------------------------------------------------------
@@ -147,11 +150,14 @@ static const char * const audio_interfaces[] = {
AudioFlinger::AudioFlinger()
: BnAudioFlinger(),
- mPrimaryHardwareDev(NULL),
- mHardwareStatus(AUDIO_HW_IDLE), // see also onFirstRef()
- mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1),
- mMode(AUDIO_MODE_INVALID),
- mBtNrecIsOff(false)
+ mPrimaryHardwareDev(NULL),
+ mHardwareStatus(AUDIO_HW_IDLE), // see also onFirstRef()
+ mMasterVolume(1.0f),
+ mMasterVolumeSupportLvl(MVS_NONE),
+ mMasterMute(false),
+ mNextUniqueId(1),
+ mMode(AUDIO_MODE_INVALID),
+ mBtNrecIsOff(false)
{
}
@@ -162,6 +168,18 @@ void AudioFlinger::onFirstRef()
Mutex::Autolock _l(mLock);
/* TODO: move all this work into an Init() function */
+ char val_str[PROPERTY_VALUE_MAX] = { 0 };
+ if (property_get("ro.audio.flinger_standbytime_ms", val_str, NULL) >= 0) {
+ uint32_t int_val;
+ if (1 == sscanf(val_str, "%u", &int_val)) {
+ mStandbyTimeInNsecs = milliseconds(int_val);
+ ALOGI("Using %u mSec as standby time.", int_val);
+ } else {
+ mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;
+ ALOGI("Using default %u mSec as standby time.",
+ (uint32_t)(mStandbyTimeInNsecs / 1000000));
+ }
+ }
for (size_t i = 0; i < ARRAY_SIZE(audio_interfaces); i++) {
const hw_module_t *mod;
@@ -193,6 +211,32 @@ void AudioFlinger::onFirstRef()
AutoMutex lock(mHardwareLock);
+ // Determine the level of master volume support the primary audio HAL has,
+ // and set the initial master volume at the same time.
+ float initialVolume = 1.0;
+ mMasterVolumeSupportLvl = MVS_NONE;
+ if (0 == mPrimaryHardwareDev->init_check(mPrimaryHardwareDev)) {
+ audio_hw_device_t *dev = mPrimaryHardwareDev;
+
+ mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME;
+ if ((NULL != dev->get_master_volume) &&
+ (NO_ERROR == dev->get_master_volume(dev, &initialVolume))) {
+ mMasterVolumeSupportLvl = MVS_FULL;
+ } else {
+ mMasterVolumeSupportLvl = MVS_SETONLY;
+ initialVolume = 1.0;
+ }
+
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ if ((NULL == dev->set_master_volume) ||
+ (NO_ERROR != dev->set_master_volume(dev, initialVolume))) {
+ mMasterVolumeSupportLvl = MVS_NONE;
+ }
+ mHardwareStatus = AUDIO_HW_INIT;
+ }
+
+ // Set the mode for each audio HAL, and try to set the initial volume (if
+ // supported) for all of the non-primary audio HALs.
for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
audio_hw_device_t *dev = mAudioHwDevs[i];
@@ -203,11 +247,22 @@ void AudioFlinger::onFirstRef()
mMode = AUDIO_MODE_NORMAL; // assigned multiple times with same value
mHardwareStatus = AUDIO_HW_SET_MODE;
dev->set_mode(dev, mMode);
- mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
- dev->set_master_volume(dev, 1.0f);
- mHardwareStatus = AUDIO_HW_IDLE;
+
+ if ((dev != mPrimaryHardwareDev) &&
+ (NULL != dev->set_master_volume)) {
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ dev->set_master_volume(dev, initialVolume);
+ }
+
+ mHardwareStatus = AUDIO_HW_INIT;
}
}
+
+ mMasterVolumeSW = (MVS_NONE == mMasterVolumeSupportLvl)
+ ? initialVolume
+ : 1.0;
+ mMasterVolume = initialVolume;
+ mHardwareStatus = AUDIO_HW_IDLE;
}
AudioFlinger::~AudioFlinger()
@@ -273,7 +328,10 @@ status_t AudioFlinger::dumpInternals(int fd, const Vector<String16>& args)
String8 result;
hardware_call_state hardwareStatus = mHardwareStatus;
- snprintf(buffer, SIZE, "Hardware status: %d\n", hardwareStatus);
+ snprintf(buffer, SIZE, "Hardware status: %d\n"
+ "Standby Time mSec: %u\n",
+ hardwareStatus,
+ (uint32_t)(mStandbyTimeInNsecs / 1000000));
result.append(buffer);
write(fd, result.string(), result.size());
return NO_ERROR;
@@ -377,6 +435,7 @@ sp<IAudioTrack> AudioFlinger::createTrack(
uint32_t flags,
const sp<IMemory>& sharedBuffer,
audio_io_handle_t output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -435,7 +494,7 @@ sp<IAudioTrack> AudioFlinger::createTrack(
ALOGV("createTrack() lSessionId: %d", lSessionId);
track = thread->createTrack_l(client, streamType, sampleRate, format,
- channelMask, frameCount, sharedBuffer, lSessionId, &lStatus);
+ channelMask, frameCount, sharedBuffer, lSessionId, isTimed, &lStatus);
// move effect chain to this output thread if an effect on same session was waiting
// for a track to be created
@@ -528,20 +587,29 @@ status_t AudioFlinger::setMasterVolume(float value)
return PERMISSION_DENIED;
}
+ float swmv = value;
+
// when hw supports master volume, don't scale in sw mixer
- { // scope for the lock
- AutoMutex lock(mHardwareLock);
- mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
- if (mPrimaryHardwareDev->set_master_volume(mPrimaryHardwareDev, value) == NO_ERROR) {
- value = 1.0f;
+ if (MVS_NONE != mMasterVolumeSupportLvl) {
+ for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
+ AutoMutex lock(mHardwareLock);
+ audio_hw_device_t *dev = mAudioHwDevs[i];
+
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ if (NULL != dev->set_master_volume) {
+ dev->set_master_volume(dev, value);
+ }
+ mHardwareStatus = AUDIO_HW_IDLE;
}
- mHardwareStatus = AUDIO_HW_IDLE;
+
+ swmv = 1.0;
}
Mutex::Autolock _l(mLock);
- mMasterVolume = value;
+ mMasterVolume = value;
+ mMasterVolumeSW = swmv;
for (size_t i = 0; i < mPlaybackThreads.size(); i++)
- mPlaybackThreads.valueAt(i)->setMasterVolume(value);
+ mPlaybackThreads.valueAt(i)->setMasterVolume(swmv);
return NO_ERROR;
}
@@ -635,12 +703,36 @@ float AudioFlinger::masterVolume() const
return masterVolume_l();
}
+float AudioFlinger::masterVolumeSW() const
+{
+ Mutex::Autolock _l(mLock);
+ return masterVolumeSW_l();
+}
+
bool AudioFlinger::masterMute() const
{
Mutex::Autolock _l(mLock);
return masterMute_l();
}
+float AudioFlinger::masterVolume_l() const
+{
+ if (MVS_FULL == mMasterVolumeSupportLvl) {
+ float ret_val;
+ AutoMutex lock(mHardwareLock);
+
+ mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME;
+ assert(NULL != mPrimaryHardwareDev);
+ assert(NULL != mPrimaryHardwareDev->get_master_volume);
+
+ mPrimaryHardwareDev->get_master_volume(mPrimaryHardwareDev, &ret_val);
+ mHardwareStatus = AUDIO_HW_IDLE;
+ return ret_val;
+ }
+
+ return mMasterVolume;
+}
+
status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,
audio_io_handle_t output)
{
@@ -814,7 +906,7 @@ String8 AudioFlinger::getParameters(audio_io_handle_t ioHandle, const String8& k
for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
audio_hw_device_t *dev = mAudioHwDevs[i];
char *s = dev->get_parameters(dev, keys.string());
- out_s8 += String8(s);
+ out_s8 += String8(s ? s : "");
free(s);
}
return out_s8;
@@ -1367,7 +1459,7 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge
mOutput(output),
// Assumes constructor is called by AudioFlinger with it's mLock held,
// but it would be safer to explicitly pass initial masterVolume as parameter
- mMasterVolume(audioFlinger->masterVolume_l()),
+ mMasterVolume(audioFlinger->masterVolumeSW_l()),
mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false)
{
snprintf(mName, kNameLength, "AudioOut_%d", id);
@@ -1485,6 +1577,7 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status)
{
sp<Track> track;
@@ -1535,9 +1628,14 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra
}
}
- track = new Track(this, client, streamType, sampleRate, format,
- channelMask, frameCount, sharedBuffer, sessionId);
- if (track->getCblk() == NULL || track->name() < 0) {
+ if (!isTimed) {
+ track = new Track(this, client, streamType, sampleRate, format,
+ channelMask, frameCount, sharedBuffer, sessionId);
+ } else {
+ track = TimedTrack::create(this, client, streamType, sampleRate, format,
+ channelMask, frameCount, sharedBuffer, sessionId);
+ }
+ if (track == NULL || track->getCblk() == NULL || track->name() < 0) {
lStatus = NO_MEMORY;
goto Exit;
}
@@ -1941,7 +2039,7 @@ bool AudioFlinger::MixerThread::threadLoop()
}
}
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
sleepTime = idleSleepTime;
sleepTimeShift = 0;
continue;
@@ -1957,8 +2055,21 @@ bool AudioFlinger::MixerThread::threadLoop()
}
if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
+ // obtain the presentation timestamp of the next output buffer
+ int64_t pts;
+ status_t status = INVALID_OPERATION;
+
+ if (NULL != mOutput->stream->get_next_write_timestamp) {
+ status = mOutput->stream->get_next_write_timestamp(
+ mOutput->stream, &pts);
+ }
+
+ if (status != NO_ERROR) {
+ pts = AudioBufferProvider::kInvalidPTS;
+ }
+
// mix buffers...
- mAudioMixer->process();
+ mAudioMixer->process(pts);
// increase sleep time progressively when application underrun condition clears.
// Only increase sleep time if the mixer is ready for two consecutive times to avoid
// that a steady state of alternating ready/not ready conditions keeps the sleep time
@@ -1967,7 +2078,7 @@ bool AudioFlinger::MixerThread::threadLoop()
sleepTimeShift--;
}
sleepTime = 0;
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
//TODO: delay standby when effects have a tail
} else {
// If no tracks are ready, sleep once for the duration of an output
@@ -2114,7 +2225,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
ALOG_ASSERT(minFrames <= cblk->frameCount);
}
}
- if ((cblk->framesReady() >= minFrames) && track->isReady() &&
+ if ((track->framesReady() >= minFrames) && track->isReady() &&
!track->isPaused() && !track->isTerminated())
{
//ALOGV("track %d u=%08x, s=%08x [OK] on thread %p", name, cblk->user, cblk->server, this);
@@ -2785,7 +2896,8 @@ bool AudioFlinger::DirectOutputThread::threadLoop()
// output audio to hardware
while (frameCount) {
buffer.frameCount = frameCount;
- activeTrack->getNextBuffer(&buffer);
+ activeTrack->getNextBuffer(&buffer,
+ AudioBufferProvider::kInvalidPTS);
if (CC_UNLIKELY(buffer.raw == NULL)) {
memset(curBuf, 0, frameCount * mFrameSize);
break;
@@ -3038,7 +3150,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop()
}
}
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
sleepTime = idleSleepTime;
continue;
}
@@ -3055,7 +3167,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop()
if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
// mix buffers...
if (outputsReady(outputTracks)) {
- mAudioMixer->process();
+ mAudioMixer->process(AudioBufferProvider::kInvalidPTS);
} else {
memset(mMixBuffer, 0, mixBufferSize);
}
@@ -3092,7 +3204,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop()
// enable changes in effect chain
unlockEffectChains(effectChains);
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
for (size_t i = 0; i < outputTracks.size(); i++) {
outputTracks[i]->write(mMixBuffer, writeFrames);
}
@@ -3443,7 +3555,8 @@ void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size)
(int)mAuxBuffer);
}
-status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(
+ AudioBufferProvider::Buffer* buffer, int64_t pts)
{
audio_track_cblk_t* cblk = this->cblk();
uint32_t framesReady;
@@ -3484,10 +3597,14 @@ getNextBuffer_exit:
return NOT_ENOUGH_DATA;
}
+uint32_t AudioFlinger::PlaybackThread::Track::framesReady() const{
+ return mCblk->framesReady();
+}
+
bool AudioFlinger::PlaybackThread::Track::isReady() const {
if (mFillingUpStatus != FS_FILLING || isStopped() || isPausing()) return true;
- if (mCblk->framesReady() >= mCblk->frameCount ||
+ if (framesReady() >= mCblk->frameCount ||
(mCblk->flags & CBLK_FORCEREADY_MSK)) {
mFillingUpStatus = FS_FILLED;
android_atomic_and(~CBLK_FORCEREADY_MSK, &mCblk->flags);
@@ -3644,6 +3761,393 @@ void AudioFlinger::PlaybackThread::Track::setAuxBuffer(int EffectId, int32_t *bu
mAuxBuffer = buffer;
}
+// timed audio tracks
+
+sp<AudioFlinger::PlaybackThread::TimedTrack>
+AudioFlinger::PlaybackThread::TimedTrack::create(
+ const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ audio_stream_type_t streamType,
+ uint32_t sampleRate,
+ audio_format_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId) {
+ if (!client->reserveTimedTrack())
+ return NULL;
+
+ sp<TimedTrack> track = new TimedTrack(
+ thread, client, streamType, sampleRate, format, channelMask, frameCount,
+ sharedBuffer, sessionId);
+
+ if (track == NULL) {
+ client->releaseTimedTrack();
+ return NULL;
+ }
+
+ return track;
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedTrack(
+ const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ audio_stream_type_t streamType,
+ uint32_t sampleRate,
+ audio_format_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId)
+ : Track(thread, client, streamType, sampleRate, format, channelMask,
+ frameCount, sharedBuffer, sessionId),
+ mTimedSilenceBuffer(NULL),
+ mTimedSilenceBufferSize(0),
+ mTimedAudioOutputOnTime(false),
+ mMediaTimeTransformValid(false)
+{
+ LocalClock lc;
+ mLocalTimeFreq = lc.getLocalFreq();
+
+ mLocalTimeToSampleTransform.a_zero = 0;
+ mLocalTimeToSampleTransform.b_zero = 0;
+ mLocalTimeToSampleTransform.a_to_b_numer = sampleRate;
+ mLocalTimeToSampleTransform.a_to_b_denom = mLocalTimeFreq;
+ LinearTransform::reduce(&mLocalTimeToSampleTransform.a_to_b_numer,
+ &mLocalTimeToSampleTransform.a_to_b_denom);
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::~TimedTrack() {
+ mClient->releaseTimedTrack();
+ delete [] mTimedSilenceBuffer;
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::allocateTimedBuffer(
+ size_t size, sp<IMemory>* buffer) {
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ trimTimedBufferQueue_l();
+
+ // lazily initialize the shared memory heap for timed buffers
+ if (mTimedMemoryDealer == NULL) {
+ const int kTimedBufferHeapSize = 512 << 10;
+
+ mTimedMemoryDealer = new MemoryDealer(kTimedBufferHeapSize,
+ "AudioFlingerTimed");
+ if (mTimedMemoryDealer == NULL)
+ return NO_MEMORY;
+ }
+
+ sp<IMemory> newBuffer = mTimedMemoryDealer->allocate(size);
+ if (newBuffer == NULL) {
+ newBuffer = mTimedMemoryDealer->allocate(size);
+ if (newBuffer == NULL)
+ return NO_MEMORY;
+ }
+
+ *buffer = newBuffer;
+ return NO_ERROR;
+}
+
+// caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::trimTimedBufferQueue_l() {
+ int64_t mediaTimeNow;
+ {
+ Mutex::Autolock mttLock(mMediaTimeTransformLock);
+ if (!mMediaTimeTransformValid)
+ return;
+
+ int64_t targetTimeNow;
+ status_t res = (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME)
+ ? mCCHelper.getCommonTime(&targetTimeNow)
+ : mCCHelper.getLocalTime(&targetTimeNow);
+
+ if (OK != res)
+ return;
+
+ if (!mMediaTimeTransform.doReverseTransform(targetTimeNow,
+ &mediaTimeNow)) {
+ return;
+ }
+ }
+
+ size_t trimIndex;
+ for (trimIndex = 0; trimIndex < mTimedBufferQueue.size(); trimIndex++) {
+ if (mTimedBufferQueue[trimIndex].pts() > mediaTimeNow)
+ break;
+ }
+
+ if (trimIndex) {
+ mTimedBufferQueue.removeItemsAt(0, trimIndex);
+ }
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::queueTimedBuffer(
+ const sp<IMemory>& buffer, int64_t pts) {
+
+ {
+ Mutex::Autolock mttLock(mMediaTimeTransformLock);
+ if (!mMediaTimeTransformValid)
+ return INVALID_OPERATION;
+ }
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ mTimedBufferQueue.add(TimedBuffer(buffer, pts));
+
+ return NO_ERROR;
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::setMediaTimeTransform(
+ const LinearTransform& xform, TimedAudioTrack::TargetTimeline target) {
+
+ ALOGV("%s az=%lld bz=%lld n=%d d=%u tgt=%d", __PRETTY_FUNCTION__,
+ xform.a_zero, xform.b_zero, xform.a_to_b_numer, xform.a_to_b_denom,
+ target);
+
+ if (!(target == TimedAudioTrack::LOCAL_TIME ||
+ target == TimedAudioTrack::COMMON_TIME)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMediaTimeTransformLock);
+ mMediaTimeTransform = xform;
+ mMediaTimeTransformTarget = target;
+ mMediaTimeTransformValid = true;
+
+ return NO_ERROR;
+}
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+// implementation of getNextBuffer for tracks whose buffers have timestamps
+status_t AudioFlinger::PlaybackThread::TimedTrack::getNextBuffer(
+ AudioBufferProvider::Buffer* buffer, int64_t pts)
+{
+ if (pts == AudioBufferProvider::kInvalidPTS) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return INVALID_OPERATION;
+ }
+
+ // get ahold of the output stream that these samples will be written to
+ sp<ThreadBase> thread = mThread.promote();
+ if (thread == NULL) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return INVALID_OPERATION;
+ }
+ PlaybackThread* playbackThread = static_cast<PlaybackThread*>(thread.get());
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ while (true) {
+
+ // if we have no timed buffers, then fail
+ if (mTimedBufferQueue.isEmpty()) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return NOT_ENOUGH_DATA;
+ }
+
+ TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
+
+ // calculate the PTS of the head of the timed buffer queue expressed in
+ // local time
+ int64_t headLocalPTS;
+ {
+ Mutex::Autolock mttLock(mMediaTimeTransformLock);
+
+ assert(mMediaTimeTransformValid);
+
+ if (mMediaTimeTransform.a_to_b_denom == 0) {
+ // the transform represents a pause, so yield silence
+ timedYieldSilence(buffer->frameCount, buffer);
+ return NO_ERROR;
+ }
+
+ int64_t transformedPTS;
+ if (!mMediaTimeTransform.doForwardTransform(head.pts(),
+ &transformedPTS)) {
+ // the transform failed. this shouldn't happen, but if it does
+ // then just drop this buffer
+ ALOGW("timedGetNextBuffer transform failed");
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ mTimedBufferQueue.removeAt(0);
+ return NO_ERROR;
+ }
+
+ if (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME) {
+ if (OK != mCCHelper.commonTimeToLocalTime(transformedPTS,
+ &headLocalPTS)) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return INVALID_OPERATION;
+ }
+ } else {
+ headLocalPTS = transformedPTS;
+ }
+ }
+
+ // adjust the head buffer's PTS to reflect the portion of the head buffer
+ // that has already been consumed
+ int64_t effectivePTS = headLocalPTS +
+ ((head.position() / mCblk->frameSize) * mLocalTimeFreq / sampleRate());
+
+ // Calculate the delta in samples between the head of the input buffer
+ // queue and the start of the next output buffer that will be written.
+ // If the transformation fails because of over or underflow, it means
+ // that the sample's position in the output stream is so far out of
+ // whack that it should just be dropped.
+ int64_t sampleDelta;
+ if (llabs(effectivePTS - pts) >= (static_cast<int64_t>(1) << 31)) {
+ ALOGV("*** head buffer is too far from PTS: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ }
+ if (!mLocalTimeToSampleTransform.doForwardTransform(
+ (effectivePTS - pts) << 32, &sampleDelta)) {
+ ALOGV("*** too late during sample rate transform: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ }
+
+ ALOGV("*** %s head.pts=%lld head.pos=%d pts=%lld sampleDelta=[%d.%08x]",
+ __PRETTY_FUNCTION__, head.pts(), head.position(), pts,
+ static_cast<int32_t>((sampleDelta >= 0 ? 0 : 1) + (sampleDelta >> 32)),
+ static_cast<uint32_t>(sampleDelta & 0xFFFFFFFF));
+
+ // if the delta between the ideal placement for the next input sample and
+ // the current output position is within this threshold, then we will
+ // concatenate the next input samples to the previous output
+ const int64_t kSampleContinuityThreshold =
+ (static_cast<int64_t>(sampleRate()) << 32) / 10;
+
+ // if this is the first buffer of audio that we're emitting from this track
+ // then it should be almost exactly on time.
+ const int64_t kSampleStartupThreshold = 1LL << 32;
+
+ if ((mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleContinuityThreshold) ||
+ (!mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleStartupThreshold)) {
+ // the next input is close enough to being on time, so concatenate it
+ // with the last output
+ timedYieldSamples(buffer);
+
+ ALOGV("*** on time: head.pos=%d frameCount=%u", head.position(), buffer->frameCount);
+ return NO_ERROR;
+ } else if (sampleDelta > 0) {
+ // the gap between the current output position and the proper start of
+ // the next input sample is too big, so fill it with silence
+ uint32_t framesUntilNextInput = (sampleDelta + 0x80000000) >> 32;
+
+ timedYieldSilence(framesUntilNextInput, buffer);
+ ALOGV("*** silence: frameCount=%u", buffer->frameCount);
+ return NO_ERROR;
+ } else {
+ // the next input sample is late
+ uint32_t lateFrames = static_cast<uint32_t>(-((sampleDelta + 0x80000000) >> 32));
+ size_t onTimeSamplePosition =
+ head.position() + lateFrames * mCblk->frameSize;
+
+ if (onTimeSamplePosition > head.buffer()->size()) {
+ // all the remaining samples in the head are too late, so
+ // drop it and move on
+ ALOGV("*** too late: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ } else {
+ // skip over the late samples
+ head.setPosition(onTimeSamplePosition);
+
+ // yield the available samples
+ timedYieldSamples(buffer);
+
+ ALOGV("*** late: head.pos=%d frameCount=%u", head.position(), buffer->frameCount);
+ return NO_ERROR;
+ }
+ }
+ }
+}
+
+// Yield samples from the timed buffer queue head up to the given output
+// buffer's capacity.
+//
+// Caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSamples(
+ AudioBufferProvider::Buffer* buffer) {
+
+ const TimedBuffer& head = mTimedBufferQueue[0];
+
+ buffer->raw = (static_cast<uint8_t*>(head.buffer()->pointer()) +
+ head.position());
+
+ uint32_t framesLeftInHead = ((head.buffer()->size() - head.position()) /
+ mCblk->frameSize);
+ size_t framesRequested = buffer->frameCount;
+ buffer->frameCount = min(framesLeftInHead, framesRequested);
+
+ mTimedAudioOutputOnTime = true;
+}
+
+// Yield samples of silence up to the given output buffer's capacity
+//
+// Caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSilence(
+ uint32_t numFrames, AudioBufferProvider::Buffer* buffer) {
+
+ // lazily allocate a buffer filled with silence
+ if (mTimedSilenceBufferSize < numFrames * mCblk->frameSize) {
+ delete [] mTimedSilenceBuffer;
+ mTimedSilenceBufferSize = numFrames * mCblk->frameSize;
+ mTimedSilenceBuffer = new uint8_t[mTimedSilenceBufferSize];
+ memset(mTimedSilenceBuffer, 0, mTimedSilenceBufferSize);
+ }
+
+ buffer->raw = mTimedSilenceBuffer;
+ size_t framesRequested = buffer->frameCount;
+ buffer->frameCount = min(numFrames, framesRequested);
+
+ mTimedAudioOutputOnTime = false;
+}
+
+void AudioFlinger::PlaybackThread::TimedTrack::releaseBuffer(
+ AudioBufferProvider::Buffer* buffer) {
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ if (buffer->raw != mTimedSilenceBuffer) {
+ TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
+ head.setPosition(head.position() + buffer->frameCount * mCblk->frameSize);
+ if (static_cast<size_t>(head.position()) >= head.buffer()->size()) {
+ mTimedBufferQueue.removeAt(0);
+ }
+ }
+
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+}
+
+uint32_t AudioFlinger::PlaybackThread::TimedTrack::framesReady() const {
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ uint32_t frames = 0;
+ for (size_t i = 0; i < mTimedBufferQueue.size(); i++) {
+ const TimedBuffer& tb = mTimedBufferQueue[i];
+ frames += (tb.buffer()->size() - tb.position()) / mCblk->frameSize;
+ }
+
+ return frames;
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer()
+ : mPTS(0), mPosition(0) {}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer(
+ const sp<IMemory>& buffer, int64_t pts)
+ : mBuffer(buffer), mPTS(pts), mPosition(0) {}
+
// ----------------------------------------------------------------------------
// RecordTrack constructor must be called with AudioFlinger::mLock held
@@ -3680,7 +4184,7 @@ AudioFlinger::RecordThread::RecordTrack::~RecordTrack()
}
}
-status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
{
audio_track_cblk_t* cblk = this->cblk();
uint32_t framesAvail;
@@ -4002,7 +4506,8 @@ AudioFlinger::Client::Client(const sp<AudioFlinger>& audioFlinger, pid_t pid)
mAudioFlinger(audioFlinger),
// FIXME should be a "k" constant not hard-coded, in .h or ro. property, see 4 lines below
mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")),
- mPid(pid)
+ mPid(pid),
+ mTimedTrackCount(0)
{
// 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer
}
@@ -4018,6 +4523,31 @@ sp<MemoryDealer> AudioFlinger::Client::heap() const
return mMemoryDealer;
}
+// Reserve one of the limited slots for a timed audio track associated
+// with this client
+bool AudioFlinger::Client::reserveTimedTrack()
+{
+ const int kMaxTimedTracksPerClient = 4;
+
+ Mutex::Autolock _l(mTimedTrackLock);
+
+ if (mTimedTrackCount >= kMaxTimedTracksPerClient) {
+ ALOGW("can not create timed track - pid %d has exceeded the limit",
+ mPid);
+ return false;
+ }
+
+ mTimedTrackCount++;
+ return true;
+}
+
+// Release a slot for a timed audio track
+void AudioFlinger::Client::releaseTimedTrack()
+{
+ Mutex::Autolock _l(mTimedTrackLock);
+ mTimedTrackCount--;
+}
+
// ----------------------------------------------------------------------------
AudioFlinger::NotificationClient::NotificationClient(const sp<AudioFlinger>& audioFlinger,
@@ -4084,6 +4614,38 @@ status_t AudioFlinger::TrackHandle::attachAuxEffect(int EffectId)
return mTrack->attachAuxEffect(EffectId);
}
+status_t AudioFlinger::TrackHandle::allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer) {
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->allocateTimedBuffer(size, buffer);
+}
+
+status_t AudioFlinger::TrackHandle::queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) {
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->queueTimedBuffer(buffer, pts);
+}
+
+status_t AudioFlinger::TrackHandle::setMediaTimeTransform(
+ const LinearTransform& xform, int target) {
+
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->setMediaTimeTransform(
+ xform, static_cast<TimedAudioTrack::TargetTimeline>(target));
+}
+
status_t AudioFlinger::TrackHandle::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
@@ -4313,7 +4875,8 @@ bool AudioFlinger::RecordThread::threadLoop()
}
buffer.frameCount = mFrameCount;
- if (CC_LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) {
+ if (CC_LIKELY(mActiveTrack->getNextBuffer(
+ &buffer, AudioBufferProvider::kInvalidPTS) == NO_ERROR)) {
size_t framesOut = buffer.frameCount;
if (mResampler == NULL) {
// no resampling
@@ -4591,7 +5154,7 @@ status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args)
return NO_ERROR;
}
-status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
{
size_t framesReq = buffer->frameCount;
size_t framesReady = mFrameCount - mRsmpInIndex;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index aa0b8f8575ac..9396a0bf3acc 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -22,11 +22,14 @@
#include <sys/types.h>
#include <limits.h>
+#include <common_time/cc_helper.h>
+
#include <media/IAudioFlinger.h>
#include <media/IAudioFlingerClient.h>
#include <media/IAudioTrack.h>
#include <media/IAudioRecord.h>
#include <media/AudioSystem.h>
+#include <media/AudioTrack.h>
#include <utils/Atomic.h>
#include <utils/Errors.h>
@@ -55,7 +58,7 @@ class AudioResampler;
// ----------------------------------------------------------------------------
-static const nsecs_t kStandbyTimeInNsecs = seconds(3);
+static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
class AudioFlinger :
public BinderService<AudioFlinger>,
@@ -78,6 +81,7 @@ public:
uint32_t flags,
const sp<IMemory>& sharedBuffer,
audio_io_handle_t output,
+ bool isTimed,
int *sessionId,
status_t *status);
@@ -102,6 +106,7 @@ public:
virtual status_t setMasterMute(bool muted);
virtual float masterVolume() const;
+ virtual float masterVolumeSW() const;
virtual bool masterMute() const;
virtual status_t setStreamVolume(audio_stream_type_t stream, float value,
@@ -206,6 +211,8 @@ private:
audio_hw_device_t* findSuitableHwDev_l(uint32_t devices);
void purgeStaleEffects_l();
+ static nsecs_t mStandbyTimeInNsecs;
+
// Internal dump utilites.
status_t dumpPermissionDenial(int fd, const Vector<String16>& args);
status_t dumpClients(int fd, const Vector<String16>& args);
@@ -220,12 +227,18 @@ private:
pid_t pid() const { return mPid; }
sp<AudioFlinger> audioFlinger() const { return mAudioFlinger; }
+ bool reserveTimedTrack();
+ void releaseTimedTrack();
+
private:
Client(const Client&);
Client& operator = (const Client&);
const sp<AudioFlinger> mAudioFlinger;
const sp<MemoryDealer> mMemoryDealer;
const pid_t mPid;
+
+ Mutex mTimedTrackLock;
+ int mTimedTrackCount;
};
// --- Notification Client ---
@@ -333,7 +346,9 @@ private:
TrackBase(const TrackBase&);
TrackBase& operator = (const TrackBase&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0;
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts) = 0;
virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
audio_format_t format() const {
@@ -609,7 +624,6 @@ private:
int16_t *mainBuffer() const { return mMainBuffer; }
int auxEffectId() const { return mAuxEffectId; }
-
protected:
friend class ThreadBase;
friend class TrackHandle;
@@ -620,7 +634,11 @@ private:
Track(const Track&);
Track& operator = (const Track&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
+ virtual uint32_t framesReady() const;
+
bool isMuted() const { return mMute; }
bool isPausing() const {
return mState == PAUSING;
@@ -636,6 +654,8 @@ private:
return (mStreamType == AUDIO_STREAM_CNT);
}
+ virtual bool isTimedTrack() const { return false; }
+
// we don't really need a lock for these
volatile bool mMute;
// FILLED state is used for suppressing volume ramp at begin of playing
@@ -652,6 +672,79 @@ private:
bool mHasVolumeController;
}; // end of Track
+ class TimedTrack : public Track {
+ public:
+ static sp<TimedTrack> create(const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ audio_stream_type_t streamType,
+ uint32_t sampleRate,
+ audio_format_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId);
+ ~TimedTrack();
+
+ class TimedBuffer {
+ public:
+ TimedBuffer();
+ TimedBuffer(const sp<IMemory>& buffer, int64_t pts);
+ const sp<IMemory>& buffer() const { return mBuffer; }
+ int64_t pts() const { return mPTS; }
+ int position() const { return mPosition; }
+ void setPosition(int pos) { mPosition = pos; }
+ private:
+ sp<IMemory> mBuffer;
+ int64_t mPTS;
+ int mPosition;
+ };
+
+ virtual bool isTimedTrack() const { return true; }
+
+ virtual uint32_t framesReady() const;
+
+ virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
+ virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
+ void timedYieldSamples(AudioBufferProvider::Buffer* buffer);
+ void timedYieldSilence(uint32_t numFrames,
+ AudioBufferProvider::Buffer* buffer);
+
+ status_t allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer);
+ status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts);
+ status_t setMediaTimeTransform(const LinearTransform& xform,
+ TimedAudioTrack::TargetTimeline target);
+ void trimTimedBufferQueue_l();
+
+ private:
+ TimedTrack(const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ audio_stream_type_t streamType,
+ uint32_t sampleRate,
+ audio_format_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId);
+
+ uint64_t mLocalTimeFreq;
+ LinearTransform mLocalTimeToSampleTransform;
+ sp<MemoryDealer> mTimedMemoryDealer;
+ Vector<TimedBuffer> mTimedBufferQueue;
+ uint8_t* mTimedSilenceBuffer;
+ uint32_t mTimedSilenceBufferSize;
+ mutable Mutex mTimedBufferQueueLock;
+ bool mTimedAudioOutputOnTime;
+ CCHelper mCCHelper;
+
+ Mutex mMediaTimeTransformLock;
+ LinearTransform mMediaTimeTransform;
+ bool mMediaTimeTransformValid;
+ TimedAudioTrack::TargetTimeline mMediaTimeTransformTarget;
+ };
+
// playback track
class OutputTrack : public Track {
@@ -726,6 +819,7 @@ private:
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status);
AudioStreamOut* getOutput() const;
@@ -920,6 +1014,12 @@ private:
virtual void mute(bool);
virtual void pause();
virtual status_t attachAuxEffect(int effectId);
+ virtual status_t allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer);
+ virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts);
+ virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+ int target);
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
private:
@@ -967,7 +1067,9 @@ private:
RecordTrack(const RecordTrack&);
RecordTrack& operator = (const RecordTrack&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
bool mOverflow;
};
@@ -1004,7 +1106,8 @@ private:
AudioStreamIn* clearInput();
virtual audio_stream_t* stream();
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
virtual bool checkForNewParameters_l();
virtual String8 getParameters(const String8& keys);
@@ -1401,6 +1504,28 @@ mutable Mutex mLock; // mutex for process, commands and handl
friend class RecordThread;
friend class PlaybackThread;
+ enum master_volume_support {
+ // MVS_NONE:
+ // Audio HAL has no support for master volume, either setting or
+ // getting. All master volume control must be implemented in SW by the
+ // AudioFlinger mixing core.
+ MVS_NONE,
+
+ // MVS_SETONLY:
+ // Audio HAL has support for setting master volume, but not for getting
+ // master volume (original HAL design did not include a getter).
+ // AudioFlinger needs to keep track of the last set master volume in
+ // addition to needing to set an initial, default, master volume at HAL
+ // load time.
+ MVS_SETONLY,
+
+ // MVS_FULL:
+ // Audio HAL has support both for setting and getting master volume.
+ // AudioFlinger should send all set and get master volume requests
+ // directly to the HAL.
+ MVS_FULL,
+ };
+
mutable Mutex mLock;
DefaultKeyedVector< pid_t, wp<Client> > mClients; // see ~Client()
@@ -1429,6 +1554,7 @@ mutable Mutex mLock; // mutex for process, commands and handl
AUDIO_SET_VOICE_VOLUME,
AUDIO_SET_PARAMETER,
AUDIO_HW_GET_INPUT_BUFFER_SIZE,
+ AUDIO_HW_GET_MASTER_VOLUME,
};
mutable hardware_call_state mHardwareStatus; // for dump only
@@ -1439,6 +1565,8 @@ mutable Mutex mLock; // mutex for process, commands and handl
// both are protected by mLock
float mMasterVolume;
+ float mMasterVolumeSW;
+ master_volume_support mMasterVolumeSupportLvl;
bool mMasterMute;
DefaultKeyedVector< audio_io_handle_t, sp<RecordThread> > mRecordThreads;
@@ -1451,7 +1579,8 @@ mutable Mutex mLock; // mutex for process, commands and handl
// protected by mLock
Vector<AudioSessionRef*> mAudioSessionRefs;
- float masterVolume_l() const { return mMasterVolume; }
+ float masterVolume_l() const;
+ float masterVolumeSW_l() const { return mMasterVolumeSW; }
bool masterMute_l() const { return mMasterMute; }
private:
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index cb7678b23e71..c399691828c9 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -33,6 +33,8 @@
#include <system/audio.h>
#include <audio_utils/primitives.h>
+#include <common_time/local_clock.h>
+#include <common_time/cc_helper.h>
#include "AudioMixer.h"
@@ -45,6 +47,9 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate)
{
// AudioMixer is not yet capable of multi-channel beyond stereo
assert(2 == MAX_NUM_CHANNELS);
+
+ LocalClock lc;
+
mState.enabledTracks= 0;
mState.needsChanged = 0;
mState.frameCount = frameCount;
@@ -80,6 +85,7 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate)
t->sampleRate = mSampleRate;
t->mainBuffer = NULL;
t->auxBuffer = NULL;
+ t->localTimeFreq = lc.getLocalFreq();
t++;
}
}
@@ -289,6 +295,7 @@ bool AudioMixer::track_t::setResampler(uint32_t value, uint32_t devSampleRate)
if (resampler == NULL) {
resampler = AudioResampler::create(
format, channelCount, devSampleRate);
+ resampler->setLocalTimeFreq(localTimeFreq);
}
return true;
}
@@ -333,13 +340,13 @@ void AudioMixer::setBufferProvider(int name, AudioBufferProvider* buffer)
-void AudioMixer::process()
+void AudioMixer::process(int64_t pts)
{
- mState.hook(&mState);
+ mState.hook(&mState, pts);
}
-void AudioMixer::process__validate(state_t* state)
+void AudioMixer::process__validate(state_t* state, int64_t pts)
{
ALOGW_IF(!state->needsChanged,
"in process__validate() but nothing's invalid");
@@ -443,7 +450,7 @@ void AudioMixer::process__validate(state_t* state)
countActiveTracks, state->enabledTracks,
all16BitsStereoNoResample, resampling, volumeRamp);
- state->hook(state);
+ state->hook(state, pts);
// Now that the volume ramp has been done, set optimal state and
// track hooks for subsequent mixer process
@@ -757,7 +764,7 @@ void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount,
}
// no-op case
-void AudioMixer::process__nop(state_t* state)
+void AudioMixer::process__nop(state_t* state, int64_t pts)
{
uint32_t e0 = state->enabledTracks;
size_t bufSize = state->frameCount * sizeof(int16_t) * MAX_NUM_CHANNELS;
@@ -787,7 +794,9 @@ void AudioMixer::process__nop(state_t* state)
size_t outFrames = state->frameCount;
while (outFrames) {
t1.buffer.frameCount = outFrames;
- t1.bufferProvider->getNextBuffer(&t1.buffer);
+ int64_t outputPTS = calculateOutputPTS(
+ t1, pts, state->frameCount - outFrames);
+ t1.bufferProvider->getNextBuffer(&t1.buffer, outputPTS);
if (t1.buffer.raw == NULL) break;
outFrames -= t1.buffer.frameCount;
t1.bufferProvider->releaseBuffer(&t1.buffer);
@@ -797,7 +806,7 @@ void AudioMixer::process__nop(state_t* state)
}
// generic code without resampling
-void AudioMixer::process__genericNoResampling(state_t* state)
+void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
{
int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
@@ -809,7 +818,7 @@ void AudioMixer::process__genericNoResampling(state_t* state)
e0 &= ~(1<<i);
track_t& t = state->tracks[i];
t.buffer.frameCount = state->frameCount;
- t.bufferProvider->getNextBuffer(&t.buffer);
+ t.bufferProvider->getNextBuffer(&t.buffer, pts);
t.frameCount = t.buffer.frameCount;
t.in = t.buffer.raw;
// t.in == NULL can happen if the track was flushed just after having
@@ -863,7 +872,9 @@ void AudioMixer::process__genericNoResampling(state_t* state)
if (t.frameCount == 0 && outFrames) {
t.bufferProvider->releaseBuffer(&t.buffer);
t.buffer.frameCount = (state->frameCount - numFrames) - (BLOCKSIZE - outFrames);
- t.bufferProvider->getNextBuffer(&t.buffer);
+ int64_t outputPTS = calculateOutputPTS(
+ t, pts, numFrames + (BLOCKSIZE - outFrames));
+ t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
t.in = t.buffer.raw;
if (t.in == NULL) {
enabledTracks &= ~(1<<i);
@@ -892,7 +903,7 @@ void AudioMixer::process__genericNoResampling(state_t* state)
// generic code with resampling
-void AudioMixer::process__genericResampling(state_t* state)
+void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
{
// this const just means that local variable outTemp doesn't change
int32_t* const outTemp = state->outputTemp;
@@ -932,6 +943,7 @@ void AudioMixer::process__genericResampling(state_t* state)
// acquire/release the buffers because it's done by
// the resampler.
if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) {
+ t.resampler->setPTS(pts);
(t.hook)(&t, outTemp, numFrames, state->resampleTemp, aux);
} else {
@@ -939,7 +951,8 @@ void AudioMixer::process__genericResampling(state_t* state)
while (outFrames < numFrames) {
t.buffer.frameCount = numFrames - outFrames;
- t.bufferProvider->getNextBuffer(&t.buffer);
+ int64_t outputPTS = calculateOutputPTS(t, pts, outFrames);
+ t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
t.in = t.buffer.raw;
// t.in == NULL can happen if the track was flushed just after having
// been enabled for mixing.
@@ -959,7 +972,8 @@ void AudioMixer::process__genericResampling(state_t* state)
}
// one track, 16 bits stereo without resampling is the most common case
-void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
+ int64_t pts)
{
// This method is only called when state->enabledTracks has exactly
// one bit set. The asserts below would verify this, but are commented out
@@ -979,7 +993,8 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
const uint32_t vrl = t.volumeRL;
while (numFrames) {
b.frameCount = numFrames;
- t.bufferProvider->getNextBuffer(&b);
+ int64_t outputPTS = calculateOutputPTS(t, pts, out - t.mainBuffer);
+ t.bufferProvider->getNextBuffer(&b, outputPTS);
const int16_t *in = b.i16;
// in == NULL can happen if the track was flushed just after having
@@ -1023,7 +1038,8 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
// 2 tracks is also a common case
// NEVER used in current implementation of process__validate()
// only use if the 2 tracks have the same output buffer
-void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state,
+ int64_t pts)
{
int i;
uint32_t en = state->enabledTracks;
@@ -1057,7 +1073,9 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state)
if (frameCount0 == 0) {
b0.frameCount = numFrames;
- t0.bufferProvider->getNextBuffer(&b0);
+ int64_t outputPTS = calculateOutputPTS(t0, pts,
+ out - t0.mainBuffer);
+ t0.bufferProvider->getNextBuffer(&b0, outputPTS);
if (b0.i16 == NULL) {
if (buff == NULL) {
buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1071,7 +1089,9 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state)
}
if (frameCount1 == 0) {
b1.frameCount = numFrames;
- t1.bufferProvider->getNextBuffer(&b1);
+ int64_t outputPTS = calculateOutputPTS(t1, pts,
+ out - t0.mainBuffer);
+ t1.bufferProvider->getNextBuffer(&b1, outputPTS);
if (b1.i16 == NULL) {
if (buff == NULL) {
buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1117,5 +1137,14 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state)
}
#endif
+int64_t AudioMixer::calculateOutputPTS(const track_t& t, int64_t basePTS,
+ int outputFrameIndex)
+{
+ if (AudioBufferProvider::kInvalidPTS == basePTS)
+ return AudioBufferProvider::kInvalidPTS;
+
+ return basePTS + ((outputFrameIndex * t.localTimeFreq) / t.sampleRate);
+}
+
// ----------------------------------------------------------------------------
}; // namespace android
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index c95691865c8c..f442671c8258 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -79,7 +79,7 @@ public:
void setParameter(int name, int target, int param, void *value);
void setBufferProvider(int name, AudioBufferProvider* bufferProvider);
- void process();
+ void process(int64_t pts);
uint32_t trackNames() const { return mTrackNames; }
@@ -114,7 +114,7 @@ private:
struct state_t;
struct track_t;
- typedef void (*mix_t)(state_t* state);
+ typedef void (*mix_t)(state_t* state, int64_t pts);
typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux);
static const int BLOCKSIZE = 16; // 4 cache lines
@@ -152,6 +152,8 @@ private:
int32_t* mainBuffer;
int32_t* auxBuffer;
+ uint64_t localTimeFreq;
+
bool setResampler(uint32_t sampleRate, uint32_t devSampleRate);
bool doesResample() const { return resampler != NULL; }
void resetResampler() { if (resampler != NULL) resampler->reset(); }
@@ -187,14 +189,19 @@ private:
static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
- static void process__validate(state_t* state);
- static void process__nop(state_t* state);
- static void process__genericNoResampling(state_t* state);
- static void process__genericResampling(state_t* state);
- static void process__OneTrack16BitsStereoNoResampling(state_t* state);
+ static void process__validate(state_t* state, int64_t pts);
+ static void process__nop(state_t* state, int64_t pts);
+ static void process__genericNoResampling(state_t* state, int64_t pts);
+ static void process__genericResampling(state_t* state, int64_t pts);
+ static void process__OneTrack16BitsStereoNoResampling(state_t* state,
+ int64_t pts);
#if 0
- static void process__TwoTracks16BitsStereoNoResampling(state_t* state);
+ static void process__TwoTracks16BitsStereoNoResampling(state_t* state,
+ int64_t pts);
#endif
+
+ static int64_t calculateOutputPTS(const track_t& t, int64_t basePTS,
+ int outputFrameIndex);
};
// ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp
index 9486b9c9efe0..398ba0b6004d 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -122,7 +122,8 @@ AudioResampler::AudioResampler(int bitDepth, int inChannelCount,
int32_t sampleRate) :
mBitDepth(bitDepth), mChannelCount(inChannelCount),
mSampleRate(sampleRate), mInSampleRate(sampleRate), mInputIndex(0),
- mPhaseFraction(0) {
+ mPhaseFraction(0), mLocalTimeFreq(0),
+ mPTS(AudioBufferProvider::kInvalidPTS) {
// sanity check on format
if ((bitDepth != 16) ||(inChannelCount < 1) || (inChannelCount > 2)) {
ALOGE("Unsupported sample format, %d bits, %d channels", bitDepth,
@@ -150,6 +151,23 @@ void AudioResampler::setVolume(int16_t left, int16_t right) {
mVolume[1] = right;
}
+void AudioResampler::setLocalTimeFreq(uint64_t freq) {
+ mLocalTimeFreq = freq;
+}
+
+void AudioResampler::setPTS(int64_t pts) {
+ mPTS = pts;
+}
+
+int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) {
+
+ if (mPTS == AudioBufferProvider::kInvalidPTS) {
+ return AudioBufferProvider::kInvalidPTS;
+ } else {
+ return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate);
+ }
+}
+
void AudioResampler::reset() {
mInputIndex = 0;
mPhaseFraction = 0;
@@ -196,7 +214,8 @@ void AudioResamplerOrder1::resampleStereo16(int32_t* out, size_t outFrameCount,
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto resampleStereo16_exit;
}
@@ -290,7 +309,8 @@ void AudioResamplerOrder1::resampleMono16(int32_t* out, size_t outFrameCount,
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h
index c23016e02ca5..9deb7963d215 100644
--- a/services/audioflinger/AudioResampler.h
+++ b/services/audioflinger/AudioResampler.h
@@ -49,6 +49,10 @@ public:
virtual void init() = 0;
virtual void setSampleRate(int32_t inSampleRate);
virtual void setVolume(int16_t left, int16_t right);
+ virtual void setLocalTimeFreq(uint64_t freq);
+
+ // set the PTS of the next buffer output by the resampler
+ virtual void setPTS(int64_t pts);
virtual void resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) = 0;
@@ -72,6 +76,8 @@ protected:
AudioResampler(const AudioResampler&);
AudioResampler& operator=(const AudioResampler&);
+ int64_t calculateOutputPTS(int outputFrameIndex);
+
const int32_t mBitDepth;
const int32_t mChannelCount;
const int32_t mSampleRate;
@@ -85,6 +91,8 @@ protected:
size_t mInputIndex;
int32_t mPhaseIncrement;
uint32_t mPhaseFraction;
+ uint64_t mLocalTimeFreq;
+ int64_t mPTS;
};
// ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp
index c0e760e643ce..18e59e93ee2e 100644
--- a/services/audioflinger/AudioResamplerCubic.cpp
+++ b/services/audioflinger/AudioResamplerCubic.cpp
@@ -65,7 +65,7 @@ void AudioResamplerCubic::resampleStereo16(int32_t* out, size_t outFrameCount,
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL)
return;
// ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -95,7 +95,8 @@ void AudioResamplerCubic::resampleStereo16(int32_t* out, size_t outFrameCount,
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL)
goto save_state; // ugly, but efficient
in = mBuffer.i16;
@@ -130,7 +131,7 @@ void AudioResamplerCubic::resampleMono16(int32_t* out, size_t outFrameCount,
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL)
return;
// ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount);
@@ -160,7 +161,8 @@ void AudioResamplerCubic::resampleMono16(int32_t* out, size_t outFrameCount,
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL)
goto save_state; // ugly, but efficient
// ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -181,4 +183,3 @@ save_state:
// ----------------------------------------------------------------------------
}
; // namespace android
-
diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp
index 7a27b810afd0..d373c08385b0 100644
--- a/services/audioflinger/AudioResamplerSinc.cpp
+++ b/services/audioflinger/AudioResamplerSinc.cpp
@@ -203,7 +203,8 @@ void AudioResamplerSinc::resample(int32_t* out, size_t outFrameCount,
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto resample_exit;
}
@@ -354,4 +355,3 @@ void AudioResamplerSinc::interpolate(
// ----------------------------------------------------------------------------
}; // namespace android
-
diff --git a/services/common_time/Android.mk b/services/common_time/Android.mk
new file mode 100644
index 000000000000..aabe5728cb05
--- /dev/null
+++ b/services/common_time/Android.mk
@@ -0,0 +1,32 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# common_time_service
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ common_clock_service.cpp \
+ common_time_config_service.cpp \
+ common_time_server.cpp \
+ common_time_server_api.cpp \
+ common_time_server_packets.cpp \
+ clock_recovery.cpp \
+ common_clock.cpp \
+ main.cpp
+
+ifeq ($(TIME_SERVICE_DEBUG), true)
+LOCAL_SRC_FILES += diag_thread.cpp
+LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG
+endif
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcommon_time_client \
+ libutils
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := common_time
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/common_time/clock_recovery.cpp b/services/common_time/clock_recovery.cpp
new file mode 100644
index 000000000000..be054fb9f3a1
--- /dev/null
+++ b/services/common_time/clock_recovery.cpp
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define __STDC_LIMIT_MACROS
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+#include <stdint.h>
+
+#include <common_time/local_clock.h>
+#include <assert.h>
+
+#include "clock_recovery.h"
+#include "common_clock.h"
+#ifdef TIME_SERVICE_DEBUG
+#include "diag_thread.h"
+#endif
+
+namespace android {
+
+ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock,
+ CommonClock* common_clock) {
+ assert(NULL != local_clock);
+ assert(NULL != common_clock);
+
+ local_clock_ = local_clock;
+ common_clock_ = common_clock;
+
+ local_clock_can_slew_ = local_clock_->initCheck() &&
+ (local_clock_->setLocalSlew(0) == OK);
+
+ computePIDParams();
+ reset(true, true);
+
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_ = new DiagThread(common_clock_, local_clock_);
+ if (diag_thread_ != NULL) {
+ status_t res = diag_thread_->startWorkThread();
+ if (res != OK)
+ ALOGW("Failed to start A@H clock recovery diagnostic thread.");
+ } else
+ ALOGW("Failed to allocate diagnostic thread.");
+#endif
+}
+
+ClockRecoveryLoop::~ClockRecoveryLoop() {
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_->stopWorkThread();
+#endif
+}
+
+void ClockRecoveryLoop::reset(bool position, bool frequency) {
+ Mutex::Autolock lock(&lock_);
+ reset_l(position, frequency);
+}
+
+uint32_t ClockRecoveryLoop::findMinRTTNdx(DisciplineDataPoint* data,
+ uint32_t count) {
+ uint32_t min_rtt = 0;
+ for (uint32_t i = 1; i < count; ++i)
+ if (data[min_rtt].rtt > data[i].rtt)
+ min_rtt = i;
+
+ return min_rtt;
+}
+
+bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
+ int64_t nominal_common_time,
+ int64_t rtt) {
+ Mutex::Autolock lock(&lock_);
+
+ // If we have not defined a basis for common time, then we need to use these
+ // initial points to do so. In order to avoid significant initial error
+ // from a particularly bad startup data point, we collect the first N data
+ // points and choose the best of them before moving on.
+ if (!common_clock_->isValid()) {
+ if (startup_filter_wr_ < kStartupFilterSize) {
+ DisciplineDataPoint& d = startup_filter_data_[startup_filter_wr_];
+ d.local_time = local_time;
+ d.nominal_common_time = nominal_common_time;
+ d.rtt = rtt;
+ startup_filter_wr_++;
+ }
+
+ if (startup_filter_wr_ == kStartupFilterSize) {
+ uint32_t min_rtt = findMinRTTNdx(startup_filter_data_,
+ kStartupFilterSize);
+
+ common_clock_->setBasis(
+ startup_filter_data_[min_rtt].local_time,
+ startup_filter_data_[min_rtt].nominal_common_time);
+ }
+
+ return true;
+ }
+
+ int64_t observed_common;
+ int64_t delta;
+ int32_t delta32;
+ int32_t correction_cur;
+ int32_t correction_cur_P = 0;
+ int32_t correction_cur_I = 0;
+ int32_t correction_cur_D = 0;
+
+ if (OK != common_clock_->localToCommon(local_time, &observed_common)) {
+ // Since we just checked to make certain that this conversion was valid,
+ // and no one else in the system should be messing with it, if this
+ // conversion is suddenly invalid, it is a good reason to panic.
+ ALOGE("Failed to convert local time to common time in %s:%d",
+ __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ // Implement a filter which should match NTP filtering behavior when a
+ // client is associated with only one peer of lower stratum. Basically,
+ // always use the best of the N last data points, where best is defined as
+ // lowest round trip time. NTP uses an N of 8; we use a value of 6.
+ //
+ // TODO(johngro) : experiment with other filter strategies. The goal here
+ // is to mitigate the effects of high RTT data points which typically have
+ // large asymmetries in the TX/RX legs. Downside of the existing NTP
+ // approach (particularly because of the PID controller we are using to
+ // produce the control signal from the filtered data) are that the rate at
+ // which discipline events are actually acted upon becomes irregular and can
+ // become drawn out (the time between actionable event can go way up). If
+ // the system receives a strong high quality data point, the proportional
+ // component of the controller can produce a strong correction which is left
+ // in place for too long causing overshoot. In addition, the integral
+ // component of the system currently is an approximation based on the
+ // assumption of a more or less homogeneous sampling of the error. Its
+ // unclear what the effect of undermining this assumption would be right
+ // now.
+
+ // Two ideas which come to mind immediately would be to...
+ // 1) Keep a history of more data points (32 or so) and ignore data points
+ // whose RTT is more than a certain number of standard deviations outside
+ // of the norm.
+ // 2) Eliminate the PID controller portion of this system entirely.
+ // Instead, move to a system which uses a very wide filter (128 data
+ // points or more) with a sum-of-least-squares line fitting approach to
+ // tracking the long term drift. This would take the place of the I
+ // component in the current PID controller. Also use a much more narrow
+ // outlier-rejector filter (as described in #1) to drive a short term
+ // correction factor similar to the P component of the PID controller.
+ assert(filter_wr_ < kFilterSize);
+ filter_data_[filter_wr_].local_time = local_time;
+ filter_data_[filter_wr_].observed_common_time = observed_common;
+ filter_data_[filter_wr_].nominal_common_time = nominal_common_time;
+ filter_data_[filter_wr_].rtt = rtt;
+ filter_data_[filter_wr_].point_used = false;
+ filter_wr_ = (filter_wr_ + 1) % kFilterSize;
+ if (!filter_wr_)
+ filter_full_ = true;
+
+ // Scan the accumulated data for the point with the minimum RTT. If that
+ // point has never been used before, go ahead and use it now, otherwise just
+ // do nothing.
+ uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_;
+ uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end);
+ if (filter_data_[min_rtt].point_used)
+ return true;
+
+ local_time = filter_data_[min_rtt].local_time;
+ observed_common = filter_data_[min_rtt].observed_common_time;
+ nominal_common_time = filter_data_[min_rtt].nominal_common_time;
+ filter_data_[min_rtt].point_used = true;
+
+ // Compute the error then clamp to the panic threshold. If we ever exceed
+ // this amt of error, its time to panic and reset the system. Given that
+ // the error in the measurement of the error could be as high as the RTT of
+ // the data point, we don't actually panic until the implied error (delta)
+ // is greater than the absolute panic threashold plus the RTT. IOW - we
+ // don't panic until we are absoluely sure that our best case sync is worse
+ // than the absolute panic threshold.
+ int64_t effective_panic_thresh = panic_thresh_ + filter_data_[min_rtt].rtt;
+ delta = nominal_common_time - observed_common;
+ if ((delta > effective_panic_thresh) || (delta < -effective_panic_thresh)) {
+ // PANIC!!!
+ //
+ // TODO(johngro) : need to report this to the upper levels of
+ // code.
+ reset_l(false, true);
+ return false;
+ } else
+ delta32 = delta;
+
+ // Accumulate error into the integrated error, then clamp.
+ integrated_error_ += delta32;
+ if (integrated_error_ > pid_params_.integrated_delta_max)
+ integrated_error_ = pid_params_.integrated_delta_max;
+ else if (integrated_error_ < pid_params_.integrated_delta_min)
+ integrated_error_ = pid_params_.integrated_delta_min;
+
+ // Compute the difference in error between last time and this time, then
+ // update last_delta_
+ int32_t input_D = last_delta_valid_ ? delta32 - last_delta_ : 0;
+ last_delta_valid_ = true;
+ last_delta_ = delta32;
+
+ // Compute the various components of the correction value.
+ correction_cur_P = doGainScale(pid_params_.gain_P, delta32);
+ correction_cur_I = doGainScale(pid_params_.gain_I, integrated_error_);
+
+ // TODO(johngro) : the differential portion of this code used to rely
+ // upon a completely homogeneous discipline frequency. Now that the
+ // discipline frequency may not be homogeneous, its probably important
+ // to divide by the amt of time between discipline events during the
+ // gain calculation.
+ correction_cur_D = doGainScale(pid_params_.gain_D, input_D);
+
+ // Compute the final correction value and clamp.
+ correction_cur = correction_cur_P + correction_cur_I + correction_cur_D;
+ if (correction_cur < pid_params_.correction_min)
+ correction_cur = pid_params_.correction_min;
+ else if (correction_cur > pid_params_.correction_max)
+ correction_cur = pid_params_.correction_max;
+
+ // If there was a change in the amt of correction to use, update the
+ // system.
+ if (correction_cur_ != correction_cur) {
+ correction_cur_ = correction_cur;
+ applySlew();
+ }
+
+ ALOGV("rtt %lld observed %lld nominal %lld delta = %5lld "
+ "int = %7d correction %5d (P %5d, I %5d, D %5d)\n",
+ filter_data_[min_rtt].rtt,
+ observed_common,
+ nominal_common_time,
+ nominal_common_time - observed_common,
+ integrated_error_,
+ correction_cur,
+ correction_cur_P,
+ correction_cur_I,
+ correction_cur_D);
+
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_->pushDisciplineEvent(
+ local_time,
+ observed_common,
+ nominal_common_time,
+ correction_cur,
+ correction_cur_P,
+ correction_cur_I,
+ correction_cur_D);
+#endif
+
+ return true;
+}
+
+int32_t ClockRecoveryLoop::getLastErrorEstimate() {
+ Mutex::Autolock lock(&lock_);
+
+ if (last_delta_valid_)
+ return last_delta_;
+ else
+ return ICommonClock::kErrorEstimateUnknown;
+}
+
+void ClockRecoveryLoop::computePIDParams() {
+ // TODO(johngro) : add the ability to fetch parameters from the driver/board
+ // level in case they have a HW clock discipline solution with parameters
+ // tuned specifically for it.
+
+ // Correction factor is limited to MIN/MAX_INT_16
+ pid_params_.correction_min = -0x8000;
+ pid_params_.correction_max = 0x7FFF;
+
+ // Default proportional gain to 2^15:1000. (max proportional drive at 1mSec
+ // of instantaneous error)
+ memset(&pid_params_.gain_P, 0, sizeof(pid_params_.gain_P));
+ pid_params_.gain_P.a_to_b_numer = 0x8000;
+ pid_params_.gain_P.a_to_b_denom = 1000;
+
+ // Set the integral gain to 2^15:5000
+ memset(&pid_params_.gain_I, 0, sizeof(pid_params_.gain_I));
+ pid_params_.gain_I.a_to_b_numer = 0x8000;
+ pid_params_.gain_I.a_to_b_denom = 5000;
+
+ // Default controller is just a PI controller. Right now, the network based
+ // measurements of the error are way to noisy to feed into the differential
+ // component of a PID controller. Someday we might come back and add some
+ // filtering of the error channel, but until then leave the controller as a
+ // simple PI controller.
+ memset(&pid_params_.gain_D, 0, sizeof(pid_params_.gain_D));
+
+ // Don't let the integral component of the controller wind up to
+ // the point where it would want to drive the correction factor
+ // past saturation.
+ int64_t tmp;
+ pid_params_.gain_I.doReverseTransform(pid_params_.correction_min, &tmp);
+ pid_params_.integrated_delta_min = static_cast<int32_t>(tmp);
+ pid_params_.gain_I.doReverseTransform(pid_params_.correction_max, &tmp);
+ pid_params_.integrated_delta_max = static_cast<int32_t>(tmp);
+
+ // By default, panic when are certain that the sync error is > 20mSec;
+ panic_thresh_ = 20000;
+}
+
+void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
+ assert(NULL != common_clock_);
+
+ if (position) {
+ common_clock_->resetBasis();
+ startup_filter_wr_ = 0;
+ }
+
+ if (frequency) {
+ last_delta_valid_ = false;
+ last_delta_ = 0;
+ integrated_error_ = 0;
+ correction_cur_ = 0;
+ applySlew();
+ }
+
+ filter_wr_ = 0;
+ filter_full_ = false;
+}
+
+int32_t ClockRecoveryLoop::doGainScale(const LinearTransform& gain,
+ int32_t val) {
+ if (!gain.a_to_b_numer || !gain.a_to_b_denom || !val)
+ return 0;
+
+ int64_t tmp;
+ int64_t val64 = static_cast<int64_t>(val);
+ if (!gain.doForwardTransform(val64, &tmp)) {
+ ALOGW("Overflow/Underflow while scaling %d in %s",
+ val, __PRETTY_FUNCTION__);
+ return (val < 0) ? INT32_MIN : INT32_MAX;
+ }
+
+ if (tmp > INT32_MAX) {
+ ALOGW("Overflow while scaling %d in %s", val, __PRETTY_FUNCTION__);
+ return INT32_MAX;
+ }
+
+ if (tmp < INT32_MIN) {
+ ALOGW("Underflow while scaling %d in %s", val, __PRETTY_FUNCTION__);
+ return INT32_MIN;
+ }
+
+ return static_cast<int32_t>(tmp);
+}
+
+void ClockRecoveryLoop::applySlew() {
+ if (local_clock_can_slew_) {
+ local_clock_->setLocalSlew(correction_cur_);
+ } else {
+ // The SW clock recovery implemented by the common clock class expects
+ // values expressed in PPM. Map the MIN/MAX_INT_16 drive range to +/-
+ // 100ppm.
+ int sw_correction;
+ sw_correction = correction_cur_ - pid_params_.correction_min;
+ sw_correction *= 200;
+ sw_correction /= (pid_params_.correction_max -
+ pid_params_.correction_min);
+ sw_correction -= 100;
+
+ common_clock_->setSlew(local_clock_->getLocalTime(), sw_correction);
+ }
+}
+
+} // namespace android
diff --git a/services/common_time/clock_recovery.h b/services/common_time/clock_recovery.h
new file mode 100644
index 000000000000..5c35c381f5e0
--- /dev/null
+++ b/services/common_time/clock_recovery.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 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 __CLOCK_RECOVERY_H__
+#define __CLOCK_RECOVERY_H__
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#ifdef TIME_SERVICE_DEBUG
+#include "diag_thread.h"
+#endif
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class ClockRecoveryLoop {
+ public:
+ ClockRecoveryLoop(LocalClock* local_clock, CommonClock* common_clock);
+ ~ClockRecoveryLoop();
+
+ void reset(bool position, bool frequency);
+ bool pushDisciplineEvent(int64_t local_time,
+ int64_t nominal_common_time,
+ int64_t data_point_rtt);
+ int32_t getLastErrorEstimate();
+
+ private:
+ typedef struct {
+ // Limits for the correction factor supplied to set_counter_slew_rate.
+ // The controller will always clamp its output to the range expressed by
+ // correction_(min|max)
+ int32_t correction_min;
+ int32_t correction_max;
+
+ // Limits for the internal integration accumulator in the PID
+ // controller. The value of the accumulator is scaled by gain_I to
+ // produce the integral component of the PID controller output.
+ // Platforms can use these limits to prevent windup in the system
+ // if/when the correction factor needs to be driven to saturation for
+ // extended periods of time.
+ int32_t integrated_delta_min;
+ int32_t integrated_delta_max;
+
+ // Gain for the P, I and D components of the controller.
+ LinearTransform gain_P;
+ LinearTransform gain_I;
+ LinearTransform gain_D;
+ } PIDParams;
+
+ typedef struct {
+ int64_t local_time;
+ int64_t observed_common_time;
+ int64_t nominal_common_time;
+ int64_t rtt;
+ bool point_used;
+ } DisciplineDataPoint;
+
+ static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count);
+
+ void computePIDParams();
+ void reset_l(bool position, bool frequency);
+ static int32_t doGainScale(const LinearTransform& gain, int32_t val);
+ void applySlew();
+
+ // The local clock HW abstraction we use as the basis for common time.
+ LocalClock* local_clock_;
+ bool local_clock_can_slew_;
+
+ // The common clock we end up controlling along with the lock used to
+ // serialize operations.
+ CommonClock* common_clock_;
+ Mutex lock_;
+
+ // The parameters computed to be used for the PID Controller.
+ PIDParams pid_params_;
+
+ // The maximum allowed error (as indicated by a pushDisciplineEvent) before
+ // we panic.
+ int32_t panic_thresh_;
+
+ // parameters maintained while running and reset during a reset
+ // of the frequency correction.
+ bool last_delta_valid_;
+ int32_t last_delta_;
+ int32_t integrated_error_;
+ int32_t correction_cur_;
+
+ // State kept for filtering the discipline data.
+ static const uint32_t kFilterSize = 6;
+ DisciplineDataPoint filter_data_[kFilterSize];
+ uint32_t filter_wr_;
+ bool filter_full_;
+
+ static const uint32_t kStartupFilterSize = 4;
+ DisciplineDataPoint startup_filter_data_[kStartupFilterSize];
+ uint32_t startup_filter_wr_;
+
+#ifdef TIME_SERVICE_DEBUG
+ sp<DiagThread> diag_thread_;
+#endif
+};
+
+} // namespace android
+
+#endif // __CLOCK_RECOVERY_H__
diff --git a/services/common_time/common_clock.cpp b/services/common_time/common_clock.cpp
new file mode 100644
index 000000000000..c9eb3884440d
--- /dev/null
+++ b/services/common_time/common_clock.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 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 __STDC_LIMIT_MACROS
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+
+#include "common_clock.h"
+
+namespace android {
+
+CommonClock::CommonClock() {
+ cur_slew_ = 0;
+ cur_trans_valid_ = false;
+
+ cur_trans_.a_zero = 0;
+ cur_trans_.b_zero = 0;
+ cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = 1;
+ cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = 1;
+ duration_trans_ = cur_trans_;
+}
+
+bool CommonClock::init(uint64_t local_freq) {
+ Mutex::Autolock lock(&lock_);
+
+ if (!local_freq)
+ return false;
+
+ uint64_t numer = kCommonFreq;
+ uint64_t denom = local_freq;
+
+ LinearTransform::reduce(&numer, &denom);
+ if ((numer > UINT32_MAX) || (denom > UINT32_MAX)) {
+ ALOGE("Overflow in CommonClock::init while trying to reduce %lld/%lld",
+ kCommonFreq, local_freq);
+ return false;
+ }
+
+ cur_trans_.a_to_b_numer = local_to_common_freq_numer_ =
+ static_cast<uint32_t>(numer);
+ cur_trans_.a_to_b_denom = local_to_common_freq_denom_ =
+ static_cast<uint32_t>(denom);
+ duration_trans_ = cur_trans_;
+
+ return true;
+}
+
+status_t CommonClock::localToCommon(int64_t local, int64_t *common_out) const {
+ Mutex::Autolock lock(&lock_);
+
+ if (!cur_trans_valid_)
+ return INVALID_OPERATION;
+
+ if (!cur_trans_.doForwardTransform(local, common_out))
+ return INVALID_OPERATION;
+
+ return OK;
+}
+
+status_t CommonClock::commonToLocal(int64_t common, int64_t *local_out) const {
+ Mutex::Autolock lock(&lock_);
+
+ if (!cur_trans_valid_)
+ return INVALID_OPERATION;
+
+ if (!cur_trans_.doReverseTransform(common, local_out))
+ return INVALID_OPERATION;
+
+ return OK;
+}
+
+int64_t CommonClock::localDurationToCommonDuration(int64_t localDur) const {
+ int64_t ret;
+ duration_trans_.doForwardTransform(localDur, &ret);
+ return ret;
+}
+
+void CommonClock::setBasis(int64_t local, int64_t common) {
+ Mutex::Autolock lock(&lock_);
+
+ cur_trans_.a_zero = local;
+ cur_trans_.b_zero = common;
+ cur_trans_valid_ = true;
+}
+
+void CommonClock::resetBasis() {
+ Mutex::Autolock lock(&lock_);
+
+ cur_trans_.a_zero = 0;
+ cur_trans_.b_zero = 0;
+ cur_trans_valid_ = false;
+}
+
+status_t CommonClock::setSlew(int64_t change_time, int32_t ppm) {
+ Mutex::Autolock lock(&lock_);
+
+ int64_t new_local_basis;
+ int64_t new_common_basis;
+
+ if (cur_trans_valid_) {
+ new_local_basis = change_time;
+ if (!cur_trans_.doForwardTransform(change_time, &new_common_basis)) {
+ ALOGE("Overflow when attempting to set slew rate to %d", ppm);
+ return INVALID_OPERATION;
+ }
+ } else {
+ new_local_basis = 0;
+ new_common_basis = 0;
+ }
+
+ cur_slew_ = ppm;
+ uint32_t n1 = local_to_common_freq_numer_;
+ uint32_t n2 = 1000000 + cur_slew_;
+
+ uint32_t d1 = local_to_common_freq_denom_;
+ uint32_t d2 = 1000000;
+
+ // n1/d1 has already been reduced, no need to do so here.
+ LinearTransform::reduce(&n1, &d2);
+ LinearTransform::reduce(&n2, &d1);
+ LinearTransform::reduce(&n2, &d2);
+
+ cur_trans_.a_zero = new_local_basis;
+ cur_trans_.b_zero = new_common_basis;
+ cur_trans_.a_to_b_numer = n1 * n2;
+ cur_trans_.a_to_b_denom = d1 * d2;
+
+ return OK;
+}
+
+} // namespace android
diff --git a/services/common_time/common_clock.h b/services/common_time/common_clock.h
new file mode 100644
index 000000000000..b786fdceba5f
--- /dev/null
+++ b/services/common_time/common_clock.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 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 __COMMON_CLOCK_H__
+#define __COMMON_CLOCK_H__
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock {
+ public:
+ CommonClock();
+
+ bool init(uint64_t local_freq);
+
+ status_t localToCommon(int64_t local, int64_t *common_out) const;
+ status_t commonToLocal(int64_t common, int64_t *local_out) const;
+ int64_t localDurationToCommonDuration(int64_t localDur) const;
+ uint64_t getCommonFreq() const { return kCommonFreq; }
+ bool isValid() const { return cur_trans_valid_; }
+ status_t setSlew(int64_t change_time, int32_t ppm);
+ void setBasis(int64_t local, int64_t common);
+ void resetBasis();
+ private:
+ mutable Mutex lock_;
+
+ int32_t cur_slew_;
+ uint32_t local_to_common_freq_numer_;
+ uint32_t local_to_common_freq_denom_;
+
+ LinearTransform duration_trans_;
+ LinearTransform cur_trans_;
+ bool cur_trans_valid_;
+
+ static const uint64_t kCommonFreq = 1000000ull;
+};
+
+} // namespace android
+#endif // __COMMON_CLOCK_H__
diff --git a/services/common_time/common_clock_service.cpp b/services/common_time/common_clock_service.cpp
new file mode 100644
index 000000000000..9ca6f3523084
--- /dev/null
+++ b/services/common_time/common_clock_service.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <common_time/local_clock.h>
+#include <utils/String8.h>
+
+#include "common_clock_service.h"
+#include "common_clock.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonClockService> CommonClockService::instantiate(
+ CommonTimeServer& timeServer) {
+ sp<CommonClockService> tcc = new CommonClockService(timeServer);
+ if (tcc == NULL)
+ return NULL;
+
+ defaultServiceManager()->addService(ICommonClock::kServiceName, tcc);
+ return tcc;
+}
+
+status_t CommonClockService::dump(int fd, const Vector<String16>& args) {
+ Mutex::Autolock lock(mRegistrationLock);
+ return mTimeServer.dumpClockInterface(fd, args, mListeners.size());
+}
+
+status_t CommonClockService::isCommonTimeValid(bool* valid,
+ uint32_t* timelineID) {
+ return mTimeServer.isCommonTimeValid(valid, timelineID);
+}
+
+status_t CommonClockService::commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) {
+ return mTimeServer.getCommonClock().commonToLocal(commonTime, localTime);
+}
+
+status_t CommonClockService::localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) {
+ return mTimeServer.getCommonClock().localToCommon(localTime, commonTime);
+}
+
+status_t CommonClockService::getCommonTime(int64_t* commonTime) {
+ return localTimeToCommonTime(mTimeServer.getLocalClock().getLocalTime(), commonTime);
+}
+
+status_t CommonClockService::getCommonFreq(uint64_t* freq) {
+ *freq = mTimeServer.getCommonClock().getCommonFreq();
+ return OK;
+}
+
+status_t CommonClockService::getLocalTime(int64_t* localTime) {
+ *localTime = mTimeServer.getLocalClock().getLocalTime();
+ return OK;
+}
+
+status_t CommonClockService::getLocalFreq(uint64_t* freq) {
+ *freq = mTimeServer.getLocalClock().getLocalFreq();
+ return OK;
+}
+
+status_t CommonClockService::getEstimatedError(int32_t* estimate) {
+ *estimate = mTimeServer.getEstimatedError();
+ return OK;
+}
+
+status_t CommonClockService::getTimelineID(uint64_t* id) {
+ *id = mTimeServer.getTimelineID();
+ return OK;
+}
+
+status_t CommonClockService::getState(State* state) {
+ *state = mTimeServer.getState();
+ return OK;
+}
+
+status_t CommonClockService::getMasterAddr(struct sockaddr_storage* addr) {
+ return mTimeServer.getMasterAddr(addr);
+}
+
+status_t CommonClockService::registerListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mRegistrationLock);
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ // check whether this is a duplicate
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder())
+ return ALREADY_EXISTS;
+ }
+ }
+
+ mListeners.add(listener);
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+ return listener->asBinder()->linkToDeath(this);
+}
+
+status_t CommonClockService::unregisterListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mRegistrationLock);
+ status_t ret_val = NAME_NOT_FOUND;
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder()) {
+ mListeners[i]->asBinder()->unlinkToDeath(this);
+ mListeners.removeAt(i);
+ ret_val = OK;
+ break;
+ }
+ }
+ }
+
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+ return ret_val;
+}
+
+void CommonClockService::binderDied(const wp<IBinder>& who) {
+ Mutex::Autolock lock(mRegistrationLock);
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == who) {
+ mListeners.removeAt(i);
+ break;
+ }
+ }
+ }
+
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+}
+
+void CommonClockService::notifyOnTimelineChanged(uint64_t timelineID) {
+ Mutex::Autolock lock(mCallbackLock);
+
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ mListeners[i]->onTimelineChanged(timelineID);
+ }
+}
+
+}; // namespace android
diff --git a/services/common_time/common_clock_service.h b/services/common_time/common_clock_service.h
new file mode 100644
index 000000000000..a65e398d276f
--- /dev/null
+++ b/services/common_time/common_clock_service.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <common_time/ICommonClock.h>
+
+#ifndef ANDROID_COMMON_CLOCK_SERVICE_H
+#define ANDROID_COMMON_CLOCK_SERVICE_H
+
+namespace android {
+
+class CommonTimeServer;
+
+class CommonClockService : public BnCommonClock,
+ public android::IBinder::DeathRecipient {
+ public:
+ static sp<CommonClockService> instantiate(CommonTimeServer& timeServer);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t *timelineID);
+ virtual status_t commonTimeToLocalTime(int64_t common_time,
+ int64_t* local_time);
+ virtual status_t localTimeToCommonTime(int64_t local_time,
+ int64_t* common_time);
+ virtual status_t getCommonTime(int64_t* common_time);
+ virtual status_t getCommonFreq(uint64_t* freq);
+ virtual status_t getLocalTime(int64_t* local_time);
+ virtual status_t getLocalFreq(uint64_t* freq);
+ virtual status_t getEstimatedError(int32_t* estimate);
+ virtual status_t getTimelineID(uint64_t* id);
+ virtual status_t getState(ICommonClock::State* state);
+ virtual status_t getMasterAddr(struct sockaddr_storage* addr);
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener);
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener);
+
+ void notifyOnTimelineChanged(uint64_t timelineID);
+
+ private:
+ CommonClockService(CommonTimeServer& timeServer)
+ : mTimeServer(timeServer) { };
+
+ virtual void binderDied(const wp<IBinder>& who);
+
+ CommonTimeServer& mTimeServer;
+
+ // locks used to synchronize access to the list of registered listeners.
+ // The callback lock is held whenever the list is used to perform callbacks
+ // or while the list is being modified. The registration lock used to
+ // serialize access across registerListener, unregisterListener, and
+ // binderDied.
+ //
+ // The reason for two locks is that registerListener, unregisterListener,
+ // and binderDied each call into the core service and obtain the core
+ // service thread lock when they call reevaluateAutoDisableState. The core
+ // service thread obtains the main thread lock whenever its thread is
+ // running, and sometimes needs to call notifyOnTimelineChanged which then
+ // obtains the callback lock. If callers of registration functions were
+ // holding the callback lock when they called into the core service, we
+ // would have a classic A/B, B/A ordering deadlock. To avoid this, the
+ // registration functions hold the registration lock for the duration of
+ // their call, but hold the callback lock only while they mutate the list.
+ // This way, the list's size cannot change (because of the registration
+ // lock) during the call into reevaluateAutoDisableState, but the core work
+ // thread can still safely call notifyOnTimelineChanged while holding the
+ // main thread lock.
+ Mutex mCallbackLock;
+ Mutex mRegistrationLock;
+
+ Vector<sp<ICommonClockListener> > mListeners;
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_CLOCK_SERVICE_H
diff --git a/services/common_time/common_time_config_service.cpp b/services/common_time/common_time_config_service.cpp
new file mode 100644
index 000000000000..958561818423
--- /dev/null
+++ b/services/common_time/common_time_config_service.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <utils/String8.h>
+
+#include "common_time_config_service.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonTimeConfigService> CommonTimeConfigService::instantiate(
+ CommonTimeServer& timeServer) {
+ sp<CommonTimeConfigService> ctcs = new CommonTimeConfigService(timeServer);
+ if (ctcs == NULL)
+ return NULL;
+
+ defaultServiceManager()->addService(ICommonTimeConfig::kServiceName, ctcs);
+ return ctcs;
+}
+
+status_t CommonTimeConfigService::dump(int fd, const Vector<String16>& args) {
+ return mTimeServer.dumpConfigInterface(fd, args);
+}
+
+status_t CommonTimeConfigService::getMasterElectionPriority(uint8_t *priority) {
+ return mTimeServer.getMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::setMasterElectionPriority(uint8_t priority) {
+ return mTimeServer.setMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::getMasterElectionEndpoint(
+ struct sockaddr_storage *addr) {
+ return mTimeServer.getMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::setMasterElectionEndpoint(
+ const struct sockaddr_storage *addr) {
+ return mTimeServer.setMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::getMasterElectionGroupId(uint64_t *id) {
+ return mTimeServer.getMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::setMasterElectionGroupId(uint64_t id) {
+ return mTimeServer.setMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::getInterfaceBinding(String16& ifaceName) {
+ String8 tmp;
+ status_t ret = mTimeServer.getInterfaceBinding(tmp);
+ ifaceName = String16(tmp);
+ return ret;
+}
+
+status_t CommonTimeConfigService::setInterfaceBinding(const String16& ifaceName) {
+ String8 tmp(ifaceName);
+ return mTimeServer.setInterfaceBinding(tmp);
+}
+
+status_t CommonTimeConfigService::getMasterAnnounceInterval(int *interval) {
+ return mTimeServer.getMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::setMasterAnnounceInterval(int interval) {
+ return mTimeServer.setMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::getClientSyncInterval(int *interval) {
+ return mTimeServer.getClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::setClientSyncInterval(int interval) {
+ return mTimeServer.setClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::getPanicThreshold(int *threshold) {
+ return mTimeServer.getPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::setPanicThreshold(int threshold) {
+ return mTimeServer.setPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::getAutoDisable(bool *autoDisable) {
+ return mTimeServer.getAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::setAutoDisable(bool autoDisable) {
+ return mTimeServer.setAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::forceNetworklessMasterMode() {
+ return mTimeServer.forceNetworklessMasterMode();
+}
+
+}; // namespace android
diff --git a/services/common_time/common_time_config_service.h b/services/common_time/common_time_config_service.h
new file mode 100644
index 000000000000..8283c24e0f4c
--- /dev/null
+++ b/services/common_time/common_time_config_service.h
@@ -0,0 +1,59 @@
+/* * Copyright (C) 2012 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.
+ */
+
+#include <common_time/ICommonTimeConfig.h>
+
+#ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+#define ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+
+namespace android {
+
+class String16;
+class CommonTimeServer;
+
+class CommonTimeConfigService : public BnCommonTimeConfig {
+ public:
+ static sp<CommonTimeConfigService> instantiate(CommonTimeServer& timeServer);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t getMasterElectionPriority(uint8_t *priority);
+ virtual status_t setMasterElectionPriority(uint8_t priority);
+ virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr);
+ virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr);
+ virtual status_t getMasterElectionGroupId(uint64_t *id);
+ virtual status_t setMasterElectionGroupId(uint64_t id);
+ virtual status_t getInterfaceBinding(String16& ifaceName);
+ virtual status_t setInterfaceBinding(const String16& ifaceName);
+ virtual status_t getMasterAnnounceInterval(int *interval);
+ virtual status_t setMasterAnnounceInterval(int interval);
+ virtual status_t getClientSyncInterval(int *interval);
+ virtual status_t setClientSyncInterval(int interval);
+ virtual status_t getPanicThreshold(int *threshold);
+ virtual status_t setPanicThreshold(int threshold);
+ virtual status_t getAutoDisable(bool *autoDisable);
+ virtual status_t setAutoDisable(bool autoDisable);
+ virtual status_t forceNetworklessMasterMode();
+
+ private:
+ CommonTimeConfigService(CommonTimeServer& timeServer)
+ : mTimeServer(timeServer) { }
+ CommonTimeServer& mTimeServer;
+
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_TIME_CONFIG_SERVICE_H
diff --git a/services/common_time/common_time_server.cpp b/services/common_time/common_time_server.cpp
new file mode 100644
index 000000000000..4fed0d0a2470
--- /dev/null
+++ b/services/common_time/common_time_server.cpp
@@ -0,0 +1,1380 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <stdio.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <common_time/local_clock.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <utils/Timers.h>
+
+#include "common_clock_service.h"
+#include "common_time_config_service.h"
+#include "common_time_server.h"
+#include "common_time_server_packets.h"
+#include "clock_recovery.h"
+#include "common_clock.h"
+
+#define MAX_INT ((int)0x7FFFFFFF)
+
+namespace android {
+
+const char* CommonTimeServer::kDefaultMasterElectionAddr = "239.195.128.88";
+const uint16_t CommonTimeServer::kDefaultMasterElectionPort = 8887;
+const uint64_t CommonTimeServer::kDefaultSyncGroupID = 0;
+const uint8_t CommonTimeServer::kDefaultMasterPriority = 1;
+const uint32_t CommonTimeServer::kDefaultMasterAnnounceIntervalMs = 10000;
+const uint32_t CommonTimeServer::kDefaultSyncRequestIntervalMs = 1000;
+const uint32_t CommonTimeServer::kDefaultPanicThresholdUsec = 50000;
+const bool CommonTimeServer::kDefaultAutoDisable = true;
+const int CommonTimeServer::kSetupRetryTimeoutMs = 30000;
+const int64_t CommonTimeServer::kNoGoodDataPanicThresholdUsec = 600000000ll;
+const uint32_t CommonTimeServer::kRTTDiscardPanicThreshMultiplier = 5;
+
+// timeout value representing an infinite timeout
+const int CommonTimeServer::kInfiniteTimeout = -1;
+
+/*** Initial state constants ***/
+
+// number of WhoIsMaster attempts sent before giving up
+const int CommonTimeServer::kInitial_NumWhoIsMasterRetries = 6;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int CommonTimeServer::kInitial_WhoIsMasterTimeoutMs = 500;
+
+/*** Client state constants ***/
+
+// number of sync requests that can fail before a client assumes its master
+// is dead
+const int CommonTimeServer::kClient_NumSyncRequestRetries = 5;
+
+/*** Master state constants ***/
+
+/*** Ronin state constants ***/
+
+// number of WhoIsMaster attempts sent before declaring ourselves master
+const int CommonTimeServer::kRonin_NumWhoIsMasterRetries = 4;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int CommonTimeServer::kRonin_WhoIsMasterTimeoutMs = 500;
+
+/*** WaitForElection state constants ***/
+
+// how long do we wait for an announcement from a master before
+// trying another election?
+const int CommonTimeServer::kWaitForElection_TimeoutMs = 5000;
+
+CommonTimeServer::CommonTimeServer()
+ : Thread(false)
+ , mState(ICommonClock::STATE_INITIAL)
+ , mClockRecovery(&mLocalClock, &mCommonClock)
+ , mSocket(-1)
+ , mLastPacketRxLocalTime(0)
+ , mTimelineID(ICommonClock::kInvalidTimelineID)
+ , mClockSynced(false)
+ , mCommonClockHasClients(false)
+ , mInitial_WhoIsMasterRequestTimeouts(0)
+ , mClient_MasterDeviceID(0)
+ , mClient_MasterDevicePriority(0)
+ , mRonin_WhoIsMasterRequestTimeouts(0) {
+ // zero out sync stats
+ resetSyncStats();
+
+ // Setup the master election endpoint to use the default.
+ struct sockaddr_in* meep =
+ reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP);
+ memset(&mMasterElectionEP, 0, sizeof(mMasterElectionEP));
+ inet_aton(kDefaultMasterElectionAddr, &meep->sin_addr);
+ meep->sin_family = AF_INET;
+ meep->sin_port = htons(kDefaultMasterElectionPort);
+
+ // Zero out the master endpoint.
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+ mBindIfaceValid = false;
+ setForceLowPriority(false);
+
+ // Set all remaining configuration parameters to their defaults.
+ mDeviceID = 0;
+ mSyncGroupID = kDefaultSyncGroupID;
+ mMasterPriority = kDefaultMasterPriority;
+ mMasterAnnounceIntervalMs = kDefaultMasterAnnounceIntervalMs;
+ mSyncRequestIntervalMs = kDefaultSyncRequestIntervalMs;
+ mPanicThresholdUsec = kDefaultPanicThresholdUsec;
+ mAutoDisable = kDefaultAutoDisable;
+
+ // Create the eventfd we will use to signal our thread to wake up when
+ // needed.
+ mWakeupThreadFD = eventfd(0, EFD_NONBLOCK);
+
+ // seed the random number generator (used to generated timeline IDs)
+ srand48(static_cast<unsigned int>(systemTime()));
+}
+
+CommonTimeServer::~CommonTimeServer() {
+ shutdownThread();
+
+ // No need to grab the lock here. We are in the destructor; if the the user
+ // has a thread in any of the APIs while the destructor is being called,
+ // there is a threading problem a the application level we cannot reasonably
+ // do anything about.
+ cleanupSocket_l();
+
+ if (mWakeupThreadFD >= 0) {
+ close(mWakeupThreadFD);
+ mWakeupThreadFD = -1;
+ }
+}
+
+bool CommonTimeServer::startServices() {
+ // start the ICommonClock service
+ mICommonClock = CommonClockService::instantiate(*this);
+ if (mICommonClock == NULL)
+ return false;
+
+ // start the ICommonTimeConfig service
+ mICommonTimeConfig = CommonTimeConfigService::instantiate(*this);
+ if (mICommonTimeConfig == NULL)
+ return false;
+
+ return true;
+}
+
+bool CommonTimeServer::threadLoop() {
+ // Register our service interfaces.
+ if (!startServices())
+ return false;
+
+ // Hold the lock while we are in the main thread loop. It will release the
+ // lock when it blocks, and hold the lock at all other times.
+ mLock.lock();
+ runStateMachine_l();
+ mLock.unlock();
+
+ IPCThreadState::self()->stopProcess();
+ return false;
+}
+
+bool CommonTimeServer::runStateMachine_l() {
+ if (!mLocalClock.initCheck())
+ return false;
+
+ if (!mCommonClock.init(mLocalClock.getLocalFreq()))
+ return false;
+
+ // Enter the initial state.
+ becomeInitial("startup");
+
+ // run the state machine
+ while (!exitPending()) {
+ struct pollfd pfds[2];
+ int rc;
+ int eventCnt = 0;
+ int64_t wakeupTime;
+
+ // We are always interested in our wakeup FD.
+ pfds[eventCnt].fd = mWakeupThreadFD;
+ pfds[eventCnt].events = POLLIN;
+ pfds[eventCnt].revents = 0;
+ eventCnt++;
+
+ // If we have a valid socket, then we are interested in what it has to
+ // say as well.
+ if (mSocket >= 0) {
+ pfds[eventCnt].fd = mSocket;
+ pfds[eventCnt].events = POLLIN;
+ pfds[eventCnt].revents = 0;
+ eventCnt++;
+ }
+
+ // Note, we were holding mLock when this function was called. We
+ // release it only while we are blocking and hold it at all other times.
+ mLock.unlock();
+ rc = poll(pfds, eventCnt, mCurTimeout.msecTillTimeout());
+ wakeupTime = mLocalClock.getLocalTime();
+ mLock.lock();
+
+ // Is it time to shutdown? If so, don't hesitate... just do it.
+ if (exitPending())
+ break;
+
+ // Did the poll fail? This should never happen and is fatal if it does.
+ if (rc < 0) {
+ ALOGE("%s:%d poll failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (rc == 0)
+ mCurTimeout.setTimeout(kInfiniteTimeout);
+
+ // Were we woken up on purpose? If so, clear the eventfd with a read.
+ if (pfds[0].revents)
+ clearPendingWakeupEvents_l();
+
+ // Is out bind address dirty? If so, clean up our socket (if any).
+ // Alternatively, do we have an active socket but should be auto
+ // disabled? If so, release the socket and enter the proper sync state.
+ bool droppedSocket = false;
+ if (mBindIfaceDirty || ((mSocket >= 0) && shouldAutoDisable())) {
+ cleanupSocket_l();
+ mBindIfaceDirty = false;
+ droppedSocket = true;
+ }
+
+ // Do we not have a socket but should have one? If so, try to set one
+ // up.
+ if ((mSocket < 0) && mBindIfaceValid && !shouldAutoDisable()) {
+ if (setupSocket_l()) {
+ // Success! We are now joining a new network (either coming
+ // from no network, or coming from a potentially different
+ // network). Force our priority to be lower so that we defer to
+ // any other masters which may already be on the network we are
+ // joining. Later, when we enter either the client or the
+ // master state, we will clear this flag and go back to our
+ // normal election priority.
+ setForceLowPriority(true);
+ switch (mState) {
+ // If we were in initial (whether we had a immediately
+ // before this network or not) we want to simply reset the
+ // system and start again. Forcing a transition from
+ // INITIAL to INITIAL should do the job.
+ case CommonClockService::STATE_INITIAL:
+ becomeInitial("bound interface");
+ break;
+
+ // If we were in the master state, then either we were the
+ // master in a no-network situation, or we were the master
+ // of a different network and have moved to a new interface.
+ // In either case, immediately send out a master
+ // announcement at low priority.
+ case CommonClockService::STATE_MASTER:
+ sendMasterAnnouncement();
+ break;
+
+ // If we were in any other state (CLIENT, RONIN, or
+ // WAIT_FOR_ELECTION) then we must be moving from one
+ // network to another. We have lost our old master;
+ // transition to RONIN in an attempt to find a new master.
+ // If there are none out there, we will just assume
+ // responsibility for the timeline we used to be a client
+ // of.
+ default:
+ becomeRonin("bound interface");
+ break;
+ }
+ } else {
+ // That's odd... we failed to set up our socket. This could be
+ // due to some transient network change which will work itself
+ // out shortly; schedule a retry attempt in the near future.
+ mCurTimeout.setTimeout(kSetupRetryTimeoutMs);
+ }
+
+ // One way or the other, we don't have any data to process at this
+ // point (since we just tried to bulid a new socket). Loop back
+ // around and wait for the next thing to do.
+ continue;
+ } else if (droppedSocket) {
+ // We just lost our socket, and for whatever reason (either no
+ // config, or auto disable engaged) we are not supposed to rebuild
+ // one at this time. We are not going to rebuild our socket until
+ // something about our config/auto-disabled status changes, so we
+ // are basically in network-less mode. If we are already in either
+ // INITIAL or MASTER, just stay there until something changes. If
+ // we are in any other state (CLIENT, RONIN or WAIT_FOR_ELECTION),
+ // then transition to either INITIAL or MASTER depending on whether
+ // or not our timeline is valid.
+ ALOGI("Entering networkless mode interface is %s, "
+ "shouldAutoDisable = %s",
+ mBindIfaceValid ? "valid" : "invalid",
+ shouldAutoDisable() ? "true" : "false");
+ if ((mState != ICommonClock::STATE_INITIAL) &&
+ (mState != ICommonClock::STATE_MASTER)) {
+ if (mTimelineID == ICommonClock::kInvalidTimelineID)
+ becomeInitial("network-less mode");
+ else
+ becomeMaster("network-less mode");
+ }
+
+ continue;
+ }
+
+ // Did we wakeup with no signalled events across all of our FDs? If so,
+ // we must have hit our timeout.
+ if (rc == 0) {
+ if (!handleTimeout())
+ ALOGE("handleTimeout failed");
+ continue;
+ }
+
+ // Does our socket have data for us (assuming we still have one, we
+ // may have RXed a packet at the same time as a config change telling us
+ // to shut our socket down)? If so, process its data.
+ if ((mSocket >= 0) && (eventCnt > 1) && (pfds[1].revents)) {
+ mLastPacketRxLocalTime = wakeupTime;
+ if (!handlePacket())
+ ALOGE("handlePacket failed");
+ }
+ }
+
+ cleanupSocket_l();
+ return true;
+}
+
+void CommonTimeServer::clearPendingWakeupEvents_l() {
+ int64_t tmp;
+ read(mWakeupThreadFD, &tmp, sizeof(tmp));
+}
+
+void CommonTimeServer::wakeupThread_l() {
+ int64_t tmp = 1;
+ write(mWakeupThreadFD, &tmp, sizeof(tmp));
+}
+
+void CommonTimeServer::cleanupSocket_l() {
+ if (mSocket >= 0) {
+ close(mSocket);
+ mSocket = -1;
+ }
+}
+
+void CommonTimeServer::shutdownThread() {
+ // Flag the work thread for shutdown.
+ this->requestExit();
+
+ // Signal the thread in case its sleeping.
+ mLock.lock();
+ wakeupThread_l();
+ mLock.unlock();
+
+ // Wait for the thread to exit.
+ this->join();
+}
+
+bool CommonTimeServer::setupSocket_l() {
+ int rc;
+ bool ret_val = false;
+ struct sockaddr_in* ipv4_addr = NULL;
+ char masterElectionEPStr[64];
+ const int one = 1;
+
+ // This should never be needed, but if we happened to have an old socket
+ // lying around, be sure to not leak it before proceeding.
+ cleanupSocket_l();
+
+ // If we don't have a valid endpoint to bind to, then how did we get here in
+ // the first place? Regardless, we know that we are going to fail to bind,
+ // so don't even try.
+ if (!mBindIfaceValid)
+ return false;
+
+ sockaddrToString(mMasterElectionEP, true, masterElectionEPStr,
+ sizeof(masterElectionEPStr));
+ ALOGI("Building socket :: bind = %s master election = %s",
+ mBindIface.string(), masterElectionEPStr);
+
+ // TODO: add proper support for IPv6. Right now, we block IPv6 addresses at
+ // the configuration interface level.
+ if (AF_INET != mMasterElectionEP.ss_family) {
+ ALOGW("TODO: add proper IPv6 support");
+ goto bailout;
+ }
+
+ // open a UDP socket for the timeline serivce
+ mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (mSocket < 0) {
+ ALOGE("Failed to create socket (errno = %d)", errno);
+ goto bailout;
+ }
+
+ // Bind to the selected interface using Linux's spiffy SO_BINDTODEVICE.
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", mBindIface.string());
+ ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0;
+ rc = setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE,
+ (void *)&ifr, sizeof(ifr));
+ if (rc) {
+ ALOGE("Failed to bind socket at to interface %s (errno = %d)",
+ ifr.ifr_name, errno);
+ goto bailout;
+ }
+
+ // Bind our socket to INADDR_ANY and the master election port. The
+ // interface binding we made using SO_BINDTODEVICE should limit us to
+ // traffic only on the interface we are interested in. We need to bind to
+ // INADDR_ANY and the specific master election port in order to be able to
+ // receive both unicast traffic and master election multicast traffic with
+ // just a single socket.
+ struct sockaddr_in bindAddr;
+ ipv4_addr = reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP);
+ memcpy(&bindAddr, ipv4_addr, sizeof(bindAddr));
+ bindAddr.sin_addr.s_addr = INADDR_ANY;
+ rc = bind(mSocket,
+ reinterpret_cast<const sockaddr *>(&bindAddr),
+ sizeof(bindAddr));
+ if (rc) {
+ ALOGE("Failed to bind socket to port %hu (errno = %d)",
+ ntohs(bindAddr.sin_port), errno);
+ goto bailout;
+ }
+
+ if (0xE0000000 == (ntohl(ipv4_addr->sin_addr.s_addr) & 0xF0000000)) {
+ // If our master election endpoint is a multicast address, be sure to join
+ // the multicast group.
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = ipv4_addr->sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (rc == -1) {
+ ALOGE("Failed to join multicast group at %s. (errno = %d)",
+ masterElectionEPStr, errno);
+ goto bailout;
+ }
+
+ // disable loopback of multicast packets
+ const int zero = 0;
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &zero, sizeof(zero));
+ if (rc == -1) {
+ ALOGE("Failed to disable multicast loopback (errno = %d)", errno);
+ goto bailout;
+ }
+ } else
+ if (ntohl(ipv4_addr->sin_addr.s_addr) != 0xFFFFFFFF) {
+ // If the master election address is neither broadcast, nor multicast,
+ // then we are misconfigured. The config API layer should prevent this
+ // from ever happening.
+ goto bailout;
+ }
+
+ // Set the TTL of sent packets to 1. (Time protocol sync should never leave
+ // the local subnet)
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+ if (rc == -1) {
+ ALOGE("Failed to set TTL to %d (errno = %d)", one, errno);
+ goto bailout;
+ }
+
+ // get the device's unique ID
+ if (!assignDeviceID())
+ goto bailout;
+
+ ret_val = true;
+
+bailout:
+ if (!ret_val)
+ cleanupSocket_l();
+ return ret_val;
+}
+
+// generate a unique device ID that can be used for arbitration
+bool CommonTimeServer::assignDeviceID() {
+ if (!mBindIfaceValid)
+ return false;
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_addr.sa_family = AF_INET;
+ strlcpy(ifr.ifr_name, mBindIface.string(), IFNAMSIZ);
+
+ int rc = ioctl(mSocket, SIOCGIFHWADDR, &ifr);
+ if (rc) {
+ ALOGE("%s:%d ioctl failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) {
+ ALOGE("%s:%d got non-Ethernet address", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ mDeviceID = 0;
+ for (int i = 0; i < ETH_ALEN; i++) {
+ mDeviceID = (mDeviceID << 8) | ifr.ifr_hwaddr.sa_data[i];
+ }
+
+ return true;
+}
+
+// generate a new timeline ID
+void CommonTimeServer::assignTimelineID() {
+ do {
+ mTimelineID = (static_cast<uint64_t>(lrand48()) << 32)
+ | static_cast<uint64_t>(lrand48());
+ } while (mTimelineID == ICommonClock::kInvalidTimelineID);
+}
+
+// Select a preference between the device IDs of two potential masters.
+// Returns true if the first ID wins, or false if the second ID wins.
+bool CommonTimeServer::arbitrateMaster(
+ uint64_t deviceID1, uint8_t devicePrio1,
+ uint64_t deviceID2, uint8_t devicePrio2) {
+ return ((devicePrio1 > devicePrio2) ||
+ ((devicePrio1 == devicePrio2) && (deviceID1 > deviceID2)));
+}
+
+bool CommonTimeServer::handlePacket() {
+ uint8_t buf[256];
+ struct sockaddr_storage srcAddr;
+ socklen_t srcAddrLen = sizeof(srcAddr);
+
+ ssize_t recvBytes = recvfrom(
+ mSocket, buf, sizeof(buf), 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr), &srcAddrLen);
+
+ if (recvBytes < 0) {
+ ALOGE("%s:%d recvfrom failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ UniversalTimeServicePacket pkt;
+ recvBytes = pkt.deserializePacket(buf, recvBytes, mSyncGroupID);
+ if (recvBytes < 0)
+ return false;
+
+ bool result;
+ switch (pkt.packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ result = handleWhoIsMasterRequest(&pkt.p.who_is_master_request,
+ srcAddr);
+ break;
+
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ result = handleWhoIsMasterResponse(&pkt.p.who_is_master_response,
+ srcAddr);
+ break;
+
+ case TIME_PACKET_SYNC_REQUEST:
+ result = handleSyncRequest(&pkt.p.sync_request, srcAddr);
+ break;
+
+ case TIME_PACKET_SYNC_RESPONSE:
+ result = handleSyncResponse(&pkt.p.sync_response, srcAddr);
+ break;
+
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ result = handleMasterAnnouncement(&pkt.p.master_announcement,
+ srcAddr);
+ break;
+
+ default: {
+ ALOGD("%s:%d unknown packet type(%d)",
+ __PRETTY_FUNCTION__, __LINE__, pkt.packetType);
+ result = false;
+ } break;
+ }
+
+ return result;
+}
+
+bool CommonTimeServer::handleTimeout() {
+ // If we have no socket, then this must be a timeout to retry socket setup.
+ if (mSocket < 0)
+ return true;
+
+ switch (mState) {
+ case ICommonClock::STATE_INITIAL:
+ return handleTimeoutInitial();
+ case ICommonClock::STATE_CLIENT:
+ return handleTimeoutClient();
+ case ICommonClock::STATE_MASTER:
+ return handleTimeoutMaster();
+ case ICommonClock::STATE_RONIN:
+ return handleTimeoutRonin();
+ case ICommonClock::STATE_WAIT_FOR_ELECTION:
+ return handleTimeoutWaitForElection();
+ }
+
+ return false;
+}
+
+bool CommonTimeServer::handleTimeoutInitial() {
+ if (++mInitial_WhoIsMasterRequestTimeouts ==
+ kInitial_NumWhoIsMasterRetries) {
+ // none of our attempts to discover a master succeeded, so make
+ // this device the master
+ return becomeMaster("initial timeout");
+ } else {
+ // retry the WhoIsMaster request
+ return sendWhoIsMasterRequest();
+ }
+}
+
+bool CommonTimeServer::handleTimeoutClient() {
+ if (shouldPanicNotGettingGoodData())
+ return becomeInitial("timeout panic, no good data");
+
+ if (mClient_SyncRequestPending) {
+ mClient_SyncRequestPending = false;
+
+ if (++mClient_SyncRequestTimeouts < kClient_NumSyncRequestRetries) {
+ // a sync request has timed out, so retry
+ return sendSyncRequest();
+ } else {
+ // The master has failed to respond to a sync request for too many
+ // times in a row. Assume the master is dead and start electing
+ // a new master.
+ return becomeRonin("master not responding");
+ }
+ } else {
+ // initiate the next sync request
+ return sendSyncRequest();
+ }
+}
+
+bool CommonTimeServer::handleTimeoutMaster() {
+ // send another announcement from the master
+ return sendMasterAnnouncement();
+}
+
+bool CommonTimeServer::handleTimeoutRonin() {
+ if (++mRonin_WhoIsMasterRequestTimeouts == kRonin_NumWhoIsMasterRetries) {
+ // no other master is out there, so we won the election
+ return becomeMaster("no better masters detected");
+ } else {
+ return sendWhoIsMasterRequest();
+ }
+}
+
+bool CommonTimeServer::handleTimeoutWaitForElection() {
+ return becomeRonin("timeout waiting for election conclusion");
+}
+
+bool CommonTimeServer::handleWhoIsMasterRequest(
+ const WhoIsMasterRequestPacket* request,
+ const sockaddr_storage& srcAddr) {
+ if (mState == ICommonClock::STATE_MASTER) {
+ // is this request related to this master's timeline?
+ if (request->timelineID != ICommonClock::kInvalidTimelineID &&
+ request->timelineID != mTimelineID)
+ return true;
+
+ WhoIsMasterResponsePacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+ pkt.deviceID = mDeviceID;
+ pkt.devicePriority = effectivePriority();
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz < 0)
+ return false;
+
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr),
+ sizeof(srcAddr));
+ if (sendBytes == -1) {
+ ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+ } else if (mState == ICommonClock::STATE_RONIN) {
+ // if we hear a WhoIsMaster request from another device following
+ // the same timeline and that device wins arbitration, then we will stop
+ // trying to elect ourselves master and will instead wait for an
+ // announcement from the election winner
+ if (request->timelineID != mTimelineID)
+ return true;
+
+ if (arbitrateMaster(request->senderDeviceID,
+ request->senderDevicePriority,
+ mDeviceID,
+ effectivePriority()))
+ return becomeWaitForElection("would lose election");
+
+ return true;
+ } else if (mState == ICommonClock::STATE_INITIAL) {
+ // If a group of devices booted simultaneously (e.g. after a power
+ // outage) and all of them are in the initial state and there is no
+ // master, then each device may time out and declare itself master at
+ // the same time. To avoid this, listen for
+ // WhoIsMaster(InvalidTimeline) requests from peers. If we would lose
+ // arbitration against that peer, reset our timeout count so that the
+ // peer has a chance to become master before we time out.
+ if (request->timelineID == ICommonClock::kInvalidTimelineID &&
+ arbitrateMaster(request->senderDeviceID,
+ request->senderDevicePriority,
+ mDeviceID,
+ effectivePriority())) {
+ mInitial_WhoIsMasterRequestTimeouts = 0;
+ }
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::handleWhoIsMasterResponse(
+ const WhoIsMasterResponsePacket* response,
+ const sockaddr_storage& srcAddr) {
+ if (mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN) {
+ return becomeClient(srcAddr,
+ response->deviceID,
+ response->devicePriority,
+ response->timelineID,
+ "heard whois response");
+ } else if (mState == ICommonClock::STATE_CLIENT) {
+ // if we get multiple responses because there are multiple devices
+ // who believe that they are master, then follow the master that
+ // wins arbitration
+ if (arbitrateMaster(response->deviceID,
+ response->devicePriority,
+ mClient_MasterDeviceID,
+ mClient_MasterDevicePriority)) {
+ return becomeClient(srcAddr,
+ response->deviceID,
+ response->devicePriority,
+ response->timelineID,
+ "heard whois response");
+ }
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::handleSyncRequest(const SyncRequestPacket* request,
+ const sockaddr_storage& srcAddr) {
+ SyncResponsePacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+
+ if ((mState == ICommonClock::STATE_MASTER) &&
+ (mTimelineID == request->timelineID)) {
+ int64_t rxLocalTime = mLastPacketRxLocalTime;
+ int64_t rxCommonTime;
+
+ // If we are master on an actual network and have actual clients, then
+ // we are no longer low priority.
+ setForceLowPriority(false);
+
+ if (OK != mCommonClock.localToCommon(rxLocalTime, &rxCommonTime)) {
+ return false;
+ }
+
+ int64_t txLocalTime = mLocalClock.getLocalTime();;
+ int64_t txCommonTime;
+ if (OK != mCommonClock.localToCommon(txLocalTime, &txCommonTime)) {
+ return false;
+ }
+
+ pkt.nak = 0;
+ pkt.clientTxLocalTime = request->clientTxLocalTime;
+ pkt.masterRxCommonTime = rxCommonTime;
+ pkt.masterTxCommonTime = txCommonTime;
+ } else {
+ pkt.nak = 1;
+ pkt.clientTxLocalTime = 0;
+ pkt.masterRxCommonTime = 0;
+ pkt.masterTxCommonTime = 0;
+ }
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz < 0)
+ return false;
+
+ ssize_t sendBytes = sendto(
+ mSocket, &buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr),
+ sizeof(srcAddr));
+ if (sendBytes == -1) {
+ ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::handleSyncResponse(
+ const SyncResponsePacket* response,
+ const sockaddr_storage& srcAddr) {
+ if (mState != ICommonClock::STATE_CLIENT)
+ return true;
+
+ assert(mMasterEPValid);
+ if (!sockaddrMatch(srcAddr, mMasterEP, true)) {
+ char srcEP[64], expectedEP[64];
+ sockaddrToString(srcAddr, true, srcEP, sizeof(srcEP));
+ sockaddrToString(mMasterEP, true, expectedEP, sizeof(expectedEP));
+ ALOGI("Dropping sync response from unexpected address."
+ " Expected %s Got %s", expectedEP, srcEP);
+ return true;
+ }
+
+ if (response->nak) {
+ // if our master is no longer accepting requests, then we need to find
+ // a new master
+ return becomeRonin("master NAK'ed");
+ }
+
+ mClient_SyncRequestPending = 0;
+ mClient_SyncRequestTimeouts = 0;
+ mClient_PacketRTTLog.logRX(response->clientTxLocalTime,
+ mLastPacketRxLocalTime);
+
+ bool result;
+ if (!(mClient_SyncRespsRXedFromCurMaster++)) {
+ // the first request/response exchange between a client and a master
+ // may take unusually long due to ARP, so discard it.
+ result = true;
+ } else {
+ int64_t clientTxLocalTime = response->clientTxLocalTime;
+ int64_t clientRxLocalTime = mLastPacketRxLocalTime;
+ int64_t masterTxCommonTime = response->masterTxCommonTime;
+ int64_t masterRxCommonTime = response->masterRxCommonTime;
+
+ int64_t rtt = (clientRxLocalTime - clientTxLocalTime);
+ int64_t avgLocal = (clientTxLocalTime + clientRxLocalTime) >> 1;
+ int64_t avgCommon = (masterTxCommonTime + masterRxCommonTime) >> 1;
+
+ // if the RTT of the packet is significantly larger than the panic
+ // threshold, we should simply discard it. Its better to do nothing
+ // than to take cues from a packet like that.
+ int rttCommon = mCommonClock.localDurationToCommonDuration(rtt);
+ if (rttCommon > (static_cast<int64_t>(mPanicThresholdUsec) *
+ kRTTDiscardPanicThreshMultiplier)) {
+ ALOGV("Dropping sync response with RTT of %lld uSec", rttCommon);
+ mClient_ExpiredSyncRespsRXedFromCurMaster++;
+ if (shouldPanicNotGettingGoodData())
+ return becomeInitial("RX panic, no good data");
+ } else {
+ result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rtt);
+ mClient_LastGoodSyncRX = clientRxLocalTime;
+
+ if (result) {
+ // indicate to listeners that we've synced to the common timeline
+ notifyClockSync();
+ } else {
+ ALOGE("Panic! Observed clock sync error is too high to tolerate,"
+ " resetting state machine and starting over.");
+ notifyClockSyncLoss();
+ return becomeInitial("panic");
+ }
+ }
+ }
+
+ mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+ return result;
+}
+
+bool CommonTimeServer::handleMasterAnnouncement(
+ const MasterAnnouncementPacket* packet,
+ const sockaddr_storage& srcAddr) {
+ uint64_t newDeviceID = packet->deviceID;
+ uint8_t newDevicePrio = packet->devicePriority;
+ uint64_t newTimelineID = packet->timelineID;
+
+ if (mState == ICommonClock::STATE_INITIAL ||
+ mState == ICommonClock::STATE_RONIN ||
+ mState == ICommonClock::STATE_WAIT_FOR_ELECTION) {
+ // if we aren't currently following a master, then start following
+ // this new master
+ return becomeClient(srcAddr,
+ newDeviceID,
+ newDevicePrio,
+ newTimelineID,
+ "heard master announcement");
+ } else if (mState == ICommonClock::STATE_CLIENT) {
+ // if the new master wins arbitration against our current master,
+ // then become a client of the new master
+ if (arbitrateMaster(newDeviceID,
+ newDevicePrio,
+ mClient_MasterDeviceID,
+ mClient_MasterDevicePriority))
+ return becomeClient(srcAddr,
+ newDeviceID,
+ newDevicePrio,
+ newTimelineID,
+ "heard master announcement");
+ } else if (mState == ICommonClock::STATE_MASTER) {
+ // two masters are competing - if the new one wins arbitration, then
+ // cease acting as master
+ if (arbitrateMaster(newDeviceID, newDevicePrio,
+ mDeviceID, effectivePriority()))
+ return becomeClient(srcAddr, newDeviceID,
+ newDevicePrio, newTimelineID,
+ "heard master announcement");
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::sendWhoIsMasterRequest() {
+ assert(mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN);
+
+ // If we have no socket, then we must be in the unconfigured initial state.
+ // Don't report any errors, just don't try to send the initial who-is-master
+ // query. Eventually, our network will either become configured, or we will
+ // be forced into network-less master mode by higher level code.
+ if (mSocket < 0) {
+ assert(mState == ICommonClock::STATE_INITIAL);
+ return true;
+ }
+
+ bool ret = false;
+ WhoIsMasterRequestPacket pkt;
+ pkt.initHeader(mSyncGroupID);
+ pkt.senderDeviceID = mDeviceID;
+ pkt.senderDevicePriority = effectivePriority();
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz >= 0) {
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&mMasterElectionEP),
+ sizeof(mMasterElectionEP));
+ if (sendBytes < 0)
+ ALOGE("WhoIsMaster sendto failed (errno %d)", errno);
+ ret = true;
+ }
+
+ if (mState == ICommonClock::STATE_INITIAL) {
+ mCurTimeout.setTimeout(kInitial_WhoIsMasterTimeoutMs);
+ } else {
+ mCurTimeout.setTimeout(kRonin_WhoIsMasterTimeoutMs);
+ }
+
+ return ret;
+}
+
+bool CommonTimeServer::sendSyncRequest() {
+ // If we are sending sync requests, then we must be in the client state and
+ // we must have a socket (when we have no network, we are only supposed to
+ // be in INITIAL or MASTER)
+ assert(mState == ICommonClock::STATE_CLIENT);
+ assert(mSocket >= 0);
+
+ bool ret = false;
+ SyncRequestPacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+ pkt.clientTxLocalTime = mLocalClock.getLocalTime();
+
+ if (!mClient_FirstSyncTX)
+ mClient_FirstSyncTX = pkt.clientTxLocalTime;
+
+ mClient_PacketRTTLog.logTX(pkt.clientTxLocalTime);
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz >= 0) {
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&mMasterEP),
+ sizeof(mMasterEP));
+ if (sendBytes < 0)
+ ALOGE("SyncRequest sendto failed (errno %d)", errno);
+ ret = true;
+ }
+
+ mClient_SyncsSentToCurMaster++;
+ mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+ mClient_SyncRequestPending = true;
+
+ return ret;
+}
+
+bool CommonTimeServer::sendMasterAnnouncement() {
+ bool ret = false;
+ assert(mState == ICommonClock::STATE_MASTER);
+
+ // If we are being asked to send a master announcement, but we have no
+ // socket, we must be in network-less master mode. Don't bother to send the
+ // announcement, and don't bother to schedule a timeout. When the network
+ // comes up, the work thread will get poked and start the process of
+ // figuring out who the current master should be.
+ if (mSocket < 0) {
+ mCurTimeout.setTimeout(kInfiniteTimeout);
+ return true;
+ }
+
+ MasterAnnouncementPacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+ pkt.deviceID = mDeviceID;
+ pkt.devicePriority = effectivePriority();
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz >= 0) {
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&mMasterElectionEP),
+ sizeof(mMasterElectionEP));
+ if (sendBytes < 0)
+ ALOGE("MasterAnnouncement sendto failed (errno %d)", errno);
+ ret = true;
+ }
+
+ mCurTimeout.setTimeout(mMasterAnnounceIntervalMs);
+ return ret;
+}
+
+bool CommonTimeServer::becomeClient(const sockaddr_storage& masterEP,
+ uint64_t masterDeviceID,
+ uint8_t masterDevicePriority,
+ uint64_t timelineID,
+ const char* cause) {
+ char newEPStr[64], oldEPStr[64];
+ sockaddrToString(masterEP, true, newEPStr, sizeof(newEPStr));
+ sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr));
+
+ ALOGI("%s --> CLIENT (%s) :%s"
+ " OldMaster: %02x-%014llx::%016llx::%s"
+ " NewMaster: %02x-%014llx::%016llx::%s",
+ stateToString(mState), cause,
+ (mTimelineID != timelineID) ? " (new timeline)" : "",
+ mClient_MasterDevicePriority, mClient_MasterDeviceID,
+ mTimelineID, oldEPStr,
+ masterDevicePriority, masterDeviceID,
+ timelineID, newEPStr);
+
+ if (mTimelineID != timelineID) {
+ // start following a new timeline
+ mTimelineID = timelineID;
+ mClockRecovery.reset(true, true);
+ notifyClockSyncLoss();
+ } else {
+ // start following a new master on the existing timeline
+ mClockRecovery.reset(false, true);
+ }
+
+ mMasterEP = masterEP;
+ mMasterEPValid = true;
+ setForceLowPriority(false);
+
+ mClient_MasterDeviceID = masterDeviceID;
+ mClient_MasterDevicePriority = masterDevicePriority;
+ resetSyncStats();
+
+ setState(ICommonClock::STATE_CLIENT);
+
+ // add some jitter to when the various clients send their requests
+ // in order to reduce the likelihood that a group of clients overload
+ // the master after receiving a master announcement
+ usleep((lrand48() % 100) * 1000);
+
+ return sendSyncRequest();
+}
+
+bool CommonTimeServer::becomeMaster(const char* cause) {
+ uint64_t oldTimelineID = mTimelineID;
+ if (mTimelineID == ICommonClock::kInvalidTimelineID) {
+ // this device has not been following any existing timeline,
+ // so it will create a new timeline and declare itself master
+ assert(!mCommonClock.isValid());
+
+ // set the common time basis
+ mCommonClock.setBasis(mLocalClock.getLocalTime(), 0);
+
+ // assign an arbitrary timeline iD
+ assignTimelineID();
+
+ // notify listeners that we've created a common timeline
+ notifyClockSync();
+ }
+
+ ALOGI("%s --> MASTER (%s) : %s timeline %016llx",
+ stateToString(mState), cause,
+ (oldTimelineID == mTimelineID) ? "taking ownership of"
+ : "creating new",
+ mTimelineID);
+
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+ setForceLowPriority(false);
+ mClient_MasterDevicePriority = effectivePriority();
+ mClient_MasterDeviceID = mDeviceID;
+ mClockRecovery.reset(false, true);
+ resetSyncStats();
+
+ setState(ICommonClock::STATE_MASTER);
+ return sendMasterAnnouncement();
+}
+
+bool CommonTimeServer::becomeRonin(const char* cause) {
+ // If we were the client of a given timeline, but had never received even a
+ // single time sync packet, then we transition back to Initial instead of
+ // Ronin. If we transition to Ronin and end up becoming the new Master, we
+ // will be unable to service requests for other clients because we never
+ // actually knew what time it was. By going to initial, we ensure that
+ // other clients who know what time it is, but would lose master arbitration
+ // in the Ronin case, will step up and become the proper new master of the
+ // old timeline.
+
+ char oldEPStr[64];
+ sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr));
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+
+ if (mCommonClock.isValid()) {
+ ALOGI("%s --> RONIN (%s) : lost track of previously valid timeline "
+ "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)",
+ stateToString(mState), cause,
+ mClient_MasterDevicePriority, mClient_MasterDeviceID,
+ mTimelineID, oldEPStr,
+ mClient_SyncsSentToCurMaster,
+ mClient_SyncRespsRXedFromCurMaster,
+ mClient_ExpiredSyncRespsRXedFromCurMaster);
+
+ mRonin_WhoIsMasterRequestTimeouts = 0;
+ setState(ICommonClock::STATE_RONIN);
+ return sendWhoIsMasterRequest();
+ } else {
+ ALOGI("%s --> INITIAL (%s) : never synced timeline "
+ "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)",
+ stateToString(mState), cause,
+ mClient_MasterDevicePriority, mClient_MasterDeviceID,
+ mTimelineID, oldEPStr,
+ mClient_SyncsSentToCurMaster,
+ mClient_SyncRespsRXedFromCurMaster,
+ mClient_ExpiredSyncRespsRXedFromCurMaster);
+
+ return becomeInitial("ronin, no timeline");
+ }
+}
+
+bool CommonTimeServer::becomeWaitForElection(const char* cause) {
+ ALOGI("%s --> WAIT_FOR_ELECTION (%s) : dropping out of election,"
+ " waiting %d mSec for completion.",
+ stateToString(mState), cause, kWaitForElection_TimeoutMs);
+
+ setState(ICommonClock::STATE_WAIT_FOR_ELECTION);
+ mCurTimeout.setTimeout(kWaitForElection_TimeoutMs);
+ return true;
+}
+
+bool CommonTimeServer::becomeInitial(const char* cause) {
+ ALOGI("Entering INITIAL (%s), total reset.", cause);
+
+ setState(ICommonClock::STATE_INITIAL);
+
+ // reset clock recovery
+ mClockRecovery.reset(true, true);
+
+ // reset internal state bookkeeping.
+ mCurTimeout.setTimeout(kInfiniteTimeout);
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+ mLastPacketRxLocalTime = 0;
+ mTimelineID = ICommonClock::kInvalidTimelineID;
+ mClockSynced = false;
+ mInitial_WhoIsMasterRequestTimeouts = 0;
+ mClient_MasterDeviceID = 0;
+ mClient_MasterDevicePriority = 0;
+ mRonin_WhoIsMasterRequestTimeouts = 0;
+ resetSyncStats();
+
+ // send the first request to discover the master
+ return sendWhoIsMasterRequest();
+}
+
+void CommonTimeServer::notifyClockSync() {
+ if (!mClockSynced) {
+ mClockSynced = true;
+ mICommonClock->notifyOnTimelineChanged(mTimelineID);
+ }
+}
+
+void CommonTimeServer::notifyClockSyncLoss() {
+ if (mClockSynced) {
+ mClockSynced = false;
+ mICommonClock->notifyOnTimelineChanged(
+ ICommonClock::kInvalidTimelineID);
+ }
+}
+
+void CommonTimeServer::setState(ICommonClock::State s) {
+ mState = s;
+}
+
+const char* CommonTimeServer::stateToString(ICommonClock::State s) {
+ switch(s) {
+ case ICommonClock::STATE_INITIAL:
+ return "INITIAL";
+ case ICommonClock::STATE_CLIENT:
+ return "CLIENT";
+ case ICommonClock::STATE_MASTER:
+ return "MASTER";
+ case ICommonClock::STATE_RONIN:
+ return "RONIN";
+ case ICommonClock::STATE_WAIT_FOR_ELECTION:
+ return "WAIT_FOR_ELECTION";
+ default:
+ return "unknown";
+ }
+}
+
+void CommonTimeServer::sockaddrToString(const sockaddr_storage& addr,
+ bool addrValid,
+ char* buf, size_t bufLen) {
+ if (!bufLen || !buf)
+ return;
+
+ if (addrValid) {
+ switch (addr.ss_family) {
+ case AF_INET: {
+ const struct sockaddr_in* sa =
+ reinterpret_cast<const struct sockaddr_in*>(&addr);
+ unsigned long a = ntohl(sa->sin_addr.s_addr);
+ uint16_t p = ntohs(sa->sin_port);
+ snprintf(buf, bufLen, "%lu.%lu.%lu.%lu:%hu",
+ ((a >> 24) & 0xFF), ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF), (a & 0xFF), p);
+ } break;
+
+ case AF_INET6: {
+ const struct sockaddr_in6* sa =
+ reinterpret_cast<const struct sockaddr_in6*>(&addr);
+ const uint8_t* a = sa->sin6_addr.s6_addr;
+ uint16_t p = ntohs(sa->sin6_port);
+ snprintf(buf, bufLen,
+ "%02X%02X:%02X%02X:%02X%02X:%02X%02X:"
+ "%02X%02X:%02X%02X:%02X%02X:%02X%02X port %hd",
+ a[0], a[1], a[ 2], a[ 3], a[ 4], a[ 5], a[ 6], a[ 7],
+ a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15],
+ p);
+ } break;
+
+ default:
+ snprintf(buf, bufLen,
+ "<unknown sockaddr family %d>", addr.ss_family);
+ break;
+ }
+ } else {
+ snprintf(buf, bufLen, "<none>");
+ }
+
+ buf[bufLen - 1] = 0;
+}
+
+bool CommonTimeServer::sockaddrMatch(const sockaddr_storage& a1,
+ const sockaddr_storage& a2,
+ bool matchAddressOnly) {
+ if (a1.ss_family != a2.ss_family)
+ return false;
+
+ switch (a1.ss_family) {
+ case AF_INET: {
+ const struct sockaddr_in* sa1 =
+ reinterpret_cast<const struct sockaddr_in*>(&a1);
+ const struct sockaddr_in* sa2 =
+ reinterpret_cast<const struct sockaddr_in*>(&a2);
+
+ if (sa1->sin_addr.s_addr != sa2->sin_addr.s_addr)
+ return false;
+
+ return (matchAddressOnly || (sa1->sin_port == sa2->sin_port));
+ } break;
+
+ case AF_INET6: {
+ const struct sockaddr_in6* sa1 =
+ reinterpret_cast<const struct sockaddr_in6*>(&a1);
+ const struct sockaddr_in6* sa2 =
+ reinterpret_cast<const struct sockaddr_in6*>(&a2);
+
+ if (memcmp(&sa1->sin6_addr, &sa2->sin6_addr, sizeof(sa2->sin6_addr)))
+ return false;
+
+ return (matchAddressOnly || (sa1->sin6_port == sa2->sin6_port));
+ } break;
+
+ // Huh? We don't deal in non-IPv[46] addresses. Not sure how we got
+ // here, but we don't know how to comapre these addresses and simply
+ // default to a no-match decision.
+ default: return false;
+ }
+}
+
+void CommonTimeServer::TimeoutHelper::setTimeout(int msec) {
+ mTimeoutValid = (msec >= 0);
+ if (mTimeoutValid)
+ mEndTime = systemTime() +
+ (static_cast<nsecs_t>(msec) * 1000000);
+}
+
+int CommonTimeServer::TimeoutHelper::msecTillTimeout() {
+ if (!mTimeoutValid)
+ return kInfiniteTimeout;
+
+ nsecs_t now = systemTime();
+ if (now >= mEndTime)
+ return 0;
+
+ uint64_t deltaMsec = (((mEndTime - now) + 999999) / 1000000);
+
+ if (deltaMsec > static_cast<uint64_t>(MAX_INT))
+ return MAX_INT;
+
+ return static_cast<int>(deltaMsec);
+}
+
+bool CommonTimeServer::shouldPanicNotGettingGoodData() {
+ if (mClient_FirstSyncTX) {
+ int64_t now = mLocalClock.getLocalTime();
+ int64_t delta = now - (mClient_LastGoodSyncRX
+ ? mClient_LastGoodSyncRX
+ : mClient_FirstSyncTX);
+ int64_t deltaUsec = mCommonClock.localDurationToCommonDuration(delta);
+
+ if (deltaUsec >= kNoGoodDataPanicThresholdUsec)
+ return true;
+ }
+
+ return false;
+}
+
+void CommonTimeServer::PacketRTTLog::logTX(int64_t txTime) {
+ txTimes[wrPtr] = txTime;
+ rxTimes[wrPtr] = 0;
+ wrPtr = (wrPtr + 1) % RTT_LOG_SIZE;
+ if (!wrPtr)
+ logFull = true;
+}
+
+void CommonTimeServer::PacketRTTLog::logRX(int64_t txTime, int64_t rxTime) {
+ if (!logFull && !wrPtr)
+ return;
+
+ uint32_t i = logFull ? wrPtr : 0;
+ do {
+ if (txTimes[i] == txTime) {
+ rxTimes[i] = rxTime;
+ break;
+ }
+ i = (i + 1) % RTT_LOG_SIZE;
+ } while (i != wrPtr);
+}
+
+} // namespace android
diff --git a/services/common_time/common_time_server.h b/services/common_time/common_time_server.h
new file mode 100644
index 000000000000..1b5520286b43
--- /dev/null
+++ b/services/common_time/common_time_server.h
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2012 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_COMMON_TIME_SERVER_H
+#define ANDROID_COMMON_TIME_SERVER_H
+
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <common_time/ICommonClock.h>
+#include <common_time/local_clock.h>
+#include <utils/String8.h>
+
+#include "clock_recovery.h"
+#include "common_clock.h"
+#include "common_time_server_packets.h"
+
+#define RTT_LOG_SIZE 30
+
+namespace android {
+
+class CommonClockService;
+class CommonTimeConfigService;
+
+/***** time service implementation *****/
+
+class CommonTimeServer : public Thread {
+ public:
+ CommonTimeServer();
+ ~CommonTimeServer();
+
+ bool startServices();
+
+ // Common Clock API methods
+ CommonClock& getCommonClock() { return mCommonClock; }
+ LocalClock& getLocalClock() { return mLocalClock; }
+ uint64_t getTimelineID();
+ int32_t getEstimatedError();
+ ICommonClock::State getState();
+ status_t getMasterAddr(struct sockaddr_storage* addr);
+ status_t isCommonTimeValid(bool* valid, uint32_t* timelineID);
+
+ // Config API methods
+ status_t getMasterElectionPriority(uint8_t *priority);
+ status_t setMasterElectionPriority(uint8_t priority);
+ status_t getMasterElectionEndpoint(struct sockaddr_storage *addr);
+ status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr);
+ status_t getMasterElectionGroupId(uint64_t *id);
+ status_t setMasterElectionGroupId(uint64_t id);
+ status_t getInterfaceBinding(String8& ifaceName);
+ status_t setInterfaceBinding(const String8& ifaceName);
+ status_t getMasterAnnounceInterval(int *interval);
+ status_t setMasterAnnounceInterval(int interval);
+ status_t getClientSyncInterval(int *interval);
+ status_t setClientSyncInterval(int interval);
+ status_t getPanicThreshold(int *threshold);
+ status_t setPanicThreshold(int threshold);
+ status_t getAutoDisable(bool *autoDisable);
+ status_t setAutoDisable(bool autoDisable);
+ status_t forceNetworklessMasterMode();
+
+ // Method used by the CommonClockService to notify the core service about
+ // changes in the number of active common clock clients.
+ void reevaluateAutoDisableState(bool commonClockHasClients);
+
+ status_t dumpClockInterface(int fd, const Vector<String16>& args,
+ size_t activeClients);
+ status_t dumpConfigInterface(int fd, const Vector<String16>& args);
+
+ private:
+ class PacketRTTLog {
+ public:
+ PacketRTTLog() {
+ resetLog();
+ }
+
+ void resetLog() {
+ wrPtr = 0;
+ logFull = 0;
+ }
+
+ void logTX(int64_t txTime);
+ void logRX(int64_t txTime, int64_t rxTime);
+ void dumpLog(int fd, const CommonClock& cclk);
+
+ private:
+ uint32_t wrPtr;
+ bool logFull;
+ int64_t txTimes[RTT_LOG_SIZE];
+ int64_t rxTimes[RTT_LOG_SIZE];
+ };
+
+ class TimeoutHelper {
+ public:
+ TimeoutHelper() : mTimeoutValid(false) { }
+
+ void setTimeout(int msec);
+ int msecTillTimeout();
+
+ private:
+ bool mTimeoutValid;
+ nsecs_t mEndTime;
+ };
+
+ bool threadLoop();
+
+ bool runStateMachine_l();
+ bool setupSocket_l();
+
+ void assignTimelineID();
+ bool assignDeviceID();
+
+ static bool arbitrateMaster(uint64_t deviceID1, uint8_t devicePrio1,
+ uint64_t deviceID2, uint8_t devicePrio2);
+
+ bool handlePacket();
+ bool handleWhoIsMasterRequest (const WhoIsMasterRequestPacket* request,
+ const sockaddr_storage& srcAddr);
+ bool handleWhoIsMasterResponse(const WhoIsMasterResponsePacket* response,
+ const sockaddr_storage& srcAddr);
+ bool handleSyncRequest (const SyncRequestPacket* request,
+ const sockaddr_storage& srcAddr);
+ bool handleSyncResponse (const SyncResponsePacket* response,
+ const sockaddr_storage& srcAddr);
+ bool handleMasterAnnouncement (const MasterAnnouncementPacket* packet,
+ const sockaddr_storage& srcAddr);
+
+ bool handleTimeout();
+ bool handleTimeoutInitial();
+ bool handleTimeoutClient();
+ bool handleTimeoutMaster();
+ bool handleTimeoutRonin();
+ bool handleTimeoutWaitForElection();
+
+ bool sendWhoIsMasterRequest();
+ bool sendSyncRequest();
+ bool sendMasterAnnouncement();
+
+ bool becomeClient(const sockaddr_storage& masterAddr,
+ uint64_t masterDeviceID,
+ uint8_t masterDevicePriority,
+ uint64_t timelineID,
+ const char* cause);
+ bool becomeMaster(const char* cause);
+ bool becomeRonin(const char* cause);
+ bool becomeWaitForElection(const char* cause);
+ bool becomeInitial(const char* cause);
+
+ void notifyClockSync();
+ void notifyClockSyncLoss();
+
+ ICommonClock::State mState;
+ void setState(ICommonClock::State s);
+
+ void clearPendingWakeupEvents_l();
+ void wakeupThread_l();
+ void cleanupSocket_l();
+ void shutdownThread();
+
+ inline uint8_t effectivePriority() const {
+ return (mMasterPriority & 0x7F) |
+ (mForceLowPriority ? 0x00 : 0x80);
+ }
+
+ inline bool shouldAutoDisable() const {
+ return (mAutoDisable && !mCommonClockHasClients);
+ }
+
+ inline void resetSyncStats() {
+ mClient_SyncRequestPending = false;
+ mClient_SyncRequestTimeouts = 0;
+ mClient_SyncsSentToCurMaster = 0;
+ mClient_SyncRespsRXedFromCurMaster = 0;
+ mClient_ExpiredSyncRespsRXedFromCurMaster = 0;
+ mClient_FirstSyncTX = 0;
+ mClient_LastGoodSyncRX = 0;
+ mClient_PacketRTTLog.resetLog();
+ }
+
+ bool shouldPanicNotGettingGoodData();
+
+ // Helper to keep track of the state machine's current timeout
+ TimeoutHelper mCurTimeout;
+
+ // common clock, local clock abstraction, and clock recovery loop
+ CommonClock mCommonClock;
+ LocalClock mLocalClock;
+ ClockRecoveryLoop mClockRecovery;
+
+ // implementation of ICommonClock
+ sp<CommonClockService> mICommonClock;
+
+ // implementation of ICommonTimeConfig
+ sp<CommonTimeConfigService> mICommonTimeConfig;
+
+ // UDP socket for the time sync protocol
+ int mSocket;
+
+ // eventfd used to wakeup the work thread in response to configuration
+ // changes.
+ int mWakeupThreadFD;
+
+ // timestamp captured when a packet is received
+ int64_t mLastPacketRxLocalTime;
+
+ // ID of the timeline that this device is following
+ uint64_t mTimelineID;
+
+ // flag for whether the clock has been synced to a timeline
+ bool mClockSynced;
+
+ // flag used to indicate that clients should be considered to be lower
+ // priority than all of their peers during elections. This flag is set and
+ // cleared by the state machine. It is set when the client joins a new
+ // network. If the client had been a master in the old network (or an
+ // isolated master with no network connectivity) it should defer to any
+ // masters which may already be on the network. It will be cleared whenever
+ // the state machine transitions to the master state.
+ bool mForceLowPriority;
+ inline void setForceLowPriority(bool val) {
+ mForceLowPriority = val;
+ if (mState == ICommonClock::STATE_MASTER)
+ mClient_MasterDevicePriority = effectivePriority();
+ }
+
+ // Lock to synchronize access to internal state and configuration.
+ Mutex mLock;
+
+ // Flag updated by the common clock service to indicate that it does or does
+ // not currently have registered clients. When the the auto disable flag is
+ // cleared on the common time service, the service will participate in
+ // network synchronization whenever it has a valid network interface to bind
+ // to. When the auto disable flag is set on the common time service, it
+ // will only participate in network synchronization when it has both a valid
+ // interface AND currently active common clock clients.
+ bool mCommonClockHasClients;
+
+ // Configuration info
+ struct sockaddr_storage mMasterElectionEP; // Endpoint over which we conduct master election
+ String8 mBindIface; // Endpoint for the service to bind to.
+ bool mBindIfaceValid; // whether or not the bind Iface is valid.
+ bool mBindIfaceDirty; // whether or not the bind Iface is valid.
+ struct sockaddr_storage mMasterEP; // Endpoint of our current master (if any)
+ bool mMasterEPValid;
+ uint64_t mDeviceID; // unique ID of this device
+ uint64_t mSyncGroupID; // synchronization group ID of this device.
+ uint8_t mMasterPriority; // Priority of this device in master election.
+ uint32_t mMasterAnnounceIntervalMs;
+ uint32_t mSyncRequestIntervalMs;
+ uint32_t mPanicThresholdUsec;
+ bool mAutoDisable;
+
+ // Config defaults.
+ static const char* kDefaultMasterElectionAddr;
+ static const uint16_t kDefaultMasterElectionPort;
+ static const uint64_t kDefaultSyncGroupID;
+ static const uint8_t kDefaultMasterPriority;
+ static const uint32_t kDefaultMasterAnnounceIntervalMs;
+ static const uint32_t kDefaultSyncRequestIntervalMs;
+ static const uint32_t kDefaultPanicThresholdUsec;
+ static const bool kDefaultAutoDisable;
+
+ // Priority mask and shift fields.
+ static const uint64_t kDeviceIDMask;
+ static const uint8_t kDevicePriorityMask;
+ static const uint8_t kDevicePriorityHiLowBit;
+ static const uint32_t kDevicePriorityShift;
+
+ // Unconfgurable constants
+ static const int kSetupRetryTimeoutMs;
+ static const int64_t kNoGoodDataPanicThresholdUsec;
+ static const uint32_t kRTTDiscardPanicThreshMultiplier;
+
+ /*** status while in the Initial state ***/
+ int mInitial_WhoIsMasterRequestTimeouts;
+ static const int kInitial_NumWhoIsMasterRetries;
+ static const int kInitial_WhoIsMasterTimeoutMs;
+
+ /*** status while in the Client state ***/
+ uint64_t mClient_MasterDeviceID;
+ uint8_t mClient_MasterDevicePriority;
+ bool mClient_SyncRequestPending;
+ int mClient_SyncRequestTimeouts;
+ uint32_t mClient_SyncsSentToCurMaster;
+ uint32_t mClient_SyncRespsRXedFromCurMaster;
+ uint32_t mClient_ExpiredSyncRespsRXedFromCurMaster;
+ int64_t mClient_FirstSyncTX;
+ int64_t mClient_LastGoodSyncRX;
+ PacketRTTLog mClient_PacketRTTLog;
+ static const int kClient_NumSyncRequestRetries;
+
+
+ /*** status while in the Master state ***/
+ static const uint32_t kDefaultMaster_AnnouncementIntervalMs;
+
+ /*** status while in the Ronin state ***/
+ int mRonin_WhoIsMasterRequestTimeouts;
+ static const int kRonin_NumWhoIsMasterRetries;
+ static const int kRonin_WhoIsMasterTimeoutMs;
+
+ /*** status while in the WaitForElection state ***/
+ static const int kWaitForElection_TimeoutMs;
+
+ static const int kInfiniteTimeout;
+
+ static const char* stateToString(ICommonClock::State s);
+ static void sockaddrToString(const sockaddr_storage& addr, bool addrValid,
+ char* buf, size_t bufLen);
+ static bool sockaddrMatch(const sockaddr_storage& a1,
+ const sockaddr_storage& a2,
+ bool matchAddressOnly);
+};
+
+} // namespace android
+
+#endif // ANDROID_COMMON_TIME_SERVER_H
+
diff --git a/services/common_time/common_time_server_api.cpp b/services/common_time/common_time_server_api.cpp
new file mode 100644
index 000000000000..fb8c2615e1c0
--- /dev/null
+++ b/services/common_time/common_time_server_api.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <binder/IServiceManager.h>
+#include <binder/IPCThreadState.h>
+
+#include "common_time_server.h"
+
+namespace android {
+
+//
+// Clock API
+//
+uint64_t CommonTimeServer::getTimelineID() {
+ AutoMutex _lock(&mLock);
+ return mTimelineID;
+}
+
+ICommonClock::State CommonTimeServer::getState() {
+ AutoMutex _lock(&mLock);
+ return mState;
+}
+
+status_t CommonTimeServer::getMasterAddr(struct sockaddr_storage* addr) {
+ AutoMutex _lock(&mLock);
+ if (mMasterEPValid) {
+ memcpy(addr, &mMasterEP, sizeof(*addr));
+ return OK;
+ }
+
+ return UNKNOWN_ERROR;
+}
+
+int32_t CommonTimeServer::getEstimatedError() {
+ AutoMutex _lock(&mLock);
+
+ if (ICommonClock::STATE_MASTER == mState)
+ return 0;
+
+ if (!mClockSynced)
+ return ICommonClock::kErrorEstimateUnknown;
+
+ return mClockRecovery.getLastErrorEstimate();
+}
+
+status_t CommonTimeServer::isCommonTimeValid(bool* valid,
+ uint32_t* timelineID) {
+ AutoMutex _lock(&mLock);
+ *valid = mCommonClock.isValid();
+ *timelineID = mTimelineID;
+ return OK;
+}
+
+//
+// Config API
+//
+status_t CommonTimeServer::getMasterElectionPriority(uint8_t *priority) {
+ AutoMutex _lock(&mLock);
+ *priority = mMasterPriority;
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionPriority(uint8_t priority) {
+ AutoMutex _lock(&mLock);
+
+ if (priority > 0x7F)
+ return BAD_VALUE;
+
+ mMasterPriority = priority;
+ return OK;
+}
+
+status_t CommonTimeServer::getMasterElectionEndpoint(
+ struct sockaddr_storage *addr) {
+ AutoMutex _lock(&mLock);
+ memcpy(addr, &mMasterElectionEP, sizeof(*addr));
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionEndpoint(
+ const struct sockaddr_storage *addr) {
+ AutoMutex _lock(&mLock);
+
+ if (!addr)
+ return BAD_VALUE;
+
+ // TODO: add proper support for IPv6
+ if (addr->ss_family != AF_INET)
+ return BAD_VALUE;
+
+ // Only multicast and broadcast endpoints with explicit ports are allowed.
+ uint16_t ipv4Port = ntohs(
+ reinterpret_cast<const struct sockaddr_in*>(addr)->sin_port);
+ if (!ipv4Port)
+ return BAD_VALUE;
+
+ uint32_t ipv4Addr = ntohl(
+ reinterpret_cast<const struct sockaddr_in*>(addr)->sin_addr.s_addr);
+ if ((ipv4Addr != 0xFFFFFFFF) && (0xE0000000 != (ipv4Addr & 0xF0000000)))
+ return BAD_VALUE;
+
+ memcpy(&mMasterElectionEP, addr, sizeof(mMasterElectionEP));
+
+ // Force a rebind in order to change election enpoints.
+ mBindIfaceDirty = true;
+ wakeupThread_l();
+ return OK;
+}
+
+status_t CommonTimeServer::getMasterElectionGroupId(uint64_t *id) {
+ AutoMutex _lock(&mLock);
+ *id = mSyncGroupID;
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionGroupId(uint64_t id) {
+ AutoMutex _lock(&mLock);
+ mSyncGroupID = id;
+ return OK;
+}
+
+status_t CommonTimeServer::getInterfaceBinding(String8& ifaceName) {
+ AutoMutex _lock(&mLock);
+ if (!mBindIfaceValid)
+ return INVALID_OPERATION;
+ ifaceName = mBindIface;
+ return OK;
+}
+
+status_t CommonTimeServer::setInterfaceBinding(const String8& ifaceName) {
+ AutoMutex _lock(&mLock);
+
+ mBindIfaceDirty = true;
+ if (ifaceName.size()) {
+ mBindIfaceValid = true;
+ mBindIface = ifaceName;
+ } else {
+ mBindIfaceValid = false;
+ mBindIface.clear();
+ }
+
+ wakeupThread_l();
+ return OK;
+}
+
+status_t CommonTimeServer::getMasterAnnounceInterval(int *interval) {
+ AutoMutex _lock(&mLock);
+ *interval = mMasterAnnounceIntervalMs;
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterAnnounceInterval(int interval) {
+ AutoMutex _lock(&mLock);
+
+ if (interval > (6 *3600000)) // Max interval is once every 6 hrs
+ return BAD_VALUE;
+
+ if (interval < 500) // Min interval is once per 0.5 seconds
+ return BAD_VALUE;
+
+ mMasterAnnounceIntervalMs = interval;
+ if (ICommonClock::STATE_MASTER == mState) {
+ int pendingTimeout = mCurTimeout.msecTillTimeout();
+ if ((kInfiniteTimeout == pendingTimeout) ||
+ (pendingTimeout > interval)) {
+ mCurTimeout.setTimeout(mMasterAnnounceIntervalMs);
+ wakeupThread_l();
+ }
+ }
+
+ return OK;
+}
+
+status_t CommonTimeServer::getClientSyncInterval(int *interval) {
+ AutoMutex _lock(&mLock);
+ *interval = mSyncRequestIntervalMs;
+ return OK;
+}
+
+status_t CommonTimeServer::setClientSyncInterval(int interval) {
+ AutoMutex _lock(&mLock);
+
+ if (interval > (3600000)) // Max interval is once every 60 min
+ return BAD_VALUE;
+
+ if (interval < 250) // Min interval is once per 0.25 seconds
+ return BAD_VALUE;
+
+ mSyncRequestIntervalMs = interval;
+ if (ICommonClock::STATE_CLIENT == mState) {
+ int pendingTimeout = mCurTimeout.msecTillTimeout();
+ if ((kInfiniteTimeout == pendingTimeout) ||
+ (pendingTimeout > interval)) {
+ mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+ wakeupThread_l();
+ }
+ }
+
+ return OK;
+}
+
+status_t CommonTimeServer::getPanicThreshold(int *threshold) {
+ AutoMutex _lock(&mLock);
+ *threshold = mPanicThresholdUsec;
+ return OK;
+}
+
+status_t CommonTimeServer::setPanicThreshold(int threshold) {
+ AutoMutex _lock(&mLock);
+
+ if (threshold < 1000) // Min threshold is 1mSec
+ return BAD_VALUE;
+
+ mPanicThresholdUsec = threshold;
+ return OK;
+}
+
+status_t CommonTimeServer::getAutoDisable(bool *autoDisable) {
+ AutoMutex _lock(&mLock);
+ *autoDisable = mAutoDisable;
+ return OK;
+}
+
+status_t CommonTimeServer::setAutoDisable(bool autoDisable) {
+ AutoMutex _lock(&mLock);
+ mAutoDisable = autoDisable;
+ wakeupThread_l();
+ return OK;
+}
+
+status_t CommonTimeServer::forceNetworklessMasterMode() {
+ AutoMutex _lock(&mLock);
+
+ // Can't force networkless master mode if we are currently bound to a
+ // network.
+ if (mSocket >= 0)
+ return INVALID_OPERATION;
+
+ becomeMaster("force networkless");
+
+ return OK;
+}
+
+void CommonTimeServer::reevaluateAutoDisableState(bool commonClockHasClients) {
+ AutoMutex _lock(&mLock);
+ bool needWakeup = (mAutoDisable && mMasterEPValid &&
+ (commonClockHasClients != mCommonClockHasClients));
+
+ mCommonClockHasClients = commonClockHasClients;
+
+ if (needWakeup) {
+ ALOGI("Waking up service, auto-disable is engaged and service now has%s"
+ " clients", mCommonClockHasClients ? "" : " no");
+ wakeupThread_l();
+ }
+}
+
+#define dump_printf(a, b...) do { \
+ int res; \
+ res = snprintf(buffer, sizeof(buffer), a, b); \
+ buffer[sizeof(buffer) - 1] = 0; \
+ if (res > 0) \
+ write(fd, buffer, res); \
+} while (0)
+#define checked_percentage(a, b) ((0 == b) ? 0.0f : ((100.0f * a) / b))
+
+status_t CommonTimeServer::dumpClockInterface(int fd,
+ const Vector<String16>& args,
+ size_t activeClients) {
+ AutoMutex _lock(&mLock);
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump CommonClockService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, buffer, strlen(buffer));
+ } else {
+ int64_t commonTime;
+ int64_t localTime;
+ bool synced;
+ char maStr[64];
+
+ localTime = mLocalClock.getLocalTime();
+ synced = (OK == mCommonClock.localToCommon(localTime, &commonTime));
+ sockaddrToString(mMasterEP, mMasterEPValid, maStr, sizeof(maStr));
+
+ dump_printf("Common Clock Service Status\nLocal time : %lld\n",
+ localTime);
+
+ if (synced)
+ dump_printf("Common time : %lld\n", commonTime);
+ else
+ dump_printf("Common time : %s\n", "not synced");
+
+ dump_printf("Timeline ID : %016llx\n", mTimelineID);
+ dump_printf("State : %s\n", stateToString(mState));
+ dump_printf("Master Addr : %s\n", maStr);
+
+
+ if (synced) {
+ int32_t est = (ICommonClock::STATE_MASTER != mState)
+ ? mClockRecovery.getLastErrorEstimate()
+ : 0;
+ dump_printf("Error Est. : %.3f msec\n",
+ static_cast<float>(est) / 1000.0);
+ } else {
+ dump_printf("Error Est. : %s\n", "unknown");
+ }
+
+ dump_printf("Syncs TXes : %u\n", mClient_SyncsSentToCurMaster);
+ dump_printf("Syncs RXes : %u (%.2f%%)\n",
+ mClient_SyncRespsRXedFromCurMaster,
+ checked_percentage(
+ mClient_SyncRespsRXedFromCurMaster,
+ mClient_SyncsSentToCurMaster));
+ dump_printf("RXs Expired : %u (%.2f%%)\n",
+ mClient_ExpiredSyncRespsRXedFromCurMaster,
+ checked_percentage(
+ mClient_ExpiredSyncRespsRXedFromCurMaster,
+ mClient_SyncsSentToCurMaster));
+
+ if (!mClient_LastGoodSyncRX) {
+ dump_printf("Last Good RX : %s\n", "unknown");
+ } else {
+ int64_t localDelta, usecDelta;
+ localDelta = localTime - mClient_LastGoodSyncRX;
+ usecDelta = mCommonClock.localDurationToCommonDuration(localDelta);
+ dump_printf("Last Good RX : %lld uSec ago\n", usecDelta);
+ }
+
+ dump_printf("Active Clients : %u\n", activeClients);
+ mClient_PacketRTTLog.dumpLog(fd, mCommonClock);
+ }
+
+ return NO_ERROR;
+}
+
+status_t CommonTimeServer::dumpConfigInterface(int fd,
+ const Vector<String16>& args) {
+ AutoMutex _lock(&mLock);
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump CommonTimeConfigService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, buffer, strlen(buffer));
+ } else {
+ char meStr[64];
+
+ sockaddrToString(mMasterElectionEP, true, meStr, sizeof(meStr));
+
+ dump_printf("Common Time Config Service Status\n"
+ "Bound Interface : %s\n",
+ mBindIfaceValid ? mBindIface.string() : "<unbound>");
+ dump_printf("Master Election Endpoint : %s\n", meStr);
+ dump_printf("Master Election Group ID : %016llx\n", mSyncGroupID);
+ dump_printf("Master Announce Interval : %d mSec\n",
+ mMasterAnnounceIntervalMs);
+ dump_printf("Client Sync Interval : %d mSec\n",
+ mSyncRequestIntervalMs);
+ dump_printf("Panic Threshold : %d uSec\n",
+ mPanicThresholdUsec);
+ dump_printf("Base ME Prio : 0x%02x\n",
+ static_cast<uint32_t>(mMasterPriority));
+ dump_printf("Effective ME Prio : 0x%02x\n",
+ static_cast<uint32_t>(effectivePriority()));
+ dump_printf("Auto Disable Allowed : %s\n",
+ mAutoDisable ? "yes" : "no");
+ dump_printf("Auto Disable Engaged : %s\n",
+ shouldAutoDisable() ? "yes" : "no");
+ }
+
+ return NO_ERROR;
+}
+
+void CommonTimeServer::PacketRTTLog::dumpLog(int fd, const CommonClock& cclk) {
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ uint32_t avail = !logFull ? wrPtr : RTT_LOG_SIZE;
+
+ if (!avail)
+ return;
+
+ dump_printf("\nPacket Log (%d entries)\n", avail);
+
+ uint32_t ndx = 0;
+ uint32_t i = logFull ? wrPtr : 0;
+ do {
+ if (rxTimes[i]) {
+ int64_t delta = rxTimes[i] - txTimes[i];
+ int64_t deltaUsec = cclk.localDurationToCommonDuration(delta);
+ dump_printf("pkt[%2d] : localTX %12lld localRX %12lld "
+ "(%.3f msec RTT)\n",
+ ndx, txTimes[i], rxTimes[i],
+ static_cast<float>(deltaUsec) / 1000.0);
+ } else {
+ dump_printf("pkt[%2d] : localTX %12lld localRX never\n",
+ ndx, txTimes[i]);
+ }
+ i = (i + 1) % RTT_LOG_SIZE;
+ ndx++;
+ } while (i != wrPtr);
+}
+
+#undef dump_printf
+#undef checked_percentage
+
+} // namespace android
diff --git a/services/common_time/common_time_server_packets.cpp b/services/common_time/common_time_server_packets.cpp
new file mode 100644
index 000000000000..9833c37f2519
--- /dev/null
+++ b/services/common_time/common_time_server_packets.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include "common_time_server_packets.h"
+
+namespace android {
+
+const uint32_t TimeServicePacketHeader::kMagic =
+ (static_cast<uint32_t>('c') << 24) |
+ (static_cast<uint32_t>('c') << 16) |
+ (static_cast<uint32_t>('l') << 8) |
+ static_cast<uint32_t>('k');
+
+const uint16_t TimeServicePacketHeader::kCurVersion = 1;
+
+#define SERIALIZE_FIELD(field_name, type, converter) \
+ do { \
+ if ((offset + sizeof(field_name)) > length) \
+ return -1; \
+ *((type*)(data + offset)) = converter(field_name); \
+ offset += sizeof(field_name); \
+ } while (0)
+#define SERIALIZE_INT16(field_name) SERIALIZE_FIELD(field_name, int16_t, htons)
+#define SERIALIZE_INT32(field_name) SERIALIZE_FIELD(field_name, int32_t, htonl)
+#define SERIALIZE_INT64(field_name) SERIALIZE_FIELD(field_name, int64_t, htonq)
+
+#define DESERIALIZE_FIELD(field_name, type, converter) \
+ do { \
+ if ((offset + sizeof(field_name)) > length) \
+ return -1; \
+ field_name = converter(*((type*)(data + offset))); \
+ offset += sizeof(field_name); \
+ } while (0)
+#define DESERIALIZE_INT16(field_name) DESERIALIZE_FIELD(field_name, int16_t, ntohs)
+#define DESERIALIZE_INT32(field_name) DESERIALIZE_FIELD(field_name, int32_t, ntohl)
+#define DESERIALIZE_INT64(field_name) DESERIALIZE_FIELD(field_name, int64_t, ntohq)
+
+#define kDevicePriorityShift 56
+#define kDeviceIDMask ((static_cast<uint64_t>(1) << kDevicePriorityShift) - 1)
+
+inline uint64_t packDeviceID(uint64_t devID, uint8_t prio) {
+ return (devID & kDeviceIDMask) |
+ (static_cast<uint64_t>(prio) << kDevicePriorityShift);
+}
+
+inline uint64_t unpackDeviceID(uint64_t packed) {
+ return (packed & kDeviceIDMask);
+}
+
+inline uint8_t unpackDevicePriority(uint64_t packed) {
+ return static_cast<uint8_t>(packed >> kDevicePriorityShift);
+}
+
+ssize_t TimeServicePacketHeader::serializeHeader(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = 0;
+ int16_t pktType = static_cast<int16_t>(packetType);
+ SERIALIZE_INT32(magic);
+ SERIALIZE_INT16(version);
+ SERIALIZE_INT16(pktType);
+ SERIALIZE_INT64(timelineID);
+ SERIALIZE_INT64(syncGroupID);
+ return offset;
+}
+
+ssize_t TimeServicePacketHeader::deserializeHeader(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = 0;
+ int16_t tmp;
+ DESERIALIZE_INT32(magic);
+ DESERIALIZE_INT16(version);
+ DESERIALIZE_INT16(tmp);
+ DESERIALIZE_INT64(timelineID);
+ DESERIALIZE_INT64(syncGroupID);
+ packetType = static_cast<TimeServicePacketType>(tmp);
+ return offset;
+}
+
+ssize_t TimeServicePacketHeader::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t ret, tmp;
+
+ ret = serializeHeader(data, length);
+ if (ret < 0)
+ return ret;
+
+ data += ret;
+ length -= ret;
+
+ switch (packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ tmp =((WhoIsMasterRequestPacket*)(this))->serializePacket(data,
+ length);
+ break;
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ tmp =((WhoIsMasterResponsePacket*)(this))->serializePacket(data,
+ length);
+ break;
+ case TIME_PACKET_SYNC_REQUEST:
+ tmp =((SyncRequestPacket*)(this))->serializePacket(data, length);
+ break;
+ case TIME_PACKET_SYNC_RESPONSE:
+ tmp =((SyncResponsePacket*)(this))->serializePacket(data, length);
+ break;
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ tmp =((MasterAnnouncementPacket*)(this))->serializePacket(data,
+ length);
+ break;
+ default:
+ return -1;
+ }
+
+ if (tmp < 0)
+ return tmp;
+
+ return ret + tmp;
+}
+
+ssize_t UniversalTimeServicePacket::deserializePacket(
+ const uint8_t* data,
+ uint32_t length,
+ uint64_t expectedSyncGroupID) {
+ ssize_t ret;
+ TimeServicePacketHeader* header;
+ if (length < 8)
+ return -1;
+
+ packetType = ntohs(*((uint16_t*)(data + 6)));
+ switch (packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ ret = p.who_is_master_request.deserializePacket(data, length);
+ header = &p.who_is_master_request;
+ break;
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ ret = p.who_is_master_response.deserializePacket(data, length);
+ header = &p.who_is_master_response;
+ break;
+ case TIME_PACKET_SYNC_REQUEST:
+ ret = p.sync_request.deserializePacket(data, length);
+ header = &p.sync_request;
+ break;
+ case TIME_PACKET_SYNC_RESPONSE:
+ ret = p.sync_response.deserializePacket(data, length);
+ header = &p.sync_response;
+ break;
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ ret = p.master_announcement.deserializePacket(data, length);
+ header = &p.master_announcement;
+ break;
+ default:
+ return -1;
+ }
+
+ if ((ret >= 0) && !header->checkPacket(expectedSyncGroupID))
+ ret = -1;
+
+ return ret;
+}
+
+ssize_t WhoIsMasterRequestPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(senderDeviceID, senderDevicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterRequestPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ senderDeviceID = unpackDeviceID(packed);
+ senderDevicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(deviceID, devicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ deviceID = unpackDeviceID(packed);
+ devicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+ssize_t SyncRequestPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ SERIALIZE_INT64(clientTxLocalTime);
+ }
+ return offset;
+}
+
+ssize_t SyncRequestPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ DESERIALIZE_INT64(clientTxLocalTime);
+ }
+ return offset;
+}
+
+ssize_t SyncResponsePacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ SERIALIZE_INT64(clientTxLocalTime);
+ SERIALIZE_INT64(masterRxCommonTime);
+ SERIALIZE_INT64(masterTxCommonTime);
+ SERIALIZE_INT32(nak);
+ }
+ return offset;
+}
+
+ssize_t SyncResponsePacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ DESERIALIZE_INT64(clientTxLocalTime);
+ DESERIALIZE_INT64(masterRxCommonTime);
+ DESERIALIZE_INT64(masterTxCommonTime);
+ DESERIALIZE_INT32(nak);
+ }
+ return offset;
+}
+
+ssize_t MasterAnnouncementPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(deviceID, devicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t MasterAnnouncementPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ deviceID = unpackDeviceID(packed);
+ devicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+} // namespace android
+
diff --git a/services/common_time/common_time_server_packets.h b/services/common_time/common_time_server_packets.h
new file mode 100644
index 000000000000..57ba8a256308
--- /dev/null
+++ b/services/common_time/common_time_server_packets.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 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_COMMON_TIME_SERVER_PACKETS_H
+#define ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+
+namespace android {
+
+/***** time sync protocol packets *****/
+
+enum TimeServicePacketType {
+ TIME_PACKET_WHO_IS_MASTER_REQUEST = 1,
+ TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+ TIME_PACKET_SYNC_REQUEST,
+ TIME_PACKET_SYNC_RESPONSE,
+ TIME_PACKET_MASTER_ANNOUNCEMENT,
+};
+
+class TimeServicePacketHeader {
+ public:
+ friend class UniversalTimeServicePacket;
+ // magic number identifying the protocol
+ uint32_t magic;
+
+ // protocol version of the packet
+ uint16_t version;
+
+ // type of the packet
+ TimeServicePacketType packetType;
+
+ // the timeline ID
+ uint64_t timelineID;
+
+ // synchronization group this packet belongs to (used to operate multiple
+ // synchronization domains which all use the same master election endpoint)
+ uint64_t syncGroupID;
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+
+ protected:
+ void initHeader(TimeServicePacketType type,
+ const uint64_t tlID,
+ const uint64_t groupID) {
+ magic = kMagic;
+ version = kCurVersion;
+ packetType = type;
+ timelineID = tlID;
+ syncGroupID = groupID;
+ }
+
+ bool checkPacket(uint64_t expectedSyncGroupID) const {
+ return ((magic == kMagic) &&
+ (version == kCurVersion) &&
+ (!expectedSyncGroupID || (syncGroupID == expectedSyncGroupID)));
+ }
+
+ ssize_t serializeHeader(uint8_t* data, uint32_t length);
+ ssize_t deserializeHeader(const uint8_t* data, uint32_t length);
+
+ private:
+ static const uint32_t kMagic;
+ static const uint16_t kCurVersion;
+};
+
+// packet querying for a suitable master
+class WhoIsMasterRequestPacket : public TimeServicePacketHeader {
+ public:
+ uint64_t senderDeviceID;
+ uint8_t senderDevicePriority;
+
+ void initHeader(const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_REQUEST,
+ ICommonClock::kInvalidTimelineID,
+ groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a WhoIsMaster request
+class WhoIsMasterResponsePacket : public TimeServicePacketHeader {
+ public:
+ uint64_t deviceID;
+ uint8_t devicePriority;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// packet sent by a client requesting correspondence between local
+// and common time
+class SyncRequestPacket : public TimeServicePacketHeader {
+ public:
+ // local time when this request was transmitted
+ int64_t clientTxLocalTime;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_REQUEST,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a sync request sent by the master
+class SyncResponsePacket : public TimeServicePacketHeader {
+ public:
+ // local time when this request was transmitted by the client
+ int64_t clientTxLocalTime;
+
+ // common time when the master received the request
+ int64_t masterRxCommonTime;
+
+ // common time when the master transmitted the response
+ int64_t masterTxCommonTime;
+
+ // flag that is set if the recipient of the sync request is not acting
+ // as a master for the requested timeline
+ uint32_t nak;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_RESPONSE,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// announcement of the master's presence
+class MasterAnnouncementPacket : public TimeServicePacketHeader {
+ public:
+ // the master's device ID
+ uint64_t deviceID;
+ uint8_t devicePriority;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_MASTER_ANNOUNCEMENT,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+class UniversalTimeServicePacket {
+ public:
+ uint16_t packetType;
+ union {
+ WhoIsMasterRequestPacket who_is_master_request;
+ WhoIsMasterResponsePacket who_is_master_response;
+ SyncRequestPacket sync_request;
+ SyncResponsePacket sync_response;
+ MasterAnnouncementPacket master_announcement;
+ } p;
+
+ ssize_t deserializePacket(const uint8_t* data,
+ uint32_t length,
+ uint64_t expectedSyncGroupID);
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+
diff --git a/services/common_time/diag_thread.cpp b/services/common_time/diag_thread.cpp
new file mode 100644
index 000000000000..c8e605316867
--- /dev/null
+++ b/services/common_time/diag_thread.cpp
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2011 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 "common_time"
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Errors.h>
+#include <utils/misc.h>
+
+#include <common_time/local_clock.h>
+
+#include "common_clock.h"
+#include "diag_thread.h"
+
+#define kMaxEvents 16
+#define kListenPort 9876
+
+static bool setNonblocking(int fd) {
+ int flags = fcntl(fd, F_GETFL);
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+ fd, errno);
+ return false;
+ }
+
+ return true;
+}
+
+static bool setNodelay(int fd) {
+ int tmp = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) {
+ ALOGE("Failed to set socket (%d) to no-delay mode (errno %d)",
+ fd, errno);
+ return false;
+ }
+
+ return true;
+}
+
+namespace android {
+
+DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) {
+ common_clock_ = common_clock;
+ local_clock_ = local_clock;
+ listen_fd_ = -1;
+ data_fd_ = -1;
+ kernel_logID_basis_known_ = false;
+ discipline_log_ID_ = 0;
+}
+
+DiagThread::~DiagThread() {
+}
+
+status_t DiagThread::startWorkThread() {
+ status_t res;
+ stopWorkThread();
+ res = run("Diag");
+
+ if (res != OK)
+ ALOGE("Failed to start work thread (res = %d)", res);
+
+ return res;
+}
+
+void DiagThread::stopWorkThread() {
+ status_t res;
+ res = requestExitAndWait(); // block until thread exit.
+ if (res != OK)
+ ALOGE("Failed to stop work thread (res = %d)", res);
+}
+
+bool DiagThread::openListenSocket() {
+ bool ret = false;
+ int flags;
+ cleanupListenSocket();
+
+ if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ ALOGE("Socket failed.");
+ goto bailout;
+ }
+
+ // Set non-blocking operation
+ if (!setNonblocking(listen_fd_))
+ goto bailout;
+
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = htons(kListenPort);
+
+ if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ ALOGE("Bind failed.");
+ goto bailout;
+ }
+
+ if (listen(listen_fd_, 1) < 0) {
+ ALOGE("Listen failed.");
+ goto bailout;
+ }
+
+ ret = true;
+bailout:
+ if (!ret)
+ cleanupListenSocket();
+
+ return ret;
+}
+
+void DiagThread::cleanupListenSocket() {
+ if (listen_fd_ >= 0) {
+ int res;
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+ shutdown(listen_fd_, SHUT_RDWR);
+ close(listen_fd_);
+ listen_fd_ = -1;
+ }
+}
+
+void DiagThread::cleanupDataSocket() {
+ if (data_fd_ >= 0) {
+ int res;
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+ shutdown(data_fd_, SHUT_RDWR);
+ close(data_fd_);
+ data_fd_ = -1;
+ }
+}
+
+void DiagThread::resetLogIDs() {
+ // Drain and discard all of the events from the kernel
+ struct local_time_debug_event events[kMaxEvents];
+ while(local_clock_->getDebugLog(events, kMaxEvents) > 0)
+ ;
+
+ {
+ Mutex::Autolock lock(&discipline_log_lock_);
+ discipline_log_.clear();
+ discipline_log_ID_ = 0;
+ }
+
+ kernel_logID_basis_known_ = false;
+}
+
+void DiagThread::pushDisciplineEvent(int64_t observed_local_time,
+ int64_t observed_common_time,
+ int64_t nominal_common_time,
+ int32_t total_correction,
+ int32_t P_correction,
+ int32_t I_correction,
+ int32_t D_correction) {
+ Mutex::Autolock lock(&discipline_log_lock_);
+
+ DisciplineEventRecord evt;
+
+ evt.event_id = discipline_log_ID_++;
+
+ evt.action_local_time = local_clock_->getLocalTime();
+ common_clock_->localToCommon(evt.action_local_time,
+ &evt.action_common_time);
+
+ evt.observed_local_time = observed_local_time;
+ evt.observed_common_time = observed_common_time;
+ evt.nominal_common_time = nominal_common_time;
+ evt.total_correction = total_correction;
+ evt.P_correction = P_correction;
+ evt.I_correction = I_correction;
+ evt.D_correction = D_correction;
+
+ discipline_log_.push_back(evt);
+ while (discipline_log_.size() > kMaxDisciplineLogSize)
+ discipline_log_.erase(discipline_log_.begin());
+}
+
+bool DiagThread::threadLoop() {
+ struct pollfd poll_fds[1];
+
+ if (!openListenSocket()) {
+ ALOGE("Failed to open listen socket");
+ goto bailout;
+ }
+
+ while (!exitPending()) {
+ memset(&poll_fds, 0, sizeof(poll_fds));
+
+ if (data_fd_ < 0) {
+ poll_fds[0].fd = listen_fd_;
+ poll_fds[0].events = POLLIN;
+ } else {
+ poll_fds[0].fd = data_fd_;
+ poll_fds[0].events = POLLRDHUP | POLLIN;
+ }
+
+ int poll_res = poll(poll_fds, NELEM(poll_fds), 50);
+ if (poll_res < 0) {
+ ALOGE("Fatal error (%d,%d) while waiting on events",
+ poll_res, errno);
+ goto bailout;
+ }
+
+ if (exitPending())
+ break;
+
+ if (poll_fds[0].revents) {
+ if (poll_fds[0].fd == listen_fd_) {
+ data_fd_ = accept(listen_fd_, NULL, NULL);
+
+ if (data_fd_ < 0) {
+ ALOGW("Failed accept on socket %d with err %d",
+ listen_fd_, errno);
+ } else {
+ if (!setNonblocking(data_fd_))
+ cleanupDataSocket();
+ if (!setNodelay(data_fd_))
+ cleanupDataSocket();
+ }
+ } else
+ if (poll_fds[0].fd == data_fd_) {
+ if (poll_fds[0].revents & POLLRDHUP) {
+ // Connection hung up; time to clean up.
+ cleanupDataSocket();
+ } else
+ if (poll_fds[0].revents & POLLIN) {
+ uint8_t cmd;
+ if (read(data_fd_, &cmd, sizeof(cmd)) > 0) {
+ switch(cmd) {
+ case 'r':
+ case 'R':
+ resetLogIDs();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ struct local_time_debug_event events[kMaxEvents];
+ int amt = local_clock_->getDebugLog(events, kMaxEvents);
+
+ if (amt > 0) {
+ for (int i = 0; i < amt; i++) {
+ struct local_time_debug_event& e = events[i];
+
+ if (!kernel_logID_basis_known_) {
+ kernel_logID_basis_ = e.local_timesync_event_id;
+ kernel_logID_basis_known_ = true;
+ }
+
+ char buf[1024];
+ int64_t common_time;
+ status_t res = common_clock_->localToCommon(e.local_time,
+ &common_time);
+ snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n",
+ e.local_timesync_event_id - kernel_logID_basis_,
+ e.local_time,
+ common_time,
+ (OK == res) ? 1 : 0);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (data_fd_ >= 0)
+ write(data_fd_, buf, strlen(buf));
+ }
+ }
+
+ { // scope for autolock pattern
+ Mutex::Autolock lock(&discipline_log_lock_);
+
+ while (discipline_log_.size() > 0) {
+ char buf[1024];
+ DisciplineEventRecord& e = *discipline_log_.begin();
+ snprintf(buf, sizeof(buf),
+ "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d,%d,%d\n",
+ e.event_id,
+ e.action_local_time,
+ e.action_common_time,
+ e.observed_local_time,
+ e.observed_common_time,
+ e.nominal_common_time,
+ e.total_correction,
+ e.P_correction,
+ e.I_correction,
+ e.D_correction);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (data_fd_ >= 0)
+ write(data_fd_, buf, strlen(buf));
+
+ discipline_log_.erase(discipline_log_.begin());
+ }
+ }
+ }
+
+bailout:
+ cleanupDataSocket();
+ cleanupListenSocket();
+ return false;
+}
+
+} // namespace android
diff --git a/services/common_time/diag_thread.h b/services/common_time/diag_thread.h
new file mode 100644
index 000000000000..6ebe8295b327
--- /dev/null
+++ b/services/common_time/diag_thread.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 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 __DIAG_THREAD_H__
+#define __DIAG_THREAD_H__
+
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class DiagThread : public Thread {
+ public:
+ DiagThread(CommonClock* common_clock, LocalClock* local_clock);
+ ~DiagThread();
+
+ status_t startWorkThread();
+ void stopWorkThread();
+ virtual bool threadLoop();
+
+ void pushDisciplineEvent(int64_t observed_local_time,
+ int64_t observed_common_time,
+ int64_t nominal_common_time,
+ int32_t total_correction,
+ int32_t P_correction,
+ int32_t I_correction,
+ int32_t D_correction);
+
+ private:
+ typedef struct {
+ int64_t event_id;
+ int64_t action_local_time;
+ int64_t action_common_time;
+ int64_t observed_local_time;
+ int64_t observed_common_time;
+ int64_t nominal_common_time;
+ int32_t total_correction;
+ int32_t P_correction;
+ int32_t I_correction;
+ int32_t D_correction;
+ } DisciplineEventRecord;
+
+ bool openListenSocket();
+ void cleanupListenSocket();
+ void cleanupDataSocket();
+ void resetLogIDs();
+
+ CommonClock* common_clock_;
+ LocalClock* local_clock_;
+ int listen_fd_;
+ int data_fd_;
+
+ int64_t kernel_logID_basis_;
+ bool kernel_logID_basis_known_;
+
+ static const size_t kMaxDisciplineLogSize = 16;
+ Mutex discipline_log_lock_;
+ List<DisciplineEventRecord> discipline_log_;
+ int64_t discipline_log_ID_;
+};
+
+} // namespace android
+
+#endif //__ DIAG_THREAD_H__
diff --git a/services/common_time/main.cpp b/services/common_time/main.cpp
new file mode 100644
index 000000000000..49eb30abab66
--- /dev/null
+++ b/services/common_time/main.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include "common_time_server.h"
+
+int main(int argc, char *argv[]) {
+ using namespace android;
+
+ sp<CommonTimeServer> service = new CommonTimeServer();
+ if (service == NULL)
+ return 1;
+
+ ProcessState::self()->startThreadPool();
+ service->run("CommonTimeServer", ANDROID_PRIORITY_NORMAL);
+
+ IPCThreadState::self()->joinThreadPool();
+ return 0;
+}
+
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 41ede2e4b608..9c408c40826b 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -463,7 +463,7 @@ class AppWidgetServiceImpl {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
intent.setComponent(p.info.provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, mUserId);
if (p.instances.size() == 0) {
// cancel the future updates
cancelBroadcasts(p);
@@ -471,7 +471,7 @@ class AppWidgetServiceImpl {
// send the broacast saying that the provider is not in use any more
intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, mUserId);
}
}
}
@@ -515,8 +515,6 @@ class AppWidgetServiceImpl {
+ " safe mode: " + provider);
}
- Binder.restoreCallingIdentity(ident);
-
id.provider = p;
p.instances.add(id);
int instancesSize = p.instances.size();
@@ -1066,7 +1064,7 @@ class AppWidgetServiceImpl {
void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, mUserId);
}
void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
@@ -1074,7 +1072,7 @@ class AppWidgetServiceImpl {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(p.info.provider);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, mUserId);
}
}
@@ -1477,12 +1475,11 @@ class AppWidgetServiceImpl {
}
AtomicFile savedStateFile() {
- int userId = UserId.getCallingUserId();
- File dir = new File("/data/system/users/" + userId);
+ File dir = new File("/data/system/users/" + mUserId);
File settingsFile = new File(dir, SETTINGS_FILENAME);
if (!dir.exists()) {
dir.mkdirs();
- if (userId == 0) {
+ if (mUserId == 0) {
// Migrate old data
File oldFile = new File("/data/system/" + SETTINGS_FILENAME);
// Method doesn't throw an exception on failure. Ignore any errors
diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java
new file mode 100644
index 000000000000..9a25d2ea5f39
--- /dev/null
+++ b/services/java/com/android/server/CommonTimeManagementService.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
+import android.net.NetworkInfo;
+import android.os.Binder;
+import android.os.CommonTimeConfig;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+/**
+ * @hide
+ * <p>CommonTimeManagementService manages the configuration of the native Common Time service,
+ * reconfiguring the native service as appropriate in response to changes in network configuration.
+ */
+class CommonTimeManagementService extends Binder {
+ /*
+ * Constants and globals.
+ */
+ private static final String TAG = CommonTimeManagementService.class.getSimpleName();
+ private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000;
+ private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable";
+ private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi";
+ private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio";
+ private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout";
+ private static final boolean AUTO_DISABLE;
+ private static final boolean ALLOW_WIFI;
+ private static final byte BASE_SERVER_PRIO;
+ private static final int NO_INTERFACE_TIMEOUT;
+ private static final InterfaceScoreRule[] IFACE_SCORE_RULES;
+
+ static {
+ int tmp;
+ AUTO_DISABLE = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1));
+ ALLOW_WIFI = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0));
+ tmp = SystemProperties.getInt(SERVER_PRIO_PROP, 1);
+ NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000);
+
+ if (tmp < 1)
+ BASE_SERVER_PRIO = 1;
+ else
+ if (tmp > 30)
+ BASE_SERVER_PRIO = 30;
+ else
+ BASE_SERVER_PRIO = (byte)tmp;
+
+ if (ALLOW_WIFI) {
+ IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+ new InterfaceScoreRule("wlan", (byte)1),
+ new InterfaceScoreRule("eth", (byte)2),
+ };
+ } else {
+ IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+ new InterfaceScoreRule("eth", (byte)2),
+ };
+ }
+ };
+
+ /*
+ * Internal state
+ */
+ private final Context mContext;
+ private INetworkManagementService mNetMgr;
+ private CommonTimeConfig mCTConfig;
+ private String mCurIface;
+ private Handler mReconnectHandler = new Handler();
+ private Handler mNoInterfaceHandler = new Handler();
+ private Object mLock = new Object();
+ private boolean mDetectedAtStartup = false;
+ private byte mEffectivePrio = BASE_SERVER_PRIO;
+
+ /*
+ * Callback handler implementations.
+ */
+ private INetworkManagementEventObserver mIfaceObserver =
+ new INetworkManagementEventObserver.Stub() {
+
+ public void interfaceStatusChanged(String iface, boolean up) {
+ reevaluateServiceState();
+ }
+ public void interfaceLinkStateChanged(String iface, boolean up) {
+ reevaluateServiceState();
+ }
+ public void interfaceAdded(String iface) {
+ reevaluateServiceState();
+ }
+ public void interfaceRemoved(String iface) {
+ reevaluateServiceState();
+ }
+ public void limitReached(String limitName, String iface) { }
+ };
+
+ private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reevaluateServiceState();
+ }
+ };
+
+ private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener =
+ new CommonTimeConfig.OnServerDiedListener() {
+ public void onServerDied() {
+ scheduleTimeConfigReconnect();
+ }
+ };
+
+ private Runnable mReconnectRunnable = new Runnable() {
+ public void run() { connectToTimeConfig(); }
+ };
+
+ private Runnable mNoInterfaceRunnable = new Runnable() {
+ public void run() { handleNoInterfaceTimeout(); }
+ };
+
+ /*
+ * Public interface (constructor, systemReady and dump)
+ */
+ public CommonTimeManagementService(Context context) {
+ mContext = context;
+ }
+
+ void systemReady() {
+ if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) {
+ Log.i(TAG, "No common time service detected on this platform. " +
+ "Common time services will be unavailable.");
+ return;
+ }
+
+ mDetectedAtStartup = true;
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNetMgr = INetworkManagementService.Stub.asInterface(b);
+
+ // Network manager is running along-side us, so we should never receiver a remote exception
+ // while trying to register this observer.
+ try {
+ mNetMgr.registerObserver(mIfaceObserver);
+ }
+ catch (RemoteException e) { }
+
+ // Register with the connectivity manager for connectivity changed intents.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiver(mConnectivityMangerObserver, filter);
+
+ // Connect to the common time config service and apply the initial configuration.
+ connectToTimeConfig();
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println(String.format(
+ "Permission Denial: can't dump CommonTimeManagement service from from " +
+ "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid()));
+ return;
+ }
+
+ if (!mDetectedAtStartup) {
+ pw.println("Native Common Time service was not detected at startup. " +
+ "Service is unavailable");
+ return;
+ }
+
+ synchronized (mLock) {
+ pw.println("Current Common Time Management Service Config:");
+ pw.println(String.format(" Native service : %s",
+ (null == mCTConfig) ? "reconnecting"
+ : "alive"));
+ pw.println(String.format(" Bound interface : %s",
+ (null == mCurIface ? "unbound" : mCurIface)));
+ pw.println(String.format(" Allow WiFi : %s", ALLOW_WIFI ? "yes" : "no"));
+ pw.println(String.format(" Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no"));
+ pw.println(String.format(" Server Priority : %d", mEffectivePrio));
+ pw.println(String.format(" No iface timeout : %d", NO_INTERFACE_TIMEOUT));
+ }
+ }
+
+ /*
+ * Inner helper classes
+ */
+ private static class InterfaceScoreRule {
+ public final String mPrefix;
+ public final byte mScore;
+ public InterfaceScoreRule(String prefix, byte score) {
+ mPrefix = prefix;
+ mScore = score;
+ }
+ };
+
+ /*
+ * Internal implementation
+ */
+ private void cleanupTimeConfig() {
+ mReconnectHandler.removeCallbacks(mReconnectRunnable);
+ mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+ if (null != mCTConfig) {
+ mCTConfig.release();
+ mCTConfig = null;
+ }
+ }
+
+ private void connectToTimeConfig() {
+ // Get access to the common time service configuration interface. If we catch a remote
+ // exception in the process (service crashed or no running for w/e reason), schedule an
+ // attempt to reconnect in the future.
+ cleanupTimeConfig();
+ try {
+ synchronized (mLock) {
+ mCTConfig = new CommonTimeConfig();
+ mCTConfig.setServerDiedListener(mCTServerDiedListener);
+ mCurIface = mCTConfig.getInterfaceBinding();
+ mCTConfig.setAutoDisable(AUTO_DISABLE);
+ mCTConfig.setMasterElectionPriority(mEffectivePrio);
+ }
+
+ if (NO_INTERFACE_TIMEOUT >= 0)
+ mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+
+ reevaluateServiceState();
+ }
+ catch (RemoteException e) {
+ scheduleTimeConfigReconnect();
+ }
+ }
+
+ private void scheduleTimeConfigReconnect() {
+ cleanupTimeConfig();
+ Log.w(TAG, String.format("Native service died, will reconnect in %d mSec",
+ NATIVE_SERVICE_RECONNECT_TIMEOUT));
+ mReconnectHandler.postDelayed(mReconnectRunnable,
+ NATIVE_SERVICE_RECONNECT_TIMEOUT);
+ }
+
+ private void handleNoInterfaceTimeout() {
+ if (null != mCTConfig) {
+ Log.i(TAG, "Timeout waiting for interface to come up. " +
+ "Forcing networkless master mode.");
+ if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode())
+ scheduleTimeConfigReconnect();
+ }
+ }
+
+ private void reevaluateServiceState() {
+ String bindIface = null;
+ byte bestScore = -1;
+ try {
+ // Check to see if this interface is suitable to use for time synchronization.
+ //
+ // TODO : This selection algorithm needs to be enhanced for use with mobile devices. In
+ // particular, the choice of whether to a wireless interface or not should not be an all
+ // or nothing thing controlled by properties. It would probably be better if the
+ // platform had some concept of public wireless networks vs. home or friendly wireless
+ // networks (something a user would configure in settings or when a new interface is
+ // added). Then this algorithm could pick only wireless interfaces which were flagged
+ // as friendly, and be dormant when on public wireless networks.
+ //
+ // Another issue which needs to be dealt with is the use of driver supplied interface
+ // name to determine the network type. The fact that the wireless interface on a device
+ // is named "wlan0" is just a matter of convention; its not a 100% rule. For example,
+ // there are devices out there where the wireless is name "tiwlan0", not "wlan0". The
+ // internal network management interfaces in Android have all of the information needed
+ // to make a proper classification, there is just no way (currently) to fetch an
+ // interface's type (available from the ConnectionManager) as well as its address
+ // (available from either the java.net interfaces or from the NetworkManagment service).
+ // Both can enumerate interfaces, but that is no way to correlate their results (no
+ // common shared key; although using the interface name in the connection manager would
+ // be a good start). Until this gets resolved, we resort to substring searching for
+ // tags like wlan and eth.
+ //
+ String ifaceList[] = mNetMgr.listInterfaces();
+ if (null != ifaceList) {
+ for (String iface : ifaceList) {
+
+ byte thisScore = -1;
+ for (InterfaceScoreRule r : IFACE_SCORE_RULES) {
+ if (iface.contains(r.mPrefix)) {
+ thisScore = r.mScore;
+ break;
+ }
+ }
+
+ if (thisScore <= bestScore)
+ continue;
+
+ InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface);
+ if (null == config)
+ continue;
+
+ if (config.isActive()) {
+ bindIface = iface;
+ bestScore = thisScore;
+ }
+ }
+ }
+ }
+ catch (RemoteException e) {
+ // Bad news; we should not be getting remote exceptions from the connectivity manager
+ // since it is running in SystemServer along side of us. It probably does not matter
+ // what we do here, but go ahead and unbind the common time service in this case, just
+ // so we have some defined behavior.
+ bindIface = null;
+ }
+
+ boolean doRebind = true;
+ synchronized (mLock) {
+ if ((null != bindIface) && (null == mCurIface)) {
+ Log.e(TAG, String.format("Binding common time service to %s.", bindIface));
+ mCurIface = bindIface;
+ } else
+ if ((null == bindIface) && (null != mCurIface)) {
+ Log.e(TAG, "Unbinding common time service.");
+ mCurIface = null;
+ } else
+ if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) {
+ Log.e(TAG, String.format("Switching common time service binding from %s to %s.",
+ mCurIface, bindIface));
+ mCurIface = bindIface;
+ } else {
+ doRebind = false;
+ }
+ }
+
+ if (doRebind && (null != mCTConfig)) {
+ byte newPrio = (bestScore > 0)
+ ? (byte)(bestScore * BASE_SERVER_PRIO)
+ : BASE_SERVER_PRIO;
+ if (newPrio != mEffectivePrio) {
+ mEffectivePrio = newPrio;
+ mCTConfig.setMasterElectionPriority(mEffectivePrio);
+ }
+
+ int res = mCTConfig.setNetworkBinding(mCurIface);
+ if (res != CommonTimeConfig.SUCCESS)
+ scheduleTimeConfigReconnect();
+
+ else if (NO_INTERFACE_TIMEOUT >= 0) {
+ mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+ if (null == mCurIface)
+ mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0dbc7c367b46..c9b59975c32f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -132,6 +132,7 @@ class ServerThread extends Thread {
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
NetworkTimeUpdateService networkTimeUpdater = null;
+ CommonTimeManagementService commonTimeMgmtService = null;
// Critical services...
try {
@@ -575,6 +576,14 @@ class ServerThread extends Thread {
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
+
+ try {
+ Slog.i(TAG, "CommonTimeManagementService");
+ commonTimeMgmtService = new CommonTimeManagementService(context);
+ ServiceManager.addService("commontime_management", commonTimeMgmtService);
+ } catch (Throwable e) {
+ reportWtf("starting CommonTimeManagementService service", e);
+ }
}
// Before things start rolling, be sure we have decided whether
@@ -653,6 +662,7 @@ class ServerThread extends Thread {
final LocationManagerService locationF = location;
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
+ final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
final TextServicesManagerService textServiceManagerServiceF = tsms;
final StatusBarManagerService statusBarF = statusBar;
@@ -752,6 +762,11 @@ class ServerThread extends Thread {
reportWtf("making Network Time Service ready", e);
}
try {
+ if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making Common time management service ready", e);
+ }
+ try {
if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady();
} catch (Throwable e) {
reportWtf("making Text Services Manager Service ready", e);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 9d5caaef6d1f..785212378ad4 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -5754,7 +5754,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId);
if (cpr == null) {
- cpr = new ContentProviderRecord(cpi, app.info, comp);
+ cpr = new ContentProviderRecord(this, cpi, app.info, comp);
mProviderMap.putProviderByClass(comp, cpr);
}
if (DEBUG_MU)
@@ -5826,7 +5826,8 @@ public final class ActivityManagerService extends ActivityManagerNative
return msg;
}
- boolean incProviderCount(ProcessRecord r, ContentProviderRecord cpr) {
+ boolean incProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
+ IBinder externalProcessToken) {
if (r != null) {
Integer cnt = r.conProviders.get(cpr);
if (DEBUG_PROVIDER) Slog.v(TAG,
@@ -5842,12 +5843,13 @@ public final class ActivityManagerService extends ActivityManagerNative
r.conProviders.put(cpr, new Integer(cnt.intValue()+1));
}
} else {
- cpr.externals++;
+ cpr.addExternalProcessHandleLocked(externalProcessToken);
}
return false;
}
- boolean decProviderCount(ProcessRecord r, ContentProviderRecord cpr) {
+ boolean decProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
+ IBinder externalProcessToken) {
if (r != null) {
Integer cnt = r.conProviders.get(cpr);
if (DEBUG_PROVIDER) Slog.v(TAG,
@@ -5863,13 +5865,13 @@ public final class ActivityManagerService extends ActivityManagerNative
r.conProviders.put(cpr, new Integer(cnt.intValue()-1));
}
} else {
- cpr.externals++;
+ cpr.removeExternalProcessHandleLocked(externalProcessToken);
}
return false;
}
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
- String name) {
+ String name, IBinder token) {
ContentProviderRecord cpr;
ProviderInfo cpi = null;
@@ -5913,7 +5915,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// In this case the provider instance already exists, so we can
// return it right away.
- final boolean countChanged = incProviderCount(r, cpr);
+ final boolean countChanged = incProviderCount(r, cpr, token);
if (countChanged) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
// If this is a perceptible app accessing the provider,
@@ -5947,7 +5949,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.i(TAG,
"Existing provider " + cpr.name.flattenToShortString()
+ " is crashing; detaching " + r);
- boolean lastRef = decProviderCount(r, cpr);
+ boolean lastRef = decProviderCount(r, cpr, token);
appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread);
if (!lastRef) {
// This wasn't the last ref our process had on
@@ -6005,7 +6007,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
}
ai = getAppInfoForUser(ai, Binder.getOrigCallingUser());
- cpr = new ContentProviderRecord(cpi, ai, comp);
+ cpr = new ContentProviderRecord(this, cpi, ai, comp);
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
@@ -6075,8 +6077,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}
+
mProviderMap.putProviderByName(name, cpr);
- incProviderCount(r, cpr);
+ incProviderCount(r, cpr, token);
}
}
@@ -6116,12 +6119,17 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
- ContentProviderHolder contentProvider = getContentProviderImpl(caller, name);
- return contentProvider;
+ return getContentProviderImpl(caller, name, null);
+ }
+
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token) {
+ enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
+ "Do not have permission in call getContentProviderExternal()");
+ return getContentProviderExternalUnchecked(name, token);
}
- private ContentProviderHolder getContentProviderExternal(String name) {
- return getContentProviderImpl(null, name);
+ private ContentProviderHolder getContentProviderExternalUnchecked(String name,IBinder token) {
+ return getContentProviderImpl(null, name, token);
}
/**
@@ -6157,14 +6165,20 @@ public final class ActivityManagerService extends ActivityManagerNative
+ cpr.info.name + " in process " + r.processName);
return;
} else {
- if (decProviderCount(r, localCpr)) {
+ if (decProviderCount(r, localCpr, null)) {
updateOomAdjLocked();
}
}
}
}
- private void removeContentProviderExternal(String name) {
+ public void removeContentProviderExternal(String name, IBinder token) {
+ enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
+ "Do not have permission in call removeContentProviderExternal()");
+ removeContentProviderExternalUnchecked(name, token);
+ }
+
+ private void removeContentProviderExternalUnchecked(String name, IBinder token) {
synchronized (this) {
ContentProviderRecord cpr = mProviderMap.getProviderByName(name,
Binder.getOrigCallingUser());
@@ -6178,11 +6192,18 @@ public final class ActivityManagerService extends ActivityManagerNative
ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name);
ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp,
Binder.getOrigCallingUser());
- localCpr.externals--;
- if (localCpr.externals < 0) {
- Slog.e(TAG, "Externals < 0 for content provider " + localCpr);
+ if (localCpr.hasExternalProcessHandles()) {
+ if (localCpr.removeExternalProcessHandleLocked(token)) {
+ updateOomAdjLocked();
+ } else {
+ Slog.e(TAG, "Attmpt to remove content provider " + localCpr
+ + " with no external reference for token: "
+ + token + ".");
+ }
+ } else {
+ Slog.e(TAG, "Attmpt to remove content provider: " + localCpr
+ + " with no external references.");
}
- updateOomAdjLocked();
}
}
@@ -6286,7 +6307,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ContentProviderHolder holder = null;
try {
- holder = getContentProviderExternal(name);
+ holder = getContentProviderExternalUnchecked(name, null);
if (holder != null) {
return holder.provider.getType(uri);
}
@@ -6295,7 +6316,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
} finally {
if (holder != null) {
- removeContentProviderExternal(name);
+ removeContentProviderExternalUnchecked(name, null);
}
Binder.restoreCallingIdentity(ident);
}
@@ -6400,7 +6421,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException {
enforceNotIsolatedCaller("openContentUri");
String name = uri.getAuthority();
- ContentProviderHolder cph = getContentProviderExternal(name);
+ ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null);
ParcelFileDescriptor pfd = null;
if (cph != null) {
// We record the binder invoker's uid in thread-local storage before
@@ -6422,7 +6443,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
// We've got the fd now, so we're done with the provider.
- removeContentProviderExternal(name);
+ removeContentProviderExternalUnchecked(name, null);
} else {
Slog.d(TAG, "Failed to get provider for authority '" + name + "'");
}
@@ -10253,7 +10274,7 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i=0; i<NL; i++) {
ContentProviderRecord cpr = (ContentProviderRecord)
mLaunchingProviders.get(i);
- if (cpr.clients.size() <= 0 && cpr.externals <= 0) {
+ if (cpr.clients.size() <= 0 && !cpr.hasExternalProcessHandles()) {
synchronized (cpr) {
cpr.launchingApp = null;
cpr.notifyAll();
@@ -13455,7 +13476,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// If the provider has external (non-framework) process
// dependencies, ensure that its adjustment is at least
// FOREGROUND_APP_ADJ.
- if (cpr.externals != 0) {
+ if (cpr.hasExternalProcessHandles()) {
if (adj > ProcessList.FOREGROUND_APP_ADJ) {
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = Process.THREAD_GROUP_DEFAULT;
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index 38355537e1c6..f338cfcb7737 100644
--- a/services/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/java/com/android/server/am/ContentProviderRecord.java
@@ -20,24 +20,35 @@ import android.app.IActivityManager.ContentProviderHolder;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
import java.io.PrintWriter;
+import java.util.HashMap;
import java.util.HashSet;
class ContentProviderRecord extends ContentProviderHolder {
// All attached clients
final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
+ // Handles for non-framework processes supported by this provider
+ HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle;
+ // Count for external process for which we have no handles.
+ int externalProcessNoHandleCount;
+ final ActivityManagerService service;
final int uid;
final ApplicationInfo appInfo;
final ComponentName name;
- int externals; // number of non-framework processes supported by this provider
ProcessRecord proc; // if non-null, hosting process.
ProcessRecord launchingApp; // if non-null, waiting for this app to be launched.
String stringName;
-
- public ContentProviderRecord(ProviderInfo _info, ApplicationInfo ai, ComponentName _name) {
+
+ public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info,
+ ApplicationInfo ai, ComponentName _name) {
super(_info);
+ service = _service;
uid = ai.uid;
appInfo = ai;
name = _name;
@@ -50,6 +61,7 @@ class ContentProviderRecord extends ContentProviderHolder {
appInfo = cpr.appInfo;
name = cpr.name;
noReleaseNeeded = cpr.noReleaseNeeded;
+ service = cpr.service;
}
public boolean canRunHere(ProcessRecord app) {
@@ -57,6 +69,57 @@ class ContentProviderRecord extends ContentProviderHolder {
&& (uid == Process.SYSTEM_UID || uid == app.info.uid);
}
+ public void addExternalProcessHandleLocked(IBinder token) {
+ if (token == null) {
+ externalProcessNoHandleCount++;
+ } else {
+ if (externalProcessTokenToHandle == null) {
+ externalProcessTokenToHandle = new HashMap<IBinder, ExternalProcessHandle>();
+ }
+ ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+ if (handle == null) {
+ handle = new ExternalProcessHandle(token);
+ externalProcessTokenToHandle.put(token, handle);
+ }
+ handle.mAcquisitionCount++;
+ }
+ }
+
+ public boolean removeExternalProcessHandleLocked(IBinder token) {
+ if (hasExternalProcessHandles()) {
+ boolean hasHandle = false;
+ if (externalProcessTokenToHandle != null) {
+ ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+ if (handle != null) {
+ hasHandle = true;
+ handle.mAcquisitionCount--;
+ if (handle.mAcquisitionCount == 0) {
+ removeExternalProcessHandleInternalLocked(token);
+ return true;
+ }
+ }
+ }
+ if (!hasHandle) {
+ externalProcessNoHandleCount--;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void removeExternalProcessHandleInternalLocked(IBinder token) {
+ ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+ handle.unlinkFromOwnDeathLocked();
+ externalProcessTokenToHandle.remove(token);
+ if (externalProcessTokenToHandle.size() == 0) {
+ externalProcessTokenToHandle = null;
+ }
+ }
+
+ public boolean hasExternalProcessHandles() {
+ return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0);
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("package=");
pw.print(info.applicationInfo.packageName);
@@ -73,8 +136,9 @@ class ContentProviderRecord extends ContentProviderHolder {
pw.print("multiprocess="); pw.print(info.multiprocess);
pw.print(" initOrder="); pw.println(info.initOrder);
}
- if (externals != 0) {
- pw.print(prefix); pw.print("externals="); pw.println(externals);
+ if (hasExternalProcessHandles()) {
+ pw.print(prefix); pw.print("externals=");
+ pw.println(externalProcessTokenToHandle.size());
}
if (clients.size() > 0) {
pw.print(prefix); pw.println("Clients:");
@@ -84,6 +148,7 @@ class ContentProviderRecord extends ContentProviderHolder {
}
}
+ @Override
public String toString() {
if (stringName != null) {
return stringName;
@@ -96,4 +161,35 @@ class ContentProviderRecord extends ContentProviderHolder {
sb.append('}');
return stringName = sb.toString();
}
+
+ // This class represents a handle from an external process to a provider.
+ private class ExternalProcessHandle implements DeathRecipient {
+ private static final String LOG_TAG = "ExternalProcessHanldle";
+
+ private final IBinder mToken;
+ private int mAcquisitionCount;
+
+ public ExternalProcessHandle(IBinder token) {
+ mToken = token;
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Couldn't register for death for token: " + mToken, re);
+ }
+ }
+
+ public void unlinkFromOwnDeathLocked() {
+ mToken.unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (service) {
+ if (hasExternalProcessHandles() &&
+ externalProcessTokenToHandle.get(mToken) != null) {
+ removeExternalProcessHandleInternalLocked(mToken);
+ }
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index bdbaab498b97..1c81bb1debcf 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -142,8 +142,7 @@ import java.util.List;
/** {@hide} */
public class WindowManagerService extends IWindowManager.Stub
- implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs,
- Choreographer.OnAnimateListener {
+ implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
static final String TAG = "WindowManager";
static final boolean DEBUG = false;
static final boolean DEBUG_ADD_REMOVE = false;
@@ -603,6 +602,18 @@ public class WindowManagerService extends IWindowManager.Stub
}
private LayoutAndSurfaceFields mInnerFields = new LayoutAndSurfaceFields();
+ private final class AnimationRunnable implements Runnable {
+ @Override
+ public void run() {
+ synchronized(mWindowMap) {
+ mAnimationScheduled = false;
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ }
+ }
+ final AnimationRunnable mAnimationRunnable = new AnimationRunnable();
+ boolean mAnimationScheduled;
+
final class DragInputEventReceiver extends InputEventReceiver {
public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
@@ -724,7 +735,6 @@ public class WindowManagerService extends IWindowManager.Stub
Looper.prepare();
WindowManagerService s = new WindowManagerService(mContext, mPM,
mHaveInputMethods, mAllowBootMessages);
- s.mChoreographer.addOnAnimateListener(s);
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_DISPLAY);
android.os.Process.setCanSelfBackground(false);
@@ -5441,7 +5451,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (mScreenRotationAnimation.setRotation(rotation, mFxSession,
MAX_ANIMATION_DURATION, mTransitionAnimationScale,
mCurDisplayWidth, mCurDisplayHeight)) {
- mChoreographer.scheduleAnimation();
+ scheduleAnimationLocked();
}
}
Surface.setOrientation(0, rotation);
@@ -6882,7 +6892,7 @@ public class WindowManagerService extends IWindowManager.Stub
case FORCE_GC: {
synchronized(mWindowMap) {
- if (mChoreographer.isAnimationScheduled()) {
+ if (mAnimationScheduled) {
// If we are animating, don't do the gc now but
// delay a bit so we don't interrupt the animation.
mH.sendMessageDelayed(mH.obtainMessage(H.FORCE_GC),
@@ -8980,7 +8990,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (needRelayout) {
requestTraversalLocked();
} else if (mInnerFields.mAnimating) {
- mChoreographer.scheduleAnimation();
+ scheduleAnimationLocked();
}
// Finally update all input windows now that the window changes have stabilized.
@@ -9100,10 +9110,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @Override
- public void onAnimate() {
- synchronized(mWindowMap) {
- performLayoutAndPlaceSurfacesLocked();
+ void scheduleAnimationLocked() {
+ if (!mAnimationScheduled) {
+ mChoreographer.postAnimationCallback(mAnimationRunnable);
+ mAnimationScheduled = true;
}
}
@@ -9423,7 +9433,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation");
if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) {
- mChoreographer.scheduleAnimation();
+ scheduleAnimationLocked();
} else {
mScreenRotationAnimation = null;
updateRotation = true;
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 6868cf6b82ea..b013d2799520 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -1593,7 +1593,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true);
}
if (requestAnim) {
- mService.mChoreographer.scheduleAnimation();
+ mService.scheduleAnimationLocked();
}
return true;
}
@@ -1634,7 +1634,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
if (requestAnim) {
- mService.mChoreographer.scheduleAnimation();
+ mService.scheduleAnimationLocked();
}
return true;
}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 24a4876ee4ef..8cfdb794c03d 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -292,6 +292,20 @@ public class PhoneNumberUtils
}
/**
+ * Translates keypad letters to actual digits (e.g. 1-800-GOOG-411 will
+ * become 1-800-4664-411), and then strips all separators (e.g. 1-800-4664-411 will become
+ * 18004664411).
+ *
+ * @see #convertKeypadLettersToDigits(String)
+ * @see #stripSeparators(String)
+ *
+ * @hide
+ */
+ public static String convertAndStrip(String phoneNumber) {
+ return stripSeparators(convertKeypadLettersToDigits(phoneNumber));
+ }
+
+ /**
* Converts pause and tonewait pause characters
* to Android representation.
* RFC 3601 says pause is 'p' and tonewait is 'w'.
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 5d764844406d..2b9fb913241c 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -1040,7 +1040,8 @@ public abstract class DataConnectionTracker extends Handler {
}
}
if (didDisable) {
- if (enabledCount == 0) {
+ if ((enabledCount == 0) || (apnId == APN_DUN_ID)) {
+ mRequestedApnType = Phone.APN_TYPE_DEFAULT;
onCleanUpConnection(true, apnId, Phone.REASON_DATA_DISABLED);
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index 3fe57aeb20b5..6e4dd58d6ff3 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -95,6 +95,9 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
Phone.APN_TYPE_MMS,
Phone.APN_TYPE_HIPRI };
+ private String[] mDunApnTypes = {
+ Phone.APN_TYPE_DUN };
+
private static final int mDefaultApnId = DataConnectionTracker.APN_DEFAULT_ID;
/* Constructor */
@@ -121,11 +124,26 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
createAllDataConnectionList();
broadcastMessenger();
+
+ Context c = mCdmaPhone.getContext();
+ String[] t = c.getResources().getStringArray(
+ com.android.internal.R.array.config_cdma_dun_supported_types);
+ if (t != null && t.length > 0) {
+ ArrayList<String> temp = new ArrayList<String>();
+ for(int i=0; i< t.length; i++) {
+ if (!Phone.APN_TYPE_DUN.equalsIgnoreCase(t[i])) {
+ temp.add(t[i]);
+ }
+ }
+ temp.add(0, Phone.APN_TYPE_DUN);
+ mDunApnTypes = temp.toArray(t);
+ }
+
}
@Override
public void dispose() {
- cleanUpConnection(false, null);
+ cleanUpConnection(false, null, false);
super.dispose();
@@ -282,7 +300,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
* @param tearDown true if the underlying DataConnection should be disconnected.
* @param reason for the clean up.
*/
- private void cleanUpConnection(boolean tearDown, String reason) {
+ private void cleanUpConnection(boolean tearDown, String reason, boolean doAll) {
if (DBG) log("cleanUpConnection: reason: " + reason);
// Clear the reconnect alarm, if set.
@@ -302,9 +320,15 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
DataConnectionAc dcac =
mDataConnectionAsyncChannels.get(conn.getDataConnectionId());
if (tearDown) {
- if (DBG) log("cleanUpConnection: teardown, call conn.disconnect");
- conn.tearDown(reason, obtainMessage(EVENT_DISCONNECT_DONE,
- conn.getDataConnectionId(), 0, reason));
+ if (doAll) {
+ if (DBG) log("cleanUpConnection: teardown, conn.tearDownAll");
+ conn.tearDownAll(reason, obtainMessage(EVENT_DISCONNECT_DONE,
+ conn.getDataConnectionId(), 0, reason));
+ } else {
+ if (DBG) log("cleanUpConnection: teardown, conn.tearDown");
+ conn.tearDown(reason, obtainMessage(EVENT_DISCONNECT_DONE,
+ conn.getDataConnectionId(), 0, reason));
+ }
notificationDeferred = true;
} else {
if (DBG) log("cleanUpConnection: !tearDown, call conn.resetSynchronously");
@@ -348,8 +372,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
String[] types;
int apnId;
if (mRequestedApnType.equals(Phone.APN_TYPE_DUN)) {
- types = new String[1];
- types[0] = Phone.APN_TYPE_DUN;
+ types = mDunApnTypes;
apnId = DataConnectionTracker.APN_DUN_ID;
} else {
types = mDefaultApnTypes;
@@ -587,7 +610,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
@Override
protected void onEnableNewApn() {
// No mRequestedApnType check; only one connection is supported
- cleanUpConnection(true, Phone.REASON_APN_SWITCHED);
+ cleanUpConnection(true, Phone.REASON_APN_SWITCHED, false);
}
/**
@@ -769,13 +792,13 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
@Override
protected void onCleanUpConnection(boolean tearDown, int apnId, String reason) {
// No apnId check; only one connection is supported
- cleanUpConnection(tearDown, reason);
+ cleanUpConnection(tearDown, reason, (apnId == APN_DUN_ID));
}
@Override
protected void onCleanUpAllConnections(String cause) {
// Only one CDMA connection is supported
- cleanUpConnection(true, cause);
+ cleanUpConnection(true, cause, false);
}
private void createAllDataConnectionList() {
@@ -821,7 +844,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
} else {
if (mState == State.FAILED) {
- cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED);
+ cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED, false);
mDataConnections.get(0).resetRetryCount();
CdmaCellLocation loc = (CdmaCellLocation)(mPhone.getCellLocation());
@@ -900,7 +923,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
log("onDataStateChanged: No active connection"
+ "state is CONNECTED, disconnecting/cleanup");
writeEventLogCdmaDataDrop();
- cleanUpConnection(true, null);
+ cleanUpConnection(true, null, false);
return;
}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index 9d9680d4a43f..db670f8919de 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -623,4 +623,35 @@ public class PhoneNumberUtilsTest extends AndroidTestCase {
// Brazil.
assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "BR"));
}
+
+ @SmallTest
+ public void testStripSeparators() {
+ // Smoke tests which should never fail.
+ assertEquals("1234567890", PhoneNumberUtils.stripSeparators("1234567890"));
+ assertEquals("911", PhoneNumberUtils.stripSeparators("911"));
+ assertEquals("112", PhoneNumberUtils.stripSeparators("112"));
+
+ // Separators should be removed, while '+' or any other digits should not.
+ assertEquals("+16502910000", PhoneNumberUtils.stripSeparators("+1 (650) 291-0000"));
+
+ // WAIT, PAUSE should *not* be stripped
+ assertEquals("+16502910000,300;",
+ PhoneNumberUtils.stripSeparators("+1 (650) 291-0000, 300;"));
+ }
+
+ @SmallTest
+ public void testConvertAndStrip() {
+ // Smoke tests which should never fail.
+ assertEquals("1234567890", PhoneNumberUtils.convertAndStrip("1234567890"));
+ assertEquals("911", PhoneNumberUtils.convertAndStrip("911"));
+ assertEquals("112", PhoneNumberUtils.convertAndStrip("112"));
+
+ // It should convert keypad characters into digits, and strip separators
+ assertEquals("22233344455566677778889999",
+ PhoneNumberUtils.convertAndStrip("ABC DEF GHI JKL MNO PQR STUV WXYZ"));
+
+ // Test real cases.
+ assertEquals("18004664411", PhoneNumberUtils.convertAndStrip("1-800-GOOG-411"));
+ assertEquals("8002223334", PhoneNumberUtils.convertAndStrip("(800) ABC-DEFG"));
+ }
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 674c0b700b5a..5fab2bb2d65d 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -267,6 +267,12 @@ public class MockContext extends Context {
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public void sendBroadcast(Intent intent, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void sendBroadcast(Intent intent, String receiverPermission) {
throw new UnsupportedOperationException();
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 013445664843..1b64f3e3a2f5 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1754,7 +1754,9 @@ public class WifiStateMachine extends StateMachine {
* If we've exceeded the maximum number of retries for DHCP
* to a given network, disable the network
*/
- if (++mReconnectCount > getMaxDhcpRetries()) {
+ int maxRetries = getMaxDhcpRetries();
+ // maxRetries == 0 means keep trying forever
+ if (maxRetries > 0 && ++mReconnectCount > maxRetries) {
loge("Failed " +
mReconnectCount + " times, Disabling " + mLastNetworkId);
mWifiConfigStore.disableNetwork(mLastNetworkId,