Merge "Import translations."
diff --git a/api/current.txt b/api/current.txt
index 8c0dc14..d034b89 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23310,6 +23310,9 @@
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 onResetResolvedTextDirection();
+ method public void onResolvePadding(int);
+ method public void onResolveTextDirection();
method protected void onRestoreInstanceState(android.os.Parcelable);
method protected android.os.Parcelable onSaveInstanceState();
method protected void onScrollChanged(int, int, int, int);
@@ -23344,10 +23347,11 @@
method public void requestLayout();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
- method protected void resetResolvedTextDirection();
+ method public 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();
+ method public void resolveTextDirection();
method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
diff --git a/cmds/content/Android.mk b/cmds/content/Android.mk
new file mode 100644
index 0000000..a3d83cf
--- /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 0000000..e69de29
--- /dev/null
+++ b/cmds/content/MODULE_LICENSE_APACHE2
diff --git a/cmds/content/NOTICE b/cmds/content/NOTICE
new file mode 100644
index 0000000..33ff961
--- /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 0000000..a8e056d
--- /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 0000000..1dcba70
--- /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/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index cfc2d16..71c5622 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -43,6 +43,8 @@
{ 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/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 5a36466..24079a5d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -581,6 +581,21 @@
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 @@
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 @@
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 @@
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 ebf692a..6d5cce5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -911,6 +911,19 @@
}
}
+ /** @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 7deb615..53a71db 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -150,8 +150,11 @@
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 @@
source.readStrongBinder());
noReleaseNeeded = source.readInt() != 0;
}
- };
+ }
/** Information returned after waiting for an activity start. */
public static class WaitResult implements Parcelable {
@@ -601,4 +604,6 @@
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 6d4cdae..a1198de 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -905,6 +905,16 @@
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 cd8d87f..5ba9dcc 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -294,6 +294,12 @@
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/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index aeb5d92..3fdf246 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -616,6 +616,7 @@
Message msg = Message.obtain();
msg.what = 0;
msg.obj = t;
+ msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index f94d320..06c6c6e 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.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 @@
}
/**
+ * 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/os/CommonClock.java b/core/java/android/os/CommonClock.java
new file mode 100644
index 0000000..3a1da97
--- /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 0000000..3355ee3
--- /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 0000000..9081ee4
--- /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/Handler.java b/core/java/android/os/Handler.java
index af2fa9b..610b3550 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -513,7 +513,7 @@
* message queue.
*/
public final void removeMessages(int what) {
- mQueue.removeMessages(this, what, null, true);
+ mQueue.removeMessages(this, what, null);
}
/**
@@ -522,7 +522,7 @@
* all messages will be removed.
*/
public final void removeMessages(int what, Object object) {
- mQueue.removeMessages(this, what, object, true);
+ mQueue.removeMessages(this, what, object);
}
/**
@@ -539,7 +539,7 @@
* the message queue.
*/
public final boolean hasMessages(int what) {
- return mQueue.removeMessages(this, what, null, false);
+ return mQueue.hasMessages(this, what, null);
}
/**
@@ -547,7 +547,7 @@
* whose obj is 'object' in the message queue.
*/
public final boolean hasMessages(int what, Object object) {
- return mQueue.removeMessages(this, what, object, false);
+ return mQueue.hasMessages(this, what, object);
}
// if we can get rid of this method, the handler need not remember its loop
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 5607f7f..a06aadb6 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -55,13 +55,13 @@
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
+ private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
volatile boolean mRun;
- private Printer mLogging = null;
- private static Looper mMainLooper = null; // guarded by Looper.class
+ private Printer mLogging;
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
@@ -70,10 +70,14 @@
* {@link #quit()}.
*/
public static void prepare() {
+ prepare(true);
+ }
+
+ private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
- sThreadLocal.set(new Looper());
+ sThreadLocal.set(new Looper(quitAllowed));
}
/**
@@ -83,19 +87,21 @@
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
- prepare();
- setMainLooper(myLooper());
- myLooper().mQueue.mQuitAllowed = false;
- }
-
- private synchronized static void setMainLooper(Looper looper) {
- mMainLooper = looper;
+ prepare(false);
+ synchronized (Looper.class) {
+ if (sMainLooper != null) {
+ throw new IllegalStateException("The main Looper has already been prepared.");
+ }
+ sMainLooper = myLooper();
+ }
}
/** Returns the application's main looper, which lives in the main thread of the application.
*/
- public synchronized static Looper getMainLooper() {
- return mMainLooper;
+ public static Looper getMainLooper() {
+ synchronized (Looper.class) {
+ return sMainLooper;
+ }
}
/**
@@ -103,63 +109,61 @@
* {@link #quit()} to end the loop.
*/
public static void loop() {
- Looper me = myLooper();
+ final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
- MessageQueue queue = me.mQueue;
-
+ final MessageQueue queue = me.mQueue;
+
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
-
- while (true) {
+
+ for (;;) {
Message msg = queue.next(); // might block
- if (msg != null) {
- if (msg.target == null) {
- // No target is a magic identifier for the quit message.
- return;
- }
-
- long wallStart = 0;
- long threadStart = 0;
-
- // This must be in a local variable, in case a UI event sets the logger
- Printer logging = me.mLogging;
- if (logging != null) {
- logging.println(">>>>> Dispatching to " + msg.target + " " +
- msg.callback + ": " + msg.what);
- wallStart = SystemClock.currentTimeMicro();
- threadStart = SystemClock.currentThreadTimeMicro();
- }
-
- msg.target.dispatchMessage(msg);
-
- if (logging != null) {
- long wallTime = SystemClock.currentTimeMicro() - wallStart;
- long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
-
- logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
- if (logging instanceof Profiler) {
- ((Profiler) logging).profile(msg, wallStart, wallTime,
- threadStart, threadTime);
- }
- }
-
- // Make sure that during the course of dispatching the
- // identity of the thread wasn't corrupted.
- final long newIdent = Binder.clearCallingIdentity();
- if (ident != newIdent) {
- Log.wtf(TAG, "Thread identity changed from 0x"
- + Long.toHexString(ident) + " to 0x"
- + Long.toHexString(newIdent) + " while dispatching to "
- + msg.target.getClass().getName() + " "
- + msg.callback + " what=" + msg.what);
- }
-
- msg.recycle();
+ if (msg == null) {
+ // No message indicates that the message queue is quitting.
+ return;
}
+
+ long wallStart = 0;
+ long threadStart = 0;
+
+ // This must be in a local variable, in case a UI event sets the logger
+ Printer logging = me.mLogging;
+ if (logging != null) {
+ logging.println(">>>>> Dispatching to " + msg.target + " " +
+ msg.callback + ": " + msg.what);
+ wallStart = SystemClock.currentTimeMicro();
+ threadStart = SystemClock.currentThreadTimeMicro();
+ }
+
+ msg.target.dispatchMessage(msg);
+
+ if (logging != null) {
+ long wallTime = SystemClock.currentTimeMicro() - wallStart;
+ long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
+
+ logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
+ if (logging instanceof Profiler) {
+ ((Profiler) logging).profile(msg, wallStart, wallTime,
+ threadStart, threadTime);
+ }
+ }
+
+ // Make sure that during the course of dispatching the
+ // identity of the thread wasn't corrupted.
+ final long newIdent = Binder.clearCallingIdentity();
+ if (ident != newIdent) {
+ Log.wtf(TAG, "Thread identity changed from 0x"
+ + Long.toHexString(ident) + " to 0x"
+ + Long.toHexString(newIdent) + " while dispatching to "
+ + msg.target.getClass().getName() + " "
+ + msg.callback + " what=" + msg.what);
+ }
+
+ msg.recycle();
}
}
@@ -193,18 +197,61 @@
return myLooper().mQueue;
}
- private Looper() {
- mQueue = new MessageQueue();
+ private Looper(boolean quitAllowed) {
+ mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
+ /**
+ * Quits the looper.
+ *
+ * Causes the {@link #loop} method to terminate as soon as possible.
+ */
public void quit() {
- Message msg = Message.obtain();
- // NOTE: By enqueueing directly into the message queue, the
- // message is left with a null target. This is how we know it is
- // a quit message.
- mQueue.enqueueMessage(msg, 0);
+ mQueue.quit();
+ }
+
+ /**
+ * Posts a synchronization barrier to the Looper's message queue.
+ *
+ * Message processing occurs as usual until the message queue encounters the
+ * synchronization barrier that has been posted. When the barrier is encountered,
+ * later synchronous messages in the queue are stalled (prevented from being executed)
+ * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+ * the token that identifies the synchronization barrier.
+ *
+ * This method is used to immediately postpone execution of all subsequently posted
+ * synchronous messages until a condition is met that releases the barrier.
+ * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+ * and continue to be processed as usual.
+ *
+ * This call must be always matched by a call to {@link #removeSyncBarrier} with
+ * the same token to ensure that the message queue resumes normal operation.
+ * Otherwise the application will probably hang!
+ *
+ * @return A token that uniquely identifies the barrier. This token must be
+ * passed to {@link #removeSyncBarrier} to release the barrier.
+ *
+ * @hide
+ */
+ public final int postSyncBarrier() {
+ return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis());
+ }
+
+
+ /**
+ * Removes a synchronization barrier.
+ *
+ * @param token The synchronization barrier token that was returned by
+ * {@link #postSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier was not found.
+ *
+ * @hide
+ */
+ public final void removeSyncBarrier(int token) {
+ mQueue.removeSyncBarrier(token);
}
/**
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 11dc124..64027ef 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -30,21 +30,24 @@
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public class MessageQueue {
+ // True if the message queue can be quit.
+ private final boolean mQuitAllowed;
+
+ @SuppressWarnings("unused")
+ private int mPtr; // used by native code
+
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuiting;
- boolean mQuitAllowed = true;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
- // Indicates the barrier nesting level.
- private int mBarrierNestCount;
+ // The next barrier token.
+ // Barriers are indicated by messages with a null target whose arg1 field carries the token.
+ private int mNextBarrierToken;
- @SuppressWarnings("unused")
- private int mPtr; // used by native code
-
private native void nativeInit();
private native void nativeDestroy();
private native void nativePollOnce(int ptr, int timeoutMillis);
@@ -97,56 +100,11 @@
}
}
- /**
- * 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() {
+ MessageQueue(boolean quitAllowed) {
+ mQuitAllowed = quitAllowed;
nativeInit();
}
-
+
@Override
protected void finalize() throws Throwable {
try {
@@ -167,26 +125,26 @@
nativePollOnce(mPtr, nextPollTimeoutMillis);
synchronized (this) {
+ if (mQuiting) {
+ return null;
+ }
+
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
-
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 (msg != null && msg.target == null) {
+ // Stalled by a barrier. Find the next asynchronous message in the queue.
+ do {
+ prevMsg = msg;
+ msg = msg.next;
+ } while (msg != null && !msg.isAsynchronous());
+ }
+ if (msg != null) {
+ if (now < msg.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()) {
+ nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
+ } else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
@@ -199,16 +157,16 @@
msg.markInUse();
return msg;
}
-
- // 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;
+ } else {
+ // No more messages.
+ nextPollTimeoutMillis = -1;
}
// If first time idle, then get the number of idlers to run.
- if (pendingIdleHandlerCount < 0 && msg == mMessages) {
+ // Idle handles only run if the queue is empty or if the first message
+ // in the queue (possibly a barrier) is due to be handled in the future.
+ if (pendingIdleHandlerCount < 0
+ && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
@@ -252,27 +210,94 @@
}
}
+ final void quit() {
+ if (!mQuitAllowed) {
+ throw new RuntimeException("Main thread not allowed to quit.");
+ }
+
+ synchronized (this) {
+ if (mQuiting) {
+ return;
+ }
+ mQuiting = true;
+ }
+ nativeWake(mPtr);
+ }
+
+ final int enqueueSyncBarrier(long when) {
+ // Enqueue a new sync barrier token.
+ // We don't need to wake the queue because the purpose of a barrier is to stall it.
+ synchronized (this) {
+ final int token = mNextBarrierToken++;
+ final Message msg = Message.obtain();
+ msg.arg1 = token;
+
+ Message prev = null;
+ Message p = mMessages;
+ if (when != 0) {
+ while (p != null && p.when <= when) {
+ prev = p;
+ p = p.next;
+ }
+ }
+ if (prev != null) { // invariant: p == prev.next
+ msg.next = p;
+ prev.next = msg;
+ } else {
+ msg.next = p;
+ mMessages = msg;
+ }
+ return token;
+ }
+ }
+
+ final void removeSyncBarrier(int token) {
+ // Remove a sync barrier token from the queue.
+ // If the queue is no longer stalled by a barrier then wake it.
+ final boolean needWake;
+ synchronized (this) {
+ Message prev = null;
+ Message p = mMessages;
+ while (p != null && (p.target != null || p.arg1 != token)) {
+ prev = p;
+ p = p.next;
+ }
+ if (p == null) {
+ throw new IllegalStateException("The specified message queue synchronization "
+ + " barrier token has not been posted or has already been removed.");
+ }
+ if (prev != null) {
+ prev.next = p.next;
+ needWake = false;
+ } else {
+ mMessages = p.next;
+ needWake = mMessages == null || mMessages.target != null;
+ }
+ p.recycle();
+ }
+ if (needWake) {
+ nativeWake(mPtr);
+ }
+ }
+
final boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
- throw new AndroidRuntimeException(msg
- + " This message is already in use.");
+ throw new AndroidRuntimeException(msg + " This message is already in use.");
}
- if (msg.target == null && !mQuitAllowed) {
- throw new RuntimeException("Main thread not allowed to quit");
+ if (msg.target == null) {
+ throw new AndroidRuntimeException("Message must have a target.");
}
- final boolean needWake;
+
+ boolean needWake;
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(
- msg.target + " sending message to a Handler on a dead thread");
+ msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
- } else if (msg.target == null) {
- mQuiting = true;
}
msg.when = when;
- //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.
@@ -281,18 +306,22 @@
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) {
+ // up the event queue unless there is a barrier at the head of the queue
+ // and the message is the earliest asynchronous message in the queue.
+ needWake = mBlocked && p.target == null && msg.isAsynchronous();
+ Message prev;
+ for (;;) {
prev = p;
p = p.next;
+ if (p == null || when < p.when) {
+ break;
+ }
+ if (needWake && p.isAsynchronous()) {
+ needWake = false;
+ }
}
- msg.next = prev.next;
+ msg.next = p; // invariant: p == prev.next
prev.next = msg;
- needWake = mBlocked && mBarrierNestCount != 0 && msg.isAsynchronous()
- && !mMessages.isAsynchronous();
}
}
if (needWake) {
@@ -301,17 +330,34 @@
return true;
}
- final boolean removeMessages(Handler h, int what, Object object,
- boolean doRemove) {
+ final boolean hasMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+
synchronized (this) {
Message p = mMessages;
- boolean found = false;
+ while (p != null) {
+ if (p.target == h && p.what == what && (object == null || p.obj == object)) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
+ final void removeMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ synchronized (this) {
+ Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
- if (!doRemove) return true;
- found = true;
Message n = p.next;
mMessages = n;
p.recycle();
@@ -324,8 +370,6 @@
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
- if (!doRemove) return true;
- found = true;
Message nn = n.next;
n.recycle();
p.next = nn;
@@ -334,13 +378,11 @@
}
p = n;
}
-
- return found;
}
}
final void removeMessages(Handler h, Runnable r, Object object) {
- if (r == null) {
+ if (h == null || r == null) {
return;
}
@@ -374,6 +416,10 @@
}
final void removeCallbacksAndMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+
synchronized (this) {
Message p = mMessages;
@@ -401,16 +447,4 @@
}
}
}
-
- /*
- private void dumpQueue_l()
- {
- Message p = mMessages;
- System.out.println(this + " queue is:");
- while (p != null) {
- System.out.println(" " + p);
- p = p.next;
- }
- }
- */
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index a74b737..42c3913 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -235,8 +235,9 @@
if (isRunningOnLooperThreadLocked()) {
doScheduleVsyncLocked();
} else {
- mHandler.sendMessageAtFrontOfQueue(
- mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC));
+ Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long now = SystemClock.uptimeMillis();
@@ -244,7 +245,9 @@
if (DEBUG) {
Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms.");
}
- mHandler.sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime);
+ Message msg = mHandler.obtainMessage(MSG_DO_ANIMATION);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, nextAnimationTime);
}
}
}
@@ -258,7 +261,9 @@
if (DEBUG) {
Log.d(TAG, "Scheduling draw immediately.");
}
- mHandler.sendEmptyMessage(MSG_DO_DRAW);
+ Message msg = mHandler.obtainMessage(MSG_DO_DRAW);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
}
}
}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 1e92b43..4af5f3d 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -407,9 +407,12 @@
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
final GLES20Layer glLayer = (GLES20Layer) layer;
int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
+ } finally {
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ }
}
private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint);
@@ -607,10 +610,14 @@
return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
}
+ int count;
int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- int count = nSaveLayer(mRenderer, nativePaint, saveFlags);
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ count = nSaveLayer(mRenderer, nativePaint, saveFlags);
+ } finally {
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ }
return count;
}
@@ -620,10 +627,14 @@
public int saveLayer(float left, float top, float right, float bottom, Paint paint,
int saveFlags) {
if (left < right && top < bottom) {
+ int count;
int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- int count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
+ } finally {
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ }
return count;
}
return save(saveFlags);
@@ -707,9 +718,12 @@
public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
Paint paint) {
int modifiers = setupModifiers(paint);
- nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle,
- useCenter, paint.mNativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom,
+ startAngle, sweepAngle, useCenter, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawArc(int renderer, float left, float top,
@@ -726,10 +740,13 @@
if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps");
// Shaders are ignored when drawing patches
int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+ } finally {
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ }
}
private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks,
@@ -740,9 +757,12 @@
if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps");
// Shaders are ignored when drawing bitmaps
int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawBitmap(
@@ -753,10 +773,13 @@
if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps");
// Shaders are ignored when drawing bitmaps
int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
- matrix.native_instance, nativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
+ matrix.native_instance, nativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawBitmap(int renderer, int bitmap, byte[] buff,
@@ -767,23 +790,26 @@
if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps");
// Shaders are ignored when drawing bitmaps
int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- int left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
-
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
@Override
@@ -791,23 +817,26 @@
if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps");
// Shaders are ignored when drawing bitmaps
int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-
- float left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+
+ float left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
-
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer,
@@ -819,12 +848,15 @@
int width, int height, boolean hasAlpha, Paint paint) {
// Shaders are ignored when drawing bitmaps
int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
- final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config);
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, b.mNativeBitmap, b.mBuffer, x, y, nativePaint);
- b.recycle();
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ try {
+ final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+ final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config);
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmap(mRenderer, b.mNativeBitmap, b.mBuffer, x, y, nativePaint);
+ b.recycle();
+ } finally {
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
+ }
}
@Override
@@ -854,10 +886,13 @@
colorOffset = 0;
int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
- verts, vertOffset, colors, colorOffset, nativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
+ verts, vertOffset, colors, colorOffset, nativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawBitmapMesh(int renderer, int bitmap, byte[] buffer,
@@ -867,8 +902,11 @@
@Override
public void drawCircle(float cx, float cy, float radius, Paint paint) {
int modifiers = setupModifiers(paint);
- nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawCircle(int renderer, float cx, float cy,
@@ -901,8 +939,11 @@
throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
}
int modifiers = setupModifiers(paint);
- nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawLines(int renderer, float[] points,
@@ -916,8 +957,11 @@
@Override
public void drawOval(RectF oval, Paint paint) {
int modifiers = setupModifiers(paint);
- nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawOval(int renderer, float left, float top,
@@ -933,14 +977,17 @@
@Override
public void drawPath(Path path, Paint paint) {
int modifiers = setupModifiers(paint);
- if (path.isSimplePath) {
- if (path.rects != null) {
- nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
+ try {
+ if (path.isSimplePath) {
+ if (path.rects != null) {
+ nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
+ }
+ } else {
+ nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
}
- } else {
- nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawPath(int renderer, int path, int paint);
@@ -1001,8 +1048,11 @@
@Override
public void drawPoints(float[] pts, int offset, int count, Paint paint) {
int modifiers = setupModifiers(paint);
- nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawPoints(int renderer, float[] points,
@@ -1047,8 +1097,11 @@
@Override
public void drawRect(float left, float top, float right, float bottom, Paint paint) {
int modifiers = setupModifiers(paint);
- nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawRect(int renderer, float left, float top,
@@ -1072,9 +1125,12 @@
@Override
public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
int modifiers = setupModifiers(paint);
- nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
- rx, ry, paint.mNativePaint);
- if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ try {
+ nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
+ rx, ry, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
}
private static native void nDrawRoundRect(int renderer, float left, float top,
@@ -1223,17 +1279,17 @@
}
private int setupModifiers(Bitmap b, Paint paint) {
- if (b.getConfig() == Bitmap.Config.ALPHA_8) {
+ if (b.getConfig() != Bitmap.Config.ALPHA_8) {
+ final ColorFilter filter = paint.getColorFilter();
+ if (filter != null) {
+ nSetupColorFilter(mRenderer, filter.nativeColorFilter);
+ return MODIFIER_COLOR_FILTER;
+ }
+
+ return MODIFIER_NONE;
+ } else {
return setupModifiers(paint);
}
-
- final ColorFilter filter = paint.getColorFilter();
- if (filter != null) {
- nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- return MODIFIER_COLOR_FILTER;
- }
-
- return MODIFIER_NONE;
}
private int setupModifiers(Paint paint) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 6726c56e..c658a80 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -384,7 +384,7 @@
if (!mHaveFrame) {
return;
}
- ViewRootImpl viewRoot = (ViewRootImpl) getRootView().getParent();
+ ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null) {
mTranslator = viewRoot.mTranslator;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 363c30d..6c27c9f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -601,6 +601,8 @@
* @attr ref android.R.styleable#View_paddingLeft
* @attr ref android.R.styleable#View_paddingRight
* @attr ref android.R.styleable#View_paddingTop
+ * @attr ref android.R.styleable#View_paddingStart
+ * @attr ref android.R.styleable#View_paddingEnd
* @attr ref android.R.styleable#View_saveEnabled
* @attr ref android.R.styleable#View_rotation
* @attr ref android.R.styleable#View_rotationX
@@ -5440,12 +5442,6 @@
return true;
}
- /** Gets the ViewAncestor, or null if not attached. */
- /*package*/ ViewRootImpl getViewRootImpl() {
- View root = getRootView();
- return root != null ? (ViewRootImpl)root.getParent() : null;
- }
-
/**
* Call this to try to give focus to a specific view or to one of its descendants. This is a
* special variant of {@link #requestFocus() } that will allow views that are not focuable in
@@ -8683,6 +8679,18 @@
}
/**
+ * Gets the view root associated with the View.
+ * @return The view root, or null if none.
+ * @hide
+ */
+ public ViewRootImpl getViewRootImpl() {
+ if (mAttachInfo != null) {
+ return mAttachInfo.mViewRootImpl;
+ }
+ return null;
+ }
+
+ /**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
@@ -8696,17 +8704,13 @@
* looper processing the message queue is exiting.
*/
public boolean post(Runnable action) {
- Handler handler;
- AttachInfo attachInfo = mAttachInfo;
+ final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- handler = attachInfo.mHandler;
- } else {
- // Assume that post will succeed later
- ViewRootImpl.getRunQueue().post(action);
- return true;
+ return attachInfo.mHandler.post(action);
}
-
- return handler.post(action);
+ // Assume that post will succeed later
+ ViewRootImpl.getRunQueue().post(action);
+ return true;
}
/**
@@ -8729,17 +8733,13 @@
* occurs then the message will be dropped.
*/
public boolean postDelayed(Runnable action, long delayMillis) {
- Handler handler;
- AttachInfo attachInfo = mAttachInfo;
+ final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- handler = attachInfo.mHandler;
- } else {
- // Assume that post will succeed later
- ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
- return true;
+ return attachInfo.mHandler.postDelayed(action, delayMillis);
}
-
- return handler.postDelayed(action, delayMillis);
+ // Assume that post will succeed later
+ ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
+ return true;
}
/**
@@ -8756,17 +8756,13 @@
* (for instance, if the Runnable was not in the queue already.)
*/
public boolean removeCallbacks(Runnable action) {
- Handler handler;
- AttachInfo attachInfo = mAttachInfo;
+ final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- handler = attachInfo.mHandler;
+ attachInfo.mHandler.removeCallbacks(action);
} else {
// Assume that post will succeed later
ViewRootImpl.getRunQueue().removeCallbacks(action);
- return true;
}
-
- handler.removeCallbacks(action);
return true;
}
@@ -8815,12 +8811,9 @@
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
- AttachInfo attachInfo = mAttachInfo;
+ final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- Message msg = Message.obtain();
- msg.what = AttachInfo.INVALIDATE_MSG;
- msg.obj = this;
- attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
@@ -8843,7 +8836,7 @@
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
- AttachInfo attachInfo = mAttachInfo;
+ final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
info.target = this;
@@ -8852,10 +8845,7 @@
info.right = right;
info.bottom = bottom;
- final Message msg = Message.obtain();
- msg.what = AttachInfo.INVALIDATE_RECT_MSG;
- msg.obj = info;
- attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ attachInfo.mViewRootImpl.dispatchInvalidateRectDelayed(info, delayMilliseconds);
}
}
@@ -9540,10 +9530,6 @@
// Clear any previous layout direction resolution
mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_RTL;
- // Reset also TextDirection as a change into LayoutDirection may impact the selected
- // TextDirectionHeuristic
- resetResolvedTextDirection();
-
// Set resolved depending on layout direction
switch (getLayoutDirection()) {
case LAYOUT_DIRECTION_INHERIT:
@@ -9580,14 +9566,15 @@
}
/**
- * @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
@@ -9623,6 +9610,18 @@
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) {
}
/**
@@ -9649,8 +9648,10 @@
* @hide
*/
protected void resetResolvedLayoutDirection() {
- // Reset the current View resolution
+ // Reset the layout direction resolution
mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED;
+ // Reset also the text direction
+ resetResolvedTextDirection();
}
/**
@@ -9689,13 +9690,12 @@
}
if (mAttachInfo != null) {
- mAttachInfo.mHandler.removeMessages(AttachInfo.INVALIDATE_MSG, this);
+ mAttachInfo.mViewRootImpl.cancelInvalidate(this);
}
mCurrentAnimation = null;
resetResolvedLayoutDirection();
- resetResolvedTextDirection();
}
/**
@@ -14070,7 +14070,6 @@
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
- *
*/
public int getTextDirection() {
return mTextDirection;
@@ -14087,7 +14086,6 @@
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
- *
*/
public void setTextDirection(int textDirection) {
if (textDirection != mTextDirection) {
@@ -14107,7 +14105,6 @@
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
- *
*/
public int getResolvedTextDirection() {
if (mResolvedTextDirection == TEXT_DIRECTION_INHERIT) {
@@ -14117,27 +14114,47 @@
}
/**
- * Resolve the text direction.
- *
+ * Resolve the text direction. Will call {@link View#onResolveTextDirection()} when resolution
+ * is done.
*/
- protected void resolveTextDirection() {
+ public void resolveTextDirection() {
+ if (mResolvedTextDirection != TEXT_DIRECTION_INHERIT) {
+ // Resolution has already been done.
+ return;
+ }
if (mTextDirection != TEXT_DIRECTION_INHERIT) {
mResolvedTextDirection = mTextDirection;
- return;
- }
- if (mParent != null && mParent instanceof ViewGroup) {
+ } else if (mParent != null && mParent instanceof ViewGroup) {
mResolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection();
- return;
+ } else {
+ mResolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG;
}
- mResolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG;
+ onResolveTextDirection();
}
/**
- * Reset resolved text direction. Will be resolved during a call to getResolvedTextDirection().
- *
+ * Called when text direction has been resolved. Subclasses that care about text direction
+ * resolution should override this method. The default implementation does nothing.
*/
- protected void resetResolvedTextDirection() {
+ public void onResolveTextDirection() {
+ }
+
+ /**
+ * Reset resolved text direction. Text direction can be resolved with a call to
+ * getResolvedTextDirection(). Will call {@link View#onResetResolvedTextDirection()} when
+ * reset is done.
+ */
+ public void resetResolvedTextDirection() {
mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
+ onResetResolvedTextDirection();
+ }
+
+ /**
+ * Called when text direction is reset. Subclasses that care about text direction reset should
+ * override this method and do a reset of the text direction of their children. The default
+ * implementation does nothing.
+ */
+ public void onResetResolvedTextDirection() {
}
//
@@ -14962,24 +14979,17 @@
Canvas mCanvas;
/**
+ * The view root impl.
+ */
+ final ViewRootImpl mViewRootImpl;
+
+ /**
* A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
* handler can be used to pump events in the UI events queue.
*/
final Handler mHandler;
/**
- * Identifier for messages requesting the view to be invalidated.
- * Such messages should be sent to {@link #mHandler}.
- */
- static final int INVALIDATE_MSG = 0x1;
-
- /**
- * Identifier for messages requesting the view to invalidate a region.
- * Such messages should be sent to {@link #mHandler}.
- */
- static final int INVALIDATE_RECT_MSG = 0x2;
-
- /**
* Temporary for use in computing invalidate rectangles while
* calling up the hierarchy.
*/
@@ -15007,10 +15017,11 @@
* @param handler the events handler the view must use
*/
AttachInfo(IWindowSession session, IWindow window,
- Handler handler, Callbacks effectPlayer) {
+ ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
+ mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index c1db572..2a17845 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -375,7 +375,7 @@
}
private static BufferedWriter sHierarchyTraces;
- private static ViewRootImpl sHierarhcyRoot;
+ private static ViewRootImpl sHierarchyRoot;
private static String sHierarchyTracePrefix;
/**
@@ -855,7 +855,7 @@
return;
}
- if (sHierarhcyRoot != null) {
+ if (sHierarchyRoot != null) {
throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
" a new trace!");
}
@@ -874,7 +874,7 @@
return;
}
- sHierarhcyRoot = (ViewRootImpl) view.getRootView().getParent();
+ sHierarchyRoot = view.getViewRootImpl();
}
/**
@@ -896,7 +896,7 @@
return;
}
- if (sHierarhcyRoot == null || sHierarchyTraces == null) {
+ if (sHierarchyRoot == null || sHierarchyTraces == null) {
throw new IllegalStateException("You must call startHierarchyTracing() before" +
" stopHierarchyTracing()!");
}
@@ -921,7 +921,7 @@
return;
}
- View view = sHierarhcyRoot.getView();
+ View view = sHierarchyRoot.getView();
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
dumpViewHierarchy(group, out, 0);
@@ -932,7 +932,7 @@
}
}
- sHierarhcyRoot = null;
+ sHierarchyRoot = null;
}
static void dispatchCommand(View view, String command, String parameters,
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e6a8334..4860f5f 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4857,9 +4857,7 @@
}
@Override
- protected void resetResolvedTextDirection() {
- super.resetResolvedTextDirection();
-
+ public void onResetResolvedTextDirection() {
// Take care of resetting the children resolution too
final int count = getChildCount();
for (int i = 0; i < count; i++) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fbcb423..d41d168 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -61,6 +61,7 @@
import android.util.Pools;
import android.util.Slog;
import android.util.TypedValue;
+import android.view.View.AttachInfo;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
@@ -96,7 +97,7 @@
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
-public final class ViewRootImpl extends Handler implements ViewParent,
+public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
private static final String TAG = "ViewRootImpl";
private static final boolean DBG = false;
@@ -212,6 +213,7 @@
final Rect mVisRect; // used to retrieve visible rect of focused view.
boolean mTraversalScheduled;
+ int mTraversalBarrier;
long mLastTraversalFinishedTimeNanos;
long mLastDrawFinishedTimeNanos;
boolean mWillDrawSoon;
@@ -379,7 +381,7 @@
new AccessibilityInteractionConnectionManager();
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
- mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
+ mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, mHandler, this);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
@@ -838,22 +840,28 @@
public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
+ mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
scheduleFrame();
}
}
public void unscheduleTraversals() {
- mTraversalScheduled = false;
+ if (mTraversalScheduled) {
+ mTraversalScheduled = false;
+ mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
+ }
}
void scheduleFrame() {
if (!mFrameScheduled) {
- mChoreographer.postDrawCallback(mFrameRunnable);
mFrameScheduled = true;
+ mChoreographer.postDrawCallback(mFrameRunnable);
}
}
void unscheduleFrame() {
+ unscheduleTraversals();
+
if (mFrameScheduled) {
mFrameScheduled = false;
mChoreographer.removeDrawCallback(mFrameRunnable);
@@ -868,6 +876,7 @@
if (mTraversalScheduled) {
mTraversalScheduled = false;
+ mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
doTraversal();
}
}
@@ -1929,7 +1938,7 @@
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
- post(sFirstDrawHandlers.get(i));
+ mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
@@ -2441,283 +2450,289 @@
}
}
- public final static int DIE = 1001;
- public final static int RESIZED = 1002;
- public final static int RESIZED_REPORT = 1003;
- public final static int WINDOW_FOCUS_CHANGED = 1004;
- public final static int DISPATCH_KEY = 1005;
- public final static int DISPATCH_APP_VISIBILITY = 1008;
- public final static int DISPATCH_GET_NEW_SURFACE = 1009;
- public final static int IME_FINISHED_EVENT = 1010;
- public final static int DISPATCH_KEY_FROM_IME = 1011;
- public final static int FINISH_INPUT_CONNECTION = 1012;
- public final static int CHECK_FOCUS = 1013;
- public final static int CLOSE_SYSTEM_DIALOGS = 1014;
- public final static int DISPATCH_DRAG_EVENT = 1015;
- public final static int DISPATCH_DRAG_LOCATION_EVENT = 1016;
- public final static int DISPATCH_SYSTEM_UI_VISIBILITY = 1017;
- public final static int DISPATCH_GENERIC_MOTION = 1018;
- public final static int UPDATE_CONFIGURATION = 1019;
- public final static int DO_PERFORM_ACCESSIBILITY_ACTION = 1020;
- public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021;
- public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022;
- public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 1023;
- public final static int DO_PROCESS_INPUT_EVENTS = 1024;
+ private final static int MSG_INVALIDATE = 1;
+ private final static int MSG_INVALIDATE_RECT = 2;
+ private final static int MSG_DIE = 3;
+ private final static int MSG_RESIZED = 4;
+ private final static int MSG_RESIZED_REPORT = 5;
+ private final static int MSG_WINDOW_FOCUS_CHANGED = 6;
+ private final static int MSG_DISPATCH_KEY = 7;
+ private final static int MSG_DISPATCH_APP_VISIBILITY = 8;
+ private final static int MSG_DISPATCH_GET_NEW_SURFACE = 9;
+ private final static int MSG_IME_FINISHED_EVENT = 10;
+ private final static int MSG_DISPATCH_KEY_FROM_IME = 11;
+ private final static int MSG_FINISH_INPUT_CONNECTION = 12;
+ private final static int MSG_CHECK_FOCUS = 13;
+ private final static int MSG_CLOSE_SYSTEM_DIALOGS = 14;
+ private final static int MSG_DISPATCH_DRAG_EVENT = 15;
+ private final static int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
+ private final static int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
+ private final static int MSG_UPDATE_CONFIGURATION = 18;
+ private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 19;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 20;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 21;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 22;
+ private final static int MSG_PROCESS_INPUT_EVENTS = 23;
- @Override
- public String getMessageName(Message message) {
- switch (message.what) {
- case DIE:
- return "DIE";
- case RESIZED:
- return "RESIZED";
- case RESIZED_REPORT:
- return "RESIZED_REPORT";
- case WINDOW_FOCUS_CHANGED:
- return "WINDOW_FOCUS_CHANGED";
- case DISPATCH_KEY:
- return "DISPATCH_KEY";
- case DISPATCH_APP_VISIBILITY:
- return "DISPATCH_APP_VISIBILITY";
- case DISPATCH_GET_NEW_SURFACE:
- return "DISPATCH_GET_NEW_SURFACE";
- case IME_FINISHED_EVENT:
- return "IME_FINISHED_EVENT";
- case DISPATCH_KEY_FROM_IME:
- return "DISPATCH_KEY_FROM_IME";
- case FINISH_INPUT_CONNECTION:
- return "FINISH_INPUT_CONNECTION";
- case CHECK_FOCUS:
- return "CHECK_FOCUS";
- case CLOSE_SYSTEM_DIALOGS:
- return "CLOSE_SYSTEM_DIALOGS";
- case DISPATCH_DRAG_EVENT:
- return "DISPATCH_DRAG_EVENT";
- case DISPATCH_DRAG_LOCATION_EVENT:
- return "DISPATCH_DRAG_LOCATION_EVENT";
- case DISPATCH_SYSTEM_UI_VISIBILITY:
- return "DISPATCH_SYSTEM_UI_VISIBILITY";
- case DISPATCH_GENERIC_MOTION:
- return "DISPATCH_GENERIC_MOTION";
- case UPDATE_CONFIGURATION:
- return "UPDATE_CONFIGURATION";
- case DO_PERFORM_ACCESSIBILITY_ACTION:
- return "DO_PERFORM_ACCESSIBILITY_ACTION";
- case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
- return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
- case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
- return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
- case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
- return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
- case DO_PROCESS_INPUT_EVENTS:
- return "DO_PROCESS_INPUT_EVENTS";
+ final class ViewRootHandler extends Handler {
+ @Override
+ public String getMessageName(Message message) {
+ switch (message.what) {
+ case MSG_INVALIDATE:
+ return "MSG_INVALIDATE";
+ case MSG_INVALIDATE_RECT:
+ return "MSG_INVALIDATE_RECT";
+ case MSG_DIE:
+ return "MSG_DIE";
+ case MSG_RESIZED:
+ return "MSG_RESIZED";
+ case MSG_RESIZED_REPORT:
+ return "MSG_RESIZED_REPORT";
+ case MSG_WINDOW_FOCUS_CHANGED:
+ return "MSG_WINDOW_FOCUS_CHANGED";
+ case MSG_DISPATCH_KEY:
+ return "MSG_DISPATCH_KEY";
+ case MSG_DISPATCH_APP_VISIBILITY:
+ return "MSG_DISPATCH_APP_VISIBILITY";
+ case MSG_DISPATCH_GET_NEW_SURFACE:
+ return "MSG_DISPATCH_GET_NEW_SURFACE";
+ case MSG_IME_FINISHED_EVENT:
+ return "MSG_IME_FINISHED_EVENT";
+ case MSG_DISPATCH_KEY_FROM_IME:
+ return "MSG_DISPATCH_KEY_FROM_IME";
+ case MSG_FINISH_INPUT_CONNECTION:
+ return "MSG_FINISH_INPUT_CONNECTION";
+ case MSG_CHECK_FOCUS:
+ return "MSG_CHECK_FOCUS";
+ case MSG_CLOSE_SYSTEM_DIALOGS:
+ return "MSG_CLOSE_SYSTEM_DIALOGS";
+ case MSG_DISPATCH_DRAG_EVENT:
+ return "MSG_DISPATCH_DRAG_EVENT";
+ case MSG_DISPATCH_DRAG_LOCATION_EVENT:
+ return "MSG_DISPATCH_DRAG_LOCATION_EVENT";
+ case MSG_DISPATCH_SYSTEM_UI_VISIBILITY:
+ return "MSG_DISPATCH_SYSTEM_UI_VISIBILITY";
+ case MSG_UPDATE_CONFIGURATION:
+ return "MSG_UPDATE_CONFIGURATION";
+ case MSG_PERFORM_ACCESSIBILITY_ACTION:
+ return "MSG_PERFORM_ACCESSIBILITY_ACTION";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
+ case MSG_PROCESS_INPUT_EVENTS:
+ return "MSG_PROCESS_INPUT_EVENTS";
+ }
+ return super.getMessageName(message);
}
- return super.getMessageName(message);
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case View.AttachInfo.INVALIDATE_MSG:
- ((View) msg.obj).invalidate();
- break;
- case View.AttachInfo.INVALIDATE_RECT_MSG:
- final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
- info.target.invalidate(info.left, info.top, info.right, info.bottom);
- info.release();
- break;
- case IME_FINISHED_EVENT:
- handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
- break;
- case DO_PROCESS_INPUT_EVENTS:
- mProcessInputEventsScheduled = false;
- doProcessInputEvents();
- break;
- case DISPATCH_APP_VISIBILITY:
- handleAppVisibility(msg.arg1 != 0);
- break;
- case DISPATCH_GET_NEW_SURFACE:
- handleGetNewSurface();
- break;
- case RESIZED:
- ResizedInfo ri = (ResizedInfo)msg.obj;
-
- if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
- && mPendingContentInsets.equals(ri.coveredInsets)
- && mPendingVisibleInsets.equals(ri.visibleInsets)
- && ((ResizedInfo)msg.obj).newConfig == null) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INVALIDATE:
+ ((View) msg.obj).invalidate();
break;
- }
- // fall through...
- case RESIZED_REPORT:
- if (mAdded) {
- Configuration config = ((ResizedInfo)msg.obj).newConfig;
- if (config != null) {
- updateConfiguration(config, false);
- }
- mWinFrame.left = 0;
- mWinFrame.right = msg.arg1;
- mWinFrame.top = 0;
- mWinFrame.bottom = msg.arg2;
- mPendingContentInsets.set(((ResizedInfo)msg.obj).coveredInsets);
- mPendingVisibleInsets.set(((ResizedInfo)msg.obj).visibleInsets);
- if (msg.what == RESIZED_REPORT) {
- mReportNextDraw = true;
- }
+ case MSG_INVALIDATE_RECT:
+ final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
+ info.target.invalidate(info.left, info.top, info.right, info.bottom);
+ info.release();
+ break;
+ case MSG_IME_FINISHED_EVENT:
+ handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
+ break;
+ case MSG_PROCESS_INPUT_EVENTS:
+ mProcessInputEventsScheduled = false;
+ doProcessInputEvents();
+ break;
+ case MSG_DISPATCH_APP_VISIBILITY:
+ handleAppVisibility(msg.arg1 != 0);
+ break;
+ case MSG_DISPATCH_GET_NEW_SURFACE:
+ handleGetNewSurface();
+ break;
+ case MSG_RESIZED:
+ ResizedInfo ri = (ResizedInfo)msg.obj;
- if (mView != null) {
- forceLayout(mView);
+ if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
+ && mPendingContentInsets.equals(ri.coveredInsets)
+ && mPendingVisibleInsets.equals(ri.visibleInsets)
+ && ((ResizedInfo)msg.obj).newConfig == null) {
+ break;
}
- requestLayout();
- }
- break;
- case WINDOW_FOCUS_CHANGED: {
- if (mAdded) {
- boolean hasWindowFocus = msg.arg1 != 0;
- mAttachInfo.mHasWindowFocus = hasWindowFocus;
-
- profileRendering(hasWindowFocus);
+ // fall through...
+ case MSG_RESIZED_REPORT:
+ if (mAdded) {
+ Configuration config = ((ResizedInfo)msg.obj).newConfig;
+ if (config != null) {
+ updateConfiguration(config, false);
+ }
+ mWinFrame.left = 0;
+ mWinFrame.right = msg.arg1;
+ mWinFrame.top = 0;
+ mWinFrame.bottom = msg.arg2;
+ mPendingContentInsets.set(((ResizedInfo)msg.obj).coveredInsets);
+ mPendingVisibleInsets.set(((ResizedInfo)msg.obj).visibleInsets);
+ if (msg.what == MSG_RESIZED_REPORT) {
+ mReportNextDraw = true;
+ }
- if (hasWindowFocus) {
- boolean inTouchMode = msg.arg2 != 0;
- ensureTouchModeLocally(inTouchMode);
+ if (mView != null) {
+ forceLayout(mView);
+ }
+ requestLayout();
+ }
+ break;
+ case MSG_WINDOW_FOCUS_CHANGED: {
+ if (mAdded) {
+ boolean hasWindowFocus = msg.arg1 != 0;
+ mAttachInfo.mHasWindowFocus = hasWindowFocus;
- if (mAttachInfo.mHardwareRenderer != null &&
- mSurface != null && mSurface.isValid()) {
- mFullRedrawNeeded = true;
- try {
- mAttachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder);
- } catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException locking surface", e);
+ profileRendering(hasWindowFocus);
+
+ if (hasWindowFocus) {
+ boolean inTouchMode = msg.arg2 != 0;
+ ensureTouchModeLocally(inTouchMode);
+
+ if (mAttachInfo.mHardwareRenderer != null &&
+ mSurface != null && mSurface.isValid()) {
+ mFullRedrawNeeded = true;
try {
- if (!sWindowSession.outOfMemory(mWindow)) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
+ mAttachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
+ mHolder);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.e(TAG, "OutOfResourcesException locking surface", e);
+ try {
+ if (!sWindowSession.outOfMemory(mWindow)) {
+ Slog.w(TAG, "No processes killed for memory; killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
}
- } catch (RemoteException ex) {
+ // Retry in a bit.
+ sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
+ return;
}
- // Retry in a bit.
- sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
- return;
}
}
- }
- mLastWasImTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
+ mLastWasImTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (mView != null) {
- if (hasWindowFocus && imm != null && mLastWasImTarget) {
- imm.startGettingWindowFocus(mView);
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (mView != null) {
+ if (hasWindowFocus && imm != null && mLastWasImTarget) {
+ imm.startGettingWindowFocus(mView);
+ }
+ mAttachInfo.mKeyDispatchState.reset();
+ mView.dispatchWindowFocusChanged(hasWindowFocus);
}
- mAttachInfo.mKeyDispatchState.reset();
- mView.dispatchWindowFocusChanged(hasWindowFocus);
- }
- // Note: must be done after the focus change callbacks,
- // so all of the view state is set up correctly.
- if (hasWindowFocus) {
- if (imm != null && mLastWasImTarget) {
- imm.onWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode,
- !mHasHadWindowFocus, mWindowAttributes.flags);
- }
- // Clear the forward bit. We can just do this directly, since
- // the window manager doesn't care about it.
- mWindowAttributes.softInputMode &=
- ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- ((WindowManager.LayoutParams)mView.getLayoutParams())
- .softInputMode &=
+ // Note: must be done after the focus change callbacks,
+ // so all of the view state is set up correctly.
+ if (hasWindowFocus) {
+ if (imm != null && mLastWasImTarget) {
+ imm.onWindowFocus(mView, mView.findFocus(),
+ mWindowAttributes.softInputMode,
+ !mHasHadWindowFocus, mWindowAttributes.flags);
+ }
+ // Clear the forward bit. We can just do this directly, since
+ // the window manager doesn't care about it.
+ mWindowAttributes.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- mHasHadWindowFocus = true;
- }
+ ((WindowManager.LayoutParams)mView.getLayoutParams())
+ .softInputMode &=
+ ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ mHasHadWindowFocus = true;
+ }
- if (hasWindowFocus && mView != null) {
- sendAccessibilityEvents();
+ if (hasWindowFocus && mView != null) {
+ sendAccessibilityEvents();
+ }
}
+ } break;
+ case MSG_DIE:
+ doDie();
+ break;
+ case MSG_DISPATCH_KEY: {
+ KeyEvent event = (KeyEvent)msg.obj;
+ enqueueInputEvent(event, null, 0, true);
+ } break;
+ case MSG_DISPATCH_KEY_FROM_IME: {
+ if (LOCAL_LOGV) Log.v(
+ TAG, "Dispatching key "
+ + msg.obj + " from IME to " + mView);
+ KeyEvent event = (KeyEvent)msg.obj;
+ if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
+ // The IME is trying to say this event is from the
+ // system! Bad bad bad!
+ //noinspection UnusedAssignment
+ event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+ }
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
+ } break;
+ case MSG_FINISH_INPUT_CONNECTION: {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.reportFinishInputConnection((InputConnection)msg.obj);
+ }
+ } break;
+ case MSG_CHECK_FOCUS: {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.checkFocus();
+ }
+ } break;
+ case MSG_CLOSE_SYSTEM_DIALOGS: {
+ if (mView != null) {
+ mView.onCloseSystemDialogs((String)msg.obj);
+ }
+ } break;
+ case MSG_DISPATCH_DRAG_EVENT:
+ case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
+ DragEvent event = (DragEvent)msg.obj;
+ event.mLocalState = mLocalDragState; // only present when this app called startDrag()
+ handleDragEvent(event);
+ } break;
+ case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
+ handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo)msg.obj);
+ } break;
+ case MSG_UPDATE_CONFIGURATION: {
+ Configuration config = (Configuration)msg.obj;
+ if (config.isOtherSeqNewer(mLastConfiguration)) {
+ config = mLastConfiguration;
+ }
+ updateConfiguration(config, false);
+ } break;
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByAccessibilityIdUiThread(msg);
+ }
+ } break;
+ case MSG_PERFORM_ACCESSIBILITY_ACTION: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .perfromAccessibilityActionUiThread(msg);
+ }
+ } break;
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByViewIdUiThread(msg);
+ }
+ } break;
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
+ if (mView != null) {
+ getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByTextUiThread(msg);
+ }
+ } break;
}
- } break;
- case DIE:
- doDie();
- break;
- case DISPATCH_KEY: {
- KeyEvent event = (KeyEvent)msg.obj;
- enqueueInputEvent(event, null, 0, true);
- } break;
- case DISPATCH_KEY_FROM_IME: {
- if (LOCAL_LOGV) Log.v(
- TAG, "Dispatching key "
- + msg.obj + " from IME to " + mView);
- KeyEvent event = (KeyEvent)msg.obj;
- if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
- // The IME is trying to say this event is from the
- // system! Bad bad bad!
- //noinspection UnusedAssignment
- event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
- }
- enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
- } break;
- case FINISH_INPUT_CONNECTION: {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.reportFinishInputConnection((InputConnection)msg.obj);
- }
- } break;
- case CHECK_FOCUS: {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.checkFocus();
- }
- } break;
- case CLOSE_SYSTEM_DIALOGS: {
- if (mView != null) {
- mView.onCloseSystemDialogs((String)msg.obj);
- }
- } break;
- case DISPATCH_DRAG_EVENT:
- case DISPATCH_DRAG_LOCATION_EVENT: {
- DragEvent event = (DragEvent)msg.obj;
- event.mLocalState = mLocalDragState; // only present when this app called startDrag()
- handleDragEvent(event);
- } break;
- case DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo)msg.obj);
- } break;
- case UPDATE_CONFIGURATION: {
- Configuration config = (Configuration)msg.obj;
- if (config.isOtherSeqNewer(mLastConfiguration)) {
- config = mLastConfiguration;
- }
- updateConfiguration(config, false);
- } break;
- case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByAccessibilityIdUiThread(msg);
- }
- } break;
- case DO_PERFORM_ACCESSIBILITY_ACTION: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .perfromAccessibilityActionUiThread(msg);
- }
- } break;
- case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdUiThread(msg);
- }
- } break;
- case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfosByTextUiThread(msg);
- }
- } break;
}
}
+ final ViewRootHandler mHandler = new ViewRootHandler();
/**
* Something in the current window tells us we need to change the touch mode. For
@@ -3684,7 +3699,7 @@
if (immediate) {
doDie();
} else {
- sendEmptyMessage(DIE);
+ mHandler.sendEmptyMessage(MSG_DIE);
}
}
@@ -3721,8 +3736,8 @@
}
public void requestUpdateConfiguration(Configuration config) {
- Message msg = obtainMessage(UPDATE_CONFIGURATION, config);
- sendMessage(msg);
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_CONFIGURATION, config);
+ mHandler.sendMessage(msg);
}
private void destroyHardwareRenderer() {
@@ -3734,10 +3749,15 @@
}
void dispatchImeFinishedEvent(int seq, boolean handled) {
- Message msg = obtainMessage(IME_FINISHED_EVENT);
+ Message msg = mHandler.obtainMessage(MSG_IME_FINISHED_EVENT);
msg.arg1 = seq;
msg.arg2 = handled ? 1 : 0;
- sendMessage(msg);
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchFinishInputConnection(InputConnection connection) {
+ Message msg = mHandler.obtainMessage(MSG_FINISH_INPUT_CONNECTION, connection);
+ mHandler.sendMessage(msg);
}
public void dispatchResized(int w, int h, Rect coveredInsets,
@@ -3746,7 +3766,7 @@
+ " h=" + h + " coveredInsets=" + coveredInsets.toShortString()
+ " visibleInsets=" + visibleInsets.toShortString()
+ " reportDraw=" + reportDraw);
- Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED);
+ Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT :MSG_RESIZED);
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(coveredInsets);
mTranslator.translateRectInScreenToAppWindow(visibleInsets);
@@ -3760,7 +3780,7 @@
ri.visibleInsets = new Rect(visibleInsets);
ri.newConfig = newConfig;
msg.obj = ri;
- sendMessage(msg);
+ mHandler.sendMessage(msg);
}
/**
@@ -3857,7 +3877,7 @@
private void scheduleProcessInputEvents() {
if (!mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = true;
- sendEmptyMessage(DO_PROCESS_INPUT_EVENTS);
+ mHandler.sendEmptyMessage(MSG_PROCESS_INPUT_EVENTS);
}
}
@@ -3874,7 +3894,7 @@
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
- removeMessages(DO_PROCESS_INPUT_EVENTS);
+ mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
@@ -3960,47 +3980,69 @@
}
WindowInputEventReceiver mInputEventReceiver;
+ public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
+ Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
+ mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ }
+
+ public void cancelInvalidate(View view) {
+ mHandler.removeMessages(MSG_INVALIDATE, view);
+ }
+
+ public void dispatchInvalidateRectDelayed(AttachInfo.InvalidateInfo info,
+ long delayMilliseconds) {
+ final Message msg = mHandler.obtainMessage(MSG_INVALIDATE_RECT, info);
+ mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ }
+
public void dispatchKey(KeyEvent event) {
- Message msg = obtainMessage(DISPATCH_KEY, event);
- sendMessage(msg);
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY, event);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchKeyFromIme(KeyEvent event) {
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
}
public void dispatchAppVisibility(boolean visible) {
- Message msg = obtainMessage(DISPATCH_APP_VISIBILITY);
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY);
msg.arg1 = visible ? 1 : 0;
- sendMessage(msg);
+ mHandler.sendMessage(msg);
}
public void dispatchGetNewSurface() {
- Message msg = obtainMessage(DISPATCH_GET_NEW_SURFACE);
- sendMessage(msg);
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_GET_NEW_SURFACE);
+ mHandler.sendMessage(msg);
}
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
Message msg = Message.obtain();
- msg.what = WINDOW_FOCUS_CHANGED;
+ msg.what = MSG_WINDOW_FOCUS_CHANGED;
msg.arg1 = hasFocus ? 1 : 0;
msg.arg2 = inTouchMode ? 1 : 0;
- sendMessage(msg);
+ mHandler.sendMessage(msg);
}
public void dispatchCloseSystemDialogs(String reason) {
Message msg = Message.obtain();
- msg.what = CLOSE_SYSTEM_DIALOGS;
+ msg.what = MSG_CLOSE_SYSTEM_DIALOGS;
msg.obj = reason;
- sendMessage(msg);
+ mHandler.sendMessage(msg);
}
public void dispatchDragEvent(DragEvent event) {
final int what;
if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
- what = DISPATCH_DRAG_LOCATION_EVENT;
- removeMessages(what);
+ what = MSG_DISPATCH_DRAG_LOCATION_EVENT;
+ mHandler.removeMessages(what);
} else {
- what = DISPATCH_DRAG_EVENT;
+ what = MSG_DISPATCH_DRAG_EVENT;
}
- Message msg = obtainMessage(what, event);
- sendMessage(msg);
+ Message msg = mHandler.obtainMessage(what, event);
+ mHandler.sendMessage(msg);
}
public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
@@ -4010,7 +4052,14 @@
args.globalVisibility = globalVisibility;
args.localValue = localValue;
args.localChanges = localChanges;
- sendMessage(obtainMessage(DISPATCH_SYSTEM_UI_VISIBILITY, args));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
+ }
+
+ public void dispatchCheckFocus() {
+ if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
+ // This will result in a call to checkFocus() below.
+ mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);
+ }
}
/**
@@ -4041,7 +4090,7 @@
}
if (!mSendWindowContentChangedAccessibilityEvent.mIsPending) {
mSendWindowContentChangedAccessibilityEvent.mIsPending = true;
- postDelayed(mSendWindowContentChangedAccessibilityEvent,
+ mHandler.postDelayed(mSendWindowContentChangedAccessibilityEvent,
ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
}
}
@@ -4052,7 +4101,7 @@
*/
private void removeSendWindowContentChangedCallback() {
if (mSendWindowContentChangedAccessibilityEvent != null) {
- removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
+ mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
}
}
@@ -4512,6 +4561,9 @@
}
/**
+ * The run queue is used to enqueue pending work from Views when no Handler is
+ * attached. The work is executed during the next call to performTraversals on
+ * the thread.
* @hide
*/
static final class RunQueue {
@@ -4770,8 +4822,8 @@
long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
long interrogatingTid) {
- Message message = Message.obtain();
- message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+ Message message = mHandler.obtainMessage();
+ message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
message.arg1 = interrogatingPid;
SomeArgs args = mPool.acquire();
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
@@ -4785,11 +4837,10 @@
// client can handle the message to generate the result.
if (interrogatingPid == Process.myPid()
&& interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- message.setTarget(ViewRootImpl.this);
AccessibilityInteractionClient.getInstanceForThread(
interrogatingTid).setSameThreadMessage(message);
} else {
- sendMessage(message);
+ mHandler.sendMessage(message);
}
}
@@ -4828,8 +4879,8 @@
public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
int interrogatingPid, long interrogatingTid) {
- Message message = Message.obtain();
- message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ Message message = mHandler.obtainMessage();
+ message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
SomeArgs args = mPool.acquire();
args.argi1 = viewId;
@@ -4842,11 +4893,10 @@
// client can handle the message to generate the result.
if (interrogatingPid == Process.myPid()
&& interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- message.setTarget(ViewRootImpl.this);
AccessibilityInteractionClient.getInstanceForThread(
interrogatingTid).setSameThreadMessage(message);
} else {
- sendMessage(message);
+ mHandler.sendMessage(message);
}
}
@@ -4885,8 +4935,8 @@
String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
long interrogatingTid) {
- Message message = Message.obtain();
- message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
+ Message message = mHandler.obtainMessage();
+ message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
SomeArgs args = mPool.acquire();
args.arg1 = text;
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
@@ -4900,11 +4950,10 @@
// client can handle the message to generate the result.
if (interrogatingPid == Process.myPid()
&& interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- message.setTarget(ViewRootImpl.this);
AccessibilityInteractionClient.getInstanceForThread(
interrogatingTid).setSameThreadMessage(message);
} else {
- sendMessage(message);
+ mHandler.sendMessage(message);
}
}
@@ -4971,8 +5020,8 @@
public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
int interogatingPid, long interrogatingTid) {
- Message message = Message.obtain();
- message.what = DO_PERFORM_ACCESSIBILITY_ACTION;
+ Message message = mHandler.obtainMessage();
+ message.what = MSG_PERFORM_ACCESSIBILITY_ACTION;
message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
message.arg2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
SomeArgs args = mPool.acquire();
@@ -4986,11 +5035,10 @@
// client can handle the message to generate the result.
if (interogatingPid == Process.myPid()
&& interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- message.setTarget(ViewRootImpl.this);
AccessibilityInteractionClient.getInstanceForThread(
interrogatingTid).setSameThreadMessage(message);
} else {
- sendMessage(message);
+ mHandler.sendMessage(message);
}
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index bd02d62..89aba3c 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
-import android.os.Handler;
import android.os.SystemClock;
import android.text.Editable;
import android.text.NoCopySpan;
@@ -497,15 +496,14 @@
*/
public boolean sendKeyEvent(KeyEvent event) {
synchronized (mIMM.mH) {
- Handler h = mTargetView != null ? mTargetView.getHandler() : null;
- if (h == null) {
+ ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
+ if (viewRootImpl == null) {
if (mIMM.mServedView != null) {
- h = mIMM.mServedView.getHandler();
+ viewRootImpl = mIMM.mServedView.getViewRootImpl();
}
}
- if (h != null) {
- h.sendMessage(h.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
- event));
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchKeyFromIme(event);
}
}
return false;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f79ac93..c51d244 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -336,6 +336,7 @@
}
case MSG_UNBIND: {
final int sequence = msg.arg1;
+ boolean startInput = false;
synchronized (mH) {
if (mBindSequence == sequence) {
if (false) {
@@ -356,10 +357,13 @@
mServedConnecting = true;
}
if (mActive) {
- startInputInner();
+ startInput = true;
}
}
}
+ if (startInput) {
+ startInputInner();
+ }
return;
}
case MSG_SET_ACTIVE: {
@@ -671,11 +675,10 @@
// longer the input target, so it can reset its state. Schedule
// this call on its window's Handler so it will be on the correct
// thread and outside of our lock.
- Handler vh = mServedView.getHandler();
- if (vh != null) {
+ ViewRootImpl viewRootImpl = mServedView.getViewRootImpl();
+ if (viewRootImpl != null) {
// This will result in a call to reportFinishInputConnection() below.
- vh.sendMessage(vh.obtainMessage(ViewRootImpl.FINISH_INPUT_CONNECTION,
- mServedInputConnection));
+ viewRootImpl.dispatchFinishInputConnection(mServedInputConnection);
}
}
}
@@ -1126,13 +1129,12 @@
}
static void scheduleCheckFocusLocked(View view) {
- Handler vh = view.getHandler();
- if (vh != null && !vh.hasMessages(ViewRootImpl.CHECK_FOCUS)) {
- // This will result in a call to checkFocus() below.
- vh.sendMessage(vh.obtainMessage(ViewRootImpl.CHECK_FOCUS));
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchCheckFocus();
}
}
-
+
/**
* @hide
*/
@@ -1217,12 +1219,13 @@
mService.windowGainedFocus(mClient, rootView.getWindowToken(),
focusedView != null, isTextEditor, softInputMode, first,
windowFlags);
- if (startInput) {
- startInputInner();
- }
} catch (RemoteException e) {
}
}
+
+ if (startInput) {
+ startInputInner();
+ }
}
/** @hide */
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9cd51d0..e0f4f59 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -59,6 +59,7 @@
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 @@
init();
setupPackageListener(context);
setupProxyListener(context);
+ setupTrustStorageListener(context);
updateMultiTouchSupport(context);
if (privateBrowsing) {
@@ -1312,6 +1314,41 @@
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) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 14ecfbd..b6c5612 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -26,6 +26,7 @@
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;
@@ -775,6 +776,11 @@
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;
}
}
};
@@ -1133,6 +1139,9 @@
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
@@ -3082,4 +3091,6 @@
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 5c7e5a3..dd53325 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -142,12 +142,8 @@
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 13798ef..1241c0f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3807,21 +3807,21 @@
}
}
- Handler h = getHandler();
- if (h != null) {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
long eventTime = SystemClock.uptimeMillis();
- h.sendMessage(h.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
+ viewRootImpl.dispatchKeyFromIme(
new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
- | KeyEvent.FLAG_EDITOR_ACTION)));
- h.sendMessage(h.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
+ | KeyEvent.FLAG_EDITOR_ACTION));
+ viewRootImpl.dispatchKeyFromIme(
new KeyEvent(SystemClock.uptimeMillis(), eventTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
- | KeyEvent.FLAG_EDITOR_ACTION)));
+ | KeyEvent.FLAG_EDITOR_ACTION));
}
}
@@ -11407,7 +11407,7 @@
}
@Override
- protected void resolveTextDirection() {
+ public void onResolveTextDirection() {
if (hasPasswordTransformationMethod()) {
mTextDir = TextDirectionHeuristics.LOCALE;
return;
@@ -11416,9 +11416,6 @@
// Always need to resolve layout direction first
final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
- // Then resolve text direction on the parent
- super.resolveTextDirection();
-
// Now, we can select the heuristic
int textDir = getResolvedTextDirection();
switch (textDir) {
@@ -11447,7 +11444,6 @@
* drawables depending on the layout direction.
*
* A call to the super method will be required from the subclasses implementation.
- *
*/
protected void resolveDrawables() {
// No need to resolve twice
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index f3d891d..02dc27b 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -501,7 +501,7 @@
} else {
- ViewRootImpl viewRoot = getOwnerViewRootImpl();
+ ViewRootImpl viewRoot = mOwnerView.getViewRootImpl();
if (viewRoot != null) {
viewRoot.dispatchKey(event);
}
@@ -526,20 +526,6 @@
}
}
- private ViewRootImpl getOwnerViewRootImpl() {
- View rootViewOfOwner = mOwnerView.getRootView();
- if (rootViewOfOwner == null) {
- return null;
- }
-
- ViewParent parentOfRootView = rootViewOfOwner.getParent();
- if (parentOfRootView instanceof ViewRootImpl) {
- return (ViewRootImpl) parentOfRootView;
- } else {
- return null;
- }
- }
-
/**
* @hide The ZoomButtonsController implements the OnTouchListener, but this
* does not need to be shown in its public API.
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
index 2e7810f..26518eb 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -147,7 +147,7 @@
}
private void sendKeyEventsToTarget(int character) {
- Handler handler = mTargetView.getHandler();
+ ViewRootImpl viewRootImpl = mTargetView.getViewRootImpl();
KeyEvent[] events = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD).getEvents(
new char[] { (char) character });
if (events != null) {
@@ -156,22 +156,22 @@
KeyEvent event = events[i];
event = KeyEvent.changeFlags(event, event.getFlags()
| KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
- handler.sendMessage(handler.obtainMessage(ViewRootImpl.DISPATCH_KEY, event));
+ viewRootImpl.dispatchKey(event);
}
}
}
public void sendDownUpKeyEvents(int keyEventCode) {
long eventTime = SystemClock.uptimeMillis();
- Handler handler = mTargetView.getHandler();
- handler.sendMessage(handler.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
+ ViewRootImpl viewRootImpl = mTargetView.getViewRootImpl();
+ viewRootImpl.dispatchKeyFromIme(
new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyEventCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
- handler.sendMessage(handler.obtainMessage(ViewRootImpl.DISPATCH_KEY_FROM_IME,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ viewRootImpl.dispatchKeyFromIme(
new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyEventCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
}
public void onKey(int primaryCode, int[] keyCodes) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 47b3f66..ee41398 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -275,7 +275,7 @@
sp<NativeInputEventReceiver> receiver =
reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
status_t status = receiver->consumeEvents(true /*consumeBatches*/);
- if (status) {
+ if (status && status != DEAD_OBJECT) {
String8 message;
message.appendFormat("Failed to consume batched input event. status=%d", status);
jniThrowRuntimeException(env, message.string());
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 68c919e..a2b1117 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/values/strings.xml b/core/res/res/values/strings.xml
index 7111d00..3c1f50d 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. -->
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6cd07a3..8be1db2 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/data/fonts/DroidNaskh-Bold.ttf b/data/fonts/DroidNaskh-Bold.ttf
index 6b7d4f0..692b796 100644
--- a/data/fonts/DroidNaskh-Bold.ttf
+++ b/data/fonts/DroidNaskh-Bold.ttf
Binary files differ
diff --git a/data/fonts/DroidNaskh-Regular.ttf b/data/fonts/DroidNaskh-Regular.ttf
index d11e1ae..da9a45f 100644
--- a/data/fonts/DroidNaskh-Regular.ttf
+++ b/data/fonts/DroidNaskh-Regular.ttf
Binary files differ
diff --git a/data/fonts/DroidSansFallback.ttf b/data/fonts/DroidSansFallback.ttf
index 2379b2d..cfbc66a 100644
--- a/data/fonts/DroidSansFallback.ttf
+++ b/data/fonts/DroidSansFallback.ttf
Binary files differ
diff --git a/data/fonts/DroidSansFallbackFull.ttf b/data/fonts/DroidSansFallbackFull.ttf
index 41b015d..0cacabe 100644
--- a/data/fonts/DroidSansFallbackFull.ttf
+++ b/data/fonts/DroidSansFallbackFull.ttf
Binary files differ
diff --git a/include/common_time/ICommonClock.h b/include/common_time/ICommonClock.h
new file mode 100644
index 0000000..d7073f1
--- /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 0000000..497b666
--- /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 0000000..8c4d5c0
--- /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 0000000..845d1c21
--- /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 ac7f6cf..9f2bd3a 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -446,7 +446,7 @@
*/
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 @@
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 433ce7c..7a2ada0 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -55,6 +55,7 @@
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 e4772a1..77f3e21 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 @@
*/
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 77c82b2..23a3e49 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -46,6 +46,9 @@
// 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 db6388a..0fe7bd8 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -124,7 +124,7 @@
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 @@
// 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 0000000..526f17b
--- /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 0000000..28b43ac
--- /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 0000000..8eb37cb
--- /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 0000000..8d8556c
--- /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 0000000..a7c61fc
--- /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 0000000..6539171
--- /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 0000000..ce79d0d
--- /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/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index bcf7b89..91d0addd 100755
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -84,6 +84,7 @@
// 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 @@
* 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 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 @@
/**
* 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 @@
}
/**
+ * @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 @@
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 @@
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 @@
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 ecd4d07..f015afb 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 @@
#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 @@
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 @@
~visualizerJniStorage() {
}
-
};
@@ -93,6 +131,26 @@
// ----------------------------------------------------------------------------
+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 @@
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 @@
}
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 @@
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 @@
samplingrate,
0,
jArray);
- env->DeleteLocalRef(jArray);
}
}
@@ -220,6 +285,23 @@
}
+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 @@
// 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 @@
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 0000000..54fd9ec
--- /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 0000000..72fe43b
--- /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 0000000..f5a6529
--- /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 0000000..9dd79fd
--- /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 0000000..7a1b6e3
--- /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 0000000..d2b3386
--- /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 0000000..0d8b31f
--- /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 0000000..1e4c784
--- /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 0000000..3f6e0e9
--- /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 0000000..833803e
--- /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 0000000..a79a989
--- /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 0000000..64cf5dc1
--- /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 0000000..d991ea7
--- /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 0000000..74206c4
--- /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 0000000..b8e6960
--- /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 0000000..e53b0fd
--- /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 f9f997f..19b7e32 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -202,7 +202,7 @@
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 @@
{
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::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::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::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::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 @@
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 aead9a1..74c97ed 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -80,7 +80,9 @@
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 @@
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 @@
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 @@
{
int afSamplingRate;
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) {
return NO_INIT;
}
@@ -553,6 +563,10 @@
uint32_t AudioTrack::getSampleRate() const
{
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
AutoMutex lock(mLock);
return mCblk->sampleRate;
}
@@ -578,6 +592,10 @@
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::setPosition(uint32_t position)
{
+ if (mIsTimed) return INVALID_OPERATION;
+
AutoMutex lock(mLock);
if (!stopped_l()) return INVALID_OPERATION;
@@ -791,6 +811,7 @@
((uint16_t)flags) << 16,
sharedBuffer,
output,
+ mIsTimed,
&mSessionId,
&status);
@@ -957,6 +978,7 @@
{
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 @@
// -------------------------------------------------------------------------
+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 4507e5d..ebadbfa 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -90,6 +90,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
audio_io_handle_t output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -105,6 +106,7 @@
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 @@
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 a7958de..28ebbbfc 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -35,7 +35,10 @@
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 @@
}
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 @@
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 d469e28..5d40cc8 100644
--- a/media/libmedia/IEffect.cpp
+++ b/media/libmedia/IEffect.cpp
@@ -83,8 +83,15 @@
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 13b64e9..70f8c0c 100644
--- a/media/libmedia/Visualizer.cpp
+++ b/media/libmedia/Visualizer.cpp
@@ -168,7 +168,7 @@
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 a3e2517..e521648 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -29,7 +29,8 @@
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 4df7f3d..764eddc 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 @@
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 @@
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::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/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index 157405a..22fa752 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -7,6 +7,7 @@
AudioMixer.cpp.arm \
AudioResampler.cpp.arm \
AudioPolicyService.cpp \
+ AudioBufferProvider.cpp \
ServiceUtilities.cpp
# AudioResamplerSinc.cpp.arm
# AudioResamplerCubic.cpp.arm
@@ -17,6 +18,7 @@
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 0000000..678fd58
--- /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 81c5c39..62ad6bd 100644
--- a/services/audioflinger/AudioBufferProvider.h
+++ b/services/audioflinger/AudioBufferProvider.h
@@ -38,8 +38,15 @@
};
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 0248687..29d63de 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
audio_io_handle_t output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -435,7 +494,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status)
{
sp<Track> track;
@@ -1535,9 +1628,14 @@
}
}
- 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 @@
}
}
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
sleepTime = idleSleepTime;
sleepTimeShift = 0;
continue;
@@ -1957,8 +2055,21 @@
}
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 @@
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 @@
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 @@
// 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 @@
}
}
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
sleepTime = idleSleepTime;
continue;
}
@@ -3055,7 +3167,7 @@
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 @@
// 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 @@
(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 @@
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 @@
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 @@
}
}
-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 @@
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 @@
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 @@
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 @@
}
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 @@
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 aa0b8f8..9396a0b 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 @@
// ----------------------------------------------------------------------------
-static const nsecs_t kStandbyTimeInNsecs = seconds(3);
+static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
class AudioFlinger :
public BinderService<AudioFlinger>,
@@ -78,6 +81,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
audio_io_handle_t output,
+ bool isTimed,
int *sessionId,
status_t *status);
@@ -102,6 +106,7 @@
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 @@
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 @@
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 @@
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 @@
int16_t *mainBuffer() const { return mMainBuffer; }
int auxEffectId() const { return mAuxEffectId; }
-
protected:
friend class ThreadBase;
friend class TrackHandle;
@@ -620,7 +634,11 @@
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 @@
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 @@
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 @@
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status);
AudioStreamOut* getOutput() const;
@@ -920,6 +1014,12 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 cb7678b..c399691 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 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 @@
t->sampleRate = mSampleRate;
t->mainBuffer = NULL;
t->auxBuffer = NULL;
+ t->localTimeFreq = lc.getLocalFreq();
t++;
}
}
@@ -289,6 +295,7 @@
if (resampler == NULL) {
resampler = AudioResampler::create(
format, channelCount, devSampleRate);
+ resampler->setLocalTimeFreq(localTimeFreq);
}
return true;
}
@@ -333,13 +340,13 @@
-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 @@
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 @@
}
// 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 @@
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 @@
}
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
}
// 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 @@
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 @@
// 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 @@
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 @@
}
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 @@
}
#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 c956918..f442671 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -79,7 +79,7 @@
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 @@
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 @@
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 @@
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 9486b9c..398ba0b 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -122,7 +122,8 @@
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 @@
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 @@
// 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 @@
// 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 c23016e..9deb796 100644
--- a/services/audioflinger/AudioResampler.h
+++ b/services/audioflinger/AudioResampler.h
@@ -49,6 +49,10 @@
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 @@
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 @@
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 c0e760e..18e59e9 100644
--- a/services/audioflinger/AudioResamplerCubic.cpp
+++ b/services/audioflinger/AudioResamplerCubic.cpp
@@ -65,7 +65,7 @@
// 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 @@
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 @@
// 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 @@
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 @@
// ----------------------------------------------------------------------------
}
; // namespace android
-
diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp
index 7a27b81..d373c08 100644
--- a/services/audioflinger/AudioResamplerSinc.cpp
+++ b/services/audioflinger/AudioResamplerSinc.cpp
@@ -203,7 +203,8 @@
// 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 @@
// ----------------------------------------------------------------------------
}; // namespace android
-
diff --git a/services/common_time/Android.mk b/services/common_time/Android.mk
new file mode 100644
index 0000000..aabe572
--- /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 0000000..be054fb
--- /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 0000000..5c35c38
--- /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 0000000..c9eb388
--- /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 0000000..b786fdc
--- /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 0000000..9ca6f35
--- /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 0000000..a65e398
--- /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 0000000..9585618
--- /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 0000000..8283c24
--- /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 0000000..4fed0d0
--- /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 0000000..1b55202
--- /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 0000000..fb8c261
--- /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 0000000..9833c37
--- /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 0000000..57ba8a2
--- /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 0000000..c8e6053
--- /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 0000000..6ebe829
--- /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 0000000..49eb30a
--- /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 41ede2e..9c408c4 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -463,7 +463,7 @@
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 @@
// 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 @@
+ " safe mode: " + provider);
}
- Binder.restoreCallingIdentity(ident);
-
id.provider = p;
p.instances.add(id);
int instancesSize = p.instances.size();
@@ -1066,7 +1064,7 @@
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 @@
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 @@
}
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 0000000..9a25d2e
--- /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 0dbc7c3..c9b5997 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -132,6 +132,7 @@
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
NetworkTimeUpdateService networkTimeUpdater = null;
+ CommonTimeManagementService commonTimeMgmtService = null;
// Critical services...
try {
@@ -575,6 +576,14 @@
} 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 @@
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 @@
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 9d5caae..8a5e7fc 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -138,7 +138,6 @@
import java.lang.IllegalStateException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -2752,7 +2751,13 @@
}
// Just in case...
- mMainStack.appDiedLocked(app);
+ if (mMainStack.mPausingActivity != null && mMainStack.mPausingActivity.app == app) {
+ if (DEBUG_PAUSE) Slog.v(TAG, "App died while pausing: " +mMainStack.mPausingActivity);
+ mMainStack.mPausingActivity = null;
+ }
+ if (mMainStack.mLastPausedActivity != null && mMainStack.mLastPausedActivity.app == app) {
+ mMainStack.mLastPausedActivity = null;
+ }
// Remove this application's activities from active lists.
mMainStack.removeHistoryRecordsForAppLocked(app);
@@ -5754,7 +5759,7 @@
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 +5831,8 @@
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 +5848,13 @@
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 +5870,13 @@
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 +5920,7 @@
// 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 +5954,7 @@
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 +6012,7 @@
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 +6082,9 @@
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}
+
mProviderMap.putProviderByName(name, cpr);
- incProviderCount(r, cpr);
+ incProviderCount(r, cpr, token);
}
}
@@ -6116,12 +6124,17 @@
throw new SecurityException(msg);
}
- ContentProviderHolder contentProvider = getContentProviderImpl(caller, name);
- return contentProvider;
+ return getContentProviderImpl(caller, name, null);
}
- private ContentProviderHolder getContentProviderExternal(String name) {
- return getContentProviderImpl(null, name);
+ 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 getContentProviderExternalUnchecked(String name,IBinder token) {
+ return getContentProviderImpl(null, name, token);
}
/**
@@ -6157,14 +6170,20 @@
+ 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 +6197,18 @@
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 +6312,7 @@
ContentProviderHolder holder = null;
try {
- holder = getContentProviderExternal(name);
+ holder = getContentProviderExternalUnchecked(name, null);
if (holder != null) {
return holder.provider.getType(uri);
}
@@ -6295,7 +6321,7 @@
return null;
} finally {
if (holder != null) {
- removeContentProviderExternal(name);
+ removeContentProviderExternalUnchecked(name, null);
}
Binder.restoreCallingIdentity(ident);
}
@@ -6400,7 +6426,7 @@
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 +6448,7 @@
}
// 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 + "'");
}
@@ -6467,7 +6493,7 @@
mMainStack.stopIfSleepingLocked();
final long endTime = System.currentTimeMillis() + timeout;
while (mMainStack.mResumedActivity != null
- || mMainStack.mPausingActivities.size() > 0) {
+ || mMainStack.mPausingActivity != null) {
long delay = endTime - System.currentTimeMillis();
if (delay <= 0) {
Slog.w(TAG, "Activity manager shutdown timed out");
@@ -8274,13 +8300,8 @@
}
pw.println(" ");
- if (mMainStack.mPausingActivities.size() > 0) {
- pw.println(" mPausingActivities: " + Arrays.toString(
- mMainStack.mPausingActivities.toArray()));
- }
- if (mMainStack.mInputPausedActivities.size() > 0) {
- pw.println(" mInputPausedActivities: " + Arrays.toString(
- mMainStack.mInputPausedActivities.toArray()));
+ if (mMainStack.mPausingActivity != null) {
+ pw.println(" mPausingActivity: " + mMainStack.mPausingActivity);
}
pw.println(" mResumedActivity: " + mMainStack.mResumedActivity);
pw.println(" mFocusedActivity: " + mFocusedActivity);
@@ -10253,7 +10274,7 @@
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 @@
// 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;
@@ -13855,13 +13876,7 @@
private final ActivityRecord resumedAppLocked() {
ActivityRecord resumedActivity = mMainStack.mResumedActivity;
if (resumedActivity == null || resumedActivity.app == null) {
- for (int i=mMainStack.mPausingActivities.size()-1; i>=0; i--) {
- ActivityRecord r = mMainStack.mPausingActivities.get(i);
- if (r.app != null) {
- resumedActivity = r;
- break;
- }
- }
+ resumedActivity = mMainStack.mPausingActivity;
if (resumedActivity == null || resumedActivity.app == null) {
resumedActivity = mMainStack.topRunningActivityLocked(null);
}
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 977ee84..cdab6c6 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -602,7 +602,6 @@
public void windowsDrawn() {
synchronized(service) {
- stack.reportActivityDrawnLocked(this);
if (launchTime != 0) {
final long curTime = SystemClock.uptimeMillis();
final long thisTime = curTime - launchTime;
@@ -691,9 +690,7 @@
// Hmmm, who might we be waiting for?
r = stack.mResumedActivity;
if (r == null) {
- if (stack.mPausingActivities.size() > 0) {
- r = stack.mPausingActivities.get(stack.mPausingActivities.size()-1);
- }
+ r = stack.mPausingActivity;
}
// Both of those null? Fall back to 'this' again
if (r == null) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index e8c8275..7b8bc26 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -227,13 +227,7 @@
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*/
- final ArrayList<ActivityRecord> mPausingActivities = new ArrayList<ActivityRecord>();
-
- /**
- * These activities currently have their input paused, as they want for
- * the next top activity to have its windows visible.
- */
- final ArrayList<ActivityRecord> mInputPausedActivities = new ArrayList<ActivityRecord>();
+ ActivityRecord mPausingActivity = null;
/**
* This is the last activity that we put into the paused state. This is
@@ -813,9 +807,9 @@
startPausingLocked(false, true);
return;
}
- if (mPausingActivities.size() > 0) {
+ if (mPausingActivity != null) {
// Still waiting for something to pause; can't sleep yet.
- if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivities);
+ if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity);
return;
}
@@ -878,6 +872,11 @@
}
private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {
+ if (mPausingActivity != null) {
+ RuntimeException e = new RuntimeException();
+ Slog.e(TAG, "Trying to pause when pause is already pending for "
+ + mPausingActivity, e);
+ }
ActivityRecord prev = mResumedActivity;
if (prev == null) {
RuntimeException e = new RuntimeException();
@@ -885,25 +884,19 @@
resumeTopActivityLocked(null);
return;
}
- if (mPausingActivities.contains(prev)) {
- RuntimeException e = new RuntimeException();
- Slog.e(TAG, "Trying to pause when pause when already pausing " + prev, e);
- }
if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSING: " + prev);
else if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev);
mResumedActivity = null;
- mPausingActivities.add(prev);
+ mPausingActivity = prev;
mLastPausedActivity = prev;
prev.state = ActivityState.PAUSING;
prev.task.touchActiveTime();
prev.updateThumbnail(screenshotActivities(prev), null);
mService.updateCpuStats();
- ActivityRecord pausing;
if (prev.app != null && prev.app.thread != null) {
if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev);
- pausing = prev;
try {
EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY,
System.identityHashCode(prev),
@@ -916,14 +909,12 @@
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
- mPausingActivities.remove(prev);
+ mPausingActivity = null;
mLastPausedActivity = null;
- pausing = null;
}
} else {
- mPausingActivities.remove(prev);
+ mPausingActivity = null;
mLastPausedActivity = null;
- pausing = null;
}
// If we are not going to sleep, we want to ensure the device is
@@ -937,28 +928,18 @@
}
}
- if (pausing != null) {
+
+ if (mPausingActivity != null) {
// Have the window manager pause its key dispatching until the new
// activity has started. If we're pausing the activity just because
// the screen is being turned off and the UI is sleeping, don't interrupt
// key dispatch; the same activity will pick it up again on wakeup.
if (!uiSleeping) {
- pausing.pauseKeyDispatchingLocked();
- mInputPausedActivities.add(prev);
+ prev.pauseKeyDispatchingLocked();
} else {
if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off");
}
- if (pausing.configDestroy) {
- // The previous is being paused because the configuration
- // is changing, which means it is actually stopping...
- // To juggle the fact that we are also starting a new
- // instance right now, we need to first completely stop
- // the current instance before starting the new one.
- if (DEBUG_PAUSE) Slog.v(TAG, "Destroying at pause: " + prev);
- destroyActivityLocked(pausing, true, false, "pause-config");
- }
-
// Schedule a pause timeout in case the app doesn't respond.
// We don't give it much time because this directly impacts the
// responsiveness seen by the user.
@@ -970,10 +951,7 @@
// This activity failed to schedule the
// pause, so just treat it as being paused now.
if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next.");
- }
-
- if (!mService.isSleeping()) {
- resumeTopActivityLocked(pausing);
+ resumeTopActivityLocked(null);
}
}
@@ -988,17 +966,16 @@
if (index >= 0) {
r = mHistory.get(index);
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
- if (mPausingActivities.contains(r)) {
+ if (mPausingActivity == r) {
if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r
+ (timeout ? " (due to timeout)" : " (pause complete)"));
r.state = ActivityState.PAUSED;
- completePauseLocked(r);
+ completePauseLocked();
} else {
- ActivityRecord old = mPausingActivities.size() > 0
- ? mPausingActivities.get(0) : null;
EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE,
System.identityHashCode(r), r.shortComponentName,
- old != null ? old.shortComponentName : "(none)");
+ mPausingActivity != null
+ ? mPausingActivity.shortComponentName : "(none)");
}
}
}
@@ -1024,14 +1001,8 @@
ProcessRecord fgApp = null;
if (mResumedActivity != null) {
fgApp = mResumedActivity.app;
- } else {
- for (int i=mPausingActivities.size()-1; i>=0; i--) {
- ActivityRecord pausing = mPausingActivities.get(i);
- if (pausing.app == r.app) {
- fgApp = pausing.app;
- break;
- }
- }
+ } else if (mPausingActivity != null) {
+ fgApp = mPausingActivity.app;
}
if (r.app != null && fgApp != null && r.app != fgApp
&& r.lastVisibleTime > mService.mPreviousProcessVisibleTime
@@ -1043,49 +1014,58 @@
}
}
- private final void completePauseLocked(ActivityRecord prev) {
+ private final void completePauseLocked() {
+ ActivityRecord prev = mPausingActivity;
if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev);
- if (prev.finishing) {
- if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev);
- prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE);
- } else if (prev.app != null) {
- if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev);
- if (prev.waitingVisible) {
- prev.waitingVisible = false;
- mWaitingVisibleActivities.remove(prev);
- if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(
- TAG, "Complete pause, no longer waiting: " + prev);
- }
- if (prev.configDestroy) {
- // The previous is being paused because the configuration
- // is changing, which means it is actually stopping...
- // To juggle the fact that we are also starting a new
- // instance right now, we need to first completely stop
- // the current instance before starting the new one.
- if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev);
- destroyActivityLocked(prev, true, false, "pause-config");
- } else {
- mStoppingActivities.add(prev);
- if (mStoppingActivities.size() > 3) {
- // If we already have a few activities waiting to stop,
- // then give up on things going idle and start clearing
- // them out.
- if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle");
- scheduleIdleLocked();
- } else {
- checkReadyForSleepLocked();
+ if (prev != null) {
+ if (prev.finishing) {
+ if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev);
+ prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE);
+ } else if (prev.app != null) {
+ if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev);
+ if (prev.waitingVisible) {
+ prev.waitingVisible = false;
+ mWaitingVisibleActivities.remove(prev);
+ if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(
+ TAG, "Complete pause, no longer waiting: " + prev);
}
+ if (prev.configDestroy) {
+ // The previous is being paused because the configuration
+ // is changing, which means it is actually stopping...
+ // To juggle the fact that we are also starting a new
+ // instance right now, we need to first completely stop
+ // the current instance before starting the new one.
+ if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev);
+ destroyActivityLocked(prev, true, false, "pause-config");
+ } else {
+ mStoppingActivities.add(prev);
+ if (mStoppingActivities.size() > 3) {
+ // If we already have a few activities waiting to stop,
+ // then give up on things going idle and start clearing
+ // them out.
+ if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle");
+ scheduleIdleLocked();
+ } else {
+ checkReadyForSleepLocked();
+ }
+ }
+ } else {
+ if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev);
+ prev = null;
}
- } else {
- if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev);
- prev = null;
+ mPausingActivity = null;
}
- mPausingActivities.remove(prev);
- if (mService.isSleeping()) {
+ if (!mService.isSleeping()) {
+ resumeTopActivityLocked(prev);
+ } else {
checkReadyForSleepLocked();
}
+
+ if (prev != null) {
+ prev.resumeKeyDispatchingLocked();
+ }
if (prev.app != null && prev.cpuTimeAtResume > 0
&& mService.mBatteryStatsService.isOnBattery()) {
@@ -1142,9 +1122,7 @@
if (mMainStack) {
mService.setFocusedActivityLocked(next);
}
- if (mInputPausedActivities.remove(next)) {
- next.resumeKeyDispatchingLocked();
- }
+ next.resumeKeyDispatchingLocked();
ensureActivitiesVisibleLocked(null, 0);
mService.mWindowManager.executeAppTransition();
mNoAnimActivities.clear();
@@ -1374,6 +1352,13 @@
if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next);
+ // If we are currently pausing an activity, then don't do anything
+ // until that is done.
+ if (mPausingActivity != null) {
+ if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: pausing=" + mPausingActivity);
+ return false;
+ }
+
// Okay we are now going to start a switch, to 'next'. We may first
// have to pause the current activity, but this is an important point
// where we have decided to go to 'next' so keep track of that.
@@ -2455,7 +2440,7 @@
err = startActivityUncheckedLocked(r, sourceRecord,
grantedUriPermissions, grantedMode, onlyIfNeeded, true);
- if (mDismissKeyguardOnNextActivity && mPausingActivities.size() == 0) {
+ if (mDismissKeyguardOnNextActivity && mPausingActivity == null) {
// Someone asked to have the keyguard dismissed on the next
// activity start, but we are not actually doing an activity
// switch... just dismiss the keyguard now, because we
@@ -3126,18 +3111,7 @@
}
mService.notifyAll();
}
-
- void reportActivityDrawnLocked(ActivityRecord r) {
- if (mResumedActivity == r) {
- // Once the resumed activity has been drawn, we can stop
- // pausing input on all other activities.
- for (int i=mInputPausedActivities.size()-1; i>=0; i--) {
- mInputPausedActivities.get(i).resumeKeyDispatchingLocked();
- }
- mInputPausedActivities.clear();
- }
- }
-
+
void reportActivityVisibleLocked(ActivityRecord r) {
for (int i=mWaitingActivityVisible.size()-1; i>=0; i--) {
WaitResult w = mWaitingActivityVisible.get(i);
@@ -3196,9 +3170,7 @@
mService.setFocusedActivityLocked(topRunningActivityLocked(null));
}
}
- if (mInputPausedActivities.remove(r)) {
- r.resumeKeyDispatchingLocked();
- }
+ r.resumeKeyDispatchingLocked();
try {
r.stopped = false;
if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r
@@ -3524,7 +3496,7 @@
// Tell window manager to prepare for this one to be removed.
mService.mWindowManager.setAppVisibility(r.appToken, false);
- if (!mPausingActivities.contains(r)) {
+ if (mPausingActivity == null) {
if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
startPausingLocked(false, false);
@@ -3608,20 +3580,6 @@
return r;
}
- final void appDiedLocked(ProcessRecord app) {
- for (int i=mPausingActivities.size()-1; i>=0; i--) {
- ActivityRecord r = mPausingActivities.get(i);
- if (r.app == app) {
- if (DEBUG_PAUSE) Slog.v(TAG, "App died while pausing: " + r);
- mPausingActivities.remove(i);
- mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
- }
- }
- if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
- mLastPausedActivity = null;
- }
- }
-
/**
* Perform the common clean-up of an activity record. This is called both
* as part of destroyActivityLocked() (when destroying the client-side
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index 38355537..f338cfc 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.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 @@
appInfo = cpr.appInfo;
name = cpr.name;
noReleaseNeeded = cpr.noReleaseNeeded;
+ service = cpr.service;
}
public boolean canRunHere(ProcessRecord app) {
@@ -57,6 +69,57 @@
&& (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 @@
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 @@
}
}
+ @Override
public String toString() {
if (stringName != null) {
return stringName;
@@ -96,4 +161,35 @@
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/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 674c0b7..5fab2bb 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -267,6 +267,12 @@
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/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
index 96de51c..97d9969 100644
--- a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
+++ b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
@@ -29,7 +29,7 @@
public static void setAttachInfo(View view) {
AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
- new Handler(), null);
+ new ViewRootImpl(view.getContext()), new Handler(), null);
info.mHasWindowFocus = true;
info.mWindowVisibility = View.VISIBLE;
info.mInTouchMode = false; // this is so that we can display selections.