diff options
230 files changed, 17261 insertions, 3006 deletions
diff --git a/api/current.txt b/api/current.txt index 97be454b50f4..6c7833b40e35 100644 --- a/api/current.txt +++ b/api/current.txt @@ -614,8 +614,10 @@ package android { field public static final int layout_height = 16842997; // 0x10100f5 field public static final int layout_margin = 16842998; // 0x10100f6 field public static final int layout_marginBottom = 16843002; // 0x10100fa + field public static final int layout_marginEnd = 16843692; // 0x10103ac field public static final int layout_marginLeft = 16842999; // 0x10100f7 field public static final int layout_marginRight = 16843001; // 0x10100f9 + field public static final int layout_marginStart = 16843691; // 0x10103ab field public static final int layout_marginTop = 16843000; // 0x10100f8 field public static final int layout_row = 16843643; // 0x101037b field public static final int layout_rowSpan = 16843644; // 0x101037c @@ -711,8 +713,10 @@ package android { field public static final int packageNames = 16843649; // 0x1010381 field public static final int padding = 16842965; // 0x10100d5 field public static final int paddingBottom = 16842969; // 0x10100d9 + field public static final int paddingEnd = 16843690; // 0x10103aa field public static final int paddingLeft = 16842966; // 0x10100d6 field public static final int paddingRight = 16842968; // 0x10100d8 + field public static final int paddingStart = 16843689; // 0x10103a9 field public static final int paddingTop = 16842967; // 0x10100d7 field public static final int panelBackground = 16842846; // 0x101005e field public static final int panelColorBackground = 16842849; // 0x1010061 @@ -23179,8 +23183,10 @@ package android.view { method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method public int getOverScrollMode(); method public int getPaddingBottom(); + method public int getPaddingEnd(); method public int getPaddingLeft(); method public int getPaddingRight(); + method public int getPaddingStart(); method public int getPaddingTop(); method public final android.view.ViewParent getParent(); method public float getPivotX(); @@ -23256,6 +23262,7 @@ package android.view { method public boolean isLongClickable(); method public boolean isOpaque(); method protected boolean isPaddingOffsetRequired(); + method public boolean isPaddingRelative(); method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); @@ -23303,6 +23310,7 @@ package android.view { method protected void onMeasure(int, int); method protected void onOverScrolled(int, int, boolean, boolean); method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); + method public void onResolvePadding(int); method protected void onRestoreInstanceState(android.os.Parcelable); method protected android.os.Parcelable onSaveInstanceState(); method protected void onScrollChanged(int, int, int, int); @@ -23338,6 +23346,7 @@ package android.view { method public boolean requestRectangleOnScreen(android.graphics.Rect); method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean); method protected void resetResolvedTextDirection(); + method public void resolvePadding(); method public static int resolveSize(int, int); method public static int resolveSizeAndState(int, int, int); method protected void resolveTextDirection(); @@ -23399,6 +23408,7 @@ package android.view { method public void setOnTouchListener(android.view.View.OnTouchListener); method public void setOverScrollMode(int); method public void setPadding(int, int, int, int); + method public void setPaddingRelative(int, int, int, int); method public void setPivotX(float); method public void setPivotY(float); method public void setPressed(boolean); @@ -23843,10 +23853,15 @@ package android.view { ctor public ViewGroup.MarginLayoutParams(int, int); ctor public ViewGroup.MarginLayoutParams(android.view.ViewGroup.MarginLayoutParams); ctor public ViewGroup.MarginLayoutParams(android.view.ViewGroup.LayoutParams); + method public int getMarginEnd(); + method public int getMarginStart(); + method public boolean isMarginRelative(); method public void setMargins(int, int, int, int); field public int bottomMargin; + field public int endMargin; field public int leftMargin; field public int rightMargin; + field public int startMargin; field public int topMargin; } diff --git a/cmds/content/Android.mk b/cmds/content/Android.mk new file mode 100644 index 000000000000..a3d83cf8848a --- /dev/null +++ b/cmds/content/Android.mk @@ -0,0 +1,35 @@ +# Copyright 2012 The Android Open Source Project + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_MODULE := content + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_JAVA_LIBRARY) + +include $(CLEAR_VARS) +ALL_PREBUILT += $(TARGET_OUT)/bin/content +$(TARGET_OUT)/bin/content : $(LOCAL_PATH)/content | $(ACP) + $(transform-prebuilt-to-target) + +NOTICE_FILE := NOTICE +files_noticed := bin/content + +# Generate rules for a single file. The argument is the file path relative to +# the installation root +define make-notice-file + +$(TARGET_OUT_NOTICE_FILES)/src/$(1).txt: $(LOCAL_PATH)/$(NOTICE_FILE) + @echo Notice file: $$< -- $$@ + @mkdir -p $$(dir $$@) + @cat $$< >> $$@ + +$(TARGET_OUT_NOTICE_FILES)/hash-timestamp: $(TARGET_OUT_NOTICE_FILES)/src/$(1).txt + +endef + +$(foreach file,$(files_noticed),$(eval $(call make-notice-file,$(file)))) diff --git a/cmds/content/MODULE_LICENSE_APACHE2 b/cmds/content/MODULE_LICENSE_APACHE2 new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/cmds/content/MODULE_LICENSE_APACHE2 diff --git a/cmds/content/NOTICE b/cmds/content/NOTICE new file mode 100644 index 000000000000..33ff96160b77 --- /dev/null +++ b/cmds/content/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2012, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/content/content b/cmds/content/content new file mode 100755 index 000000000000..a8e056d04d69 --- /dev/null +++ b/cmds/content/content @@ -0,0 +1,5 @@ +# Script to start "content" on the device, which has a very rudimentary shell. +base=/system +export CLASSPATH=$base/framework/content.jar +exec app_process $base/bin com.android.commands.content.Content "$@" + diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java new file mode 100644 index 000000000000..1dcba70405fc --- /dev/null +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -0,0 +1,442 @@ +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package com.android.commands.content; + +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.IActivityManager.ContentProviderHolder; +import android.content.ContentValues; +import android.content.IContentProvider; +import android.database.Cursor; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.text.TextUtils; + +/** + * This class is a command line utility for manipulating content. A client + * can insert, update, and remove records in a content provider. For example, + * some settings may be configured before running the CTS tests, etc. + * <p> + * Examples: + * <ul> + * <li> + * # Add "new_setting" secure setting with value "new_value".</br> + * adb shell content insert --uri content://settings/secure --bind name:s:new_setting + * --bind value:s:new_value + * </li> + * <li> + * # Change "new_setting" secure setting to "newer_value" (You have to escape single quotes in + * the where clause).</br> + * adb shell content update --uri content://settings/secure --bind value:s:newer_value + * --where "name=\'new_setting\'" + * </li> + * <li> + * # Remove "new_setting" secure setting.</br> + * adb shell content delete --uri content://settings/secure --where "name=\'new_setting\'" + * </li> + * <li> + * # Query \"name\" and \"value\" columns from secure settings where \"name\" is equal to" + * \"new_setting\" and sort the result by name in ascending order.\n" + * adb shell content query --uri content://settings/secure --projection name:value + * --where "name=\'new_setting\'" --sort \"name ASC\" + * </li> + * </ul> + * </p> + */ +public class Content { + + private static final String USAGE = + "usage: adb shell content [subcommand] [options]\n" + + "\n" + + "usage: adb shell content insert --uri <URI> --bind <BINDING> [--bind <BINDING>...]\n" + + " <URI> a content provider URI.\n" + + " <BINDING> binds a typed value to a column and is formatted:\n" + + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n" + + " <TYPE> specifies data type such as:\n" + + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n" + + " Example:\n" + + " # Add \"new_setting\" secure setting with value \"new_value\".\n" + + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting" + + " --bind value:s:new_value\n" + + "\n" + + "usage: adb shell content update --uri <URI> [--where <WHERE>]\n" + + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes" + + " - see example below).\n" + + " Example:\n" + + " # Change \"new_setting\" secure setting to \"newer_value\".\n" + + " adb shell content update --uri content://settings/secure --bind" + + " value:s:newer_value --where \"name=\'new_setting\'\"\n" + + "\n" + + "usage: adb shell content delete --uri <URI> --bind <BINDING>" + + " [--bind <BINDING>...] [--where <WHERE>]\n" + + " Example:\n" + + " # Remove \"new_setting\" secure setting.\n" + + " adb shell content delete --uri content://settings/secure " + + "--where \"name=\'new_setting\'\"\n" + + "\n" + + "usage: adb shell content query --uri <URI> [--projection <PROJECTION>]" + + " [--where <WHERE>] [--sort <SORT_ORDER>]\n" + + " <PROJECTION> is a list of colon separated column names and is formatted:\n" + + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n" + + " <SORT_OREDER> is the order in which rows in the result should be sorted.\n" + + " Example:\n" + + " # Select \"name\" and \"value\" columns from secure settings where \"name\" is " + + "equal to \"new_setting\" and sort the result by name in ascending order.\n" + + " adb shell content query --uri content://settings/secure --projection name:value" + + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n" + + "\n"; + + private static class Parser { + private static final String ARGUMENT_INSERT = "insert"; + private static final String ARGUMENT_DELETE = "delete"; + private static final String ARGUMENT_UPDATE = "update"; + private static final String ARGUMENT_QUERY = "query"; + private static final String ARGUMENT_WHERE = "--where"; + private static final String ARGUMENT_BIND = "--bind"; + private static final String ARGUMENT_URI = "--uri"; + private static final String ARGUMENT_PROJECTION = "--projection"; + private static final String ARGUMENT_SORT = "--sort"; + private static final String TYPE_BOOLEAN = "b"; + private static final String TYPE_STRING = "s"; + private static final String TYPE_INTEGER = "i"; + private static final String TYPE_LONG = "l"; + private static final String TYPE_FLOAT = "f"; + private static final String TYPE_DOUBLE = "d"; + private static final String COLON = ":"; + private static final String ARGUMENT_PREFIX = "--"; + + private final Tokenizer mTokenizer; + + public Parser(String[] args) { + mTokenizer = new Tokenizer(args); + } + + public Command parseCommand() { + try { + String operation = mTokenizer.nextArg(); + if (ARGUMENT_INSERT.equals(operation)) { + return parseInsertCommand(); + } else if (ARGUMENT_DELETE.equals(operation)) { + return parseDeleteCommand(); + } else if (ARGUMENT_UPDATE.equals(operation)) { + return parseUpdateCommand(); + } else if (ARGUMENT_QUERY.equals(operation)) { + return parseQueryCommand(); + } else { + throw new IllegalArgumentException("Unsupported operation: " + operation); + } + } catch (IllegalArgumentException iae) { + System.out.println(USAGE); + System.out.println("[ERROR] " + iae.getMessage()); + return null; + } + } + + private InsertCommand parseInsertCommand() { + Uri uri = null; + ContentValues values = new ContentValues(); + for (String argument; (argument = mTokenizer.nextArg()) != null;) { + if (ARGUMENT_URI.equals(argument)) { + uri = Uri.parse(argumentValueRequired(argument)); + } else if (ARGUMENT_BIND.equals(argument)) { + parseBindValue(values); + } else { + throw new IllegalArgumentException("Unsupported argument: " + argument); + } + } + if (uri == null) { + throw new IllegalArgumentException("Content provider URI not specified." + + " Did you specify --uri argument?"); + } + if (values.size() == 0) { + throw new IllegalArgumentException("Bindings not specified." + + " Did you specify --bind argument(s)?"); + } + return new InsertCommand(uri, values); + } + + private DeleteCommand parseDeleteCommand() { + Uri uri = null; + String where = null; + for (String argument; (argument = mTokenizer.nextArg())!= null;) { + if (ARGUMENT_URI.equals(argument)) { + uri = Uri.parse(argumentValueRequired(argument)); + } else if (ARGUMENT_WHERE.equals(argument)) { + where = argumentValueRequired(argument); + } else { + throw new IllegalArgumentException("Unsupported argument: " + argument); + } + } + if (uri == null) { + throw new IllegalArgumentException("Content provider URI not specified." + + " Did you specify --uri argument?"); + } + return new DeleteCommand(uri, where); + } + + private UpdateCommand parseUpdateCommand() { + Uri uri = null; + String where = null; + ContentValues values = new ContentValues(); + for (String argument; (argument = mTokenizer.nextArg())!= null;) { + if (ARGUMENT_URI.equals(argument)) { + uri = Uri.parse(argumentValueRequired(argument)); + } else if (ARGUMENT_WHERE.equals(argument)) { + where = argumentValueRequired(argument); + } else if (ARGUMENT_BIND.equals(argument)) { + parseBindValue(values); + } else { + throw new IllegalArgumentException("Unsupported argument: " + argument); + } + } + if (uri == null) { + throw new IllegalArgumentException("Content provider URI not specified." + + " Did you specify --uri argument?"); + } + if (values.size() == 0) { + throw new IllegalArgumentException("Bindings not specified." + + " Did you specify --bind argument(s)?"); + } + return new UpdateCommand(uri, values, where); + } + + public QueryCommand parseQueryCommand() { + Uri uri = null; + String[] projection = null; + String sort = null; + String where = null; + for (String argument; (argument = mTokenizer.nextArg())!= null;) { + if (ARGUMENT_URI.equals(argument)) { + uri = Uri.parse(argumentValueRequired(argument)); + } else if (ARGUMENT_WHERE.equals(argument)) { + where = argumentValueRequired(argument); + } else if (ARGUMENT_SORT.equals(argument)) { + sort = argumentValueRequired(argument); + } else if (ARGUMENT_PROJECTION.equals(argument)) { + projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*"); + } else { + throw new IllegalArgumentException("Unsupported argument: " + argument); + } + } + if (uri == null) { + throw new IllegalArgumentException("Content provider URI not specified." + + " Did you specify --uri argument?"); + } + return new QueryCommand(uri, projection, where, sort); + } + + private void parseBindValue(ContentValues values) { + String argument = mTokenizer.nextArg(); + if (TextUtils.isEmpty(argument)) { + throw new IllegalArgumentException("Binding not well formed: " + argument); + } + String[] binding = argument.split(COLON); + if (binding.length != 3) { + throw new IllegalArgumentException("Binding not well formed: " + argument); + } + String column = binding[0]; + String type = binding[1]; + String value = binding[2]; + if (TYPE_STRING.equals(type)) { + values.put(column, value); + } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) { + values.put(column, Boolean.parseBoolean(value)); + } else if (TYPE_INTEGER.equalsIgnoreCase(type) || TYPE_LONG.equalsIgnoreCase(type)) { + values.put(column, Long.parseLong(value)); + } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) { + values.put(column, Double.parseDouble(value)); + } else { + throw new IllegalArgumentException("Unsupported type: " + type); + } + } + + private String argumentValueRequired(String argument) { + String value = mTokenizer.nextArg(); + if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) { + throw new IllegalArgumentException("No value for argument: " + argument); + } + return value; + } + } + + private static class Tokenizer { + private final String[] mArgs; + private int mNextArg; + + public Tokenizer(String[] args) { + mArgs = args; + } + + private String nextArg() { + if (mNextArg < mArgs.length) { + return mArgs[mNextArg++]; + } else { + return null; + } + } + } + + private static abstract class Command { + final Uri mUri; + + public Command(Uri uri) { + mUri = uri; + } + + public final void execute() { + String providerName = mUri.getAuthority(); + try { + IActivityManager activityManager = ActivityManagerNative.getDefault(); + IContentProvider provider = null; + IBinder token = new Binder(); + try { + ContentProviderHolder holder = activityManager.getContentProviderExternal( + providerName, token); + if (holder == null) { + throw new IllegalStateException("Could not find provider: " + providerName); + } + provider = holder.provider; + onExecute(provider); + } finally { + if (provider != null) { + activityManager.removeContentProviderExternal(providerName, token); + } + } + } catch (Exception e) { + System.err.println("Error while accessing provider:" + providerName); + e.printStackTrace(); + } + } + + protected abstract void onExecute(IContentProvider provider) throws Exception; + } + + private static class InsertCommand extends Command { + final ContentValues mContentValues; + + public InsertCommand(Uri uri, ContentValues contentValues) { + super(uri); + mContentValues = contentValues; + } + + @Override + public void onExecute(IContentProvider provider) throws Exception { + provider.insert(mUri, mContentValues); + } + } + + private static class DeleteCommand extends Command { + final String mWhere; + + public DeleteCommand(Uri uri, String where) { + super(uri); + mWhere = where; + } + + @Override + public void onExecute(IContentProvider provider) throws Exception { + provider.delete(mUri, mWhere, null); + } + } + + private static class QueryCommand extends DeleteCommand { + final String[] mProjection; + final String mSortOrder; + + public QueryCommand(Uri uri, String[] projection, String where, String sortOrder) { + super(uri, where); + mProjection = projection; + mSortOrder = sortOrder; + } + + @Override + public void onExecute(IContentProvider provider) throws Exception { + Cursor cursor = provider.query(mUri, mProjection, mWhere, null, mSortOrder, null); + if (cursor == null) { + System.out.println("No result found."); + return; + } + try { + if (cursor.moveToFirst()) { + int rowIndex = 0; + StringBuilder builder = new StringBuilder(); + do { + builder.setLength(0); + builder.append("Row: ").append(rowIndex).append(" "); + rowIndex++; + final int columnCount = cursor.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + if (i > 0) { + builder.append(", "); + } + String columnName = cursor.getColumnName(i); + String columnValue = null; + final int columnIndex = cursor.getColumnIndex(columnName); + final int type = cursor.getType(columnIndex); + switch (type) { + case Cursor.FIELD_TYPE_FLOAT: + columnValue = String.valueOf(cursor.getFloat(columnIndex)); + break; + case Cursor.FIELD_TYPE_INTEGER: + columnValue = String.valueOf(cursor.getInt(columnIndex)); + break; + case Cursor.FIELD_TYPE_STRING: + columnValue = cursor.getString(columnIndex); + break; + case Cursor.FIELD_TYPE_BLOB: + columnValue = "BLOB"; + break; + case Cursor.FIELD_TYPE_NULL: + columnValue = "NULL"; + break; + } + builder.append(columnName).append("=").append(columnValue); + } + System.out.println(builder); + } while (cursor.moveToNext()); + } else { + System.out.println("No reuslt found."); + } + } finally { + cursor.close(); + } + } + } + + private static class UpdateCommand extends InsertCommand { + final String mWhere; + + public UpdateCommand(Uri uri, ContentValues contentValues, String where) { + super(uri, contentValues); + mWhere = where; + } + + @Override + public void onExecute(IContentProvider provider) throws Exception { + provider.update(mUri, mContentValues, mWhere, null); + } + } + + public static void main(String[] args) { + Parser parser = new Parser(args); + Command command = parser.parseCommand(); + if (command != null) { + command.execute(); + } + } +} diff --git a/cmds/keystore/Android.mk b/cmds/keystore/Android.mk deleted file mode 100644 index 5a9b979c6406..000000000000 --- a/cmds/keystore/Android.mk +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (C) 2009 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := keystore.cpp -LOCAL_C_INCLUDES := external/openssl/include -LOCAL_SHARED_LIBRARIES := libcutils libcrypto -LOCAL_MODULE:= keystore -include $(BUILD_EXECUTABLE) - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := keystore_cli.cpp -LOCAL_C_INCLUDES := external/openssl/include -LOCAL_SHARED_LIBRARIES := libcutils libcrypto -LOCAL_MODULE:= keystore_cli -LOCAL_MODULE_TAGS := debug -include $(BUILD_EXECUTABLE) diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp deleted file mode 100644 index 2c9cb35b7b25..000000000000 --- a/cmds/keystore/keystore.cpp +++ /dev/null @@ -1,810 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdio.h> -#include <stdint.h> -#include <string.h> -#include <unistd.h> -#include <signal.h> -#include <errno.h> -#include <dirent.h> -#include <fcntl.h> -#include <limits.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <arpa/inet.h> - -#include <openssl/aes.h> -#include <openssl/evp.h> -#include <openssl/md5.h> - -#define LOG_TAG "keystore" -#include <cutils/log.h> -#include <cutils/sockets.h> -#include <private/android_filesystem_config.h> - -#include "keystore.h" - -/* KeyStore is a secured storage for key-value pairs. In this implementation, - * each file stores one key-value pair. Keys are encoded in file names, and - * values are encrypted with checksums. The encryption key is protected by a - * user-defined password. To keep things simple, buffers are always larger than - * the maximum space we needed, so boundary checks on buffers are omitted. */ - -#define KEY_SIZE ((NAME_MAX - 15) / 2) -#define VALUE_SIZE 32768 -#define PASSWORD_SIZE VALUE_SIZE - -struct Value { - int length; - uint8_t value[VALUE_SIZE]; -}; - -/* Here is the encoding of keys. This is necessary in order to allow arbitrary - * characters in keys. Characters in [0-~] are not encoded. Others are encoded - * into two bytes. The first byte is one of [+-.] which represents the first - * two bits of the character. The second byte encodes the rest of the bits into - * [0-o]. Therefore in the worst case the length of a key gets doubled. Note - * that Base64 cannot be used here due to the need of prefix match on keys. */ - -static int encode_key(char* out, uid_t uid, const Value* key) { - int n = snprintf(out, NAME_MAX, "%u_", uid); - out += n; - const uint8_t* in = key->value; - int length = key->length; - for (int i = length; i > 0; --i, ++in, ++out) { - if (*in >= '0' && *in <= '~') { - *out = *in; - } else { - *out = '+' + (*in >> 6); - *++out = '0' + (*in & 0x3F); - ++length; - } - } - *out = '\0'; - return n + length; -} - -static int decode_key(uint8_t* out, char* in, int length) { - for (int i = 0; i < length; ++i, ++in, ++out) { - if (*in >= '0' && *in <= '~') { - *out = *in; - } else { - *out = (*in - '+') << 6; - *out |= (*++in - '0') & 0x3F; - --length; - } - } - *out = '\0'; - return length; -} - -static size_t readFully(int fd, uint8_t* data, size_t size) { - size_t remaining = size; - while (remaining > 0) { - ssize_t n = TEMP_FAILURE_RETRY(read(fd, data, size)); - if (n == -1 || n == 0) { - return size-remaining; - } - data += n; - remaining -= n; - } - return size; -} - -static size_t writeFully(int fd, uint8_t* data, size_t size) { - size_t remaining = size; - while (remaining > 0) { - ssize_t n = TEMP_FAILURE_RETRY(write(fd, data, size)); - if (n == -1 || n == 0) { - return size-remaining; - } - data += n; - remaining -= n; - } - return size; -} - -class Entropy { -public: - Entropy() : mRandom(-1) {} - ~Entropy() { - if (mRandom != -1) { - close(mRandom); - } - } - - bool open() { - const char* randomDevice = "/dev/urandom"; - mRandom = ::open(randomDevice, O_RDONLY); - if (mRandom == -1) { - ALOGE("open: %s: %s", randomDevice, strerror(errno)); - return false; - } - return true; - } - - bool generate_random_data(uint8_t* data, size_t size) { - return (readFully(mRandom, data, size) == size); - } - -private: - int mRandom; -}; - -/* Here is the file format. There are two parts in blob.value, the secret and - * the description. The secret is stored in ciphertext, and its original size - * can be found in blob.length. The description is stored after the secret in - * plaintext, and its size is specified in blob.info. The total size of the two - * parts must be no more than VALUE_SIZE bytes. The first three bytes of the - * file are reserved for future use and are always set to zero. Fields other - * than blob.info, blob.length, and blob.value are modified by encryptBlob() - * and decryptBlob(). Thus they should not be accessed from outside. */ - -struct __attribute__((packed)) blob { - uint8_t reserved[3]; - uint8_t info; - uint8_t vector[AES_BLOCK_SIZE]; - uint8_t encrypted[0]; - uint8_t digest[MD5_DIGEST_LENGTH]; - uint8_t digested[0]; - int32_t length; // in network byte order when encrypted - uint8_t value[VALUE_SIZE + AES_BLOCK_SIZE]; -}; - -class Blob { -public: - Blob(uint8_t* value, int32_t valueLength, uint8_t* info, uint8_t infoLength) { - mBlob.length = valueLength; - memcpy(mBlob.value, value, valueLength); - - mBlob.info = infoLength; - memcpy(mBlob.value + valueLength, info, infoLength); - } - - Blob(blob b) { - mBlob = b; - } - - Blob() {} - - uint8_t* getValue() { - return mBlob.value; - } - - int32_t getLength() { - return mBlob.length; - } - - uint8_t getInfo() { - return mBlob.info; - } - - ResponseCode encryptBlob(const char* filename, AES_KEY *aes_key, Entropy* entropy) { - if (!entropy->generate_random_data(mBlob.vector, AES_BLOCK_SIZE)) { - return SYSTEM_ERROR; - } - - // data includes the value and the value's length - size_t dataLength = mBlob.length + sizeof(mBlob.length); - // pad data to the AES_BLOCK_SIZE - size_t digestedLength = ((dataLength + AES_BLOCK_SIZE - 1) - / AES_BLOCK_SIZE * AES_BLOCK_SIZE); - // encrypted data includes the digest value - size_t encryptedLength = digestedLength + MD5_DIGEST_LENGTH; - // move info after space for padding - memmove(&mBlob.encrypted[encryptedLength], &mBlob.value[mBlob.length], mBlob.info); - // zero padding area - memset(mBlob.value + mBlob.length, 0, digestedLength - dataLength); - - mBlob.length = htonl(mBlob.length); - MD5(mBlob.digested, digestedLength, mBlob.digest); - - uint8_t vector[AES_BLOCK_SIZE]; - memcpy(vector, mBlob.vector, AES_BLOCK_SIZE); - AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, - aes_key, vector, AES_ENCRYPT); - - memset(mBlob.reserved, 0, sizeof(mBlob.reserved)); - size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob); - size_t fileLength = encryptedLength + headerLength + mBlob.info; - - const char* tmpFileName = ".tmp"; - int out = open(tmpFileName, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); - if (out == -1) { - return SYSTEM_ERROR; - } - size_t writtenBytes = writeFully(out, (uint8_t*) &mBlob, fileLength); - if (close(out) != 0) { - return SYSTEM_ERROR; - } - if (writtenBytes != fileLength) { - unlink(tmpFileName); - return SYSTEM_ERROR; - } - return (rename(tmpFileName, filename) == 0) ? NO_ERROR : SYSTEM_ERROR; - } - - ResponseCode decryptBlob(const char* filename, AES_KEY *aes_key) { - int in = open(filename, O_RDONLY); - if (in == -1) { - return (errno == ENOENT) ? KEY_NOT_FOUND : SYSTEM_ERROR; - } - // fileLength may be less than sizeof(mBlob) since the in - // memory version has extra padding to tolerate rounding up to - // the AES_BLOCK_SIZE - size_t fileLength = readFully(in, (uint8_t*) &mBlob, sizeof(mBlob)); - if (close(in) != 0) { - return SYSTEM_ERROR; - } - size_t headerLength = (mBlob.encrypted - (uint8_t*) &mBlob); - if (fileLength < headerLength) { - return VALUE_CORRUPTED; - } - - ssize_t encryptedLength = fileLength - (headerLength + mBlob.info); - if (encryptedLength < 0 || encryptedLength % AES_BLOCK_SIZE != 0) { - return VALUE_CORRUPTED; - } - AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, aes_key, - mBlob.vector, AES_DECRYPT); - size_t digestedLength = encryptedLength - MD5_DIGEST_LENGTH; - uint8_t computedDigest[MD5_DIGEST_LENGTH]; - MD5(mBlob.digested, digestedLength, computedDigest); - if (memcmp(mBlob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) { - return VALUE_CORRUPTED; - } - - ssize_t maxValueLength = digestedLength - sizeof(mBlob.length); - mBlob.length = ntohl(mBlob.length); - if (mBlob.length < 0 || mBlob.length > maxValueLength) { - return VALUE_CORRUPTED; - } - if (mBlob.info != 0) { - // move info from after padding to after data - memmove(&mBlob.value[mBlob.length], &mBlob.value[maxValueLength], mBlob.info); - } - return NO_ERROR; - } - -private: - struct blob mBlob; -}; - -class KeyStore { -public: - KeyStore(Entropy* entropy) : mEntropy(entropy), mRetry(MAX_RETRY) { - if (access(MASTER_KEY_FILE, R_OK) == 0) { - setState(STATE_LOCKED); - } else { - setState(STATE_UNINITIALIZED); - } - } - - State getState() { - return mState; - } - - int8_t getRetry() { - return mRetry; - } - - ResponseCode initialize(Value* pw) { - if (!generateMasterKey()) { - return SYSTEM_ERROR; - } - ResponseCode response = writeMasterKey(pw); - if (response != NO_ERROR) { - return response; - } - setupMasterKeys(); - return NO_ERROR; - } - - ResponseCode writeMasterKey(Value* pw) { - uint8_t passwordKey[MASTER_KEY_SIZE_BYTES]; - generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, mSalt); - AES_KEY passwordAesKey; - AES_set_encrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey); - Blob masterKeyBlob(mMasterKey, sizeof(mMasterKey), mSalt, sizeof(mSalt)); - return masterKeyBlob.encryptBlob(MASTER_KEY_FILE, &passwordAesKey, mEntropy); - } - - ResponseCode readMasterKey(Value* pw) { - int in = open(MASTER_KEY_FILE, O_RDONLY); - if (in == -1) { - return SYSTEM_ERROR; - } - - // we read the raw blob to just to get the salt to generate - // the AES key, then we create the Blob to use with decryptBlob - blob rawBlob; - size_t length = readFully(in, (uint8_t*) &rawBlob, sizeof(rawBlob)); - if (close(in) != 0) { - return SYSTEM_ERROR; - } - // find salt at EOF if present, otherwise we have an old file - uint8_t* salt; - if (length > SALT_SIZE && rawBlob.info == SALT_SIZE) { - salt = (uint8_t*) &rawBlob + length - SALT_SIZE; - } else { - salt = NULL; - } - uint8_t passwordKey[MASTER_KEY_SIZE_BYTES]; - generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, salt); - AES_KEY passwordAesKey; - AES_set_decrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey); - Blob masterKeyBlob(rawBlob); - ResponseCode response = masterKeyBlob.decryptBlob(MASTER_KEY_FILE, &passwordAesKey); - if (response == SYSTEM_ERROR) { - return SYSTEM_ERROR; - } - if (response == NO_ERROR && masterKeyBlob.getLength() == MASTER_KEY_SIZE_BYTES) { - // if salt was missing, generate one and write a new master key file with the salt. - if (salt == NULL) { - if (!generateSalt()) { - return SYSTEM_ERROR; - } - response = writeMasterKey(pw); - } - if (response == NO_ERROR) { - memcpy(mMasterKey, masterKeyBlob.getValue(), MASTER_KEY_SIZE_BYTES); - setupMasterKeys(); - } - return response; - } - if (mRetry <= 0) { - reset(); - return UNINITIALIZED; - } - --mRetry; - switch (mRetry) { - case 0: return WRONG_PASSWORD_0; - case 1: return WRONG_PASSWORD_1; - case 2: return WRONG_PASSWORD_2; - case 3: return WRONG_PASSWORD_3; - default: return WRONG_PASSWORD_3; - } - } - - bool reset() { - clearMasterKeys(); - setState(STATE_UNINITIALIZED); - - DIR* dir = opendir("."); - struct dirent* file; - - if (!dir) { - return false; - } - while ((file = readdir(dir)) != NULL) { - unlink(file->d_name); - } - closedir(dir); - return true; - } - - bool isEmpty() { - DIR* dir = opendir("."); - struct dirent* file; - if (!dir) { - return true; - } - bool result = true; - while ((file = readdir(dir)) != NULL) { - if (isKeyFile(file->d_name)) { - result = false; - break; - } - } - closedir(dir); - return result; - } - - void lock() { - clearMasterKeys(); - setState(STATE_LOCKED); - } - - ResponseCode get(const char* filename, Blob* keyBlob) { - return keyBlob->decryptBlob(filename, &mMasterKeyDecryption); - } - - ResponseCode put(const char* filename, Blob* keyBlob) { - return keyBlob->encryptBlob(filename, &mMasterKeyEncryption, mEntropy); - } - -private: - static const char* MASTER_KEY_FILE; - static const int MASTER_KEY_SIZE_BYTES = 16; - static const int MASTER_KEY_SIZE_BITS = MASTER_KEY_SIZE_BYTES * 8; - - static const int MAX_RETRY = 4; - static const size_t SALT_SIZE = 16; - - Entropy* mEntropy; - - State mState; - int8_t mRetry; - - uint8_t mMasterKey[MASTER_KEY_SIZE_BYTES]; - uint8_t mSalt[SALT_SIZE]; - - AES_KEY mMasterKeyEncryption; - AES_KEY mMasterKeyDecryption; - - void setState(State state) { - mState = state; - if (mState == STATE_NO_ERROR || mState == STATE_UNINITIALIZED) { - mRetry = MAX_RETRY; - } - } - - bool generateSalt() { - return mEntropy->generate_random_data(mSalt, sizeof(mSalt)); - } - - bool generateMasterKey() { - if (!mEntropy->generate_random_data(mMasterKey, sizeof(mMasterKey))) { - return false; - } - if (!generateSalt()) { - return false; - } - return true; - } - - void setupMasterKeys() { - AES_set_encrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyEncryption); - AES_set_decrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyDecryption); - setState(STATE_NO_ERROR); - } - - void clearMasterKeys() { - memset(mMasterKey, 0, sizeof(mMasterKey)); - memset(mSalt, 0, sizeof(mSalt)); - memset(&mMasterKeyEncryption, 0, sizeof(mMasterKeyEncryption)); - memset(&mMasterKeyDecryption, 0, sizeof(mMasterKeyDecryption)); - } - - static void generateKeyFromPassword(uint8_t* key, ssize_t keySize, Value* pw, uint8_t* salt) { - size_t saltSize; - if (salt != NULL) { - saltSize = SALT_SIZE; - } else { - // pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found - salt = (uint8_t*) "keystore"; - // sizeof = 9, not strlen = 8 - saltSize = sizeof("keystore"); - } - PKCS5_PBKDF2_HMAC_SHA1((char*) pw->value, pw->length, salt, saltSize, 8192, keySize, key); - } - - static bool isKeyFile(const char* filename) { - return ((strcmp(filename, MASTER_KEY_FILE) != 0) - && (strcmp(filename, ".") != 0) - && (strcmp(filename, "..") != 0)); - } -}; - -const char* KeyStore::MASTER_KEY_FILE = ".masterkey"; - -/* Here is the protocol used in both requests and responses: - * code [length_1 message_1 ... length_n message_n] end-of-file - * where code is one byte long and lengths are unsigned 16-bit integers in - * network order. Thus the maximum length of a message is 65535 bytes. */ - -static int recv_code(int sock, int8_t* code) { - return recv(sock, code, 1, 0) == 1; -} - -static int recv_message(int sock, uint8_t* message, int length) { - uint8_t bytes[2]; - if (recv(sock, &bytes[0], 1, 0) != 1 || - recv(sock, &bytes[1], 1, 0) != 1) { - return -1; - } else { - int offset = bytes[0] << 8 | bytes[1]; - if (length < offset) { - return -1; - } - length = offset; - offset = 0; - while (offset < length) { - int n = recv(sock, &message[offset], length - offset, 0); - if (n <= 0) { - return -1; - } - offset += n; - } - } - return length; -} - -static int recv_end_of_file(int sock) { - uint8_t byte; - return recv(sock, &byte, 1, 0) == 0; -} - -static void send_code(int sock, int8_t code) { - send(sock, &code, 1, 0); -} - -static void send_message(int sock, uint8_t* message, int length) { - uint16_t bytes = htons(length); - send(sock, &bytes, 2, 0); - send(sock, message, length, 0); -} - -/* Here are the actions. Each of them is a function without arguments. All - * information is defined in global variables, which are set properly before - * performing an action. The number of parameters required by each action is - * fixed and defined in a table. If the return value of an action is positive, - * it will be treated as a response code and transmitted to the client. Note - * that the lengths of parameters are checked when they are received, so - * boundary checks on parameters are omitted. */ - -static const ResponseCode NO_ERROR_RESPONSE_CODE_SENT = (ResponseCode) 0; - -static ResponseCode test(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { - return (ResponseCode) keyStore->getState(); -} - -static ResponseCode get(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) { - char filename[NAME_MAX]; - encode_key(filename, uid, keyName); - Blob keyBlob; - ResponseCode responseCode = keyStore->get(filename, &keyBlob); - if (responseCode != NO_ERROR) { - return responseCode; - } - send_code(sock, NO_ERROR); - send_message(sock, keyBlob.getValue(), keyBlob.getLength()); - return NO_ERROR_RESPONSE_CODE_SENT; -} - -static ResponseCode insert(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value* val) { - char filename[NAME_MAX]; - encode_key(filename, uid, keyName); - Blob keyBlob(val->value, val->length, NULL, 0); - return keyStore->put(filename, &keyBlob); -} - -static ResponseCode del(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) { - char filename[NAME_MAX]; - encode_key(filename, uid, keyName); - return (unlink(filename) && errno != ENOENT) ? SYSTEM_ERROR : NO_ERROR; -} - -static ResponseCode exist(KeyStore* keyStore, int sock, uid_t uid, Value* keyName, Value*) { - char filename[NAME_MAX]; - encode_key(filename, uid, keyName); - if (access(filename, R_OK) == -1) { - return (errno != ENOENT) ? SYSTEM_ERROR : KEY_NOT_FOUND; - } - return NO_ERROR; -} - -static ResponseCode saw(KeyStore* keyStore, int sock, uid_t uid, Value* keyPrefix, Value*) { - DIR* dir = opendir("."); - if (!dir) { - return SYSTEM_ERROR; - } - char filename[NAME_MAX]; - int n = encode_key(filename, uid, keyPrefix); - send_code(sock, NO_ERROR); - - struct dirent* file; - while ((file = readdir(dir)) != NULL) { - if (!strncmp(filename, file->d_name, n)) { - char* p = &file->d_name[n]; - keyPrefix->length = decode_key(keyPrefix->value, p, strlen(p)); - send_message(sock, keyPrefix->value, keyPrefix->length); - } - } - closedir(dir); - return NO_ERROR_RESPONSE_CODE_SENT; -} - -static ResponseCode reset(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { - return keyStore->reset() ? NO_ERROR : SYSTEM_ERROR; -} - -/* Here is the history. To improve the security, the parameters to generate the - * master key has been changed. To make a seamless transition, we update the - * file using the same password when the user unlock it for the first time. If - * any thing goes wrong during the transition, the new file will not overwrite - * the old one. This avoids permanent damages of the existing data. */ - -static ResponseCode password(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value*) { - switch (keyStore->getState()) { - case STATE_UNINITIALIZED: { - // generate master key, encrypt with password, write to file, initialize mMasterKey*. - return keyStore->initialize(pw); - } - case STATE_NO_ERROR: { - // rewrite master key with new password. - return keyStore->writeMasterKey(pw); - } - case STATE_LOCKED: { - // read master key, decrypt with password, initialize mMasterKey*. - return keyStore->readMasterKey(pw); - } - } - return SYSTEM_ERROR; -} - -static ResponseCode lock(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { - keyStore->lock(); - return NO_ERROR; -} - -static ResponseCode unlock(KeyStore* keyStore, int sock, uid_t uid, Value* pw, Value* unused) { - return password(keyStore, sock, uid, pw, unused); -} - -static ResponseCode zero(KeyStore* keyStore, int sock, uid_t uid, Value*, Value*) { - return keyStore->isEmpty() ? KEY_NOT_FOUND : NO_ERROR; -} - -/* Here are the permissions, actions, users, and the main function. */ - -enum perm { - TEST = 1, - GET = 2, - INSERT = 4, - DELETE = 8, - EXIST = 16, - SAW = 32, - RESET = 64, - PASSWORD = 128, - LOCK = 256, - UNLOCK = 512, - ZERO = 1024, -}; - -static const int MAX_PARAM = 2; - -static const State STATE_ANY = (State) 0; - -static struct action { - ResponseCode (*run)(KeyStore* keyStore, int sock, uid_t uid, Value* param1, Value* param2); - int8_t code; - State state; - uint32_t perm; - int lengths[MAX_PARAM]; -} actions[] = { - {test, 't', STATE_ANY, TEST, {0, 0}}, - {get, 'g', STATE_NO_ERROR, GET, {KEY_SIZE, 0}}, - {insert, 'i', STATE_NO_ERROR, INSERT, {KEY_SIZE, VALUE_SIZE}}, - {del, 'd', STATE_ANY, DELETE, {KEY_SIZE, 0}}, - {exist, 'e', STATE_ANY, EXIST, {KEY_SIZE, 0}}, - {saw, 's', STATE_ANY, SAW, {KEY_SIZE, 0}}, - {reset, 'r', STATE_ANY, RESET, {0, 0}}, - {password, 'p', STATE_ANY, PASSWORD, {PASSWORD_SIZE, 0}}, - {lock, 'l', STATE_NO_ERROR, LOCK, {0, 0}}, - {unlock, 'u', STATE_LOCKED, UNLOCK, {PASSWORD_SIZE, 0}}, - {zero, 'z', STATE_ANY, ZERO, {0, 0}}, - {NULL, 0 , STATE_ANY, 0, {0, 0}}, -}; - -static struct user { - uid_t uid; - uid_t euid; - uint32_t perms; -} users[] = { - {AID_SYSTEM, ~0, ~0}, - {AID_VPN, AID_SYSTEM, GET}, - {AID_WIFI, AID_SYSTEM, GET}, - {AID_ROOT, AID_SYSTEM, GET}, - {~0, ~0, TEST | GET | INSERT | DELETE | EXIST | SAW}, -}; - -static ResponseCode process(KeyStore* keyStore, int sock, uid_t uid, int8_t code) { - struct user* user = users; - struct action* action = actions; - int i; - - while (~user->uid && user->uid != uid) { - ++user; - } - while (action->code && action->code != code) { - ++action; - } - if (!action->code) { - return UNDEFINED_ACTION; - } - if (!(action->perm & user->perms)) { - return PERMISSION_DENIED; - } - if (action->state != STATE_ANY && action->state != keyStore->getState()) { - return (ResponseCode) keyStore->getState(); - } - if (~user->euid) { - uid = user->euid; - } - Value params[MAX_PARAM]; - for (i = 0; i < MAX_PARAM && action->lengths[i] != 0; ++i) { - params[i].length = recv_message(sock, params[i].value, action->lengths[i]); - if (params[i].length < 0) { - return PROTOCOL_ERROR; - } - } - if (!recv_end_of_file(sock)) { - return PROTOCOL_ERROR; - } - return action->run(keyStore, sock, uid, ¶ms[0], ¶ms[1]); -} - -int main(int argc, char* argv[]) { - int controlSocket = android_get_control_socket("keystore"); - if (argc < 2) { - ALOGE("A directory must be specified!"); - return 1; - } - if (chdir(argv[1]) == -1) { - ALOGE("chdir: %s: %s", argv[1], strerror(errno)); - return 1; - } - - Entropy entropy; - if (!entropy.open()) { - return 1; - } - if (listen(controlSocket, 3) == -1) { - ALOGE("listen: %s", strerror(errno)); - return 1; - } - - signal(SIGPIPE, SIG_IGN); - - KeyStore keyStore(&entropy); - int sock; - while ((sock = accept(controlSocket, NULL, 0)) != -1) { - struct timeval tv; - tv.tv_sec = 3; - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - - struct ucred cred; - socklen_t size = sizeof(cred); - int credResult = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &cred, &size); - if (credResult != 0) { - ALOGW("getsockopt: %s", strerror(errno)); - } else { - int8_t request; - if (recv_code(sock, &request)) { - State old_state = keyStore.getState(); - ResponseCode response = process(&keyStore, sock, cred.uid, request); - if (response == NO_ERROR_RESPONSE_CODE_SENT) { - response = NO_ERROR; - } else { - send_code(sock, response); - } - ALOGI("uid: %d action: %c -> %d state: %d -> %d retry: %d", - cred.uid, - request, response, - old_state, keyStore.getState(), - keyStore.getRetry()); - } - } - close(sock); - } - ALOGE("accept: %s", strerror(errno)); - return 1; -} diff --git a/cmds/keystore/keystore.h b/cmds/keystore/keystore.h deleted file mode 100644 index 5ae3d24acee0..000000000000 --- a/cmds/keystore/keystore.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __KEYSTORE_H__ -#define __KEYSTORE_H__ - -// note state values overlap with ResponseCode for the purposes of the state() API -enum State { - STATE_NO_ERROR = 1, - STATE_LOCKED = 2, - STATE_UNINITIALIZED = 3, -}; - -enum ResponseCode { - NO_ERROR = STATE_NO_ERROR, // 1 - LOCKED = STATE_LOCKED, // 2 - UNINITIALIZED = STATE_UNINITIALIZED, // 3 - SYSTEM_ERROR = 4, - PROTOCOL_ERROR = 5, - PERMISSION_DENIED = 6, - KEY_NOT_FOUND = 7, - VALUE_CORRUPTED = 8, - UNDEFINED_ACTION = 9, - WRONG_PASSWORD_0 = 10, - WRONG_PASSWORD_1 = 11, - WRONG_PASSWORD_2 = 12, - WRONG_PASSWORD_3 = 13, // MAX_RETRY = 4 -}; - -#endif diff --git a/cmds/keystore/keystore_cli.cpp b/cmds/keystore/keystore_cli.cpp deleted file mode 100644 index dcd3bcb8fc01..000000000000 --- a/cmds/keystore/keystore_cli.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdio.h> -#include <stdint.h> -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> - -#include <cutils/sockets.h> - -#include "keystore.h" - -static const char* responses[] = { - NULL, - /* [NO_ERROR] = */ "No error", - /* [LOCKED] = */ "Locked", - /* [UNINITIALIZED] = */ "Uninitialized", - /* [SYSTEM_ERROR] = */ "System error", - /* [PROTOCOL_ERROR] = */ "Protocol error", - /* [PERMISSION_DENIED] = */ "Permission denied", - /* [KEY_NOT_FOUND] = */ "Key not found", - /* [VALUE_CORRUPTED] = */ "Value corrupted", - /* [UNDEFINED_ACTION] = */ "Undefined action", - /* [WRONG_PASSWORD] = */ "Wrong password (last chance)", - /* [WRONG_PASSWORD + 1] = */ "Wrong password (2 tries left)", - /* [WRONG_PASSWORD + 2] = */ "Wrong password (3 tries left)", - /* [WRONG_PASSWORD + 3] = */ "Wrong password (4 tries left)", -}; - -int main(int argc, char* argv[]) -{ - if (argc < 2) { - printf("Usage: %s action [parameter ...]\n", argv[0]); - return 0; - } - - int sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED, - SOCK_STREAM); - if (sock == -1) { - puts("Failed to connect"); - return 1; - } - - send(sock, argv[1], 1, 0); - uint8_t bytes[65536]; - for (int i = 2; i < argc; ++i) { - uint16_t length = strlen(argv[i]); - bytes[0] = length >> 8; - bytes[1] = length; - send(sock, &bytes, 2, 0); - send(sock, argv[i], length, 0); - } - shutdown(sock, SHUT_WR); - - uint8_t code; - if (recv(sock, &code, 1, 0) != 1) { - puts("Failed to receive"); - return 1; - } - printf("%d %s\n", code , responses[code] ? responses[code] : "Unknown"); - int i; - while ((i = recv(sock, &bytes[0], 1, 0)) == 1) { - int length; - int offset; - if ((i = recv(sock, &bytes[1], 1, 0)) != 1) { - puts("Failed to receive"); - return 1; - } - length = bytes[0] << 8 | bytes[1]; - for (offset = 0; offset < length; offset += i) { - i = recv(sock, &bytes[offset], length - offset, 0); - if (i <= 0) { - puts("Failed to receive"); - return 1; - } - } - fwrite(bytes, 1, length, stdout); - puts(""); - } - return 0; -} diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h deleted file mode 100644 index 4b4923e5c736..000000000000 --- a/cmds/keystore/keystore_get.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __KEYSTORE_GET_H__ -#define __KEYSTORE_GET_H__ - -#include <stdio.h> -#include <stdint.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/socket.h> - -#include <cutils/sockets.h> - -#define KEYSTORE_MESSAGE_SIZE 65535 - -#ifdef __cplusplus -extern "C" { -#endif - -/* This function is provided for native components to get values from keystore. - * Users are required to link against libcutils. Keys and values are 8-bit safe. - * The first two arguments are the key and its length. The third argument - * specifies the buffer to store the retrieved value, which must be an array of - * KEYSTORE_MESSAGE_SIZE bytes. This function returns the length of the value or - * -1 if an error happens. */ -static int keystore_get(const char *key, int length, char *value) -{ - uint8_t bytes[2] = {length >> 8, length}; - uint8_t code = 'g'; - int sock; - - if (length < 0 || length > KEYSTORE_MESSAGE_SIZE) { - return -1; - } - sock = socket_local_client("keystore", ANDROID_SOCKET_NAMESPACE_RESERVED, - SOCK_STREAM); - if (sock == -1) { - return -1; - } - if (send(sock, &code, 1, 0) == 1 && send(sock, bytes, 2, 0) == 2 && - send(sock, key, length, 0) == length && shutdown(sock, SHUT_WR) == 0 && - recv(sock, &code, 1, 0) == 1 && code == /* NO_ERROR */ 1 && - recv(sock, &bytes[0], 1, 0) == 1 && recv(sock, &bytes[1], 1, 0) == 1) { - int offset = 0; - length = bytes[0] << 8 | bytes[1]; - while (offset < length) { - int n = recv(sock, &value[offset], length - offset, 0); - if (n <= 0) { - length = -1; - break; - } - offset += n; - } - } else { - length = -1; - } - - close(sock); - return length; -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/cmds/keystore/test-keystore b/cmds/keystore/test-keystore deleted file mode 100755 index 3be51b3e7bfb..000000000000 --- a/cmds/keystore/test-keystore +++ /dev/null @@ -1,273 +0,0 @@ -#!/bin/bash -# -# Copyright 2011, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e - -prefix=$0 -log_file=$prefix.log -baseline_file=$prefix.baseline - -function cleanup_output() { - rm -f $log_file - rm -f $baseline_file -} - -function log() { - echo "$@" - append $log_file \# "$@" - append $baseline_file \# "$@" -} - -function expect() { - append $baseline_file "$@" -} - -function append() { - declare -r file=$1 - shift - echo "$@" >> $file -} - -function run() { - # strip out carriage returns from adb - # strip out date/time from ls -l - "$@" | tr --delete '\r' | sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2} +[0-9]{1,2}:[0-9]{2} //' >> $log_file -} - -function keystore() { - declare -r user=$1 - shift - run adb shell su $user keystore_cli "$@" -} - -function list_keystore_directory() { - run adb shell ls -al /data/misc/keystore -} - -function compare() { - log "comparing $baseline_file and $log_file" - diff $baseline_file $log_file || (log $tag FAILED && exit 1) -} - -function test_basic() { - - # - # reset - # - log "reset keystore as system user" - keystore system r - expect "1 No error" - list_keystore_directory - - # - # basic tests as system/root - # - log "root does not have permission to run test" - keystore root t - expect "6 Permission denied" - - log "but system user does" - keystore system t - expect "3 Uninitialized" - list_keystore_directory - - log "password is now bar" - keystore system p bar - expect "1 No error" - list_keystore_directory - expect "-rw------- keystore keystore 84 .masterkey" - - log "no error implies initialized and unlocked" - keystore system t - expect "1 No error" - - log "saw with no argument" - keystore system s - expect "5 Protocol error" - - log "saw nothing" - keystore system s "" - expect "1 No error" - - log "add key baz" - keystore system i baz quux - expect "1 No error" - - log "1000 is uid of system" - list_keystore_directory - expect "-rw------- keystore keystore 84 .masterkey" - expect "-rw------- keystore keystore 52 1000_baz" - - log "saw baz" - keystore system s "" - expect "1 No error" - expect "baz" - - log "get baz" - keystore system g baz - expect "1 No error" - expect "quux" - - log "root can read system user keys (as can wifi or vpn users)" - keystore root g baz - expect "1 No error" - expect "quux" - - # - # app user tests - # - - # app_0 has uid 10000, as seen below - log "other uses cannot see the system keys" - keystore app_0 g baz - expect "7 Key not found" - - log "app user cannot use reset, password, lock, unlock" - keystore app_0 r - expect "6 Permission denied" - keystore app_0 p - expect "6 Permission denied" - keystore app_0 l - expect "6 Permission denied" - keystore app_0 u - expect "6 Permission denied" - - log "install app_0 key" - keystore app_0 i 0x deadbeef - expect 1 No error - list_keystore_directory - expect "-rw------- keystore keystore 84 .masterkey" - expect "-rw------- keystore keystore 52 10000_0x" - expect "-rw------- keystore keystore 52 1000_baz" - - log "get with no argument" - keystore app_0 g - expect "5 Protocol error" - - keystore app_0 g 0x - expect "1 No error" - expect "deadbeef" - - keystore app_0 i fred barney - expect "1 No error" - - keystore app_0 s "" - expect "1 No error" - expect "0x" - expect "fred" - - log "note that saw returns the suffix of prefix matches" - keystore app_0 s fr # fred - expect "1 No error" - expect "ed" # fred - - # - # lock tests - # - log "lock the store as system" - keystore system l - expect "1 No error" - keystore system t - expect "2 Locked" - - log "saw works while locked" - keystore app_0 s "" - expect "1 No error" - expect "0x" - expect "fred" - - log "...but cannot read keys..." - keystore app_0 g 0x - expect "2 Locked" - - log "...but they can be deleted." - keystore app_0 e 0x - expect "1 No error" - keystore app_0 d 0x - expect "1 No error" - keystore app_0 e 0x - expect "7 Key not found" - - # - # password - # - log "wrong password" - keystore system u foo - expect "13 Wrong password (4 tries left)" - log "right password" - keystore system u bar - expect "1 No error" - - log "make the password foo" - keystore system p foo - expect "1 No error" - - # - # final reset - # - log "reset wipes everything for all users" - keystore system r - expect "1 No error" - list_keystore_directory - - keystore system t - expect "3 Uninitialized" - -} - -function test_4599735() { - # http://b/4599735 - log "start regression test for b/4599735" - keystore system r - expect "1 No error" - - keystore system p foo - expect "1 No error" - - keystore system i baz quux - expect "1 No error" - - keystore root g baz - expect "1 No error" - expect "quux" - - keystore system l - expect "1 No error" - - keystore system p foo - expect "1 No error" - - log "after unlock, regression led to result of '8 Value corrupted'" - keystore root g baz - expect "1 No error" - expect "quux" - - keystore system r - expect "1 No error" - log "end regression test for b/4599735" -} - -function main() { - cleanup_output - log $tag START - test_basic - test_4599735 - compare - log $tag PASSED - cleanup_output -} - -main diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c index cfc2d1611628..71c5622ec382 100644 --- a/cmds/servicemanager/service_manager.c +++ b/cmds/servicemanager/service_manager.c @@ -43,6 +43,8 @@ static struct { { AID_RADIO, "isms" }, { AID_RADIO, "iphonesubinfo" }, { AID_RADIO, "simphonebook" }, + { AID_MEDIA, "common_time.clock" }, + { AID_MEDIA, "common_time.config" }, }; void *svcmgr_handle; diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index cc1efb9acd98..6fbeee36e03a 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -522,8 +522,7 @@ public class ValueAnimator extends Animator { * animations possible. * */ - private static class AnimationHandler extends Handler - implements Choreographer.OnAnimateListener { + private static class AnimationHandler extends Handler implements Runnable { // The per-thread list of all active animations private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>(); @@ -539,7 +538,7 @@ public class ValueAnimator extends Animator { private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>(); private final Choreographer mChoreographer; - private boolean mIsChoreographed; + private boolean mAnimationScheduled; private AnimationHandler() { mChoreographer = Choreographer.getInstance(); @@ -644,22 +643,17 @@ public class ValueAnimator extends Animator { // If there are still active or delayed animations, schedule a future call to // onAnimate to process the next frame of the animations. - if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { - if (!mIsChoreographed) { - mIsChoreographed = true; - mChoreographer.addOnAnimateListener(this); - } - mChoreographer.scheduleAnimation(); - } else { - if (mIsChoreographed) { - mIsChoreographed = false; - mChoreographer.removeOnAnimateListener(this); - } + if (!mAnimationScheduled + && (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) { + mChoreographer.postAnimationCallback(this); + mAnimationScheduled = true; } } + // Called by the Choreographer. @Override - public void onAnimate() { + public void run() { + mAnimationScheduled = false; doAnimationFrame(); } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 5a364665768d..24079a5d8a2f 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -581,6 +581,21 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String name = data.readString(); + IBinder token = data.readStrongBinder(); + ContentProviderHolder cph = getContentProviderExternal(name, token); + reply.writeNoException(); + if (cph != null) { + reply.writeInt(1); + cph.writeToParcel(reply, 0); + } else { + reply.writeInt(0); + } + return true; + } + case PUBLISH_CONTENT_PROVIDERS_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); @@ -601,7 +616,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } - + + case REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String name = data.readString(); + IBinder token = data.readStrongBinder(); + removeContentProviderExternal(name, token); + reply.writeNoException(); + return true; + } + case GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); ComponentName comp = ComponentName.CREATOR.createFromParcel(data); @@ -2178,6 +2202,25 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return cph; } + public ContentProviderHolder getContentProviderExternal(String name, IBinder token) + throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(name); + data.writeStrongBinder(token); + mRemote.transact(GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + ContentProviderHolder cph = null; + if (res != 0) { + cph = ContentProviderHolder.CREATOR.createFromParcel(reply); + } + data.recycle(); + reply.recycle(); + return cph; + } public void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) throws RemoteException { @@ -2204,7 +2247,19 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - + + public void removeContentProviderExternal(String name, IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(name); + data.writeStrongBinder(token); + mRemote.transact(REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public PendingIntent getRunningServiceControlPanel(ComponentName service) throws RemoteException { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ebf692a99080..6d5cce5efdc3 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -911,6 +911,19 @@ class ContextImpl extends Context { } } + /** @hide */ + @Override + public void sendBroadcast(Intent intent, int userId) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), + intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false, + userId); + } catch (RemoteException e) { + } + } + @Override public void sendBroadcast(Intent intent, String receiverPermission) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 7deb615f6f9f..53a71db82b14 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -150,8 +150,11 @@ public interface IActivityManager extends IInterface { Bitmap thumbnail, CharSequence description) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, String name) throws RemoteException; + public ContentProviderHolder getContentProviderExternal(String name, IBinder token) + throws RemoteException; public void removeContentProvider(IApplicationThread caller, String name) throws RemoteException; + public void removeContentProviderExternal(String name, IBinder token) throws RemoteException; public void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) throws RemoteException; public PendingIntent getRunningServiceControlPanel(ComponentName service) @@ -415,7 +418,7 @@ public interface IActivityManager extends IInterface { source.readStrongBinder()); noReleaseNeeded = source.readInt() != 0; } - }; + } /** Information returned after waiting for an activity start. */ public static class WaitResult implements Parcelable { @@ -601,4 +604,6 @@ public interface IActivityManager extends IInterface { int SHOW_BOOT_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+137; int DISMISS_KEYGUARD_ON_NEXT_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+138; int KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+139; + int GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+140; + int REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+141; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6d4cdaee1060..a1198dedef13 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -905,6 +905,16 @@ public abstract class Context { public abstract void sendBroadcast(Intent intent); /** + * Same as #sendBroadcast(Intent intent), but for a specific user. Used by the system only. + * @param intent the intent to broadcast + * @param userId user to send the intent to + * @hide + */ + public void sendBroadcast(Intent intent, int userId) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Broadcast the given intent to all interested BroadcastReceivers, allowing * an optional required permission to be enforced. This * call is asynchronous; it returns immediately, and you will continue diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index cd8d87f9d057..5ba9dccd36b3 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -294,6 +294,12 @@ public class ContextWrapper extends Context { mBase.sendBroadcast(intent); } + /** @hide */ + @Override + public void sendBroadcast(Intent intent, int userId) { + mBase.sendBroadcast(intent, userId); + } + @Override public void sendBroadcast(Intent intent, String receiverPermission) { mBase.sendBroadcast(intent, receiverPermission); diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java index f94d3207f23d..06c6c6ec248f 100644 --- a/core/java/android/net/http/CertificateChainValidator.java +++ b/core/java/android/net/http/CertificateChainValidator.java @@ -25,15 +25,17 @@ import javax.net.ssl.DefaultHostnameVerifier; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; +import javax.net.ssl.X509TrustManager; import org.apache.harmony.security.provider.cert.X509CertImpl; import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl; +import org.apache.harmony.xnet.provider.jsse.TrustManagerImpl; /** * Class responsible for all server certificate validation functionality * * {@hide} */ -class CertificateChainValidator { +public class CertificateChainValidator { /** * The singleton instance of the certificate chain validator @@ -122,6 +124,18 @@ class CertificateChainValidator { } /** + * Handles updates to credential storage. + */ + public static void handleTrustStorageUpdate() { + + X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager(); + if( x509TrustManager instanceof TrustManagerImpl ) { + TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager; + trustManager.handleTrustStorageUpdate(); + } + } + + /** * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates. * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs. * @param chain the cert chain in X509 cert format. diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java index 21736aa3d9a2..73f3d7cc339a 100644 --- a/core/java/android/net/http/HttpResponseCache.java +++ b/core/java/android/net/http/HttpResponseCache.java @@ -22,8 +22,10 @@ import java.io.File; import java.io.IOException; import java.net.CacheRequest; import java.net.CacheResponse; +import java.net.ExtendedResponseCache; import java.net.HttpURLConnection; import java.net.ResponseCache; +import java.net.ResponseSource; import java.net.URI; import java.net.URLConnection; import java.util.List; @@ -149,7 +151,8 @@ import org.apache.http.impl.client.DefaultHttpClient; * } catch (Exception httpResponseCacheNotAvailable) { * }}</pre> */ -public final class HttpResponseCache extends ResponseCache implements Closeable { +public final class HttpResponseCache extends ResponseCache + implements Closeable, ExtendedResponseCache { private final libcore.net.http.HttpResponseCache delegate; @@ -260,6 +263,21 @@ public final class HttpResponseCache extends ResponseCache implements Closeable return delegate.getRequestCount(); } + /** @hide */ + @Override public void trackResponse(ResponseSource source) { + delegate.trackResponse(source); + } + + /** @hide */ + @Override public void trackConditionalCacheHit() { + delegate.trackConditionalCacheHit(); + } + + /** @hide */ + @Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) { + delegate.update(conditionalCacheHit, connection); + } + /** * Uninstalls the cache and releases any active resources. Stored contents * will remain on the filesystem. diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java new file mode 100644 index 000000000000..3a1da9761b6b --- /dev/null +++ b/core/java/android/os/CommonClock.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os; + +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.util.NoSuchElementException; +import static libcore.io.OsConstants.*; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.CommonTimeUtils; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * Used for accessing the android common time service's common clock and receiving notifications + * about common time synchronization status changes. + * @hide + */ +public class CommonClock { + /** + * Sentinel value returned by {@link #getTime()} and {@link #getEstimatedError()} when the + * common time service is not able to determine the current common time due to a lack of + * synchronization. + */ + public static final long TIME_NOT_SYNCED = -1; + + /** + * Sentinel value returned by {@link #getTimelineId()} when the common time service is not + * currently synced to any timeline. + */ + public static final long INVALID_TIMELINE_ID = 0; + + /** + * Sentinel value returned by {@link #getEstimatedError()} when the common time service is not + * currently synced to any timeline. + */ + public static final int ERROR_ESTIMATE_UNKNOWN = 0x7FFFFFFF; + + /** + * Value used by {@link #getState()} to indicate that there was an internal error while + * attempting to determine the state of the common time service. + */ + public static final int STATE_INVALID = -1; + + /** + * Value used by {@link #getState()} to indicate that the common time service is in its initial + * state and attempting to find the current timeline master, if any. The service will + * transition to either {@link #STATE_CLIENT} if it finds an active master, or to + * {@link #STATE_MASTER} if no active master is found and this client becomes the master of a + * new timeline. + */ + public static final int STATE_INITIAL = 0; + + /** + * Value used by {@link #getState()} to indicate that the common time service is in its client + * state and is synchronizing its time to a different timeline master on the network. + */ + public static final int STATE_CLIENT = 1; + + /** + * Value used by {@link #getState()} to indicate that the common time service is in its master + * state and is serving as the timeline master for other common time service clients on the + * network. + */ + public static final int STATE_MASTER = 2; + + /** + * Value used by {@link #getState()} to indicate that the common time service is in its Ronin + * state. Common time service instances in the client state enter the Ronin state after their + * timeline master becomes unreachable on the network. Common time services who enter the Ronin + * state will begin a new master election for the timeline they were recently clients of. As + * clients detect they are not the winner and drop out of the election, they will transition to + * the {@link #STATE_WAIT_FOR_ELECTION} state. When there is only one client remaining in the + * election, it will assume ownership of the timeline and transition to the + * {@link #STATE_MASTER} state. During the election, all clients will allow their timeline to + * drift without applying correction. + */ + public static final int STATE_RONIN = 3; + + /** + * Value used by {@link #getState()} to indicate that the common time service is waiting for a + * master election to conclude and for the new master to announce itself before transitioning to + * the {@link #STATE_CLIENT} state. If no new master announces itself within the timeout + * threshold, the time service will transition back to the {@link #STATE_RONIN} state in order + * to restart the election. + */ + public static final int STATE_WAIT_FOR_ELECTION = 4; + + /** + * Name of the underlying native binder service + */ + public static final String SERVICE_NAME = "common_time.clock"; + + /** + * Class constructor. + * @throws android.os.RemoteException + */ + public CommonClock() + throws RemoteException { + mRemote = ServiceManager.getService(SERVICE_NAME); + if (null == mRemote) + throw new RemoteException(); + + mInterfaceDesc = mRemote.getInterfaceDescriptor(); + mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc); + mRemote.linkToDeath(mDeathHandler, 0); + registerTimelineChangeListener(); + } + + /** + * Handy class factory method. + */ + static public CommonClock create() { + CommonClock retVal; + + try { + retVal = new CommonClock(); + } + catch (RemoteException e) { + retVal = null; + } + + return retVal; + } + + /** + * Release all native resources held by this {@link android.os.CommonClock} instance. Once + * resources have been released, the {@link android.os.CommonClock} instance is disconnected from + * the native service and will throw a {@link android.os.RemoteException} if any of its + * methods are called. Clients should always call release on their client instances before + * releasing their last Java reference to the instance. Failure to do this will cause + * non-deterministic native resource reclamation and may cause the common time service to remain + * active on the network for longer than it should. + */ + public void release() { + unregisterTimelineChangeListener(); + if (null != mRemote) { + try { + mRemote.unlinkToDeath(mDeathHandler, 0); + } + catch (NoSuchElementException e) { } + mRemote = null; + } + mUtils = null; + } + + /** + * Gets the common clock's current time. + * + * @return a signed 64-bit value representing the current common time in microseconds, or the + * special value {@link #TIME_NOT_SYNCED} if the common time service is currently not + * synchronized. + * @throws android.os.RemoteException + */ + public long getTime() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetLong(METHOD_GET_COMMON_TIME, TIME_NOT_SYNCED); + } + + /** + * Gets the current estimation of common clock's synchronization accuracy from the common time + * service. + * + * @return a signed 32-bit value representing the common time service's estimation of + * synchronization accuracy in microseconds, or the special value + * {@link #ERROR_ESTIMATE_UNKNOWN} if the common time service is currently not synchronized. + * Negative values indicate that the local server estimates that the nominal common time is + * behind the local server's time (in other words, the local clock is running fast) Positive + * values indicate that the local server estimates that the nominal common time is ahead of the + * local server's time (in other words, the local clock is running slow) + * @throws android.os.RemoteException + */ + public int getEstimatedError() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetInt(METHOD_GET_ESTIMATED_ERROR, ERROR_ESTIMATE_UNKNOWN); + } + + /** + * Gets the ID of the timeline the common time service is currently synchronizing its clock to. + * + * @return a long representing the unique ID of the timeline the common time service is + * currently synchronizing with, or {@link #INVALID_TIMELINE_ID} if the common time service is + * currently not synchronized. + * @throws android.os.RemoteException + */ + public long getTimelineId() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetLong(METHOD_GET_TIMELINE_ID, INVALID_TIMELINE_ID); + } + + /** + * Gets the current state of this clock's common time service in the the master election + * algorithm. + * + * @return a integer indicating the current state of the this clock's common time service in the + * master election algorithm or {@link #STATE_INVALID} if there is an internal error. + * @throws android.os.RemoteException + */ + public int getState() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetInt(METHOD_GET_STATE, STATE_INVALID); + } + + /** + * Gets the IP address and UDP port of the current timeline master. + * + * @return an InetSocketAddress containing the IP address and UDP port of the current timeline + * master, or null if there is no current master. + * @throws android.os.RemoteException + */ + public InetSocketAddress getMasterAddr() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ADDRESS); + } + + /** + * The OnTimelineChangedListener interface defines a method called by the + * {@link android.os.CommonClock} instance to indicate that the time synchronization service has + * either synchronized with a new timeline, or is no longer a member of any timeline. The + * client application can implement this interface and register the listener with the + * {@link #setTimelineChangedListener(OnTimelineChangedListener)} method. + */ + public interface OnTimelineChangedListener { + /** + * Method called when the time service's timeline has changed. + * + * @param newTimelineId a long which uniquely identifies the timeline the time + * synchronization service is now a member of, or {@link #INVALID_TIMELINE_ID} if the the + * service is not synchronized to any timeline. + */ + void onTimelineChanged(long newTimelineId); + } + + /** + * Registers an OnTimelineChangedListener interface. + * <p>Call this method with a null listener to stop receiving server death notifications. + */ + public void setTimelineChangedListener(OnTimelineChangedListener listener) { + synchronized (mListenerLock) { + mTimelineChangedListener = listener; + } + } + + /** + * The OnServerDiedListener interface defines a method called by the + * {@link android.os.CommonClock} instance to indicate that the connection to the native media + * server has been broken and that the {@link android.os.CommonClock} instance will need to be + * released and re-created. The client application can implement this interface and register + * the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method. + */ + public interface OnServerDiedListener { + /** + * Method called when the native media server has died. <p>If the native common time + * service encounters a fatal error and needs to restart, the binder connection from the + * {@link android.os.CommonClock} instance to the common time service will be broken. To + * restore functionality, clients should {@link #release()} their old visualizer and create + * a new instance. + */ + void onServerDied(); + } + + /** + * Registers an OnServerDiedListener interface. + * <p>Call this method with a null listener to stop receiving server death notifications. + */ + public void setServerDiedListener(OnServerDiedListener listener) { + synchronized (mListenerLock) { + mServerDiedListener = listener; + } + } + + protected void finalize() throws Throwable { release(); } + + private void throwOnDeadServer() throws RemoteException { + if ((null == mRemote) || (null == mUtils)) + throw new RemoteException(); + } + + private final Object mListenerLock = new Object(); + private OnTimelineChangedListener mTimelineChangedListener = null; + private OnServerDiedListener mServerDiedListener = null; + + private IBinder mRemote = null; + private String mInterfaceDesc = ""; + private CommonTimeUtils mUtils; + + private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() { + public void binderDied() { + synchronized (mListenerLock) { + if (null != mServerDiedListener) + mServerDiedListener.onServerDied(); + } + } + }; + + private class TimelineChangedListener extends Binder { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case METHOD_CBK_ON_TIMELINE_CHANGED: + data.enforceInterface(DESCRIPTOR); + long timelineId = data.readLong(); + synchronized (mListenerLock) { + if (null != mTimelineChangedListener) + mTimelineChangedListener.onTimelineChanged(timelineId); + } + return true; + } + + return super.onTransact(code, data, reply, flags); + } + + private static final String DESCRIPTOR = "android.os.ICommonClockListener"; + }; + + private TimelineChangedListener mCallbackTgt = null; + + private void registerTimelineChangeListener() throws RemoteException { + if (null != mCallbackTgt) + return; + + boolean success = false; + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + mCallbackTgt = new TimelineChangedListener(); + + try { + data.writeInterfaceToken(mInterfaceDesc); + data.writeStrongBinder(mCallbackTgt); + mRemote.transact(METHOD_REGISTER_LISTENER, data, reply, 0); + success = (0 == reply.readInt()); + } + catch (RemoteException e) { + success = false; + } + finally { + reply.recycle(); + data.recycle(); + } + + // Did we catch a remote exception or fail to register our callback target? If so, our + // object must already be dead (or be as good as dead). Clear out all of our state so that + // our other methods will properly indicate a dead object. + if (!success) { + mCallbackTgt = null; + mRemote = null; + mUtils = null; + } + } + + private void unregisterTimelineChangeListener() { + if (null == mCallbackTgt) + return; + + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + + try { + data.writeInterfaceToken(mInterfaceDesc); + data.writeStrongBinder(mCallbackTgt); + mRemote.transact(METHOD_UNREGISTER_LISTENER, data, reply, 0); + } + catch (RemoteException e) { } + finally { + reply.recycle(); + data.recycle(); + mCallbackTgt = null; + } + } + + private static final int METHOD_IS_COMMON_TIME_VALID = IBinder.FIRST_CALL_TRANSACTION; + private static final int METHOD_COMMON_TIME_TO_LOCAL_TIME = METHOD_IS_COMMON_TIME_VALID + 1; + private static final int METHOD_LOCAL_TIME_TO_COMMON_TIME = METHOD_COMMON_TIME_TO_LOCAL_TIME + 1; + private static final int METHOD_GET_COMMON_TIME = METHOD_LOCAL_TIME_TO_COMMON_TIME + 1; + private static final int METHOD_GET_COMMON_FREQ = METHOD_GET_COMMON_TIME + 1; + private static final int METHOD_GET_LOCAL_TIME = METHOD_GET_COMMON_FREQ + 1; + private static final int METHOD_GET_LOCAL_FREQ = METHOD_GET_LOCAL_TIME + 1; + private static final int METHOD_GET_ESTIMATED_ERROR = METHOD_GET_LOCAL_FREQ + 1; + private static final int METHOD_GET_TIMELINE_ID = METHOD_GET_ESTIMATED_ERROR + 1; + private static final int METHOD_GET_STATE = METHOD_GET_TIMELINE_ID + 1; + private static final int METHOD_GET_MASTER_ADDRESS = METHOD_GET_STATE + 1; + private static final int METHOD_REGISTER_LISTENER = METHOD_GET_MASTER_ADDRESS + 1; + private static final int METHOD_UNREGISTER_LISTENER = METHOD_REGISTER_LISTENER + 1; + + private static final int METHOD_CBK_ON_TIMELINE_CHANGED = IBinder.FIRST_CALL_TRANSACTION; +} diff --git a/core/java/android/os/CommonTimeConfig.java b/core/java/android/os/CommonTimeConfig.java new file mode 100644 index 000000000000..3355ee33d486 --- /dev/null +++ b/core/java/android/os/CommonTimeConfig.java @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.NoSuchElementException; + +import android.os.CommonTimeUtils; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * Used for configuring and controlling the status of the android common time service. + * @hide + */ +public class CommonTimeConfig { + /** + * Successful operation. + */ + public static final int SUCCESS = 0; + /** + * Unspecified error. + */ + public static final int ERROR = -1; + /** + * Operation failed due to bad parameter value. + */ + public static final int ERROR_BAD_VALUE = -4; + /** + * Operation failed due to dead remote object. + */ + public static final int ERROR_DEAD_OBJECT = -7; + + /** + * Sentinel value returned by {@link #getMasterElectionGroupId()} when an error occurs trying to + * fetch the master election group. + */ + public static final long INVALID_GROUP_ID = -1; + + /** + * Name of the underlying native binder service + */ + public static final String SERVICE_NAME = "common_time.config"; + + /** + * Class constructor. + * @throws android.os.RemoteException + */ + public CommonTimeConfig() + throws RemoteException { + mRemote = ServiceManager.getService(SERVICE_NAME); + if (null == mRemote) + throw new RemoteException(); + + mInterfaceDesc = mRemote.getInterfaceDescriptor(); + mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc); + mRemote.linkToDeath(mDeathHandler, 0); + } + + /** + * Handy class factory method. + */ + static public CommonTimeConfig create() { + CommonTimeConfig retVal; + + try { + retVal = new CommonTimeConfig(); + } + catch (RemoteException e) { + retVal = null; + } + + return retVal; + } + + /** + * Release all native resources held by this {@link android.os.CommonTimeConfig} instance. Once + * resources have been released, the {@link android.os.CommonTimeConfig} instance is + * disconnected from the native service and will throw a {@link android.os.RemoteException} if + * any of its methods are called. Clients should always call release on their client instances + * before releasing their last Java reference to the instance. Failure to do this will cause + * non-deterministic native resource reclamation and may cause the common time service to remain + * active on the network for longer than it should. + */ + public void release() { + if (null != mRemote) { + try { + mRemote.unlinkToDeath(mDeathHandler, 0); + } + catch (NoSuchElementException e) { } + mRemote = null; + } + mUtils = null; + } + + /** + * Gets the current priority of the common time service used in the master election protocol. + * + * @return an 8 bit value indicating the priority of this common time service relative to other + * common time services operating in the same domain. + * @throws android.os.RemoteException + */ + public byte getMasterElectionPriority() + throws RemoteException { + throwOnDeadServer(); + return (byte)mUtils.transactGetInt(METHOD_GET_MASTER_ELECTION_PRIORITY, -1); + } + + /** + * Sets the current priority of the common time service used in the master election protocol. + * + * @param priority priority of the common time service used in the master election protocol. + * Lower numbers are lower priority. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setMasterElectionPriority(byte priority) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + return mUtils.transactSetInt(METHOD_SET_MASTER_ELECTION_PRIORITY, priority); + } + + /** + * Gets the IP endpoint used by the time service to participate in the master election protocol. + * + * @return an InetSocketAddress containing the IP address and UDP port being used by the + * system's common time service to participate in the master election protocol. + * @throws android.os.RemoteException + */ + public InetSocketAddress getMasterElectionEndpoint() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ELECTION_ENDPOINT); + } + + /** + * Sets the IP endpoint used by the common time service to participate in the master election + * protocol. + * + * @param ep The IP address and UDP port to be used by the common time service to participate in + * the master election protocol. The supplied IP address must be either the broadcast or + * multicast address, unicast addresses are considered to be illegal values. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setMasterElectionEndpoint(InetSocketAddress ep) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + return mUtils.transactSetSockaddr(METHOD_SET_MASTER_ELECTION_ENDPOINT, ep); + } + + /** + * Gets the current group ID used by the common time service in the master election protocol. + * + * @return The 64-bit group ID of the common time service. + * @throws android.os.RemoteException + */ + public long getMasterElectionGroupId() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetLong(METHOD_GET_MASTER_ELECTION_GROUP_ID, INVALID_GROUP_ID); + } + + /** + * Sets the current group ID used by the common time service in the master election protocol. + * + * @param id The 64-bit group ID of the common time service. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setMasterElectionGroupId(long id) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + return mUtils.transactSetLong(METHOD_SET_MASTER_ELECTION_GROUP_ID, id); + } + + /** + * Gets the name of the network interface which the common time service attempts to bind to. + * + * @return a string with the network interface name which the common time service is bound to, + * or null if the service is currently unbound. Examples of interface names are things like + * "eth0", or "wlan0". + * @throws android.os.RemoteException + */ + public String getInterfaceBinding() + throws RemoteException { + throwOnDeadServer(); + + String ifaceName = mUtils.transactGetString(METHOD_GET_INTERFACE_BINDING, null); + + if ((null != ifaceName) && (0 == ifaceName.length())) + return null; + + return ifaceName; + } + + /** + * Sets the name of the network interface which the common time service should attempt to bind + * to. + * + * @param ifaceName The name of the network interface ("eth0", "wlan0", etc...) wich the common + * time service should attempt to bind to, or null to force the common time service to unbind + * from the network and run in networkless mode. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setNetworkBinding(String ifaceName) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + + return mUtils.transactSetString(METHOD_SET_INTERFACE_BINDING, + (null == ifaceName) ? "" : ifaceName); + } + + /** + * Gets the amount of time the common time service will wait between master announcements when + * it is the timeline master. + * + * @return The time (in milliseconds) between master announcements. + * @throws android.os.RemoteException + */ + public int getMasterAnnounceInterval() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetInt(METHOD_GET_MASTER_ANNOUNCE_INTERVAL, -1); + } + + /** + * Sets the amount of time the common time service will wait between master announcements when + * it is the timeline master. + * + * @param interval The time (in milliseconds) between master announcements. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setMasterAnnounceInterval(int interval) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + return mUtils.transactSetInt(METHOD_SET_MASTER_ANNOUNCE_INTERVAL, interval); + } + + /** + * Gets the amount of time the common time service will wait between time synchronization + * requests when it is the client of another common time service on the network. + * + * @return The time (in milliseconds) between time sync requests. + * @throws android.os.RemoteException + */ + public int getClientSyncInterval() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetInt(METHOD_GET_CLIENT_SYNC_INTERVAL, -1); + } + + /** + * Sets the amount of time the common time service will wait between time synchronization + * requests when it is the client of another common time service on the network. + * + * @param interval The time (in milliseconds) between time sync requests. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setClientSyncInterval(int interval) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + return mUtils.transactSetInt(METHOD_SET_CLIENT_SYNC_INTERVAL, interval); + } + + /** + * Gets the panic threshold for the estimated error level of the common time service. When the + * common time service's estimated error rises above this level, the service will panic and + * reset, causing a discontinuity in the currently synchronized timeline. + * + * @return The threshold (in microseconds) past which the common time service will panic. + * @throws android.os.RemoteException + */ + public int getPanicThreshold() + throws RemoteException { + throwOnDeadServer(); + return mUtils.transactGetInt(METHOD_GET_PANIC_THRESHOLD, -1); + } + + /** + * Sets the panic threshold for the estimated error level of the common time service. When the + * common time service's estimated error rises above this level, the service will panic and + * reset, causing a discontinuity in the currently synchronized timeline. + * + * @param threshold The threshold (in microseconds) past which the common time service will + * panic. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setPanicThreshold(int threshold) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + return mUtils.transactSetInt(METHOD_SET_PANIC_THRESHOLD, threshold); + } + + /** + * Gets the current state of the common time service's auto disable flag. + * + * @return The current state of the common time service's auto disable flag. + * @throws android.os.RemoteException + */ + public boolean getAutoDisable() + throws RemoteException { + throwOnDeadServer(); + return (1 == mUtils.transactGetInt(METHOD_GET_AUTO_DISABLE, 1)); + } + + /** + * Sets the current state of the common time service's auto disable flag. When the time + * service's auto disable flag is set, it will automatically cease all network activity when + * it has no active local clients, resuming activity the next time the service has interested + * local clients. When the auto disabled flag is cleared, the common time service will continue + * to participate the time synchronization group even when it has no active local clients. + * + * @param autoDisable The desired state of the common time service's auto disable flag. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int setAutoDisable(boolean autoDisable) { + if (checkDeadServer()) + return ERROR_DEAD_OBJECT; + + return mUtils.transactSetInt(METHOD_SET_AUTO_DISABLE, autoDisable ? 1 : 0); + } + + /** + * At startup, the time service enters the initial state and remains there until it is given a + * network interface to bind to. Common time will be unavailable to clients of the common time + * service until the service joins a network (even an empty network). Devices may use the + * {@link #forceNetworklessMasterMode()} method to force a time service in the INITIAL state + * with no network configuration to assume MASTER status for a brand new timeline in order to + * allow clients of the common time service to operate, even though the device is isolated and + * not on any network. When a networkless master does join a network, it will defer to any + * masters already on the network, or continue to maintain the timeline it made up during its + * networkless state if no other masters are detected. Attempting to force a client into master + * mode while it is actively bound to a network will fail with the status code {@link #ERROR} + * + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure. + */ + public int forceNetworklessMasterMode() { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + + try { + data.writeInterfaceToken(mInterfaceDesc); + mRemote.transact(METHOD_FORCE_NETWORKLESS_MASTER_MODE, data, reply, 0); + + return reply.readInt(); + } + catch (RemoteException e) { + return ERROR_DEAD_OBJECT; + } + finally { + reply.recycle(); + data.recycle(); + } + } + + /** + * The OnServerDiedListener interface defines a method called by the + * {@link android.os.CommonTimeConfig} instance to indicate that the connection to the native + * media server has been broken and that the {@link android.os.CommonTimeConfig} instance will + * need to be released and re-created. The client application can implement this interface and + * register the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method. + */ + public interface OnServerDiedListener { + /** + * Method called when the native common time service has died. <p>If the native common time + * service encounters a fatal error and needs to restart, the binder connection from the + * {@link android.os.CommonTimeConfig} instance to the common time service will be broken. + */ + void onServerDied(); + } + + /** + * Registers an OnServerDiedListener interface. + * <p>Call this method with a null listener to stop receiving server death notifications. + */ + public void setServerDiedListener(OnServerDiedListener listener) { + synchronized (mListenerLock) { + mServerDiedListener = listener; + } + } + + protected void finalize() throws Throwable { release(); } + + private boolean checkDeadServer() { + return ((null == mRemote) || (null == mUtils)); + } + + private void throwOnDeadServer() throws RemoteException { + if (checkDeadServer()) + throw new RemoteException(); + } + + private final Object mListenerLock = new Object(); + private OnServerDiedListener mServerDiedListener = null; + + private IBinder mRemote = null; + private String mInterfaceDesc = ""; + private CommonTimeUtils mUtils; + + private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() { + public void binderDied() { + synchronized (mListenerLock) { + if (null != mServerDiedListener) + mServerDiedListener.onServerDied(); + } + } + }; + + private static final int METHOD_GET_MASTER_ELECTION_PRIORITY = IBinder.FIRST_CALL_TRANSACTION; + private static final int METHOD_SET_MASTER_ELECTION_PRIORITY = METHOD_GET_MASTER_ELECTION_PRIORITY + 1; + private static final int METHOD_GET_MASTER_ELECTION_ENDPOINT = METHOD_SET_MASTER_ELECTION_PRIORITY + 1; + private static final int METHOD_SET_MASTER_ELECTION_ENDPOINT = METHOD_GET_MASTER_ELECTION_ENDPOINT + 1; + private static final int METHOD_GET_MASTER_ELECTION_GROUP_ID = METHOD_SET_MASTER_ELECTION_ENDPOINT + 1; + private static final int METHOD_SET_MASTER_ELECTION_GROUP_ID = METHOD_GET_MASTER_ELECTION_GROUP_ID + 1; + private static final int METHOD_GET_INTERFACE_BINDING = METHOD_SET_MASTER_ELECTION_GROUP_ID + 1; + private static final int METHOD_SET_INTERFACE_BINDING = METHOD_GET_INTERFACE_BINDING + 1; + private static final int METHOD_GET_MASTER_ANNOUNCE_INTERVAL = METHOD_SET_INTERFACE_BINDING + 1; + private static final int METHOD_SET_MASTER_ANNOUNCE_INTERVAL = METHOD_GET_MASTER_ANNOUNCE_INTERVAL + 1; + private static final int METHOD_GET_CLIENT_SYNC_INTERVAL = METHOD_SET_MASTER_ANNOUNCE_INTERVAL + 1; + private static final int METHOD_SET_CLIENT_SYNC_INTERVAL = METHOD_GET_CLIENT_SYNC_INTERVAL + 1; + private static final int METHOD_GET_PANIC_THRESHOLD = METHOD_SET_CLIENT_SYNC_INTERVAL + 1; + private static final int METHOD_SET_PANIC_THRESHOLD = METHOD_GET_PANIC_THRESHOLD + 1; + private static final int METHOD_GET_AUTO_DISABLE = METHOD_SET_PANIC_THRESHOLD + 1; + private static final int METHOD_SET_AUTO_DISABLE = METHOD_GET_AUTO_DISABLE + 1; + private static final int METHOD_FORCE_NETWORKLESS_MASTER_MODE = METHOD_SET_AUTO_DISABLE + 1; +} diff --git a/core/java/android/os/CommonTimeUtils.java b/core/java/android/os/CommonTimeUtils.java new file mode 100644 index 000000000000..9081ee411d61 --- /dev/null +++ b/core/java/android/os/CommonTimeUtils.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os; + +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import static libcore.io.OsConstants.*; + +class CommonTimeUtils { + /** + * Successful operation. + */ + public static final int SUCCESS = 0; + /** + * Unspecified error. + */ + public static final int ERROR = -1; + /** + * Operation failed due to bad parameter value. + */ + public static final int ERROR_BAD_VALUE = -4; + /** + * Operation failed due to dead remote object. + */ + public static final int ERROR_DEAD_OBJECT = -7; + + public CommonTimeUtils(IBinder remote, String interfaceDesc) { + mRemote = remote; + mInterfaceDesc = interfaceDesc; + } + + public int transactGetInt(int method_code, int error_ret_val) + throws RemoteException { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + int ret_val; + + try { + int res; + data.writeInterfaceToken(mInterfaceDesc); + mRemote.transact(method_code, data, reply, 0); + + res = reply.readInt(); + ret_val = (0 == res) ? reply.readInt() : error_ret_val; + } + finally { + reply.recycle(); + data.recycle(); + } + + return ret_val; + } + + public int transactSetInt(int method_code, int val) { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + + try { + data.writeInterfaceToken(mInterfaceDesc); + data.writeInt(val); + mRemote.transact(method_code, data, reply, 0); + + return reply.readInt(); + } + catch (RemoteException e) { + return ERROR_DEAD_OBJECT; + } + finally { + reply.recycle(); + data.recycle(); + } + } + + public long transactGetLong(int method_code, long error_ret_val) + throws RemoteException { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + long ret_val; + + try { + int res; + data.writeInterfaceToken(mInterfaceDesc); + mRemote.transact(method_code, data, reply, 0); + + res = reply.readInt(); + ret_val = (0 == res) ? reply.readLong() : error_ret_val; + } + finally { + reply.recycle(); + data.recycle(); + } + + return ret_val; + } + + public int transactSetLong(int method_code, long val) { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + + try { + data.writeInterfaceToken(mInterfaceDesc); + data.writeLong(val); + mRemote.transact(method_code, data, reply, 0); + + return reply.readInt(); + } + catch (RemoteException e) { + return ERROR_DEAD_OBJECT; + } + finally { + reply.recycle(); + data.recycle(); + } + } + + public String transactGetString(int method_code, String error_ret_val) + throws RemoteException { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + String ret_val; + + try { + int res; + data.writeInterfaceToken(mInterfaceDesc); + mRemote.transact(method_code, data, reply, 0); + + res = reply.readInt(); + ret_val = (0 == res) ? reply.readString() : error_ret_val; + } + finally { + reply.recycle(); + data.recycle(); + } + + return ret_val; + } + + public int transactSetString(int method_code, String val) { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + + try { + data.writeInterfaceToken(mInterfaceDesc); + data.writeString(val); + mRemote.transact(method_code, data, reply, 0); + + return reply.readInt(); + } + catch (RemoteException e) { + return ERROR_DEAD_OBJECT; + } + finally { + reply.recycle(); + data.recycle(); + } + } + + public InetSocketAddress transactGetSockaddr(int method_code) + throws RemoteException { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + InetSocketAddress ret_val = null; + + try { + int res; + data.writeInterfaceToken(mInterfaceDesc); + mRemote.transact(method_code, data, reply, 0); + + res = reply.readInt(); + if (0 == res) { + int type; + int port = 0; + String addrStr = null; + + type = reply.readInt(); + + if (AF_INET == type) { + int addr = reply.readInt(); + port = reply.readInt(); + addrStr = String.format("%d.%d.%d.%d", (addr >> 24) & 0xFF, + (addr >> 16) & 0xFF, + (addr >> 8) & 0xFF, + addr & 0xFF); + } else if (AF_INET6 == type) { + int addr1 = reply.readInt(); + int addr2 = reply.readInt(); + int addr3 = reply.readInt(); + int addr4 = reply.readInt(); + + port = reply.readInt(); + + int flowinfo = reply.readInt(); + int scope_id = reply.readInt(); + + addrStr = String.format("[%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X]", + (addr1 >> 16) & 0xFFFF, addr1 & 0xFFFF, + (addr2 >> 16) & 0xFFFF, addr2 & 0xFFFF, + (addr3 >> 16) & 0xFFFF, addr3 & 0xFFFF, + (addr4 >> 16) & 0xFFFF, addr4 & 0xFFFF); + } + + if (null != addrStr) { + ret_val = new InetSocketAddress(addrStr, port); + } + } + } + finally { + reply.recycle(); + data.recycle(); + } + + return ret_val; + } + + public int transactSetSockaddr(int method_code, InetSocketAddress addr) { + android.os.Parcel data = android.os.Parcel.obtain(); + android.os.Parcel reply = android.os.Parcel.obtain(); + int ret_val = ERROR; + + try { + data.writeInterfaceToken(mInterfaceDesc); + + if (null == addr) { + data.writeInt(0); + } else { + data.writeInt(1); + final InetAddress a = addr.getAddress(); + final byte[] b = a.getAddress(); + final int p = addr.getPort(); + + if (a instanceof Inet4Address) { + int v4addr = (((int)b[0] & 0xFF) << 24) | + (((int)b[1] & 0xFF) << 16) | + (((int)b[2] & 0xFF) << 8) | + ((int)b[3] & 0xFF); + + data.writeInt(AF_INET); + data.writeInt(v4addr); + data.writeInt(p); + } else + if (a instanceof Inet6Address) { + int i; + Inet6Address v6 = (Inet6Address)a; + data.writeInt(AF_INET6); + for (i = 0; i < 4; ++i) { + int aword = (((int)b[(i*4) + 0] & 0xFF) << 24) | + (((int)b[(i*4) + 1] & 0xFF) << 16) | + (((int)b[(i*4) + 2] & 0xFF) << 8) | + ((int)b[(i*4) + 3] & 0xFF); + data.writeInt(aword); + } + data.writeInt(p); + data.writeInt(0); // flow info + data.writeInt(v6.getScopeId()); + } else { + return ERROR_BAD_VALUE; + } + } + + mRemote.transact(method_code, data, reply, 0); + ret_val = reply.readInt(); + } + catch (RemoteException e) { + ret_val = ERROR_DEAD_OBJECT; + } + finally { + reply.recycle(); + data.recycle(); + } + + return ret_val; + } + + private IBinder mRemote; + private String mInterfaceDesc; +}; diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index 844ed6ac5d8b..b816b110ac44 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -75,13 +75,13 @@ public final class Message implements Parcelable { public Messenger replyTo; /** If set message is in use */ - /*package*/ static final int FLAG_IN_USE = 1; + /*package*/ static final int FLAG_IN_USE = 1 << 0; - /** Flags reserved for future use (All are reserved for now) */ - /*package*/ static final int FLAGS_RESERVED = ~FLAG_IN_USE; + /** If set message is asynchronous */ + /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; /** Flags to clear in the copyFrom method */ - /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAGS_RESERVED | FLAG_IN_USE; + /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; /*package*/ int flags; @@ -363,6 +363,48 @@ public final class Message implements Parcelable { target.sendMessage(this); } + /** + * Returns true if the message is asynchronous. + * + * Asynchronous messages represent interrupts or events that do not require global ordering + * with represent to synchronous messages. Asynchronous messages are not subject to + * the synchronization barriers introduced by {@link MessageQueue#acquireSyncBarrier()}. + * + * @return True if the message is asynchronous. + * + * @see #setAsynchronous(boolean) + * @see MessageQueue#acquireSyncBarrier() + * @see MessageQueue#releaseSyncBarrier() + * + * @hide + */ + public boolean isAsynchronous() { + return (flags & FLAG_ASYNCHRONOUS) != 0; + } + + /** + * Sets whether the message is asynchronous. + * + * Asynchronous messages represent interrupts or events that do not require global ordering + * with represent to synchronous messages. Asynchronous messages are not subject to + * the synchronization barriers introduced by {@link MessageQueue#acquireSyncBarrier()}. + * + * @param async True if the message is asynchronous. + * + * @see #isAsynchronous() + * @see MessageQueue#acquireSyncBarrier() + * @see MessageQueue#releaseSyncBarrier() + * + * @hide + */ + public void setAsynchronous(boolean async) { + if (async) { + flags |= FLAG_ASYNCHRONOUS; + } else { + flags &= ~FLAG_ASYNCHRONOUS; + } + } + /*package*/ void clearForRecycle() { flags = 0; what = 0; diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index a658fc45dae4..11dc1248c979 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -39,6 +39,9 @@ public class MessageQueue { // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout. private boolean mBlocked; + // Indicates the barrier nesting level. + private int mBarrierNestCount; + @SuppressWarnings("unused") private int mPtr; // used by native code @@ -93,7 +96,53 @@ public class MessageQueue { mIdleHandlers.remove(handler); } } - + + /** + * Acquires a synchronization barrier. + * + * While a synchronization barrier is active, only asynchronous messages are + * permitted to execute. Synchronous messages are retained but are not executed + * until the synchronization barrier is released. + * + * This method is used to immediately postpone execution of all synchronous messages + * until a condition is met that releases the barrier. Asynchronous messages are + * exempt from the barrier and continue to be executed as usual. + * + * This call nests and must be matched by an equal number of calls to + * {@link #releaseSyncBarrier}. + * + * @hide + */ + public final void acquireSyncBarrier() { + synchronized (this) { + mBarrierNestCount += 1; + } + } + + /** + * Releases a synchronization barrier. + * + * This class undoes one invocation of {@link #acquireSyncBarrier}. + * + * @throws IllegalStateException if the barrier is not acquired. + * + * @hide + */ + public final void releaseSyncBarrier() { + synchronized (this) { + if (mBarrierNestCount == 0) { + throw new IllegalStateException("The message queue synchronization barrier " + + "has not been acquired."); + } + + mBarrierNestCount -= 1; + if (!mBlocked || mMessages == null) { + return; + } + } + nativeWake(mPtr); + } + MessageQueue() { nativeInit(); } @@ -120,28 +169,49 @@ public class MessageQueue { synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); - final Message msg = mMessages; - if (msg != null) { + + Message prevMsg = null; + Message msg = mMessages; + for (;;) { + if (msg == null) { + // No more messages. + nextPollTimeoutMillis = -1; + break; + } + final long when = msg.when; - if (now >= when) { + if (now < when) { + // Next message is not ready. Set a timeout to wake up when it is ready. + nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE); + break; + } + + if (mBarrierNestCount == 0 || msg.isAsynchronous()) { + // Got a message. mBlocked = false; - mMessages = msg.next; + if (prevMsg != null) { + prevMsg.next = msg.next; + } else { + mMessages = msg.next; + } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); msg.markInUse(); return msg; - } else { - nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE); } - } else { - nextPollTimeoutMillis = -1; + + // We have a message that we could return except that it is + // blocked by the sync barrier. In particular, this means that + // we are not idle yet, so we do not want to run the idle handlers. + prevMsg = msg; + msg = msg.next; } - // If first time, then get the number of idlers to run. - if (pendingIdleHandlerCount < 0) { + // If first time idle, then get the number of idlers to run. + if (pendingIdleHandlerCount < 0 && msg == mMessages) { pendingIdleHandlerCount = mIdleHandlers.size(); } - if (pendingIdleHandlerCount == 0) { + if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; @@ -205,10 +275,15 @@ public class MessageQueue { //Log.d("MessageQueue", "Enqueing: " + msg); Message p = mMessages; if (p == null || when == 0 || when < p.when) { + // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; - needWake = mBlocked; // new head, might need to wake up + needWake = mBlocked; } else { + // Inserted within the middle of the queue. Usually we don't have to wake + // up the event queue unless the message is asynchronous and it might be + // possible for it to be returned out of sequence relative to an earlier + // synchronous message at the head of the queue. Message prev = null; while (p != null && p.when <= when) { prev = p; @@ -216,7 +291,8 @@ public class MessageQueue { } msg.next = prev.next; prev.next = msg; - needWake = false; // still waiting on head, no need to wake up + needWake = mBlocked && mBarrierNestCount != 0 && msg.isAsynchronous() + && !mMessages.isAsynchronous(); } } if (needWake) { diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 3e2d7fc60241..a74b73754bac 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -16,8 +16,6 @@ package android.view; -import com.android.internal.util.ArrayUtils; - import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -28,8 +26,8 @@ import android.util.Log; /** * Coordinates animations and drawing for UI on a particular thread. * - * This object is thread-safe. Other threads can add and remove listeners - * or schedule work to occur at a later time on the UI thread. + * This object is thread-safe. Other threads can post callbacks to run at a later time + * on the UI thread. * * Ensuring thread-safety is a little tricky because the {@link DisplayEventReceiver} * can only be accessed from the UI thread so operations that touch the event receiver @@ -37,14 +35,10 @@ import android.util.Log; * * @hide */ -public final class Choreographer extends Handler { +public final class Choreographer { private static final String TAG = "Choreographer"; private static final boolean DEBUG = false; - // Amount of time in ms to wait before actually disposing of the display event - // receiver after all listeners have been removed. - private static final long DISPOSE_RECEIVER_DELAY = 200; - // The default amount of time in ms between animation frames. // When vsync is not enabled, we want to have some idea of how long we should // wait before posting the next animation message. It is important that the @@ -87,25 +81,27 @@ public final class Choreographer extends Handler { private static final int MSG_DO_ANIMATION = 0; private static final int MSG_DO_DRAW = 1; private static final int MSG_DO_SCHEDULE_VSYNC = 2; - private static final int MSG_DO_DISPOSE_RECEIVER = 3; private final Object mLock = new Object(); private final Looper mLooper; + private final FrameHandler mHandler; + private final FrameDisplayEventReceiver mDisplayEventReceiver; - private OnAnimateListener[] mOnAnimateListeners; - private OnDrawListener[] mOnDrawListeners; + private Callback mCallbackPool; + + private Callback mAnimationCallbacks; + private Callback mDrawCallbacks; private boolean mAnimationScheduled; private boolean mDrawScheduled; - private boolean mFrameDisplayEventReceiverNeeded; - private FrameDisplayEventReceiver mFrameDisplayEventReceiver; private long mLastAnimationTime; private long mLastDrawTime; private Choreographer(Looper looper) { - super(looper); mLooper = looper; + mHandler = new FrameHandler(looper); + mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; mLastAnimationTime = Long.MIN_VALUE; mLastDrawTime = Long.MIN_VALUE; } @@ -154,14 +150,77 @@ public final class Choreographer extends Handler { } /** - * Schedules animation (and drawing) to occur on the next frame synchronization boundary. + * Posts a callback to run on the next animation cycle and schedules an animation cycle. + * The callback only runs once and then is automatically removed. + * + * @param runnable The callback to run during the next animation cycle. + * + * @see #removeAnimationCallback */ - public void scheduleAnimation() { + public void postAnimationCallback(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("runnable must not be null"); + } synchronized (mLock) { + mAnimationCallbacks = addCallbackLocked(mAnimationCallbacks, runnable); scheduleAnimationLocked(); } } + /** + * Removes an animation callback. + * Does nothing if the specified animation callback has not been posted or has already + * been removed. + * + * @param runnable The animation callback to remove. + * + * @see #postAnimationCallback + */ + public void removeAnimationCallback(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("runnable must not be null"); + } + synchronized (mLock) { + mAnimationCallbacks = removeCallbackLocked(mAnimationCallbacks, runnable); + } + } + + /** + * Posts a callback to run on the next draw cycle and schedules a draw cycle. + * The callback only runs once and then is automatically removed. + * + * @param runnable The callback to run during the next draw cycle. + * + * @see #removeDrawCallback + */ + public void postDrawCallback(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("runnable must not be null"); + } + synchronized (mLock) { + mDrawCallbacks = addCallbackLocked(mDrawCallbacks, runnable); + scheduleDrawLocked(); + } + } + + /** + * Removes a draw callback. + * Does nothing if the specified draw callback has not been posted or has already + * been removed. + * + * @param runnable The draw callback to remove. + * + * @see #postDrawCallback + */ + public void removeDrawCallback(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("runnable must not be null"); + } + synchronized (mLock) { + mDrawCallbacks = removeCallbackLocked(mDrawCallbacks, runnable); + } + } + private void scheduleAnimationLocked() { if (!mAnimationScheduled) { mAnimationScheduled = true; @@ -173,16 +232,11 @@ public final class Choreographer extends Handler { // If running on the Looper thread, then schedule the vsync immediately, // otherwise post a message to schedule the vsync from the UI thread // as soon as possible. - if (!mFrameDisplayEventReceiverNeeded) { - mFrameDisplayEventReceiverNeeded = true; - if (mFrameDisplayEventReceiver != null) { - removeMessages(MSG_DO_DISPOSE_RECEIVER); - } - } if (isRunningOnLooperThreadLocked()) { doScheduleVsyncLocked(); } else { - sendMessageAtFrontOfQueue(obtainMessage(MSG_DO_SCHEDULE_VSYNC)); + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC)); } } else { final long now = SystemClock.uptimeMillis(); @@ -190,72 +244,26 @@ public final class Choreographer extends Handler { if (DEBUG) { Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms."); } - sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime); + mHandler.sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime); } } } - /** - * Returns true if {@link #scheduleAnimation()} has been called but - * {@link OnAnimateListener#onAnimate() OnAnimateListener.onAnimate()} has - * not yet been called. - */ - public boolean isAnimationScheduled() { - synchronized (mLock) { - return mAnimationScheduled; - } - } - - /** - * Schedules drawing to occur on the next frame synchronization boundary. - * Must be called on the UI thread. - */ - public void scheduleDraw() { - synchronized (mLock) { - if (!mDrawScheduled) { - mDrawScheduled = true; - if (USE_ANIMATION_TIMER_FOR_DRAW) { - scheduleAnimationLocked(); - } else { - if (DEBUG) { - Log.d(TAG, "Scheduling draw immediately."); - } - sendEmptyMessage(MSG_DO_DRAW); + private void scheduleDrawLocked() { + if (!mDrawScheduled) { + mDrawScheduled = true; + if (USE_ANIMATION_TIMER_FOR_DRAW) { + scheduleAnimationLocked(); + } else { + if (DEBUG) { + Log.d(TAG, "Scheduling draw immediately."); } + mHandler.sendEmptyMessage(MSG_DO_DRAW); } } } - /** - * Returns true if {@link #scheduleDraw()} has been called but - * {@link OnDrawListener#onDraw() OnDrawListener.onDraw()} has - * not yet been called. - */ - public boolean isDrawScheduled() { - synchronized (mLock) { - return mDrawScheduled; - } - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_DO_ANIMATION: - doAnimation(); - break; - case MSG_DO_DRAW: - doDraw(); - break; - case MSG_DO_SCHEDULE_VSYNC: - doScheduleVsync(); - break; - case MSG_DO_DISPOSE_RECEIVER: - doDisposeReceiver(); - break; - } - } - - private void doAnimation() { + void doAnimation() { doAnimationInner(); if (USE_ANIMATION_TIMER_FOR_DRAW) { @@ -263,9 +271,9 @@ public final class Choreographer extends Handler { } } - private void doAnimationInner() { + void doAnimationInner() { final long start; - final OnAnimateListener[] listeners; + final Callback callbacks; synchronized (mLock) { if (!mAnimationScheduled) { return; // no work to do @@ -279,12 +287,14 @@ public final class Choreographer extends Handler { } mLastAnimationTime = start; - listeners = mOnAnimateListeners; + callbacks = mAnimationCallbacks; + mAnimationCallbacks = null; } - if (listeners != null) { - for (int i = 0; i < listeners.length; i++) { - listeners[i].onAnimate(); + if (callbacks != null) { + runCallbacks(callbacks); + synchronized (mLock) { + recycleCallbacksLocked(callbacks); } } @@ -293,9 +303,9 @@ public final class Choreographer extends Handler { } } - private void doDraw() { + void doDraw() { final long start; - final OnDrawListener[] listeners; + final Callback callbacks; synchronized (mLock) { if (!mDrawScheduled) { return; // no work to do @@ -309,12 +319,14 @@ public final class Choreographer extends Handler { } mLastDrawTime = start; - listeners = mOnDrawListeners; + callbacks = mDrawCallbacks; + mDrawCallbacks = null; } - if (listeners != null) { - for (int i = 0; i < listeners.length; i++) { - listeners[i].onDraw(); + if (callbacks != null) { + runCallbacks(callbacks); + synchronized (mLock) { + recycleCallbacksLocked(callbacks); } } @@ -323,171 +335,106 @@ public final class Choreographer extends Handler { } } - private void doScheduleVsync() { + void doScheduleVsync() { synchronized (mLock) { doScheduleVsyncLocked(); } } private void doScheduleVsyncLocked() { - if (mFrameDisplayEventReceiverNeeded && mAnimationScheduled) { - if (mFrameDisplayEventReceiver == null) { - mFrameDisplayEventReceiver = new FrameDisplayEventReceiver(mLooper); - } - mFrameDisplayEventReceiver.scheduleVsync(); + if (mAnimationScheduled) { + mDisplayEventReceiver.scheduleVsync(); } } - private void doDisposeReceiver() { - synchronized (mLock) { - if (!mFrameDisplayEventReceiverNeeded && mFrameDisplayEventReceiver != null) { - mFrameDisplayEventReceiver.dispose(); - mFrameDisplayEventReceiver = null; - } - } + private boolean isRunningOnLooperThreadLocked() { + return Looper.myLooper() == mLooper; } - /** - * Adds an animation listener. - * - * @param listener The listener to add. - */ - public void addOnAnimateListener(OnAnimateListener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - - if (DEBUG) { - Log.d(TAG, "Adding onAnimate listener: " + listener); + private Callback addCallbackLocked(Callback head, Runnable runnable) { + Callback callback = obtainCallbackLocked(runnable); + if (head == null) { + return callback; } - - synchronized (mLock) { - mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class, - mOnAnimateListeners, listener); + Callback tail = head; + while (tail.next != null) { + tail = tail.next; } + tail.next = callback; + return head; } - /** - * Removes an animation listener. - * - * @param listener The listener to remove. - */ - public void removeOnAnimateListener(OnAnimateListener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - - if (DEBUG) { - Log.d(TAG, "Removing onAnimate listener: " + listener); - } - - synchronized (mLock) { - mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class, - mOnAnimateListeners, listener); - stopTimingLoopIfNoListenersLocked(); + private Callback removeCallbackLocked(Callback head, Runnable runnable) { + Callback predecessor = null; + for (Callback callback = head; callback != null;) { + final Callback next = callback.next; + if (callback.runnable == runnable) { + if (predecessor != null) { + predecessor.next = next; + } else { + head = next; + } + recycleCallbackLocked(callback); + } else { + predecessor = callback; + } + callback = next; } + return head; } - /** - * Adds a draw listener. - * - * @param listener The listener to add. - */ - public void addOnDrawListener(OnDrawListener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - - if (DEBUG) { - Log.d(TAG, "Adding onDraw listener: " + listener); - } - - synchronized (mLock) { - mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class, - mOnDrawListeners, listener); + private void runCallbacks(Callback head) { + while (head != null) { + head.runnable.run(); + head = head.next; } } - /** - * Removes a draw listener. - * Must be called on the UI thread. - * - * @param listener The listener to remove. - */ - public void removeOnDrawListener(OnDrawListener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } - - if (DEBUG) { - Log.d(TAG, "Removing onDraw listener: " + listener); - } - - synchronized (mLock) { - mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class, - mOnDrawListeners, listener); - stopTimingLoopIfNoListenersLocked(); + private void recycleCallbacksLocked(Callback head) { + while (head != null) { + final Callback next = head.next; + recycleCallbackLocked(head); + head = next; } } - private void stopTimingLoopIfNoListenersLocked() { - if (mOnDrawListeners == null && mOnAnimateListeners == null) { - if (DEBUG) { - Log.d(TAG, "Stopping timing loop."); - } - - if (mAnimationScheduled) { - mAnimationScheduled = false; - if (USE_VSYNC) { - removeMessages(MSG_DO_SCHEDULE_VSYNC); - } else { - removeMessages(MSG_DO_ANIMATION); - } - } - - if (mDrawScheduled) { - mDrawScheduled = false; - if (!USE_ANIMATION_TIMER_FOR_DRAW) { - removeMessages(MSG_DO_DRAW); - } - } - - // Post a message to dispose the display event receiver if we haven't needed - // it again after a certain amount of time has elapsed. Another reason to - // defer disposal is that it is possible for use to attempt to dispose the - // receiver while handling a vsync event that it dispatched, which might - // cause a few problems... - if (mFrameDisplayEventReceiverNeeded) { - mFrameDisplayEventReceiverNeeded = false; - if (mFrameDisplayEventReceiver != null) { - sendEmptyMessageDelayed(MSG_DO_DISPOSE_RECEIVER, DISPOSE_RECEIVER_DELAY); - } - } + private Callback obtainCallbackLocked(Runnable runnable) { + Callback callback = mCallbackPool; + if (callback == null) { + callback = new Callback(); + } else { + mCallbackPool = callback.next; + callback.next = null; } + callback.runnable = runnable; + return callback; } - private boolean isRunningOnLooperThreadLocked() { - return Looper.myLooper() == mLooper; + private void recycleCallbackLocked(Callback callback) { + callback.runnable = null; + callback.next = mCallbackPool; + mCallbackPool = callback; } - /** - * Listens for animation frame timing events. - */ - public static interface OnAnimateListener { - /** - * Called to animate properties before drawing the frame. - */ - public void onAnimate(); - } + private final class FrameHandler extends Handler { + public FrameHandler(Looper looper) { + super(looper); + } - /** - * Listens for draw frame timing events. - */ - public static interface OnDrawListener { - /** - * Called to draw the frame. - */ - public void onDraw(); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DO_ANIMATION: + doAnimation(); + break; + case MSG_DO_DRAW: + doDraw(); + break; + case MSG_DO_SCHEDULE_VSYNC: + doScheduleVsync(); + break; + } + } } private final class FrameDisplayEventReceiver extends DisplayEventReceiver { @@ -500,4 +447,9 @@ public final class Choreographer extends Handler { doAnimation(); } } + + private static final class Callback { + public Callback next; + public Runnable runnable; + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 613a766dfbac..6327c66a1ee8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3788,6 +3788,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onFocusChanged(false, 0, null); refreshDrawableState(); + + // The view cleared focus and invoked the callbacks, so now is the + // time to give focus to the the first focusable from the top to + // ensure that the gain focus is announced after clear focus. + getRootView().requestFocus(FOCUS_FORWARD); } } @@ -9575,14 +9580,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * @hide + * Force padding depending on layout direction. */ - protected void resolvePadding() { + public void resolvePadding() { // If the user specified the absolute padding (either with android:padding or // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise // use the default padding or the padding from the background drawable // (stored at this point in mPadding*) - switch (getResolvedLayoutDirection()) { + int resolvedLayoutDirection = getResolvedLayoutDirection(); + switch (resolvedLayoutDirection) { case LAYOUT_DIRECTION_RTL: // Start user padding override Right user padding. Otherwise, if Right user // padding is not defined, use the default Right padding. If Right user padding @@ -9618,6 +9624,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom; recomputePadding(); + onResolvePadding(resolvedLayoutDirection); + } + + /** + * Resolve padding depending on the layout direction. Subclasses that care about + * padding resolution should override this method. The default implementation does + * nothing. + * + * @param layoutDirection the direction of the layout + * + */ + public void onResolvePadding(int layoutDirection) { } /** @@ -12244,8 +12262,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param top the top padding in pixels * @param end the end padding in pixels * @param bottom the bottom padding in pixels - * - * @hide */ public void setPaddingRelative(int start, int top, int end, int bottom) { mUserPaddingRelative = true; @@ -12300,8 +12316,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * scrollbars as well. * * @return the start padding in pixels - * - * @hide */ public int getPaddingStart() { return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? @@ -12325,8 +12339,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * scrollbars as well. * * @return the end padding in pixels - * - * @hide */ public int getPaddingEnd() { return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? @@ -12340,8 +12352,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @attr ref android.R.styleable#View_paddingEnd * * @return true if the padding is relative or false if it is not. - * - * @hide */ public boolean isPaddingRelative() { return mUserPaddingRelative; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d40d8af88ec6..e6a833494284 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -675,11 +675,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public void clearFocus() { - super.clearFocus(); - - // clear any child focus if it exists - if (mFocused != null) { + if (DBG) { + System.out.println(this + " clearFocus()"); + } + if (mFocused == null) { + super.clearFocus(); + } else { mFocused.clearFocus(); + mFocused = null; } } @@ -691,12 +694,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (DBG) { System.out.println(this + " unFocus()"); } - - super.unFocus(); - if (mFocused != null) { + if (mFocused == null) { + super.unFocus(); + } else { mFocused.unFocus(); + mFocused = null; } - mFocused = null; } /** @@ -5125,20 +5128,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * The start margin in pixels of the child. - * - * @hide - * + * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value + * to this field. */ @ViewDebug.ExportedProperty(category = "layout") - protected int startMargin = DEFAULT_RELATIVE; + public int startMargin = DEFAULT_RELATIVE; /** * The end margin in pixels of the child. - * - * @hide + * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value + * to this field. */ @ViewDebug.ExportedProperty(category = "layout") - protected int endMargin = DEFAULT_RELATIVE; + public int endMargin = DEFAULT_RELATIVE; /** * The default start and end margin. @@ -5270,8 +5272,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart * * @return the start margin in pixels. - * - * @hide */ public int getMarginStart() { return startMargin; @@ -5283,8 +5283,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd * * @return the end margin in pixels. - * - * @hide */ public int getMarginEnd() { return endMargin; @@ -5297,8 +5295,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd * * @return true if either marginStart or marginEnd has been set - * - * @hide */ public boolean isMarginRelative() { return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1c3bbfa42d26..fbcb423dbfe7 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -97,8 +97,7 @@ import java.util.List; */ @SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"}) public final class ViewRootImpl extends Handler implements ViewParent, - View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks, - Choreographer.OnDrawListener { + View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { private static final String TAG = "ViewRootImpl"; private static final boolean DBG = false; private static final boolean LOCAL_LOGV = false; @@ -174,6 +173,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, View mView; View mFocusedView; View mRealFocusedView; // this is not set to null in touch mode + View mOldFocusedView; int mViewVisibility; boolean mAppVisible = true; int mOrigWindowType = -1; @@ -462,8 +462,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { - mChoreographer.addOnDrawListener(this); - mView = view; mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs); @@ -840,7 +838,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; - mChoreographer.scheduleDraw(); + scheduleFrame(); } } @@ -848,8 +846,21 @@ public final class ViewRootImpl extends Handler implements ViewParent, mTraversalScheduled = false; } - @Override - public void onDraw() { + void scheduleFrame() { + if (!mFrameScheduled) { + mChoreographer.postDrawCallback(mFrameRunnable); + mFrameScheduled = true; + } + } + + void unscheduleFrame() { + if (mFrameScheduled) { + mFrameScheduled = false; + mChoreographer.removeDrawCallback(mFrameRunnable); + } + } + + void doFrame() { if (mInputEventReceiver != null) { mInputEventReceiver.consumeBatchedInputEvents(); } @@ -2272,32 +2283,33 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void requestChildFocus(View child, View focused) { checkThread(); - if (mFocusedView != focused) { - mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused); - scheduleTraversals(); + + if (DEBUG_INPUT_RESIZE) { + Log.v(TAG, "Request child focus: focus now " + focused); } + + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused); + scheduleTraversals(); + mFocusedView = mRealFocusedView = focused; - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now " - + mFocusedView); } public void clearChildFocus(View child) { checkThread(); - View oldFocus = mFocusedView; + if (DEBUG_INPUT_RESIZE) { + Log.v(TAG, "Clearing child focus"); + } - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus"); - mFocusedView = mRealFocusedView = null; - if (mView != null && !mView.hasFocus()) { - // If a view gets the focus, the listener will be invoked from requestChildFocus() - if (!mView.requestFocus(View.FOCUS_FORWARD)) { - mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); - } - } else if (oldFocus != null) { - mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); + mOldFocusedView = mFocusedView; + + // Invoke the listener only if there is no view to take focus + if (focusSearch(null, View.FOCUS_FORWARD) == null) { + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null); } - } + mFocusedView = mRealFocusedView = null; + } public void focusableViewAvailable(View v) { checkThread(); @@ -2374,7 +2386,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, mInputChannel = null; } - mChoreographer.removeOnDrawListener(this); + unscheduleFrame(); } void updateConfiguration(Configuration config, boolean force) { @@ -2770,6 +2782,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, mView.unFocus(); mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null); mFocusedView = null; + mOldFocusedView = null; return true; } } @@ -3920,6 +3933,16 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } + final class FrameRunnable implements Runnable { + @Override + public void run() { + mFrameScheduled = false; + doFrame(); + } + } + final FrameRunnable mFrameRunnable = new FrameRunnable(); + boolean mFrameScheduled; + final class WindowInputEventReceiver extends InputEventReceiver { public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); @@ -3932,7 +3955,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, @Override public void onBatchedInputEventPending() { - mChoreographer.scheduleDraw(); + scheduleFrame(); } } WindowInputEventReceiver mInputEventReceiver; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 7171b58482d4..b1d7a182a443 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -336,6 +336,7 @@ public final class InputMethodManager { } case MSG_UNBIND: { final int sequence = msg.arg1; + boolean startInput = false; synchronized (mH) { if (mBindSequence == sequence) { if (false) { @@ -355,7 +356,12 @@ public final class InputMethodManager { if (mServedView != null && mServedView.isFocused()) { mServedConnecting = true; } + if (mActive) { + startInput = true; + } } + } + if (startInput) { startInputInner(); } return; @@ -1135,20 +1141,26 @@ public final class InputMethodManager { * @hide */ public void checkFocus() { + if (checkFocusNoStartInput()) { + startInputInner(); + } + } + + private boolean checkFocusNoStartInput() { // This is called a lot, so short-circuit before locking. if (mServedView == mNextServedView && !mNextServedNeedsStart) { - return; + return false; } InputConnection ic = null; synchronized (mH) { if (mServedView == mNextServedView && !mNextServedNeedsStart) { - return; + return false; } if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView + " next=" + mNextServedView + " restart=" + mNextServedNeedsStart); - + mNextServedNeedsStart = false; if (mNextServedView == null) { finishInputLocked(); @@ -1156,22 +1168,22 @@ public final class InputMethodManager { // but no longer do. We should make sure the input method is // no longer shown, since it serves no purpose. closeCurrentInput(); - return; + return false; } - + ic = mServedInputConnection; - + mServedView = mNextServedView; mCurrentTextBoxAttribute = null; mCompletions = null; mServedConnecting = true; } - + if (ic != null) { ic.finishComposingText(); } - - startInputInner(); + + return true; } void closeCurrentInput() { @@ -1200,7 +1212,7 @@ public final class InputMethodManager { focusInLocked(focusedView != null ? focusedView : rootView); } - checkFocus(); + boolean startInput = checkFocusNoStartInput(); synchronized (mH) { try { @@ -1212,6 +1224,10 @@ public final class InputMethodManager { } catch (RemoteException e) { } } + + if (startInput) { + startInputInner(); + } } /** @hide */ diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 332a0eb577dc..e0f4f59c8a89 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -59,6 +59,7 @@ import android.os.Message; import android.os.StrictMode; import android.os.SystemClock; import android.provider.Settings; +import android.security.KeyChain; import android.speech.tts.TextToSpeech; import android.text.Editable; import android.text.InputType; @@ -1303,6 +1304,7 @@ public class WebView extends AbsoluteLayout init(); setupPackageListener(context); setupProxyListener(context); + setupTrustStorageListener(context); updateMultiTouchSupport(context); if (privateBrowsing) { @@ -1312,6 +1314,41 @@ public class WebView extends AbsoluteLayout mAutoFillData = new WebViewCore.AutoFillData(); } + private static class TrustStorageListener extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) { + handleCertTrustChanged(); + } + } + } + private static TrustStorageListener sTrustStorageListener; + + /** + * Handles update to the trust storage. + */ + private static void handleCertTrustChanged() { + // send a message for indicating trust storage change + WebViewCore.sendStaticMessage(EventHub.TRUST_STORAGE_UPDATED, null); + } + + /* + * @param context This method expects this to be a valid context. + */ + private static void setupTrustStorageListener(Context context) { + if (sTrustStorageListener != null ) { + return; + } + IntentFilter filter = new IntentFilter(); + filter.addAction(KeyChain.ACTION_STORAGE_CHANGED); + sTrustStorageListener = new TrustStorageListener(); + Intent current = + context.getApplicationContext().registerReceiver(sTrustStorageListener, filter); + if (current != null) { + handleCertTrustChanged(); + } + } + private static class ProxyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -5555,7 +5592,7 @@ public class WebView extends AbsoluteLayout return false; } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + if (isEnterActionKey(keyCode)) { switchOutDrawHistory(); boolean wantsKeyEvents = nativeCursorNodePointer() == 0 || nativeCursorWantsKeyEvents(); @@ -5704,33 +5741,35 @@ public class WebView extends AbsoluteLayout return true; // discard press if copy in progress } - // perform the single click - Rect visibleRect = sendOurVisibleRect(); - // Note that sendOurVisibleRect calls viewToContent, so the - // coordinates should be in content coordinates. - if (!nativeCursorIntersects(visibleRect)) { - return false; - } - WebViewCore.CursorData data = cursorData(); - mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); - playSoundEffect(SoundEffectConstants.CLICK); - if (nativeCursorIsTextInput()) { - rebuildWebTextView(); - centerKeyPressOnTextField(); - if (inEditingMode()) { - mWebTextView.setDefaultSelection(); + if (!sDisableNavcache) { + // perform the single click + Rect visibleRect = sendOurVisibleRect(); + // Note that sendOurVisibleRect calls viewToContent, so the + // coordinates should be in content coordinates. + if (!nativeCursorIntersects(visibleRect)) { + return false; + } + WebViewCore.CursorData data = cursorData(); + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); + playSoundEffect(SoundEffectConstants.CLICK); + if (nativeCursorIsTextInput()) { + rebuildWebTextView(); + centerKeyPressOnTextField(); + if (inEditingMode()) { + mWebTextView.setDefaultSelection(); + } + return true; + } + clearTextEntry(); + nativeShowCursorTimed(); + if (mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) { + return true; + } + if (nativeCursorNodePointer() != 0 && !nativeCursorWantsKeyEvents()) { + mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, + nativeCursorNodePointer()); + return true; } - return true; - } - clearTextEntry(); - nativeShowCursorTimed(); - if (mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) { - return true; - } - if (nativeCursorNodePointer() != 0 && !nativeCursorWantsKeyEvents()) { - mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, - nativeCursorNodePointer()); - return true; } } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 8a9c12d43c74..b6c5612c7e6d 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -26,6 +26,7 @@ import android.graphics.Region; import android.media.MediaFile; import android.net.ProxyProperties; import android.net.Uri; +import android.net.http.CertificateChainValidator; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -333,6 +334,15 @@ public final class WebViewCore { } /** + * Called by JNI when the focus node changed. + */ + private void focusNodeChanged(WebKitHitTest hitTest) { + if (mWebView == null) return; + mWebView.mPrivateHandler.obtainMessage(WebView.HIT_TEST_RESULT, hitTest) + .sendToTarget(); + } + + /** * Called by JNI. Open a file chooser to upload a file. * @param acceptType The value of the 'accept' attribute of the * input tag associated with this file picker. @@ -614,7 +624,6 @@ public final class WebViewCore { int x, int y); private native String nativeRetrieveImageSource(int nativeClass, int x, int y); - private native void nativeStopPaintingCaret(int nativeClass); private native void nativeTouchUp(int nativeClass, int touchGeneration, int framePtr, int nodePtr, int x, int y); @@ -767,6 +776,11 @@ public final class WebViewCore { Message m = (Message)msg.obj; m.sendToTarget(); break; + case EventHub.TRUST_STORAGE_UPDATED: + // post a task to network thread for updating trust manager + nativeCertTrustChanged(); + CertificateChainValidator.handleTrustStorageUpdate(); + break; } } }; @@ -1125,6 +1139,9 @@ public final class WebViewCore { static final int SELECT_WORD_AT = 214; static final int SELECT_ALL = 215; + // for updating state on trust storage change + static final int TRUST_STORAGE_UPDATED = 220; + // Private handler for WebCore messages. private Handler mHandler; // Message queue for containing messages before the WebCore thread is @@ -1530,9 +1547,6 @@ public final class WebViewCore { nativeMoveMouseIfLatest(mNativeClass, cData.mMoveGeneration, cData.mFrame, cData.mX, cData.mY); - if (msg.arg1 == 1) { - nativeStopPaintingCaret(mNativeClass); - } break; case REQUEST_CURSOR_HREF: { @@ -3077,4 +3091,6 @@ public final class WebViewCore { private native void nativeClearTextSelection(int nativeClass); private native void nativeSelectWordAt(int nativeClass, int x, int y); private native void nativeSelectAll(int nativeClass); + + private static native void nativeCertTrustChanged(); } diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 5c7e5a36c31c..dd5332586b3d 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -142,12 +142,8 @@ public class CheckedTextView extends TextView implements Checkable { resolvePadding(); } - /** - * @hide - */ @Override - protected void resolvePadding() { - super.resolvePadding(); + public void onResolvePadding(int layoutDirection) { int newPadding = (mCheckMarkDrawable != null) ? mCheckMarkWidth + mBasePadding : mBasePadding; mNeedRequestlayout |= (mPaddingRight != newPadding); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 7db8a1e6c99c..13798ef1da38 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4637,19 +4637,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Alpha is supported if and only if the drawing can be done in one pass. // TODO text with spans with a background color currently do not respect this alpha. if (getBackground() == null) { - mCurrentAlpha = alpha; - final Drawables dr = mDrawables; - if (dr != null) { - if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha); - if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha); - if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha); - if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha); - if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha); - if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha); + if (mCurrentAlpha != alpha) { + mCurrentAlpha = alpha; + final Drawables dr = mDrawables; + if (dr != null) { + if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha); + if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha); + if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha); + if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha); + if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha); + if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha); + } + mTextDisplayListIsValid = false; } return true; } + if (mCurrentAlpha != 255) { + mTextDisplayListIsValid = false; + } mCurrentAlpha = 255; return false; } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 6893ffbaf6c5..acc3c1c39de1 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -857,11 +857,6 @@ public class LockPatternUtils { * @return Whether biometric weak lock is installed and that the front facing camera exists */ public boolean isBiometricWeakInstalled() { - // Check that the system flag was set - if (!OPTION_ENABLE_FACELOCK.equals(getString(LOCKSCREEN_OPTIONS))) { - return false; - } - // Check that it's installed PackageManager pm = mContext.getPackageManager(); try { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 68c919e92cb3..a2b1117ddf95 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1521,6 +1521,17 @@ android:description="@string/permdesc_serialPort" android:protectionLevel="normal" /> + <!-- Allows the holder to access content providers from outside an ApplicationThread. + This permission is enforced by the ActivityManagerService on the corresponding APIs, + in particular ActivityManagerService#getContentProviderExternal(String) and + ActivityManagerService#removeContentProviderExternal(String). + @hide + --> + <permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" + android:label="@string/permlab_accessContentProvidersExternally" + android:description="@string/permdesc_accessContentProvidersExternally" + android:protectionLevel="signature" /> + <!-- The system process is explicitly the only one allowed to launch the confirmation UI for full backup/restore --> <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/> diff --git a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png Binary files differindex c2e4b783c31e..cb08eed75658 100644 --- a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png +++ b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png diff --git a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png Binary files differindex 51b839fd5572..ea065c36e742 100644 --- a/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png +++ b/core/res/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png diff --git a/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png Binary files differnew file mode 100644 index 000000000000..d3ba98c3ebc2 --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png diff --git a/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png Binary files differnew file mode 100644 index 000000000000..153c6ad1f6cf --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png diff --git a/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png Binary files differnew file mode 100644 index 000000000000..ec6bc54d77e2 --- /dev/null +++ b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png diff --git a/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png Binary files differnew file mode 100644 index 000000000000..9fd4f33bbf78 --- /dev/null +++ b/core/res/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 19398cf3bebd..840323e238ad 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -975,9 +975,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kon nie aan Wi-Fikoppel nie"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" het \'n swak internetverbinding."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Begin Wi-Fi Direct. Dit sal die Wi-Fi-kliënt/warmkol afskakel."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kon nie Wi-Fi Direct begin nie."</string> <string name="accept" msgid="1645267259272829559">"Aanvaar"</string> <string name="decline" msgid="2112225451706137894">"Weier"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Uitnodiging gestuur"</string> @@ -986,8 +983,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Aan:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Voer die vereiste PIN in:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direk is aan"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Raak vir instellings"</string> <string name="select_character" msgid="3365550120617701745">"Voeg karakter in"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Onbekend program"</string> <string name="sms_control_title" msgid="7296612781128917719">"Stuur SMS-boodskappe"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 8f7b70f10c7b..905b90c44288 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"ወደ Wi-Fi ለማያያዝ አልተቻለም"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ደካማ የበይነመረብ ግንኙነት ኣለው።"</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi ቀጥታ"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"የWi-Fi በቀጥታ ጀምር።ይህ የWi-Fi ደንበኛ /ድረስ ነጥብ ያጠፋል።"</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"በቀጥታ Wi-Fi ማስጀመር አልተቻለም።"</string> <string name="accept" msgid="1645267259272829559">"ተቀበል"</string> <string name="decline" msgid="2112225451706137894">"ውድቅ አድርግ"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"ግብዣ ተልኳል"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"ለ፦"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"የሚፈለገውን ፒን ተይብ፦"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ፒን፦"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"የWi-Fi ቀጥታ በርቷል"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"ለቅንብሮች ንካ"</string> <string name="select_character" msgid="3365550120617701745">"ቁምፊ አስገባ"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"ያልታወቀ መተግበሪያ"</string> <string name="sms_control_title" msgid="7296612781128917719">"የSMS መልዕክቶች መበላክ ላይ"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index c9de7de2cc31..a7c37e733cf4 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"تعذر الاتصال بـ Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" لديها اتصال إنترنت رديء."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"اتصال Wi-Fi مباشر"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"ابدأ Wi-Fi Direct. يؤدي هذا إلى إيقاف عميل/نقطة اتصال Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"تعذر بدء Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"قبول"</string> <string name="decline" msgid="2112225451706137894">"رفض"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"تم إرسال الدعوة"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"إلى:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"اكتب رقم التعريف الشخصي المطلوب:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"رقم التعريف الشخصي:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"تم تشغيل اتصال Wi-Fi المباشر"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"المس للحصول على الإعدادات"</string> <string name="select_character" msgid="3365550120617701745">"إدراج حرف"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"تطبيق غير معروف"</string> <string name="sms_control_title" msgid="7296612781128917719">"إرسال رسائل قصيرة SMS"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index adb13388b67f..4a226a157f2d 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Немагчыма падключыцца да Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" дрэннае падключэнне да Інтэрнэту."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Пачаць работу Wi-Fi Direct. Гэта адключыць кліента або кропку доступу Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Немагчыма запусціць Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Прыняць"</string> <string name="decline" msgid="2112225451706137894">"Адхіліць"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Запрашэнне адпраўлена"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Каму:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Увядзіце патрэбны PIN-код:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct уключаны"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Дакраніцеся, каб наладзіць"</string> <string name="select_character" msgid="3365550120617701745">"Уставіць сімвал"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Невядомае прыкладанне"</string> <string name="sms_control_title" msgid="7296612781128917719">"Адпраўка SMS"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 0a2213325a52..f311a28d8682 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Не можа да се свърже с Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" има лоша връзка с интернет."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Стартиране на Wi-Fi Direct. Това ще изключи клиентската програма/точката за достъп до Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct не можа да се стартира."</string> <string name="accept" msgid="1645267259272829559">"Приемам"</string> <string name="decline" msgid="2112225451706137894">"Отхвърлям"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Поканата е изпратена"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"До:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Въведете задължителния ПИН:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ПИН:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct е включено"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Докоснете за настройки"</string> <string name="select_character" msgid="3365550120617701745">"Вмъкване на знак"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Неизвестно приложение"</string> <string name="sms_control_title" msgid="7296612781128917719">"Изпращане на SMS съобщения"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 460fcb7f504a..95dae9d62b05 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"No s\'ha pogut connectar a la Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" té una mala connexió a Internet."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Inicia Wi-Fi Direct. Això desactivarà el client/la zona Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"No s\'ha pogut iniciar Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Accepta"</string> <string name="decline" msgid="2112225451706137894">"Rebutja"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"S\'ha enviat la invitació"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Per a:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introdueix el PIN sol·licitat:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct està activat"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Toca per accedir a la configuració"</string> <string name="select_character" msgid="3365550120617701745">"Insereix un caràcter"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicació desconeguda"</string> <string name="sms_control_title" msgid="7296612781128917719">"S\'estan enviant missatges SMS"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 545d957d6907..c01526d71b99 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Připojení k síti Wi-Fi se nezdařilo"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" má pomalé připojení k internetu."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Přímé připojení sítě Wi-Fi"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Spustit přímé připojení sítě Wi-Fi. Tato možnost vypne provoz sítě Wi-Fi v režimu klient/hotspot."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Přímé připojení sítě Wi-Fi se nepodařilo spustit."</string> <string name="accept" msgid="1645267259272829559">"Přijmout"</string> <string name="decline" msgid="2112225451706137894">"Odmítnout"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pozvánka odeslána."</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Komu:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Zadejte požadovaný kód PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Přímé připojení sítě Wi-Fi je zapnuto"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Nastavení otevřete dotykem"</string> <string name="select_character" msgid="3365550120617701745">"Vkládání znaků"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznámá aplikace"</string> <string name="sms_control_title" msgid="7296612781128917719">"Odesílání zpráv SMS"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 65d7cdce5f13..4904a994cb7a 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kunne ikke oprette forbindelse til Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" har en dårlig internetforbindelse."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Start Wi-Fi Direct. Dette slår Wi-Fi-klient/hotspot fra."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct kunne ikke startes."</string> <string name="accept" msgid="1645267259272829559">"Accepter"</string> <string name="decline" msgid="2112225451706137894">"Afvis"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitationen er sendt"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Til:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Skriv den påkrævede pinkode:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Pinkode:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct er slået til"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tryk for indstillinger"</string> <string name="select_character" msgid="3365550120617701745">"Indsæt tegn"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ukendt app"</string> <string name="sms_control_title" msgid="7296612781128917719">"Sender sms-beskeder"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index dbe81373bcfc..20ef4d7f7aef 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Es konnte keine WLAN-Verbindung hergestellt werden."</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" hat eine schlechte Internetverbindung."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct-Betrieb starten. Hierdurch wird der WLAN-Client-/-Hotspot-Betrieb deaktiviert."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Starten von Wi-Fi Direct nicht möglich"</string> <string name="accept" msgid="1645267259272829559">"Akzeptieren"</string> <string name="decline" msgid="2112225451706137894">"Ablehnen"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Einladung gesendet"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"An:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Geben Sie die erforderliche PIN ein:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct ist aktiviert."</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Zum Aufrufen der Einstellungen berühren"</string> <string name="select_character" msgid="3365550120617701745">"Zeichen einfügen"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Unbekannte App"</string> <string name="sms_control_title" msgid="7296612781128917719">"Kurznachrichten werden gesendet"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 154ca55f578a..200f7024ef8e 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -215,10 +215,8 @@ <string name="permdesc_reorderTasks" msgid="4175137612205663399">"Επιτρέπει στην εφαρμογή τη μετακίνηση εργασιών στο προσκήνιο και στο φόντο. Τυχόν κακόβουλες εφαρμογές μπορούν να προωθηθούν στο προσκήνιο χωρίς να μπορείτε να τις ελέγξετε."</string> <string name="permlab_removeTasks" msgid="6821513401870377403">"διακοπή εκτέλεσης εφαρμογών"</string> <string name="permdesc_removeTasks" msgid="1394714352062635493">"Επιτρέπει στην εφαρμογή την κατάργηση ενεργειών και την απομάκρυνση των εφαρμογών τους. Τυχόν κακόβουλες εφαρμογές ενδέχεται να διαταράξουν τη λειτουργία άλλων εφαρμογών."</string> - <!-- no translation found for permlab_setScreenCompatibility (6975387118861842061) --> - <skip /> - <!-- no translation found for permdesc_setScreenCompatibility (692043618693917374) --> - <skip /> + <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"ρύθμιση συμβατότητας οθόνης"</string> + <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Επιτρέπει στην εφαρμογή να ελέγξει τη λειτουργία συμβατότητας της οθόνης με άλλες εφαρμογές. Οι κακόβουλες εφαρμογές μπορεί να επηρεάσουν τη συμπεριφορά άλλων εφαρμογών."</string> <string name="permlab_setDebugApp" msgid="3022107198686584052">"ενεργοποίηση εντοπισμού σφαλμάτων εφαρμογής"</string> <string name="permdesc_setDebugApp" msgid="4474512416299013256">"Επιτρέπει στην εφαρμογή να ενεργοποιήσει τον εντοπισμό σφαλμάτων για μια άλλη εφαρμογή. Τυχόν κακόβουλες εφαρμογές ενδέχεται να χρησιμοποιήσουν αυτήν τη δυνατότητα για τον τερματισμό άλλων εφαρμογών."</string> <string name="permlab_changeConfiguration" msgid="8214475779521218295">"αλλαγή των ρυθμίσεων του UI"</string> @@ -975,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Δεν είναι δυνατή η σύνδεση στο Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" έχει κακή σύνδεση στο Διαδίκτυο."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Ξεκινήστε τη λειτουργία Wi-Fi Direct. Θα απενεργοποιηθεί η λειτουργία πελάτη/φορητού σημείου πρόσβασης Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Δεν ήταν δυνατή η εκκίνηση του Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Αποδοχή"</string> <string name="decline" msgid="2112225451706137894">"Απόρριψη"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Η πρόσκληση στάλθηκε"</string> @@ -986,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Προς:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Πληκτρολογήστε τον απαιτούμενο κωδικό PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Το Wi-Fi Direct έχει ενεργοποιηθεί"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Αγγίξτε για ρυθμίσεις"</string> <string name="select_character" msgid="3365550120617701745">"Εισαγωγή χαρακτήρα"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Άγνωστη εφαρμογή"</string> <string name="sms_control_title" msgid="7296612781128917719">"Αποστολή μηνυμάτων SMS"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 0429c8b7f3f6..285994297419 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Couldn\'t connect to Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" has a poor Internet connection."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Start Wi-Fi Direct. This will turn off Wi-Fi client/hotspot."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Couldn\'t start Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Accept"</string> <string name="decline" msgid="2112225451706137894">"Decline"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitation sent"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"To:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Type the required PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct is on"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Touch for settings"</string> <string name="select_character" msgid="3365550120617701745">"Insert character"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Unknown app"</string> <string name="sms_control_title" msgid="7296612781128917719">"Sending SMS messages"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index ad3e949899a8..dbc96e7cc32f 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -215,8 +215,8 @@ <string name="permdesc_reorderTasks" msgid="4175137612205663399">"Permite que la aplicación mueva tareas al primero o segundo plano. Las aplicaciones maliciosas pueden forzar su paso al primer plano sin que tú las controles."</string> <string name="permlab_removeTasks" msgid="6821513401870377403">"detener las aplicaciones en ejecución"</string> <string name="permdesc_removeTasks" msgid="1394714352062635493">"Permite que la aplicación elimine tareas y cierre sus aplicaciones. Las aplicaciones malintencionadas pueden usar este permiso para interferir en el comportamiento de otras aplicaciones."</string> - <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"Configurar el modo de la compatibilidad de otras pantalla"</string> - <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Permite a la aplicación controlar el modo de la compatibilidad de las pantallas de otras aplicaciones. Las aplicaciones malintencionadas pueden interrumpir el funcionamiento de otras aplicaciones."</string> + <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"Definir compatibilidad de pantalla"</string> + <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Permite a la aplicación controlar el modo de compatibilidad de las pantallas de otras aplicaciones. Las aplicaciones malintencionadas pueden interrumpir el funcionamiento de otras aplicaciones."</string> <string name="permlab_setDebugApp" msgid="3022107198686584052">"activar depuración de aplicación"</string> <string name="permdesc_setDebugApp" msgid="4474512416299013256">"Permite que la aplicación active la depuración de otra aplicación. Las aplicaciones malintencionadas pueden usar este permiso para interrumpir la ejecución de otras aplicaciones."</string> <string name="permlab_changeConfiguration" msgid="8214475779521218295">"cambiar tu configuración de la interfaz de usuario"</string> @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"No se pudo conectar a la red Wi-Fi."</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tiene una mala conexión a Internet."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar Wi-Fi Direct. Se desactivará el funcionamiento de la zona o del cliente Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"No se pudo iniciar Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Aceptar"</string> <string name="decline" msgid="2112225451706137894">"Rechazar"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Se envió la invitación."</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Escribe el PIN solicitado:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Se activó Wi-Fi Direct."</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tocar para ajustar los parámetros de configuración"</string> <string name="select_character" msgid="3365550120617701745">"Insertar caracteres"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicación desconocida"</string> <string name="sms_control_title" msgid="7296612781128917719">"Enviando mensajes SMS"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 1bd13bd2b320..d3c55a61bb7f 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"No se ha podido establecer conexión con la red Wi-Fi."</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tiene una conexión inestable a Internet."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar Wi-Fi Direct. Se desactivará el funcionamiento de la zona o del cliente Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"No se ha podido iniciar Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Aceptar"</string> <string name="decline" msgid="2112225451706137894">"Rechazar"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitación enviada"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Escribe el PIN solicitado:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct activado"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Toca para acceder a Ajustes"</string> <string name="select_character" msgid="3365550120617701745">"Insertar carácter"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicación desconocida"</string> <string name="sms_control_title" msgid="7296612781128917719">"Enviando mensajes SMS..."</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 2f42a70d72e9..c3a61693826d 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Ei saanud WiFi-ga ühendust"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" on halb Interneti-ühendus."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"WiFi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Käivitage WiFi otseühendus. See lülitab välja WiFi kliendi/leviala."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"WiFi otseühenduse käivitamine ebaõnnestus."</string> <string name="accept" msgid="1645267259272829559">"Nõustu"</string> <string name="decline" msgid="2112225451706137894">"Keeldu"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Kutse on saadetud"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Saaja:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Sisestage nõutav PIN-kood:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kood:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"WiFi Direct on sees"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Puuted seadete jaoks"</string> <string name="select_character" msgid="3365550120617701745">"Sisesta tähemärk"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Tundmatu rakendus"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS-sõnumite saatmine"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index fd7a77190b7e..87eee68d0385 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"اتصال به Wi-Fi ممکن نیست"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" اتصال اینترنتی ضعیفی دارد."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct را شروع کنید. این کار نقطه اتصال/سرویس گیرنده Wi-Fi را غیرفعال خواهد کرد."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct شروع نشد."</string> <string name="accept" msgid="1645267259272829559">"پذیرش"</string> <string name="decline" msgid="2112225451706137894">"عدم پذیرش"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"دعوتنامه ارسال شد"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"به:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"پین لازم را تایپ کنید:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"پین:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct روشن است"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"لمس کردن برای تنظیمات"</string> <string name="select_character" msgid="3365550120617701745">"درج نویسه"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"برنامه ناشناخته"</string> <string name="sms_control_title" msgid="7296612781128917719">"ارسال پیامک ها"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 9cf18ecc6237..5ab71583e236 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wifi-yhteyden muodostaminen epäonnistui"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" : huono internetyhteys."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Suora wifi-yhteys"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Käynnistä suora wifi-yhteys. Wifi-asiakas/-hotspot poistetaan käytöstä."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Suoran wifi-yhteyden käynnistäminen epäonnistui."</string> <string name="accept" msgid="1645267259272829559">"Hyväksy"</string> <string name="decline" msgid="2112225451706137894">"Hylkää"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Kutsu lähetetty."</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kohde:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Kirjoita pyydetty PIN-koodi:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-koodi:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct on käytössä"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tarkastele asetuksia koskettamalla"</string> <string name="select_character" msgid="3365550120617701745">"Lisää merkki"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Tuntematon sovellus"</string> <string name="sms_control_title" msgid="7296612781128917719">"Tekstiviestien lähettäminen"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 33b0f45bd664..606735043a20 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Impossible de se connecter au Wi-Fi."</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" dispose d\'une mauvaise connexion Internet."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Lancer le Wi-Fi Direct. Cela désactive le fonctionnement du Wi-Fi client ou via un point d\'accès."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Impossible d\'activer le Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Accepter"</string> <string name="decline" msgid="2112225451706137894">"Refuser"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitation envoyée"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"À :"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Saisissez le code PIN requis :"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Code PIN :"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct activé"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Appuyez pour accéder aux paramètres."</string> <string name="select_character" msgid="3365550120617701745">"Insérer un caractère"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Application inconnue"</string> <string name="sms_control_title" msgid="7296612781128917719">"Envoi de messages SMS"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 91ac5a370f31..1aaedbd3d0ab 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wi-Fi से कनेक्ट नहीं हो सका"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" के पास एक कमज़ोर इंटरनेट कनेक्शन है."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi प्रत्यक्ष"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi डायरेक्ट प्रारंभ करें. इससे Wi-Fi क्लाइंट/हॉटस्पॉट कार्यवाही बंद हो जाएगी."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi डायरेक्ट प्रारंभ नहीं किया जा सका."</string> <string name="accept" msgid="1645267259272829559">"स्वीकार करें"</string> <string name="decline" msgid="2112225451706137894">"अस्वीकार करें"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"आमंत्रण भेजा गया"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"प्रति:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"आवश्यक पिन लिखें:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"पिन:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi प्रत्यक्ष चालू है"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"सेटिंग के लिए स्पर्श करें"</string> <string name="select_character" msgid="3365550120617701745">"वर्ण सम्मिलित करें"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"अज्ञात एप्लिकेशन"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS संदेश भेज रहा है"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index a60dcfaddff7..07759844531a 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Ne može se spojiti na Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ima lošu internetsku vezu."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Izravni Wi-Fi"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Pokreni izravan rad s Wi-Fi mrežom. To će isključiti rad s Wi-Fi klijentom/žarišnom točkom."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Pokretanje izravne Wi-Fi veze nije moguće."</string> <string name="accept" msgid="1645267259272829559">"Prihvaćam"</string> <string name="decline" msgid="2112225451706137894">"Odbaci"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pozivnica je poslana"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Prima:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Upišite potreban PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct uključen"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Dodirnite za postavke"</string> <string name="select_character" msgid="3365550120617701745">"Umetni znak"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nepoznata aplikacija"</string> <string name="sms_control_title" msgid="7296612781128917719">"Slanje SMS poruka"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 6b789ea5da70..e125ba8d51b3 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nem sikerült csatlakozni a Wi-Fi hálózathoz"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" rossz internetkapcsolattal rendelkezik."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct elindítása. A Wi-Fi kliens/hotspot ettől leáll."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nem sikerült elindítani a Wi-Fi Direct kapcsolatot."</string> <string name="accept" msgid="1645267259272829559">"Elfogadás"</string> <string name="decline" msgid="2112225451706137894">"Elutasítás"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Meghívó elküldve"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Címzett:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Adja meg a szükséges PIN kódot:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN kód:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"A Wi-Fi Direct be van kapcsolva"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"A beállításokhoz érintse meg"</string> <string name="select_character" msgid="3365550120617701745">"Karakter beszúrása"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ismeretlen alkalmazás"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS-ek küldése"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 6ed5c0e4b600..72fcb507f652 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Tidak dapat tersambung ke Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" memiliki sambungan internet yang buruk."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Langsung"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Memulai Wi-Fi Langsung. Opsi ini akan mematikan hotspot/klien Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Tidak dapat memulai Wi-Fi Langsung."</string> <string name="accept" msgid="1645267259272829559">"Terima"</string> <string name="decline" msgid="2112225451706137894">"Tolak"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Undangan terkirim"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kepada:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ketik PIN yang diminta:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Langsung aktif"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Sentuh untuk setelan"</string> <string name="select_character" msgid="3365550120617701745">"Sisipkan huruf"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Apl tak dikenal"</string> <string name="sms_control_title" msgid="7296612781128917719">"Mengirim pesan SMS"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 8af1f5eee701..ec9457b5bef5 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Impossibile connettersi alla rete Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ha una connessione Internet debole."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Avvia Wi-Fi Direct. Verrà disattivato il client/hotspot Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Avvio di Wi-Fi Direct non riuscito."</string> <string name="accept" msgid="1645267259272829559">"Accetto"</string> <string name="decline" msgid="2112225451706137894">"Rifiuto"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invito inviato"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"A:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Inserisci il PIN richiesto:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct è attivo"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tocca per le impostazioni"</string> <string name="select_character" msgid="3365550120617701745">"Inserisci carattere"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Applicazione sconosciuta"</string> <string name="sms_control_title" msgid="7296612781128917719">"Invio SMS"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 5d5b9daed4d2..5fc15c720ac9 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"אין אפשרות להתחבר ל-Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" אינו מחובר היטב לאינטרנט."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi ישיר"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"הפעל Wi-Fi ישיר. פעולה זו תכבה את הלקוח/הנקודה החמה של ה-Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"לא ניתן להפעיל Wi-Fi ישיר"</string> <string name="accept" msgid="1645267259272829559">"קבל"</string> <string name="decline" msgid="2112225451706137894">"דחה"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"ההזמנה נשלחה"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"אל:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"הקלד את קוד ה-PIN הנדרש."</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi ישיר מופעל"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"גע עבור הגדרות"</string> <string name="select_character" msgid="3365550120617701745">"הוסף תו"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"יישום לא ידוע"</string> <string name="sms_control_title" msgid="7296612781128917719">"שולח הודעות SMS"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index cadf93565596..63b8c73f7a3c 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wi-Fiに接続できませんでした"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" はインターネット接続に問題があります。"</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Directを開始します。これによりWi-Fiクライアント/アクセスポイントがOFFになります。"</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Directを開始できませんでした。"</string> <string name="accept" msgid="1645267259272829559">"同意する"</string> <string name="decline" msgid="2112225451706137894">"同意しない"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"招待状を送信しました"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"To:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"必要なPINを入力してください:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi DirectはONです"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"設定を表示するにはタップしてください"</string> <string name="select_character" msgid="3365550120617701745">"文字を挿入"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"不明なアプリ"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMSメッセージの送信中"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 031e9f5b7f8c..f97cbdcd6497 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Wi-Fi에 연결할 수 없습니다"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" 인터넷 연결 상태가 좋지 않습니다."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wi-Fi Direct 작업을 시작합니다. 이 작업을 하면 Wi-Fi 클라이언트/핫스팟 작업이 중지됩니다."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct를 시작하지 못했습니다."</string> <string name="accept" msgid="1645267259272829559">"동의"</string> <string name="decline" msgid="2112225451706137894">"거부"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"초대장을 보냈습니다."</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"받는사람:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"필수 PIN 입력:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct 켜짐"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"설정으로 이동하려면 터치하세요."</string> <string name="select_character" msgid="3365550120617701745">"문자 삽입"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"알 수 없는 앱"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS 메시지를 보내는 중"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 22830ac6a146..a1694afb6e26 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nepavyko prisijungti prie „Wi-Fi“"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" turi prastą interneto ryšį."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Tiesioginis „Wi-Fi“ ryšys"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Paleiskite „Wi-Fi Direct“. Bus išjungta „Wi-Fi“ programa / viešosios interneto prieigos taškas."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nepavyko paleisti „Wi-Fi Direct“."</string> <string name="accept" msgid="1645267259272829559">"Sutikti"</string> <string name="decline" msgid="2112225451706137894">"Atmesti"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pakvietimas išsiųstas"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Skirta:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Įveskite reikiamą PIN kodą:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN kodas:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"„Wi-Fi Direct“ įjungta"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Jei norite peržiūrėti nustatymus, palieskite"</string> <string name="select_character" msgid="3365550120617701745">"Įterpti simbolį"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nežinoma programa"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS pranešimų siuntimas"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index f716cb99f65c..aeec70b31b14 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nevarēja izveidot savienojumu ar Wi-Fi."</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ir slikts interneta savienojums."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Palaist programmu Wi-Fi Direct. Tādējādi tiks izslēgta Wi-Fi klienta/tīklāja darbība."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nevarēja palaist programmu Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Piekrist"</string> <string name="decline" msgid="2112225451706137894">"Noraidīt"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Ielūgums ir nosūtīts."</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kam:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ierakstiet pieprasīto PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct ir ieslēgts"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Pieskarieties, lai piekļūtu iestatījumiem."</string> <string name="select_character" msgid="3365550120617701745">"Ievietojiet rakstzīmi"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nezināma lietotne"</string> <string name="sms_control_title" msgid="7296612781128917719">"Īsziņu sūtīšana"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 6aa9e4cafd29..0610e88c6b9f 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Tidak boleh menyambung kepada Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" mempunyai sambungan internet yang kurang baik."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Langsung"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Mulakan Wi-Fi Langsung. Hal ini akan mematikan pengendalian klien/liputan Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Tidak dapat memulakan Wi-Fi Langsung."</string> <string name="accept" msgid="1645267259272829559">"Terima"</string> <string name="decline" msgid="2112225451706137894">"Tolak"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Jemputan dihantar"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kepada:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Taipkan PIN yang diperlukan:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct dihidupkan"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Sentuh untuk tetapan"</string> <string name="select_character" msgid="3365550120617701745">"Masukkan aksara"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Apl tidak diketahui"</string> <string name="sms_control_title" msgid="7296612781128917719">"Menghantar mesej SMS"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index b1b3cbef1cbb..e82718ef3aa5 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kan ikke koble til Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" har en dårlig Internett-tilkobling."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Start Wi-Fi Direct. Dette deaktiverer Wi-Fi-klienten/-sonen."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kunne ikke starte Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Godta"</string> <string name="decline" msgid="2112225451706137894">"Avslå"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitasjonen er sendt"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Til:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Skriv inn påkrevd PIN-kode:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct er slått på"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Berør for å se innstillinger"</string> <string name="select_character" msgid="3365550120617701745">"Sett inn tegn"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ukjent app"</string> <string name="sms_control_title" msgid="7296612781128917719">"Sender SMS-meldinger"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 94094455845d..ef4dd6a67591 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kan geen verbinding maken met Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" heeft een slechte internetverbinding."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Wifi Direct starten. Hierdoor wordt de wifi-client/hotspot uitgeschakeld."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kan Wifi Direct niet starten."</string> <string name="accept" msgid="1645267259272829559">"Accepteren"</string> <string name="decline" msgid="2112225451706137894">"Weigeren"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Uitnodiging verzonden"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Naar:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Voer de gewenste pincode in:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Pincode"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct is actief"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Aanraken voor instellingen"</string> <string name="select_character" msgid="3365550120617701745">"Teken invoegen"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Onbekende app"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS-berichten verzenden"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 8564dc793658..f231d897d9c5 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nie można połączyć się z siecią Wi-Fi."</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ma powolne połączenie internetowe."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Uruchom Wi-Fi Direct. Spowoduje to wyłączenie klienta lub punktu dostępu Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Nie można uruchomić Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Akceptuj"</string> <string name="decline" msgid="2112225451706137894">"Odrzuć"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Wysłano zaproszenie"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Do:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Wpisz wymagany kod PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Kod PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct włączone"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Dotknij, aby zmienić ustawienia"</string> <string name="select_character" msgid="3365550120617701745">"Wstaw znak"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nieznana aplikacja"</string> <string name="sms_control_title" msgid="7296612781128917719">"Wysyłanie wiadomości SMS"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index a162aa086f3c..f3f0600904cf 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Não foi possível ligar a Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tem uma ligação à internet fraca."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar o Wi-Fi Direct. Esta opção desativará o cliente/zona Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Não foi possível iniciar o Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Aceitar"</string> <string name="decline" msgid="2112225451706137894">"Recusar"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Convite enviado"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introduza o PIN solicitado:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"O Wi-Fi Direct está ativado"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Toque para aceder às definições"</string> <string name="select_character" msgid="3365550120617701745">"Introduzir carácter"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicação desconhecida"</string> <string name="sms_control_title" msgid="7296612781128917719">"A enviar mensagens SMS"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 5c16708eec86..b49c36b61e20 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Não foi possível se conectar a redes Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" tem uma conexão de baixa qualidade com a Internet."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Iniciar o Wi-Fi Direct. Isso desativará o ponto de acesso/cliente Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Não foi possível iniciar o Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Aceitar"</string> <string name="decline" msgid="2112225451706137894">"Recusar"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Convite enviado"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Para:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Digite o PIN obrigatório:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct ativado"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tocar para acessar configurações"</string> <string name="select_character" msgid="3365550120617701745">"Inserir caractere"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicativo desconhecido"</string> <string name="sms_control_title" msgid="7296612781128917719">"Enviando mensagens SMS"</string> diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml index 57daee71770e..1447c0e085d2 100644 --- a/core/res/res/values-rm/strings.xml +++ b/core/res/res/values-rm/strings.xml @@ -1418,12 +1418,6 @@ <skip /> <!-- no translation found for wifi_watchdog_network_disabled_detailed (5548780776418332675) --> <skip /> - <!-- no translation found for wifi_p2p_dialog_title (97611782659324517) --> - <skip /> - <!-- no translation found for wifi_p2p_turnon_message (2909250942299627244) --> - <skip /> - <!-- no translation found for wifi_p2p_failed_message (3763669677935623084) --> - <skip /> <!-- no translation found for accept (1645267259272829559) --> <skip /> <!-- no translation found for decline (2112225451706137894) --> @@ -1440,10 +1434,6 @@ <skip /> <!-- no translation found for wifi_p2p_show_pin_message (8530563323880921094) --> <skip /> - <!-- no translation found for wifi_p2p_enabled_notification_title (2068321881673734886) --> - <skip /> - <!-- no translation found for wifi_p2p_enabled_notification_message (1638949953993894335) --> - <skip /> <string name="select_character" msgid="3365550120617701745">"Inserir in caracter"</string> <!-- no translation found for sms_control_default_app_name (3058577482636640465) --> <skip /> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index fe3687fbfd79..f241e8e91139 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nu se poate conecta la Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" are o conexiune la internet slabă."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Porniţi Wi-Fi Direct. Acest lucru va dezactiva clientul/hotspotul Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct nu a putut porni."</string> <string name="accept" msgid="1645267259272829559">"Acceptaţi"</string> <string name="decline" msgid="2112225451706137894">"Refuzaţi"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Invitaţia a fost trimisă."</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Către:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introduceţi codul PIN necesar:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Cod PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct este activat"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Atingeţi pentru setări"</string> <string name="select_character" msgid="3365550120617701745">"Introduceţi caracterul"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicaţie necunoscută"</string> <string name="sms_control_title" msgid="7296612781128917719">"Se trimit mesaje SMS"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 55468a07c6b2..809da8324edc 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Не удалось подключиться к сети Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" – плохое интернет-соединение."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Начать соединение через Wi-Fi Direct. Модуль Wi-Fi будет отключен."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Не удалось запустить Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Принять"</string> <string name="decline" msgid="2112225451706137894">"Отклонить"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Приглашение отправлено"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Кому:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Введите PIN-код:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct включен"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Нажмите, чтобы открыть настройки"</string> <string name="select_character" msgid="3365550120617701745">"Введите символ"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Неизвестное приложение"</string> <string name="sms_control_title" msgid="7296612781128917719">"Отправка SMS-сообщений"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 7d1aa7838e75..7d75949ee008 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Nepodarilo sa pripojiť k sieti Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" má nekvalitné internetové pripojenie."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Priame pripojenie Wi-Fi"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Spustiť priame pripojenie siete Wi-Fi. Táto možnosť vypne sieť Wi-Fi v režime klient alebo hotspot."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Priame pripojenie siete Wi-Fi sa nepodarilo spustiť"</string> <string name="accept" msgid="1645267259272829559">"Prijať"</string> <string name="decline" msgid="2112225451706137894">"Odmietnuť"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Pozvánka bola odoslaná"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Komu:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Zadajte požadovaný kód PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Priame pripojenie siete Wi-Fi je zapnuté"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Nastavenia otvoríte dotykom"</string> <string name="select_character" msgid="3365550120617701745">"Vkladanie znakov"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznáma aplikácia"</string> <string name="sms_control_title" msgid="7296612781128917719">"Odosielanie správ SMS"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index dfedb9af8372..e152a2f7b817 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Z omrežjem Wi-Fi se ni mogoče povezati"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ima slabo internetno povezavo."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Zaženite Wi-Fi Direct. S tem boste izklopili odjemalca/dostopno točko Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Wi-Fi Direct ni bilo mogoče zagnati."</string> <string name="accept" msgid="1645267259272829559">"Sprejmi"</string> <string name="decline" msgid="2112225451706137894">"Zavrni"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Povabilo je poslano"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Za:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Vnesite zahtevano kodo PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct je vklopljen"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Dotaknite se za nastavitve"</string> <string name="select_character" msgid="3365550120617701745">"Vstavljanje znaka"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznan program"</string> <string name="sms_control_title" msgid="7296612781128917719">"Pošiljanje sporočil SMS"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 2a2104fc1631..7f1dbcddc0a0 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Није било могуће повезати са Wi-Fi мрежом"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" има лошу интернет везу."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Покрените Wi-Fi Direct. Тиме ћете искључити клијента/хотспот за Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Није могуће покренути Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Прихвати"</string> <string name="decline" msgid="2112225451706137894">"Одбиј"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Позивница је послата"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Коме:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Унесите потребни PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct је укључен"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Додирните за подешавања"</string> <string name="select_character" msgid="3365550120617701745">"Уметање знака"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Непозната апликација"</string> <string name="sms_control_title" msgid="7296612781128917719">"Слање SMS порука"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 0067a12f4b09..10ecffc82728 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Det gick inte att ansluta till Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" har en dålig Internetanslutning."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi direkt"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Starta direkt Wi-Fi-användning. Detta inaktiverar Wi-Fi-användning med klient/trådlös surfzon."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Det gick inte att starta Wi-Fi direkt."</string> <string name="accept" msgid="1645267259272829559">"Godkänn"</string> <string name="decline" msgid="2112225451706137894">"Avvisa"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Inbjudan har skickats"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Till:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ange den obligatoriska PIN-koden:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kod:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct är aktiverat"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Tryck om du vill visa inställningar"</string> <string name="select_character" msgid="3365550120617701745">"Infoga tecken"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Okänd app"</string> <string name="sms_control_title" msgid="7296612781128917719">"Skickar SMS"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 369853c34966..d245685571d4 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Haikuweza kuunganisha kwa Mtandao-Hewa"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ina muunganisho duni wa Mtandao."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Mtandao hewa Moja kwa moja"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Anzisha Wi-Fi Moja kwa Moja. Hii itazima mteja/mtandao-hewa wa Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Haikuweza kuanzisha Wi-Fi Moja kwa Moja."</string> <string name="accept" msgid="1645267259272829559">"Kubali"</string> <string name="decline" msgid="2112225451706137894">"Kataa"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Mwaliko umetumwa"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kwa:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Charaza PIN inayohitajika:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi ya Moja kwa Moja imewashwa"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Gusa kwa ajili ya mipangilio"</string> <string name="select_character" msgid="3365550120617701745">"Ingiza kibambo"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Programu isiyojulikana"</string> <string name="sms_control_title" msgid="7296612781128917719">"Inatuma ujumbe wa SMS"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index debe568bc8c3..97f1c70cb863 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"ไม่สามารถเชื่อมต่อ Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" มีสัญญาณอินเทอร์เน็ตไม่ดี"</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"เริ่มการทำงาน WiFi Direct ซึ่งจะเป็นการปิดการทำงาน WiFi ไคลเอ็นต์/ฮอตสปอต"</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"ไม่สามารถเริ่ม WiFi Direct ได้"</string> <string name="accept" msgid="1645267259272829559">"ยอมรับ"</string> <string name="decline" msgid="2112225451706137894">"ปฏิเสธ"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"ส่งข้อความเชิญแล้ว"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"ถึง:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"พิมพ์ PIN ที่ต้องการ:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"เปิด Wi-Fi Direct อยู่"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"แตะเพื่อตั้งค่า"</string> <string name="select_character" msgid="3365550120617701745">"ใส่อักขระ"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"แอปพลิเคชันที่ไม่รู้จัก"</string> <string name="sms_control_title" msgid="7296612781128917719">"กำลังส่งข้อความ SMS"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index e74e309c3b1e..f59cde4360f4 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Hindi makakonekta sa Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" ay mayroong mahinang koneksyon sa Internet."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Simulan ang Wi-Fi Direct. I-o-off nito ang client/hotspot ng Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Hindi masimulan ang Wi-Fi Direct"</string> <string name="accept" msgid="1645267259272829559">"Tanggapin"</string> <string name="decline" msgid="2112225451706137894">"Tanggihan"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Naipadala ang imbitasyon"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Kay:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"I-type ang kinakailangang PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Ang Wi-Fi Direct ay naka-on"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Pindutin para sa mga setting"</string> <string name="select_character" msgid="3365550120617701745">"Magpasok ng character"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Hindi kilalang app"</string> <string name="sms_control_title" msgid="7296612781128917719">"Nagpapadala ng mga SMS na mensahe"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 8d52c8c8fc06..bcaa9a8989a8 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kablosuz bağlantısı kurulamadı"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" İnternet bağlantısı zayıf."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Kablosuz Doğrudan Bağlantı"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Kablosuz Doğrudan Bağlantıyı başlat. Bu işlem, Kablosuz istemci/hotspot kullanımını kapatacak."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Kablosuz Doğrudan bağlantı başlatılamadı."</string> <string name="accept" msgid="1645267259272829559">"Kabul et"</string> <string name="decline" msgid="2112225451706137894">"Reddet"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Davetiye gönderildi"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Alıcı:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Gerekli PIN\'i yazın:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Kablosuz Doğrudan özelliği açık"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Ayarlar için dokunun"</string> <string name="select_character" msgid="3365550120617701745">"Karakter ekle"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Bilinmeyen uygulama"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS mesajları gönderiliyor"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 9d049cf05cf1..6288aa16cd99 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Не вдалося під’єднатися до мережі Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" має погане з’єднання з Інтернетом."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Запустити Wi-Fi Direct. Це вимкне з’єднання Wi-Fi клієнт/точка доступу."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Не вдалося запустити Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Прийняти"</string> <string name="decline" msgid="2112225451706137894">"Відхилити"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Запрошення надіслано"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Кому:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Введіть потрібний PIN-код:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct увімкнено"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Торкніться, щоб побачити налаштування"</string> <string name="select_character" msgid="3365550120617701745">"Вставл-ня символу"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Невідома програма"</string> <string name="sms_control_title" msgid="7296612781128917719">"Надсил. SMS повідомлень"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 81666068ae8e..ba81838dbf6a 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Không thể kết nối với Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" có kết nối Internet không tốt."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Khởi động Wi-Fi Direct. Việc này sẽ tắt hoạt động của ứng dụng khách/điểm phát sóng Wi-Fi."</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Không thể khởi động Wi-Fi Direct."</string> <string name="accept" msgid="1645267259272829559">"Đồng ý"</string> <string name="decline" msgid="2112225451706137894">"Từ chối"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Đã gửi thư mời"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Người nhận:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Nhập PIN bắt buộc:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Mã PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct được bật"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Chạm để cài đặt"</string> <string name="select_character" msgid="3365550120617701745">"Chèn ký tự"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ứng dụng không xác định"</string> <string name="sms_control_title" msgid="7296612781128917719">"Đang gửi tin nhắn SMS"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index a9f431c54206..cbfc222f737d 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"无法连接到 Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" 互联网连接状况不佳。"</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"启动 Wi-Fi Direct。此操作将会关闭 Wi-Fi 客户端/热点。"</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"无法启动 Wi-Fi Direct。"</string> <string name="accept" msgid="1645267259272829559">"接受"</string> <string name="decline" msgid="2112225451706137894">"拒绝"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"邀请已发送"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"收件人:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"键入所需的 PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"已启用 Wi-Fi Direct"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"通过触摸进行设置"</string> <string name="select_character" msgid="3365550120617701745">"插入字符"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"未知应用程序"</string> <string name="sms_control_title" msgid="7296612781128917719">"正在发送短信"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 5aee830e55da..4d6042c713f9 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"無法連線至 Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" 的網際網路連線狀況不佳。"</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"啟動 WiFi Direct 作業,這會關閉 WiFi 用戶端/無線基地台作業。"</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"無法啟動 WiFi Direct。"</string> <string name="accept" msgid="1645267259272829559">"接受"</string> <string name="decline" msgid="2112225451706137894">"拒絕"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"邀請函已傳送"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"收件者:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"請輸入必要的 PIN:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct 已開啟"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"輕觸即可設定"</string> <string name="select_character" msgid="3365550120617701745">"插入字元"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"不明的應用程式"</string> <string name="sms_control_title" msgid="7296612781128917719">"傳送 SMS 簡訊"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 36da63de7b51..c90fe7665e55 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -215,7 +215,7 @@ <string name="permdesc_reorderTasks" msgid="4175137612205663399">"Ivumela insiza ukuthi ihambise izenzo ziye ngaphambili kanye nasemumva. Izinsiza ezinobungozi zingaziphoqelela ukuth iziye phambili ngaphandle kokulawula kwakho."</string> <string name="permlab_removeTasks" msgid="6821513401870377403">"misa izinsiza ezisebenzayo"</string> <string name="permdesc_removeTasks" msgid="1394714352062635493">"Vumela ukuthi insiza isuse okumele kwenziwe ibulale nezinsiza zakho. Izinsiza eziwubungozi zingaphazamisa ukusebenza kwezinye izinsiza."</string> - <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"misa ukuhambelana kwesikrini"</string> + <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"setha ukuhambelana kwesikrini"</string> <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"Ivumela uhlelo lokusebenza ukulawula imodi yokuhambelana kwesikrini kwezinye izinhlelo zokusebenza. Izinhlelo zokusebenza ezinonya zingase zephule ukuziphatha kwezinye izinhlelo zokusebenza."</string> <string name="permlab_setDebugApp" msgid="3022107198686584052">"vumela insiza ilungise inkinga"</string> <string name="permdesc_setDebugApp" msgid="4474512416299013256">"Ivumela insiza ukuthi ivule uhlelo lokulungisa lwenye insiza. Izinsiza ezinobungozi zingasebenzisa lokhu ukubulala ezinye izinsiza."</string> @@ -973,9 +973,6 @@ <skip /> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Ayikwazanga ukuxhuma kwi-Wi-Fi"</string> <string name="wifi_watchdog_network_disabled_detailed" msgid="5548780776418332675">" inoxhumano oluphansi lwe-inthanethi."</string> - <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"I-WiFi Eqondile"</string> - <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Qala ukusebenza kwe-WiFi Okuqondile. Lokhu kuzocima ikhasimende le-WiFi/Ukusebenza okwe-hotspot"</string> - <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Yehlulekile ukuqala i-Wi-Fi Ngqo"</string> <string name="accept" msgid="1645267259272829559">"Yamukela"</string> <string name="decline" msgid="2112225451706137894">"Nqaba"</string> <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"Isimemo sithunyelwe"</string> @@ -984,8 +981,6 @@ <string name="wifi_p2p_to_message" msgid="248968974522044099">"Ku:"</string> <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Faka i-PIN edingekayo:"</string> <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string> - <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"I-Wi-Fi Direct ivulekile"</string> - <string name="wifi_p2p_enabled_notification_message" msgid="1638949953993894335">"Thinta ukuze uthole izilungiselelo"</string> <string name="select_character" msgid="3365550120617701745">"Faka uhlamvu"</string> <string name="sms_control_default_app_name" msgid="3058577482636640465">"insiza engaziwa"</string> <string name="sms_control_title" msgid="7296612781128917719">"Ithumela imiyalezo ye-SMS"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3a7225ed1daf..1eab01aa7e58 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -246,6 +246,13 @@ <item>4</item> </integer-array> + <!-- If the DUN connection for this CDMA device supports more than just DUN --> + <!-- traffic you should list them here. --> + <!-- If this device is not CDMA this is ignored. If this list is empty on --> + <!-- a DUN-requiring CDMA device, the DUN APN will just support just DUN. --> + <string-array translatable="false" name="config_cdma_dun_supported_types"> + </string-array> + <!-- String containing the apn value for tethering. May be overriden by secure settings TETHER_DUN_APN. Value is a comma separated series of strings: "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 4e55b4f36e51..f347a4e3f3cf 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -218,7 +218,6 @@ <java-symbol type="bool" name="config_allowActionMenuItemTextWithIcon" /> <java-symbol type="bool" name="config_bluetooth_adapter_quick_switch" /> <java-symbol type="bool" name="config_bluetooth_sco_off_call" /> - <java-symbol type="bool" name="config_alwaysUseCdmaRssi" /> <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" /> <java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" /> <java-symbol type="bool" name="config_enable_puk_unlock_screen" /> @@ -864,6 +863,7 @@ <java-symbol type="array" name="special_locale_codes" /> <java-symbol type="array" name="special_locale_names" /> <java-symbol type="array" name="config_masterVolumeRamp" /> + <java-symbol type="array" name="config_cdma_dun_supported_types" /> <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="ic_suggestions_add" /> @@ -1084,7 +1084,6 @@ <java-symbol type="style" name="ActiveWallpaperSettings" /> <java-symbol type="style" name="Animation.InputMethodFancy" /> <java-symbol type="style" name="Animation.Wallpaper" /> - <java-symbol type="style" name="Animation.RecentApplications" /> <java-symbol type="style" name="Animation.ZoomButtons" /> <java-symbol type="style" name="PreviewWallpaperSettings" /> <java-symbol type="style" name="TextAppearance.SlidingTabActive" /> @@ -1440,6 +1439,7 @@ <java-symbol type="anim" name="push_down_out" /> <java-symbol type="anim" name="push_up_in" /> <java-symbol type="anim" name="push_up_out" /> + <java-symbol type="bool" name="config_alwaysUseCdmaRssi" /> <java-symbol type="dimen" name="status_bar_icon_size" /> <java-symbol type="dimen" name="system_bar_icon_size" /> <java-symbol type="drawable" name="list_selector_pressed_holo_dark" /> @@ -1477,6 +1477,7 @@ <java-symbol type="string" name="usb_storage_stop_notification_title" /> <java-symbol type="string" name="usb_storage_stop_title" /> <java-symbol type="string" name="usb_storage_title" /> + <java-symbol type="style" name="Animation.RecentApplications" /> <!-- ImfTest --> <java-symbol type="layout" name="auto_complete_list" /> @@ -3498,4 +3499,10 @@ <public type="attr" name="textDirection"/> + <public type="attr" name="paddingStart"/> + <public type="attr" name="paddingEnd"/> + + <public type="attr" name="layout_marginStart"/> + <public type="attr" name="layout_marginEnd"/> + </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index c95ddddef719..3c1f50d11b6d 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2248,6 +2248,14 @@ <!-- Description of an application permission which allows the application access serial ports via the SerialManager. [CHAR LIMIT=NONE] --> <string name="permdesc_serialPort">Allows the holder to access serial ports using the SerialManager API.</string> + <!-- Title of an application permission which allows the holder to access content + providers from outside an ApplicationThread. [CHAR LIMIT=40] --> + <string name="permlab_accessContentProvidersExternally">access content providers externally</string> + <!-- Description of an application permission which allows the holder to access + content providers from outside an ApplicationThread. [CHAR LIMIT=NONE] --> + <string name="permdesc_accessContentProvidersExternally">Allows the holder to access content + providers from the shell. Should never be needed for normal apps.</string> + <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. --> <string name="save_password_message">Do you want the browser to remember this password?</string> <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. --> @@ -2708,6 +2716,12 @@ <!-- Do not translate. Default access point SSID used for tethering --> <string name="wifi_tether_configure_ssid_default" translatable="false">AndroidAP</string> + <string name="wifi_p2p_dialog_title">Wi-Fi Direct</string> + <string name="wifi_p2p_turnon_message">Start Wi-Fi Direct. This will turn off Wi-Fi client/hotspot.</string> + <string name="wifi_p2p_failed_message">Couldn\'t start Wi-Fi Direct.</string> + <string name="wifi_p2p_enabled_notification_title">Wi-Fi Direct is on</string> + <string name="wifi_p2p_enabled_notification_message">Touch for settings</string> + <string name="accept">Accept</string> <string name="decline">Decline</string> <string name="wifi_p2p_invitation_sent_title">Invitation sent</string> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java index 9819c54aad6d..9c1922ff71e7 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java @@ -46,8 +46,7 @@ public class ConnectivityManagerTestRunner extends InstrumentationTestRunner { // create a new test suite suite.setName("ConnectivityManagerWifiOnlyFunctionalTests"); String[] methodNames = {"testConnectToWifi", "testConnectToWifWithKnownAP", - "testDisconnectWifi", "testDataConnectionOverAMWithWifi", - "testDataConnectionWithWifiToAMToWifi", "testWifiStateChange"}; + "testDisconnectWifi", "testWifiStateChange"}; Class<ConnectivityManagerMobileTest> testClass = ConnectivityManagerMobileTest.class; for (String method: methodNames) { suite.addTest(TestSuite.createTest(testClass, method)); diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index b81f7744cf37..88f3f348e76b 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, EnabledTestApp/src) LOCAL_DX_FLAGS := --core-library -LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib mockwebserver guava +LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib mockwebserver guava littlemock LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksCoreTests diff --git a/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java b/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java index 4d65588532ec..9015a6f5d7e4 100644 --- a/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java +++ b/core/tests/coretests/src/android/net/http/HttpResponseCacheTest.java @@ -16,6 +16,8 @@ package android.net.http; +import com.google.mockwebserver.MockResponse; +import com.google.mockwebserver.MockWebServer; import java.io.File; import java.net.CacheRequest; import java.net.CacheResponse; @@ -30,6 +32,7 @@ import junit.framework.TestCase; public final class HttpResponseCacheTest extends TestCase { private File cacheDir; + private MockWebServer server = new MockWebServer(); @Override public void setUp() throws Exception { super.setUp(); @@ -39,6 +42,7 @@ public final class HttpResponseCacheTest extends TestCase { @Override protected void tearDown() throws Exception { ResponseCache.setDefault(null); + server.shutdown(); super.tearDown(); } @@ -100,4 +104,32 @@ public final class HttpResponseCacheTest extends TestCase { cache.delete(); assertNull(ResponseCache.getDefault()); } + + /** + * Make sure that statistics tracking are wired all the way through the + * wrapper class. http://code.google.com/p/android/issues/detail?id=25418 + */ + public void testStatisticsTracking() throws Exception { + HttpResponseCache cache = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024); + + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("A")); + server.play(); + + URLConnection c1 = server.getUrl("/").openConnection(); + assertEquals('A', c1.getInputStream().read()); + assertEquals(1, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); + assertEquals(0, cache.getHitCount()); + + URLConnection c2 = server.getUrl("/").openConnection(); + assertEquals('A', c2.getInputStream().read()); + + URLConnection c3 = server.getUrl("/").openConnection(); + assertEquals('A', c3.getInputStream().read()); + assertEquals(3, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); + assertEquals(2, cache.getHitCount()); + } } diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocus.java b/core/tests/coretests/src/android/widget/focus/RequestFocus.java index af9ee170abe4..21d762a55061 100644 --- a/core/tests/coretests/src/android/widget/focus/RequestFocus.java +++ b/core/tests/coretests/src/android/widget/focus/RequestFocus.java @@ -21,9 +21,7 @@ import com.android.frameworks.coretests.R; import android.app.Activity; import android.os.Bundle; import android.os.Handler; -import android.widget.LinearLayout; import android.widget.Button; -import android.view.View; /** * Exercises cases where elements of the UI are requestFocus()ed. diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java index a78b0c932c4b..f2eba2335808 100644 --- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java +++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java @@ -16,21 +16,28 @@ package android.widget.focus; -import android.widget.focus.RequestFocus; -import com.android.frameworks.coretests.R; +import static com.google.testing.littlemock.LittleMock.inOrder; +import static com.google.testing.littlemock.LittleMock.mock; import android.os.Handler; -import android.test.ActivityInstrumentationTestCase; +import android.test.ActivityInstrumentationTestCase2; +import android.test.UiThreadTest; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; -import android.widget.Button; import android.util.AndroidRuntimeException; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; +import android.widget.Button; + +import com.android.frameworks.coretests.R; +import com.google.testing.littlemock.LittleMock.InOrder; /** * {@link RequestFocusTest} is set up to exercise cases where the views that * have focus become invisible or GONE. */ -public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFocus> { +public class RequestFocusTest extends ActivityInstrumentationTestCase2<RequestFocus> { private Button mTopLeftButton; private Button mBottomLeftButton; @@ -39,7 +46,7 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc private Handler mHandler; public RequestFocusTest() { - super("com.android.frameworks.coretests", RequestFocus.class); + super(RequestFocus.class); } @Override @@ -94,4 +101,90 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc e.getClass().getName()); } } + + /** + * This tests checks the case in which the first focusable View clears focus. + * In such a case the framework tries to give the focus to another View starting + * from the top. Hence, the framework will try to give focus to the view that + * wants to clear its focus. + * + * @throws Exception If an error occurs. + */ + @UiThreadTest + public void testOnFocusChangeCallbackOrderWhenClearingFocusOfFirstFocusable() + throws Exception { + // Get the first focusable. + Button clearingFocusButton = mTopLeftButton; + Button gainingFocusButton = mTopLeftButton; + + // Make sure that the clearing focus View is the first focusable. + View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null, + View.FOCUS_FORWARD); + assertSame("The clearing focus button is the first focusable.", + clearingFocusButton, focusCandidate); + assertSame("The gaining focus button is the first focusable.", + gainingFocusButton, focusCandidate); + + // Focus the clearing focus button. + clearingFocusButton.requestFocus(); + assertTrue(clearingFocusButton.hasFocus()); + + // Register the invocation order checker. + CombinedListeners mock = mock(CombinedListeners.class); + clearingFocusButton.setOnFocusChangeListener(mock); + gainingFocusButton.setOnFocusChangeListener(mock); + clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(mock); + + // Try to clear focus. + clearingFocusButton.clearFocus(); + + // Check that no callback was invoked since focus did not move. + InOrder inOrder = inOrder(mock); + inOrder.verify(mock).onFocusChange(clearingFocusButton, false); + inOrder.verify(mock).onGlobalFocusChanged(clearingFocusButton, gainingFocusButton); + inOrder.verify(mock).onFocusChange(gainingFocusButton, true); + } + + public interface CombinedListeners extends OnFocusChangeListener, OnGlobalFocusChangeListener {} + + /** + * This tests check whether the on focus change callbacks are invoked in + * the proper order when a View loses focus and the framework gives it to + * the fist focusable one. + * + * @throws Exception + */ + @UiThreadTest + public void testOnFocusChangeCallbackOrderWhenClearingFocusOfNotFirstFocusable() + throws Exception { + Button clearingFocusButton = mTopRightButton; + Button gainingFocusButton = mTopLeftButton; + + // Make sure that the clearing focus View is not the first focusable. + View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null, + View.FOCUS_FORWARD); + assertNotSame("The clearing focus button is not the first focusable.", + clearingFocusButton, focusCandidate); + assertSame("The gaining focus button is the first focusable.", + gainingFocusButton, focusCandidate); + + // Focus the clearing focus button. + clearingFocusButton.requestFocus(); + assertTrue(clearingFocusButton.hasFocus()); + + // Register the invocation order checker. + CombinedListeners mock = mock(CombinedListeners.class); + clearingFocusButton.setOnFocusChangeListener(mock); + gainingFocusButton.setOnFocusChangeListener(mock); + clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(mock); + + // Try to clear focus. + clearingFocusButton.clearFocus(); + + // Check that no callback was invoked since focus did not move. + InOrder inOrder = inOrder(mock); + inOrder.verify(mock).onFocusChange(clearingFocusButton, false); + inOrder.verify(mock).onGlobalFocusChanged(clearingFocusButton, gainingFocusButton); + inOrder.verify(mock).onFocusChange(gainingFocusButton, true); + } } diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 6cd07a31ccc5..8be1db2bb1f6 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -158,6 +158,7 @@ <assign-permission name="android.permission.BACKUP" uid="shell" /> <assign-permission name="android.permission.FORCE_STOP_PACKAGES" uid="shell" /> <assign-permission name="android.permission.STOP_APP_SWITCHES" uid="shell" /> + <assign-permission name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" uid="shell" /> <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" /> <assign-permission name="android.permission.ACCESS_DRM" uid="media" /> diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd index d390ec1474d1..d22dca1fee41 100644 --- a/docs/html/guide/developing/device.jd +++ b/docs/html/guide/developing/device.jd @@ -51,19 +51,13 @@ would on the emulator. Before you can start, there are just a few things to do:< <ol> <li>Declare your application as "debuggable" in your Android Manifest. - <p>In Eclipse, you can do this from the <b>Application</b> tab when viewing the Manifest - (on the right side, set <b>Debuggable</b> to <em>true</em>). Otherwise, in the -<code>AndroidManifest.xml</code> - file, add <code>android:debuggable="true"</code> to the <code><application></code> -element.</p> - </li> - <li>Set up your device to allow installation of non-Market applications. <p>On -the device, go to <strong>Settings > Applications</strong> and enable - -<strong>Unknown sources</strong> (on an Android 4.0 device, the setting is -located in <strong>Settings > Security</strong>).</p> - - </li> + <p>When using Eclipse, you can skip this step, because running your app directly from +the Eclipse IDE automatically enables debugging.</p> + <p>In the <code>AndroidManifest.xml</code> file, add <code>android:debuggable="true"</code> to +the <code><application></code> element.</p> + <p class="note"><strong>Note:</strong> If you manually enable debugging in the manifest + file, be sure to disable it before you build for release (your published application +should usually <em>not</em> be debuggable).</p></li> <li>Turn on "USB Debugging" on your device. <p>On the device, go to <strong>Settings > Applications > Development</strong> and enable <strong>USB debugging</strong> @@ -72,13 +66,10 @@ located in <strong>Settings > Developer options</strong>).</p> </li> <li>Set up your system to detect your device. <ul> - <li>If you're developing on Windows, you need to install a USB driver - for adb. If you're using an Android Developer Phone (ADP), Nexus One, or Nexus S, - see the <a href="{@docRoot}sdk/win-usb.html">Google Windows USB - Driver</a>. Otherwise, you can find a link to the appropriate OEM driver in the - <a href="{@docRoot}sdk/oem-usb.html">OEM USB Drivers</a> document.</li> + <li>If you're developing on Windows, you need to install a USB driver for adb. For an +installation guide and links to OEM drivers, see the <a href="{@docRoot}sdk/oem-usb.html">OEM USB +Drivers</a> document.</li> <li>If you're developing on Mac OS X, it just works. Skip this step.</li> - <li>If you're developing on Ubuntu Linux, you need to add a <code>udev</code> rules file that contains a USB configuration for each type of device you want to use for development. In the rules file, each device manufacturer @@ -114,7 +105,7 @@ rules</a>.</p> </li> </ol> -<p>You can verify that your device is connected by executing <code>adb +<p>When plugged in over USB, can verify that your device is connected by executing <code>adb devices</code> from your SDK {@code platform-tools/} directory. If connected, you'll see the device name listed as a "device."</p> diff --git a/docs/html/index.jd b/docs/html/index.jd index b9d67584aa74..431a7d26482c 100644 --- a/docs/html/index.jd +++ b/docs/html/index.jd @@ -154,7 +154,7 @@ href="{@docRoot}resources/dashboard/platform-versions.html">Learn more »</ + "href='https://plus.google.com/108967384991768947849'>+Android Developers</a>. " + "We'll use it to host Hangouts for developers, talk about the latest releases, " + "development and design tips, and much more.</p>" -+ "<div style='margin:.7em 0 0 -1.2em'><g:plus href='https://plus.google.com/108967384991768947849' " ++ "<div style='margin:.7em 0 0 0'><g:plus href='https://plus.google.com/108967384991768947849' " + "size=\"smallbadge\" width=\"275\"></g:plus></div>" }, diff --git a/docs/html/sdk/oem-usb.jd b/docs/html/sdk/oem-usb.jd index b81be71dc079..f98257df0027 100644 --- a/docs/html/sdk/oem-usb.jd +++ b/docs/html/sdk/oem-usb.jd @@ -3,9 +3,21 @@ page.title=OEM USB Drivers <div id="qv-wrapper"> <div id="qv"> + <h2>In this document</h2> + <ol> + <li><a href="#InstallingDriver">Installing a USB Driver</a> + <ol> + <li><a href="#Win7">Windows 7</a></li> + <li><a href="#WinXp">Windows XP</a></li> + <li><a href="#WinVista">Windows Vista</a></li> + </ol> + </li> + <li><a href="#Drivers">OEM Drivers</a></li> + </ol> + <h2>See also</h2> <ol> - <li><a href="{@docRoot}guide/developing/device.html">Developing on a Device</a></li> + <li><a href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a></li> <li><a href="{@docRoot}sdk/win-usb.html">Google USB Driver</a></li> </ol> </div> @@ -18,8 +30,185 @@ where you can download the appropriate USB driver for your device. However, this not exhaustive for all available Android-powered devices.</p> <p>If you're developing on Mac OS X or Linux, then you probably don't need to install a USB driver. -Refer to <a href="{@docRoot}guide/developing/device.html#setting-up">Setting up a Device</a> to -start development with a device.</p> +To start developing with your device, read <a +href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a>.</p> + +<p class="note"><strong>Note:</strong> If your device is one of the Android Developer Phones +(purchased from the Android Market publisher site), a Nexus One, or a Nexus S, then you need +the <a href="{@docRoot}sdk/win-usb.html">Google USB Driver</a>, instead of an OEM driver. The Galaxy +Nexus driver, however, is distributed by <a +href="http://www.samsung.com/us/support/downloads/verizon-wireless/SCH-I515MSAVZW">Samsung</a> +(listed as model SCH-I515).</p> + + +<h2 id="InstallingDriver">Installing a USB Driver</h2> + +<p>First, find the appropriate driver for your device from the <a href="#Drivers">OEM drivers</a> +table below.</p> + +<p>Once you've downloaded your USB driver, follow the instructions below to install or upgrade the +driver, based on your version of Windows and whether you're installing for the first time +or upgrading an existing driver.</p> + +<p class="note"><strong>Tip:</strong> When you finish the USB driver installation, +see <a +href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a> for +other important information about using an Android-powered device for +development.</p> + +<ol class="nolist"> + <li><a href="#Win7">Windows 7</a></li> + <li><a href="#WinXp">Windows XP</a></li> + <li><a href="#WinVista">Windows Vista</a></li> +</ol> + + +<p class="caution"><strong>Caution:</strong> +You may make changes to <code>android_winusb.inf</code> file found inside +<code>usb_driver\</code> (for example, to add support for other devices), +however, this will lead to security warnings when you install or upgrade the +driver. Making any other changes to the driver files may break the installation +process.</p> + + +<h3 id="Win7">Windows 7</h3> + + +<p>To install the Android USB driver on Windows 7 for the first time:</p> +<ol> + <li>Connect your Android-powered device to your computer's USB port.</li> + <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer, + and select <strong>Manage</strong>.</li> + <li>Select <strong>Devices</strong> in the left pane.</li> + <li>Locate and expand <em>Other device</em> in the right pane.</li> + <li>Right-click the device name (such as <em>Nexus S</em>) and select <strong>Update + Driver Software</strong>. + This will launch the Hardware Update Wizard.</li> + <li>Select <strong>Browse my computer for driver software</strong> and click + <strong>Next</strong>.</li> + <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB +Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> + <li>Click <strong>Next</strong> to install the driver.</li> +</ol> + +<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows 7 with the new +driver:</p> + +<ol> + <li>Connect your Android-powered device to your computer's USB port.</li> + <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer, + and select <strong>Manage</strong>.</li> + <li>Select <strong>Device Manager</strong> in the left pane of the Computer Management + window.</li> + <li>Locate and expand <em>Android Phone</em> in the right pane.</li> + <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update + Driver</strong>. + This will launch the Hardware Update Wizard.</li> + <li>Select <strong>Install from a list or specific location</strong> and click + <strong>Next</strong>.</li> + <li>Select <strong>Search for the best driver in these locations</strong>; un-check +<strong>Search removable media</strong>; and check <strong>Include this location in the +search</strong>.</li> + <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB +Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> + <li>Click <strong>Next</strong> to upgrade the driver.</li> +</ol> + + + + + +<h3 id="WinXp">Windows XP</h3> + +<p>To install the Android USB driver on Windows XP for the first time:</p> + +<ol> + <li>Connect your Android-powered device to your computer's USB port. Windows + will detect the device and launch the Hardware Update Wizard.</li> + <li>Select <strong>Install from a list or specific location</strong> and click + <strong>Next</strong>.</li> + <li>Select <strong>Search for the best driver in these locations</strong>; un-check +<strong>Search + removable media</strong>; and check <strong>Include +this location in the search</strong>.</li> + <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB +Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> + <li>Click <strong>Next</strong> to install the driver.</li> +</ol> + +<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows XP with the new +driver:</p> + +<ol> + <li>Connect your Android-powered device to your computer's USB port.</li> + <li>Right-click on <em>My Computer</em> from your desktop or Windows Explorer, + and select <strong>Manage</strong>.</li> + <li>Select <strong>Device Manager</strong> in the left pane.</li> + <li>Locate and expand <em>Android Phone</em> in the right pane.</li> + <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update + Driver</strong>. + This will launch the Hardware Update Wizard.</li> + <li>Select <strong>Install from a list or specific location</strong> and click + <strong>Next</strong>.</li> + <li>Select <strong>Search for the best driver in these locations</strong>; un-check <strong>Search + removable media</strong>; and check <strong>Include +this location in the search</strong>.</li> + <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB +Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> + <li>Click <strong>Next</strong> to upgrade the driver.</li> +</ol> + + + +<h3 id="WinVista">Windows Vista</h3> + +<p>To install the Android USB driver on Windows Vista for the first time:</p> + +<ol> + <li>Connect your Android-powered device to your computer's USB port. Windows + will detect the device and launch the Found New Hardware wizard.</li> + <li>Select <strong>Locate and install driver software</strong>.</li> + <li>Select <strong>Don't search online</strong>.</li> + <li>Select <strong>I don't have the disk. Show me other options</strong>.</li> + <li>Select <strong>Browse my computer for driver software</strong>.</li> + <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB +Driver is located in {@code <sdk>\extras\google\usb_driver\}.) As long as you specified the +exact location of the + installation package, you may leave <strong>Include subfolders</strong> checked or + unchecked—it doesn't matter.</li> + <li>Click <strong>Next</strong>. Vista may prompt you to confirm the privilege elevation + required for driver installation. Confirm it.</li> + <li>When Vista asks if you'd like to install the Google ADB Interface device, + click <strong>Install</strong> to install the driver.</li> +</ol> + +<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows Vista with the new +driver:</p> + +<ol> + <li>Connect your Android-powered device to your computer's USB port.</li> + <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer, + and select <strong>Manage</strong>.</li> + <li>Select <strong>Device Manager</strong> in the left pane.</li> + <li>Locate and expand <em>ADB Interface</em> in the right pane.</li> + <li>Right-click on <em>HTC Dream Composite ADB Interface</em>, and select <strong>Update + Driver Software</strong>.</li> + <li>When Vista starts updating the driver, a prompt will ask how you want to + search for the driver + software. Select <strong>Browse my computer for driver software</strong>.</li> + <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB +Driver is located in {@code <sdk>\extras\google\usb_driver\}.) As long as you specified the +exact location of the + installation package, you may leave <strong>Include subfolders</strong> checked or + unchecked—it doesn't matter.</li> + <li>Click <strong>Next</strong>. Vista might prompt you to confirm the privilege elevation + required for driver installation. Confirm it.</li> + <li>When Vista asks if you'd like to install the Google ADB Interface device, + click <strong>Install</strong> to upgrade the driver.</li> +</ol> + + +<h2 id="Drivers">OEM Drivers</h2> <p class="note"><strong>Note:</strong> If your device is one of the Android Developer Phones (purchased from the Android Market publisher site), a Nexus One, or a Nexus S, then you need @@ -28,10 +217,7 @@ Nexus driver, however, is distributed by <a href="http://www.samsung.com/us/support/downloads/verizon-wireless/SCH-I515MSAVZW">Samsung</a> (listed as model SCH-I515).</p> -<p>For instructions about how to install the driver on Windows, follow the guide for <a - href="{@docRoot}sdk/win-usb.html#InstallingDriver">Installing the USB Driver</a>.</p> -<p class="table-caption"><strong>Table 1.</strong> Links to OEM USB drivers</p> <table><tr> <th>OEM</th> <th>Driver URL</th></tr> @@ -92,7 +278,7 @@ href="http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index">http </tr> <tr><td>KT Tech</td> <td><a -href="http://www.kttech.co.kr/cscenter/download05.asp">http://www.kttech.co.kr/cscenter/download05.asp</a> for EV-S100(Take)</td> +href="http://www.kttech.co.kr/cscenter/download05.asp">http://www.kttech.co.kr/cscenter/download05.asp</a> for EV-S100 (Take)</td> </tr> <tr> <td> diff --git a/docs/html/sdk/win-usb.jd b/docs/html/sdk/win-usb.jd index 2d1435b8d4b5..2bd031ebb8b3 100644 --- a/docs/html/sdk/win-usb.jd +++ b/docs/html/sdk/win-usb.jd @@ -7,19 +7,12 @@ page.title=Google USB Driver <ol> <li><a href="#notes">Revisions</a></li> <li><a href="#WinUsbDriver">Downloading the Google USB Driver</a></li> - <li><a href="#InstallingDriver">Installing the USB Driver</a> - <ol> - <li><a href="#Win7">Windows 7</a></li> - <li><a href="#WinXp">Windows XP</a></li> - <li><a href="#WinVista">Windows Vista</a></li> - </ol> - </li> </ol> <h2>See also</h2> <ol> - <li><a href="{@docRoot}guide/developing/device.html">Developing on a Device</a></li> + <li><a href="{@docRoot}sdk/oem-usb.html#InstallingDriver">Installing a USB Driver</a></li> + <li><a href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a></li> <li><a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a></li> - <li><a href="{@docRoot}sdk/oem-usb.html">OEM USB Drivers</a></li> </ol> </div> </div> @@ -43,9 +36,9 @@ href="http://www.samsung.com/us/support/downloads/verizon-wireless/SCH-I515MSAVZ (listed as model SCH-I515).</p> <p class="note"><strong>Note:</strong> -If you're developing on Mac OS X or Linux, then you do not need to install a USB driver. Refer to <a -href="{@docRoot}guide/developing/device.html#setting-up">Setting up a Device</a> to start -development with a device.</p> +If you're developing on Mac OS X or Linux, then you do not need to install a USB driver. To start +developing with your device, also read <a href="{@docRoot}guide/developing/device.html">Using +Hardware Devices</a>.</p> <p>The sections below provide instructions on how to download and install the Google USB Driver for Windows. </p> @@ -170,174 +163,10 @@ included with the <a href="{@docRoot}sdk/index.html">Android SDK</a>:</p> <ol> <li>Launch the SDK and AVD Manager by double-clicking <code>SDK Manager.exe</code>, at the root of your SDK directory.</li> - <li>Expand the <em>Third party Add-ons</em> and <em>Google Inc. add-ons</em>.</li> - <li>Check <strong>Google Usb Driver package</strong> and click <strong>Install selected</strong>.</li> + <li>Expand <em>Extras</em>.</li> + <li>Check <strong>Google USB Driver package</strong> and click <strong>Install</strong>.</li> <li>Proceed to install the package. When done, the driver files are downloaded into the <code><sdk>\extras\google\usb_driver\</code> directory.</li> </ol> - - -<h2 id="InstallingDriver">Installing the USB Driver</h2> - -<p>Once you've downloaded your USB driver, follow the instructions below to install or upgrade the -driver, based on your version of Windows and whether you're installing for the first time -or upgrading an existing driver.</p> - -<p class="note"><strong>Tip:</strong> When you finish the USB driver installation, -see <a -href="{@docRoot}guide/developing/device.html">Developing on a Device</a> for -other important information about using an Android-powered device for -development.</p> - -<ol class="nolist"> - <li><a href="#Win7">Windows 7</a></li> - <li><a href="#WinXp">Windows XP</a></li> - <li><a href="#WinVista">Windows Vista</a></li> -</ol> - - -<p class="caution"><strong>Caution:</strong> -You may make changes to <code>android_winusb.inf</code> file found inside -<code>usb_driver\</code> (for example, to add support for other devices), -however, this will lead to security warnings when you install or upgrade the -driver. Making any other changes to the driver files may break the installation -process.</p> - - -<h3 id="Win7">Windows 7</h3> - - -<p>To install the Android USB driver on Windows 7 for the first time:</p> -<ol> - <li>Connect your Android-powered device to your computer's USB port.</li> - <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer, - and select <strong>Manage</strong>.</li> - <li>Select <strong>Devices</strong> in the left pane.</li> - <li>Locate and expand <em>Other device</em> in the right pane.</li> - <li>Right-click the device name (such as <em>Nexus S</em>) and select <strong>Update - Driver Software</strong>. - This will launch the Hardware Update Wizard.</li> - <li>Select <strong>Browse my computer for driver software</strong> and click - <strong>Next</strong>.</li> - <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB -Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> - <li>Click <strong>Next</strong> to install the driver.</li> -</ol> - -<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows 7 with the new -driver:</p> - -<ol> - <li>Connect your Android-powered device to your computer's USB port.</li> - <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer, - and select <strong>Manage</strong>.</li> - <li>Select <strong>Device Manager</strong> in the left pane of the Computer Management - window.</li> - <li>Locate and expand <em>Android Phone</em> in the right pane.</li> - <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update - Driver</strong>. - This will launch the Hardware Update Wizard.</li> - <li>Select <strong>Install from a list or specific location</strong> and click - <strong>Next</strong>.</li> - <li>Select <strong>Search for the best driver in these locations</strong>; un-check -<strong>Search removable media</strong>; and check <strong>Include this location in the -search</strong>.</li> - <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB -Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> - <li>Click <strong>Next</strong> to upgrade the driver.</li> -</ol> - - - - - -<h3 id="WinXp">Windows XP</h3> - -<p>To install the Android USB driver on Windows XP for the first time:</p> - -<ol> - <li>Connect your Android-powered device to your computer's USB port. Windows - will detect the device and launch the Hardware Update Wizard.</li> - <li>Select <strong>Install from a list or specific location</strong> and click - <strong>Next</strong>.</li> - <li>Select <strong>Search for the best driver in these locations</strong>; un-check -<strong>Search - removable media</strong>; and check <strong>Include -this location in the search</strong>.</li> - <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB -Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> - <li>Click <strong>Next</strong> to install the driver.</li> -</ol> - -<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows XP with the new -driver:</p> - -<ol> - <li>Connect your Android-powered device to your computer's USB port.</li> - <li>Right-click on <em>My Computer</em> from your desktop or Windows Explorer, - and select <strong>Manage</strong>.</li> - <li>Select <strong>Device Manager</strong> in the left pane.</li> - <li>Locate and expand <em>Android Phone</em> in the right pane.</li> - <li>Right-click <em>Android Composite ADB Interface</em> and select <strong>Update - Driver</strong>. - This will launch the Hardware Update Wizard.</li> - <li>Select <strong>Install from a list or specific location</strong> and click - <strong>Next</strong>.</li> - <li>Select <strong>Search for the best driver in these locations</strong>; un-check <strong>Search - removable media</strong>; and check <strong>Include -this location in the search</strong>.</li> - <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB -Driver is located in {@code <sdk>\extras\google\usb_driver\}.)</li> - <li>Click <strong>Next</strong> to upgrade the driver.</li> -</ol> - - - -<h3 id="WinVista">Windows Vista</h3> - -<p>To install the Android USB driver on Windows Vista for the first time:</p> - -<ol> - <li>Connect your Android-powered device to your computer's USB port. Windows - will detect the device and launch the Found New Hardware wizard.</li> - <li>Select <strong>Locate and install driver software</strong>.</li> - <li>Select <strong>Don't search online</strong>.</li> - <li>Select <strong>I don't have the disk. Show me other options</strong>.</li> - <li>Select <strong>Browse my computer for driver software</strong>.</li> - <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB -Driver is located in {@code <sdk>\extras\google\usb_driver\}.) As long as you specified the -exact location of the - installation package, you may leave <strong>Include subfolders</strong> checked or - unchecked—it doesn't matter.</li> - <li>Click <strong>Next</strong>. Vista may prompt you to confirm the privilege elevation - required for driver installation. Confirm it.</li> - <li>When Vista asks if you'd like to install the Google ADB Interface device, - click <strong>Install</strong> to install the driver.</li> -</ol> - -<p>Or, to <em>upgrade</em> an existing Android USB driver on Windows Vista with the new -driver:</p> - -<ol> - <li>Connect your Android-powered device to your computer's USB port.</li> - <li>Right-click on <em>Computer</em> from your desktop or Windows Explorer, - and select <strong>Manage</strong>.</li> - <li>Select <strong>Device Manager</strong> in the left pane.</li> - <li>Locate and expand <em>ADB Interface</em> in the right pane.</li> - <li>Right-click on <em>HTC Dream Composite ADB Interface</em>, and select <strong>Update - Driver Software</strong>.</li> - <li>When Vista starts updating the driver, a prompt will ask how you want to - search for the driver - software. Select <strong>Browse my computer for driver software</strong>.</li> - <li>Click <strong>Browse</strong> and locate the USB driver folder. (The Google USB -Driver is located in {@code <sdk>\extras\google\usb_driver\}.) As long as you specified the -exact location of the - installation package, you may leave <strong>Include subfolders</strong> checked or - unchecked—it doesn't matter.</li> - <li>Click <strong>Next</strong>. Vista might prompt you to confirm the privilege elevation - required for driver installation. Confirm it.</li> - <li>When Vista asks if you'd like to install the Google ADB Interface device, - click <strong>Install</strong> to upgrade the driver.</li> -</ol> - +<p>For installation information, read <a href="{@docRoot}sdk/oem-usb.html#InstallingDriver">Installing a USB Driver</a>.</p> diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java index 44401c83c1e8..37a270e9c10d 100644 --- a/graphics/java/android/renderscript/Allocation.java +++ b/graphics/java/android/renderscript/Allocation.java @@ -102,7 +102,7 @@ public class Allocation extends BaseObj { public static final int USAGE_SCRIPT = 0x0001; /** - * GRAPHICS_TEXTURE The allcation will be used as a texture + * GRAPHICS_TEXTURE The allocation will be used as a texture * source by one or more graphics programs. * */ @@ -124,34 +124,34 @@ public class Allocation extends BaseObj { public static final int USAGE_GRAPHICS_CONSTANTS = 0x0008; /** - * USAGE_GRAPHICS_RENDER_TARGET The allcation will be used as a + * USAGE_GRAPHICS_RENDER_TARGET The allocation will be used as a * target for offscreen rendering * */ public static final int USAGE_GRAPHICS_RENDER_TARGET = 0x0010; /** - * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT The allocation will be - * used with a SurfaceTexture object. This usage will cause the - * allocation to be created read only. + * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE The allocation + * will be used as a SurfaceTexture graphics consumer. This + * usage may only be used with USAGE_GRAPHICS_TEXTURE. * * @hide */ public static final int USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE = 0x0020; /** - * USAGE_IO_INPUT The allocation will be - * used with a SurfaceTexture object. This usage will cause the - * allocation to be created read only. + * USAGE_IO_INPUT The allocation will be used as SurfaceTexture + * consumer. This usage will cause the allocation to be created + * read only. * * @hide */ public static final int USAGE_IO_INPUT = 0x0040; /** - * USAGE_IO_OUTPUT The allocation will be - * used with a SurfaceTexture object. This usage will cause the - * allocation to be created write only. + * USAGE_IO_OUTPUT The allocation will be used as a + * SurfaceTexture producer. The dimensions and format of the + * SurfaceTexture will be forced to those of the allocation. * * @hide */ @@ -323,6 +323,37 @@ public class Allocation extends BaseObj { mRS.nAllocationSyncAll(getIDSafe(), srcLocation); } + /** + * Send a buffer to the output stream. The contents of the + * Allocation will be undefined after this operation. + * + * @hide + * + */ + public void ioSendOutput() { + if ((mUsage & USAGE_IO_OUTPUT) == 0) { + throw new RSIllegalArgumentException( + "Can only send buffer if IO_OUTPUT usage specified."); + } + mRS.validate(); + mRS.nAllocationIoSend(getID()); + } + + /** + * Receive the latest input into the Allocation. + * + * @hide + * + */ + public void ioGetInput() { + if ((mUsage & USAGE_IO_INPUT) == 0) { + throw new RSIllegalArgumentException( + "Can only send buffer if IO_OUTPUT usage specified."); + } + mRS.validate(); + mRS.nAllocationIoReceive(getID()); + } + public void copyFrom(BaseObj[] d) { mRS.validate(); validateIsObject(); @@ -887,17 +918,37 @@ public class Allocation extends BaseObj { updateCacheInfo(mType); } - /* + /** + * Resize a 2D allocation. The contents of the allocation are + * preserved. If new elements are allocated objects are created + * with null contents and the new region is otherwise undefined. + * + * If the new region is smaller the references of any objects + * outside the new region will be released. + * + * A new type will be created with the new dimension. + * + * @hide + * @param dimX The new size of the allocation. + * @param dimY The new size of the allocation. + */ public void resize(int dimX, int dimY) { - if ((mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) { - throw new RSIllegalStateException("Resize only support for 2D allocations at this time."); + if ((mType.getZ() > 0) || mType.hasFaces() || mType.hasMipmaps()) { + throw new RSInvalidStateException( + "Resize only support for 2D allocations at this time."); } if (mType.getY() == 0) { - throw new RSIllegalStateException("Resize only support for 2D allocations at this time."); + throw new RSInvalidStateException( + "Resize only support for 2D allocations at this time."); } mRS.nAllocationResize2D(getID(), dimX, dimY); + mRS.finish(); // Necessary because resize is fifoed and update is async. + + int typeID = mRS.nAllocationGetType(getID()); + mType = new Type(typeID, mRS); + mType.updateFromNative(); + updateCacheInfo(mType); } - */ @@ -1090,6 +1141,18 @@ public class Allocation extends BaseObj { } + /** + * @hide + */ + public void setSurfaceTexture(SurfaceTexture sur) { + if ((mUsage & USAGE_IO_OUTPUT) == 0) { + throw new RSInvalidStateException("Allocation is not USAGE_IO_OUTPUT."); + } + + mRS.validate(); + mRS.nAllocationSetSurfaceTexture(getID(), sur); + } + /** * Creates a non-mipmapped renderscript allocation to use as a diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java index f6a02817a573..3d4951f534ce 100644 --- a/graphics/java/android/renderscript/Element.java +++ b/graphics/java/android/renderscript/Element.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2008-2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -832,10 +832,12 @@ public class Element extends BaseObj { /** * Create a custom vector element of the specified DataType and vector size. - * DataKind will be set to USER. + * DataKind will be set to USER. Only primitive types (FLOAT_32, FLOAT_64, + * SIGNED_8, SIGNED_16, SIGNED_32, SIGNED_64, UNSIGNED_8, UNSIGNED_16, + * UNSIGNED_32, UNSIGNED_64, BOOLEAN) are supported. * * @param rs The context associated with the new Element. - * @param dt The DataType for the new element. + * @param dt The DataType for the new Element. * @param size Vector size for the new Element. Range 2-4 inclusive * supported. * @@ -845,10 +847,31 @@ public class Element extends BaseObj { if (size < 2 || size > 4) { throw new RSIllegalArgumentException("Vector size out of range 2-4."); } - DataKind dk = DataKind.USER; - boolean norm = false; - int id = rs.nElementCreate(dt.mID, dk.mID, norm, size); - return new Element(id, rs, dt, dk, norm, size); + + switch (dt) { + // Support only primitive integer/float/boolean types as vectors. + case FLOAT_32: + case FLOAT_64: + case SIGNED_8: + case SIGNED_16: + case SIGNED_32: + case SIGNED_64: + case UNSIGNED_8: + case UNSIGNED_16: + case UNSIGNED_32: + case UNSIGNED_64: + case BOOLEAN: { + DataKind dk = DataKind.USER; + boolean norm = false; + int id = rs.nElementCreate(dt.mID, dk.mID, norm, size); + return new Element(id, rs, dt, dk, norm, size); + } + + default: { + throw new RSIllegalArgumentException("Cannot create vector of " + + "non-primitive type."); + } + } } /** @@ -933,10 +956,10 @@ public class Element extends BaseObj { // Ignore mKind because it is allowed to be different (user vs. pixel). // We also ignore mNormalized because it can be different. The mType - // field must be non-null since we require name equivalence for - // user-created Elements. + // field must not be NONE since we require name equivalence for + // all user-created Elements. return ((mSize == e.mSize) && - (mType != null) && + (mType != DataType.NONE) && (mType == e.mType) && (mVectorSize == e.mVectorSize)); } diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java index 3f769eecdd97..4d60ac860621 100644 --- a/graphics/java/android/renderscript/Program.java +++ b/graphics/java/android/renderscript/Program.java @@ -69,6 +69,7 @@ public class Program extends BaseObj { Element mOutputs[]; Type mConstants[]; TextureType mTextures[]; + String mTextureNames[]; int mTextureCount; String mShader; @@ -111,6 +112,16 @@ public class Program extends BaseObj { } /** + * @hide + */ + public String getTextureName(int slot) { + if ((slot < 0) || (slot >= mTextureCount)) { + throw new IllegalArgumentException("Slot ID out of range."); + } + return mTextureNames[slot]; + } + + /** * Binds a constant buffer to be used as uniform inputs to the * program * @@ -180,6 +191,7 @@ public class Program extends BaseObj { Type mConstants[]; Type mTextures[]; TextureType mTextureTypes[]; + String mTextureNames[]; int mInputCount; int mOutputCount; int mConstantCount; @@ -197,6 +209,7 @@ public class Program extends BaseObj { mConstantCount = 0; mTextureCount = 0; mTextureTypes = new TextureType[MAX_TEXTURE]; + mTextureNames = new String[MAX_TEXTURE]; } /** @@ -300,10 +313,28 @@ public class Program extends BaseObj { * @return self */ public BaseProgramBuilder addTexture(TextureType texType) throws IllegalArgumentException { + addTexture(texType, "Tex" + mTextureCount); + return this; + } + + /** + * @hide + * Adds a texture input to the Program + * + * @param texType describes that the texture to append it (2D, + * Cubemap, etc.) + * @param texName what the texture should be called in the + * shader + * @return self + */ + public BaseProgramBuilder addTexture(TextureType texType, String texName) + throws IllegalArgumentException { if(mTextureCount >= MAX_TEXTURE) { throw new IllegalArgumentException("Max texture count exceeded."); } - mTextureTypes[mTextureCount ++] = texType; + mTextureTypes[mTextureCount] = texType; + mTextureNames[mTextureCount] = texName; + mTextureCount ++; return this; } @@ -317,6 +348,8 @@ public class Program extends BaseObj { p.mTextureCount = mTextureCount; p.mTextures = new TextureType[mTextureCount]; System.arraycopy(mTextureTypes, 0, p.mTextures, 0, mTextureCount); + p.mTextureNames = new String[mTextureCount]; + System.arraycopy(mTextureNames, 0, p.mTextureNames, 0, mTextureCount); } } diff --git a/graphics/java/android/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java index 21bace82f74f..ebc15e5070b5 100644 --- a/graphics/java/android/renderscript/ProgramFragment.java +++ b/graphics/java/android/renderscript/ProgramFragment.java @@ -59,6 +59,7 @@ public class ProgramFragment extends Program { public ProgramFragment create() { mRS.validate(); int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2]; + String[] texNames = new String[mTextureCount]; int idx = 0; for (int i=0; i < mInputCount; i++) { @@ -76,9 +77,10 @@ public class ProgramFragment extends Program { for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; tmp[idx++] = mTextureTypes[i].mID; + texNames[i] = mTextureNames[i]; } - int id = mRS.nProgramFragmentCreate(mShader, tmp); + int id = mRS.nProgramFragmentCreate(mShader, texNames, tmp); ProgramFragment pf = new ProgramFragment(id, mRS); initProgram(pf); return pf; diff --git a/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java b/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java index 0ab73c1b222b..cd31db3a919b 100644 --- a/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java +++ b/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java @@ -47,6 +47,7 @@ public class ProgramFragmentFixedFunction extends ProgramFragment { public ProgramFragmentFixedFunction create() { mRS.validate(); int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2]; + String[] texNames = new String[mTextureCount]; int idx = 0; for (int i=0; i < mInputCount; i++) { @@ -64,9 +65,10 @@ public class ProgramFragmentFixedFunction extends ProgramFragment { for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; tmp[idx++] = mTextureTypes[i].mID; + texNames[i] = mTextureNames[i]; } - int id = mRS.nProgramFragmentCreate(mShader, tmp); + int id = mRS.nProgramFragmentCreate(mShader, texNames, tmp); ProgramFragmentFixedFunction pf = new ProgramFragmentFixedFunction(id, mRS); initProgram(pf); return pf; diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java index b3c1bd9ee37a..a6cd15b731ea 100644 --- a/graphics/java/android/renderscript/ProgramVertex.java +++ b/graphics/java/android/renderscript/ProgramVertex.java @@ -116,6 +116,7 @@ public class ProgramVertex extends Program { public ProgramVertex create() { mRS.validate(); int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2]; + String[] texNames = new String[mTextureCount]; int idx = 0; for (int i=0; i < mInputCount; i++) { @@ -133,9 +134,10 @@ public class ProgramVertex extends Program { for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; tmp[idx++] = mTextureTypes[i].mID; + texNames[i] = mTextureNames[i]; } - int id = mRS.nProgramVertexCreate(mShader, tmp); + int id = mRS.nProgramVertexCreate(mShader, texNames, tmp); ProgramVertex pv = new ProgramVertex(id, mRS); initProgram(pv); return pv; diff --git a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java index 740d6a5a9566..9a43943da360 100644 --- a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java +++ b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java @@ -70,6 +70,7 @@ public class ProgramVertexFixedFunction extends ProgramVertex { public ProgramVertexFixedFunction create() { mRS.validate(); int[] tmp = new int[(mInputCount + mOutputCount + mConstantCount + mTextureCount) * 2]; + String[] texNames = new String[mTextureCount]; int idx = 0; for (int i=0; i < mInputCount; i++) { @@ -87,9 +88,10 @@ public class ProgramVertexFixedFunction extends ProgramVertex { for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; tmp[idx++] = mTextureTypes[i].mID; + texNames[i] = mTextureNames[i]; } - int id = mRS.nProgramVertexCreate(mShader, tmp); + int id = mRS.nProgramVertexCreate(mShader, texNames, tmp); ProgramVertexFixedFunction pv = new ProgramVertexFixedFunction(id, mRS); initProgram(pv); return pv; diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index d3c801f2f730..95175135cf3a 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -274,6 +274,22 @@ public class RenderScript { validate(); return rsnAllocationGetSurfaceTextureID(mContext, alloc); } + native void rsnAllocationSetSurfaceTexture(int con, int alloc, SurfaceTexture sur); + synchronized void nAllocationSetSurfaceTexture(int alloc, SurfaceTexture sur) { + validate(); + rsnAllocationSetSurfaceTexture(mContext, alloc, sur); + } + native void rsnAllocationIoSend(int con, int alloc); + synchronized void nAllocationIoSend(int alloc) { + validate(); + rsnAllocationIoSend(mContext, alloc); + } + native void rsnAllocationIoReceive(int con, int alloc); + synchronized void nAllocationIoReceive(int alloc) { + validate(); + rsnAllocationIoReceive(mContext, alloc); + } + native void rsnAllocationGenerateMipmaps(int con, int alloc); synchronized void nAllocationGenerateMipmaps(int alloc) { @@ -553,15 +569,15 @@ public class RenderScript { validate(); rsnProgramBindSampler(mContext, vpf, slot, s); } - native int rsnProgramFragmentCreate(int con, String shader, int[] params); - synchronized int nProgramFragmentCreate(String shader, int[] params) { + native int rsnProgramFragmentCreate(int con, String shader, String[] texNames, int[] params); + synchronized int nProgramFragmentCreate(String shader, String[] texNames, int[] params) { validate(); - return rsnProgramFragmentCreate(mContext, shader, params); + return rsnProgramFragmentCreate(mContext, shader, texNames, params); } - native int rsnProgramVertexCreate(int con, String shader, int[] params); - synchronized int nProgramVertexCreate(String shader, int[] params) { + native int rsnProgramVertexCreate(int con, String shader, String[] texNames, int[] params); + synchronized int nProgramVertexCreate(String shader, String[] texNames, int[] params) { validate(); - return rsnProgramVertexCreate(mContext, shader, params); + return rsnProgramVertexCreate(mContext, shader, texNames, params); } native int rsnMeshCreate(int con, int[] vtx, int[] idx, int[] prim); diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp index 94f19b34661d..2d8c416d8054 100644 --- a/graphics/jni/android_renderscript_RenderScript.cpp +++ b/graphics/jni/android_renderscript_RenderScript.cpp @@ -54,13 +54,11 @@ using namespace android; class AutoJavaStringToUTF8 { public: - AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str) - { + AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str) { fCStr = env->GetStringUTFChars(str, NULL); fLength = env->GetStringUTFLength(str); } - ~AutoJavaStringToUTF8() - { + ~AutoJavaStringToUTF8() { fEnv->ReleaseStringUTFChars(fJStr, fCStr); } const char* c_str() const { return fCStr; } @@ -73,6 +71,42 @@ private: jsize fLength; }; +class AutoJavaStringArrayToUTF8 { +public: + AutoJavaStringArrayToUTF8(JNIEnv* env, jobjectArray strings, jsize stringsLength) + : mEnv(env), mStrings(strings), mStringsLength(stringsLength) { + mCStrings = NULL; + mSizeArray = NULL; + if (stringsLength > 0) { + mCStrings = (const char **)calloc(stringsLength, sizeof(char *)); + mSizeArray = (size_t*)calloc(stringsLength, sizeof(size_t)); + for (jsize ct = 0; ct < stringsLength; ct ++) { + jstring s = (jstring)mEnv->GetObjectArrayElement(mStrings, ct); + mCStrings[ct] = mEnv->GetStringUTFChars(s, NULL); + mSizeArray[ct] = mEnv->GetStringUTFLength(s); + } + } + } + ~AutoJavaStringArrayToUTF8() { + for (jsize ct=0; ct < mStringsLength; ct++) { + jstring s = (jstring)mEnv->GetObjectArrayElement(mStrings, ct); + mEnv->ReleaseStringUTFChars(s, mCStrings[ct]); + } + free(mCStrings); + free(mSizeArray); + } + const char **c_str() const { return mCStrings; } + size_t *c_str_len() const { return mSizeArray; } + jsize length() const { return mStringsLength; } + +private: + JNIEnv *mEnv; + jobjectArray mStrings; + const char **mCStrings; + size_t *mSizeArray; + jsize mStringsLength; +}; + // --------------------------------------------------------------------------- static jfieldID gContextId = 0; @@ -322,33 +356,27 @@ nElementCreate(JNIEnv *_env, jobject _this, RsContext con, jint type, jint kind, } static jint -nElementCreate2(JNIEnv *_env, jobject _this, RsContext con, jintArray _ids, jobjectArray _names, jintArray _arraySizes) +nElementCreate2(JNIEnv *_env, jobject _this, RsContext con, + jintArray _ids, jobjectArray _names, jintArray _arraySizes) { int fieldCount = _env->GetArrayLength(_ids); LOG_API("nElementCreate2, con(%p)", con); jint *ids = _env->GetIntArrayElements(_ids, NULL); jint *arraySizes = _env->GetIntArrayElements(_arraySizes, NULL); - const char ** nameArray = (const char **)calloc(fieldCount, sizeof(char *)); - size_t* sizeArray = (size_t*)calloc(fieldCount, sizeof(size_t)); - for (int ct=0; ct < fieldCount; ct++) { - jstring s = (jstring)_env->GetObjectArrayElement(_names, ct); - nameArray[ct] = _env->GetStringUTFChars(s, NULL); - sizeArray[ct] = _env->GetStringUTFLength(s); - } + AutoJavaStringArrayToUTF8 names(_env, _names, fieldCount); + + const char **nameArray = names.c_str(); + size_t *sizeArray = names.c_str_len(); + jint id = (jint)rsElementCreate2(con, (RsElement *)ids, fieldCount, nameArray, fieldCount * sizeof(size_t), sizeArray, (const uint32_t *)arraySizes, fieldCount); - for (int ct=0; ct < fieldCount; ct++) { - jstring s = (jstring)_env->GetObjectArrayElement(_names, ct); - _env->ReleaseStringUTFChars(s, nameArray[ct]); - } + _env->ReleaseIntArrayElements(_ids, ids, JNI_ABORT); _env->ReleaseIntArrayElements(_arraySizes, arraySizes, JNI_ABORT); - free(nameArray); - free(sizeArray); return (jint)id; } @@ -451,6 +479,37 @@ nAllocationGetSurfaceTextureID(JNIEnv *_env, jobject _this, RsContext con, jint } static void +nAllocationSetSurfaceTexture(JNIEnv *_env, jobject _this, RsContext con, + RsAllocation alloc, jobject sur) +{ + LOG_API("nAllocationSetSurfaceTexture, con(%p), alloc(%p), surface(%p)", + con, alloc, (Surface *)sur); + + sp<ANativeWindow> window; + if (sur != 0) { + sp<SurfaceTexture> st = SurfaceTexture_getSurfaceTexture(_env, sur); + window = new SurfaceTextureClient(st); + } + + rsAllocationSetSurface(con, alloc, window.get()); +} + +static void +nAllocationIoSend(JNIEnv *_env, jobject _this, RsContext con, RsAllocation alloc) +{ + LOG_API("nAllocationIoSend, con(%p), alloc(%p)", con, alloc); + rsAllocationIoSend(con, alloc); +} + +static void +nAllocationIoReceive(JNIEnv *_env, jobject _this, RsContext con, RsAllocation alloc) +{ + LOG_API("nAllocationIoReceive, con(%p), alloc(%p)", con, alloc); + rsAllocationIoReceive(con, alloc); +} + + +static void nAllocationGenerateMipmaps(JNIEnv *_env, jobject _this, RsContext con, jint alloc) { LOG_API("nAllocationGenerateMipmaps, con(%p), a(%p)", con, (RsAllocation)alloc); @@ -1033,15 +1092,24 @@ nProgramBindSampler(JNIEnv *_env, jobject _this, RsContext con, jint vpf, jint s // --------------------------------------------------------------------------- static jint -nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params) +nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, + jobjectArray texNames, jintArray params) { AutoJavaStringToUTF8 shaderUTF(_env, shader); jint *paramPtr = _env->GetIntArrayElements(params, NULL); jint paramLen = _env->GetArrayLength(params); + int texCount = _env->GetArrayLength(texNames); + AutoJavaStringArrayToUTF8 names(_env, texNames, texCount); + const char ** nameArray = names.c_str(); + size_t* sizeArray = names.c_str_len(); + LOG_API("nProgramFragmentCreate, con(%p), paramLen(%i)", con, paramLen); - jint ret = (jint)rsProgramFragmentCreate(con, shaderUTF.c_str(), shaderUTF.length(), (uint32_t *)paramPtr, paramLen); + jint ret = (jint)rsProgramFragmentCreate(con, shaderUTF.c_str(), shaderUTF.length(), + nameArray, texCount, sizeArray, + (uint32_t *)paramPtr, paramLen); + _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT); return ret; } @@ -1050,7 +1118,8 @@ nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shade // --------------------------------------------------------------------------- static jint -nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params) +nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, + jobjectArray texNames, jintArray params) { AutoJavaStringToUTF8 shaderUTF(_env, shader); jint *paramPtr = _env->GetIntArrayElements(params, NULL); @@ -1058,7 +1127,15 @@ nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, LOG_API("nProgramVertexCreate, con(%p), paramLen(%i)", con, paramLen); - jint ret = (jint)rsProgramVertexCreate(con, shaderUTF.c_str(), shaderUTF.length(), (uint32_t *)paramPtr, paramLen); + int texCount = _env->GetArrayLength(texNames); + AutoJavaStringArrayToUTF8 names(_env, texNames, texCount); + const char ** nameArray = names.c_str(); + size_t* sizeArray = names.c_str_len(); + + jint ret = (jint)rsProgramVertexCreate(con, shaderUTF.c_str(), shaderUTF.length(), + nameArray, texCount, sizeArray, + (uint32_t *)paramPtr, paramLen); + _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT); return ret; } @@ -1277,6 +1354,9 @@ static JNINativeMethod methods[] = { {"rsnAllocationSyncAll", "(III)V", (void*)nAllocationSyncAll }, {"rsnAllocationGetSurfaceTextureID", "(II)I", (void*)nAllocationGetSurfaceTextureID }, +{"rsnAllocationSetSurfaceTexture", "(IILandroid/graphics/SurfaceTexture;)V", (void*)nAllocationSetSurfaceTexture }, +{"rsnAllocationIoSend", "(II)V", (void*)nAllocationIoSend }, +{"rsnAllocationIoReceive", "(II)V", (void*)nAllocationIoReceive }, {"rsnAllocationData1D", "(IIIII[II)V", (void*)nAllocationData1D_i }, {"rsnAllocationData1D", "(IIIII[SI)V", (void*)nAllocationData1D_s }, {"rsnAllocationData1D", "(IIIII[BI)V", (void*)nAllocationData1D_b }, @@ -1317,9 +1397,9 @@ static JNINativeMethod methods[] = { {"rsnProgramBindTexture", "(IIII)V", (void*)nProgramBindTexture }, {"rsnProgramBindSampler", "(IIII)V", (void*)nProgramBindSampler }, -{"rsnProgramFragmentCreate", "(ILjava/lang/String;[I)I", (void*)nProgramFragmentCreate }, +{"rsnProgramFragmentCreate", "(ILjava/lang/String;[Ljava/lang/String;[I)I", (void*)nProgramFragmentCreate }, {"rsnProgramRasterCreate", "(IZI)I", (void*)nProgramRasterCreate }, -{"rsnProgramVertexCreate", "(ILjava/lang/String;[I)I", (void*)nProgramVertexCreate }, +{"rsnProgramVertexCreate", "(ILjava/lang/String;[Ljava/lang/String;[I)I", (void*)nProgramVertexCreate }, {"rsnContextBindRootScript", "(II)V", (void*)nContextBindRootScript }, {"rsnContextBindProgramStore", "(II)V", (void*)nContextBindProgramStore }, diff --git a/include/common_time/ICommonClock.h b/include/common_time/ICommonClock.h new file mode 100644 index 000000000000..d7073f1337d9 --- /dev/null +++ b/include/common_time/ICommonClock.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_ICOMMONCLOCK_H +#define ANDROID_ICOMMONCLOCK_H + +#include <stdint.h> +#include <linux/socket.h> + +#include <binder/IInterface.h> +#include <binder/IServiceManager.h> + +namespace android { + +class ICommonClockListener : public IInterface { + public: + DECLARE_META_INTERFACE(CommonClockListener); + + virtual void onTimelineChanged(uint64_t timelineID) = 0; +}; + +class BnCommonClockListener : public BnInterface<ICommonClockListener> { + public: + virtual status_t onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags = 0); +}; + +class ICommonClock : public IInterface { + public: + DECLARE_META_INTERFACE(CommonClock); + + // Name of the ICommonClock service registered with the service manager. + static const String16 kServiceName; + + // a reserved invalid timeline ID + static const uint64_t kInvalidTimelineID; + + // a reserved invalid error estimate + static const int32_t kErrorEstimateUnknown; + + enum State { + // the device just came up and is trying to discover the master + STATE_INITIAL, + + // the device is a client of a master + STATE_CLIENT, + + // the device is acting as master + STATE_MASTER, + + // the device has lost contact with its master and needs to participate + // in the election of a new master + STATE_RONIN, + + // the device is waiting for announcement of the newly elected master + STATE_WAIT_FOR_ELECTION, + }; + + virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) = 0; + virtual status_t commonTimeToLocalTime(int64_t commonTime, + int64_t* localTime) = 0; + virtual status_t localTimeToCommonTime(int64_t localTime, + int64_t* commonTime) = 0; + virtual status_t getCommonTime(int64_t* commonTime) = 0; + virtual status_t getCommonFreq(uint64_t* freq) = 0; + virtual status_t getLocalTime(int64_t* localTime) = 0; + virtual status_t getLocalFreq(uint64_t* freq) = 0; + virtual status_t getEstimatedError(int32_t* estimate) = 0; + virtual status_t getTimelineID(uint64_t* id) = 0; + virtual status_t getState(State* state) = 0; + virtual status_t getMasterAddr(struct sockaddr_storage* addr) = 0; + + virtual status_t registerListener( + const sp<ICommonClockListener>& listener) = 0; + virtual status_t unregisterListener( + const sp<ICommonClockListener>& listener) = 0; + + // Simple helper to make it easier to connect to the CommonClock service. + static inline sp<ICommonClock> getInstance() { + sp<IBinder> binder = defaultServiceManager()->checkService( + ICommonClock::kServiceName); + sp<ICommonClock> clk = interface_cast<ICommonClock>(binder); + return clk; + } +}; + +class BnCommonClock : public BnInterface<ICommonClock> { + public: + virtual status_t onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags = 0); +}; + +}; // namespace android + +#endif // ANDROID_ICOMMONCLOCK_H diff --git a/include/common_time/ICommonTimeConfig.h b/include/common_time/ICommonTimeConfig.h new file mode 100644 index 000000000000..497b666de6f8 --- /dev/null +++ b/include/common_time/ICommonTimeConfig.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_ICOMMONTIMECONFIG_H +#define ANDROID_ICOMMONTIMECONFIG_H + +#include <stdint.h> +#include <linux/socket.h> + +#include <binder/IInterface.h> +#include <binder/IServiceManager.h> + +namespace android { + +class String16; + +class ICommonTimeConfig : public IInterface { + public: + DECLARE_META_INTERFACE(CommonTimeConfig); + + // Name of the ICommonTimeConfig service registered with the service + // manager. + static const String16 kServiceName; + + virtual status_t getMasterElectionPriority(uint8_t *priority) = 0; + virtual status_t setMasterElectionPriority(uint8_t priority) = 0; + virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) = 0; + virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr) = 0; + virtual status_t getMasterElectionGroupId(uint64_t *id) = 0; + virtual status_t setMasterElectionGroupId(uint64_t id) = 0; + virtual status_t getInterfaceBinding(String16& ifaceName) = 0; + virtual status_t setInterfaceBinding(const String16& ifaceName) = 0; + virtual status_t getMasterAnnounceInterval(int *interval) = 0; + virtual status_t setMasterAnnounceInterval(int interval) = 0; + virtual status_t getClientSyncInterval(int *interval) = 0; + virtual status_t setClientSyncInterval(int interval) = 0; + virtual status_t getPanicThreshold(int *threshold) = 0; + virtual status_t setPanicThreshold(int threshold) = 0; + virtual status_t getAutoDisable(bool *autoDisable) = 0; + virtual status_t setAutoDisable(bool autoDisable) = 0; + virtual status_t forceNetworklessMasterMode() = 0; + + // Simple helper to make it easier to connect to the CommonTimeConfig service. + static inline sp<ICommonTimeConfig> getInstance() { + sp<IBinder> binder = defaultServiceManager()->checkService( + ICommonTimeConfig::kServiceName); + sp<ICommonTimeConfig> clk = interface_cast<ICommonTimeConfig>(binder); + return clk; + } +}; + +class BnCommonTimeConfig : public BnInterface<ICommonTimeConfig> { + public: + virtual status_t onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags = 0); +}; + +}; // namespace android + +#endif // ANDROID_ICOMMONTIMECONFIG_H diff --git a/include/common_time/cc_helper.h b/include/common_time/cc_helper.h new file mode 100644 index 000000000000..8c4d5c063d52 --- /dev/null +++ b/include/common_time/cc_helper.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CC_HELPER_H__ +#define __CC_HELPER_H__ + +#include <stdint.h> +#include <common_time/ICommonClock.h> +#include <utils/threads.h> + +namespace android { + +// CCHelper is a simple wrapper class to help with centralizing access to the +// Common Clock service and implementing lifetime managment, as well as to +// implement a simple policy of making a basic attempt to reconnect to the +// common clock service when things go wrong. +// +// On platforms which run the native common_time service in auto-disable mode, +// the service will go into networkless mode whenever it has no active clients. +// It tracks active clients using registered CommonClockListeners (the callback +// interface for onTimelineChanged) since this provides a convienent death +// handler notification for when the service's clients die unexpectedly. This +// means that users of the common time service should really always have a +// CommonClockListener, unless they know that the time service is not running in +// auto disabled mode, or that there is at least one other registered listener +// active in the system. The CCHelper makes this a little easier by sharing a +// ref counted ICommonClock interface across all clients and automatically +// registering and unregistering a listener whenever there are CCHelper +// instances active in the process. +class CCHelper { + public: + CCHelper(); + ~CCHelper(); + + status_t isCommonTimeValid(bool* valid, uint32_t* timelineID); + status_t commonTimeToLocalTime(int64_t commonTime, int64_t* localTime); + status_t localTimeToCommonTime(int64_t localTime, int64_t* commonTime); + status_t getCommonTime(int64_t* commonTime); + status_t getCommonFreq(uint64_t* freq); + status_t getLocalTime(int64_t* localTime); + status_t getLocalFreq(uint64_t* freq); + + private: + class CommonClockListener : public BnCommonClockListener { + public: + void onTimelineChanged(uint64_t timelineID); + }; + + static bool verifyClock_l(); + + static Mutex lock_; + static sp<ICommonClock> common_clock_; + static sp<ICommonClockListener> common_clock_listener_; + static uint32_t ref_count_; +}; + + +} // namespace android +#endif // __CC_HELPER_H__ diff --git a/include/common_time/local_clock.h b/include/common_time/local_clock.h new file mode 100644 index 000000000000..845d1c21614d --- /dev/null +++ b/include/common_time/local_clock.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef __LOCAL_CLOCK_H__ +#define __LOCAL_CLOCK_H__ + +#include <stdint.h> + +#include <hardware/local_time_hal.h> +#include <utils/Errors.h> +#include <utils/threads.h> + +namespace android { + +class LocalClock { + public: + LocalClock(); + + bool initCheck(); + + int64_t getLocalTime(); + uint64_t getLocalFreq(); + status_t setLocalSlew(int16_t rate); + int32_t getDebugLog(struct local_time_debug_event* records, + int max_records); + + private: + static Mutex dev_lock_; + static local_time_hw_device_t* dev_; +}; + +} // namespace android +#endif // __LOCAL_CLOCK_H__ diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h index ac7f6cf22c10..9f2bd3a15cf4 100644 --- a/include/media/AudioTrack.h +++ b/include/media/AudioTrack.h @@ -446,7 +446,7 @@ public: */ status_t dump(int fd, const Vector<String16>& args) const; -private: +protected: /* copying audio tracks is not allowed */ AudioTrack(const AudioTrack& other); AudioTrack& operator = (const AudioTrack& other); @@ -518,10 +518,33 @@ private: int mAuxEffectId; mutable Mutex mLock; status_t mRestoreStatus; + bool mIsTimed; int mPreviousPriority; // before start() int mPreviousSchedulingGroup; }; +class TimedAudioTrack : public AudioTrack +{ +public: + TimedAudioTrack(); + + /* allocate a shared memory buffer that can be passed to queueTimedBuffer */ + status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer); + + /* queue a buffer obtained via allocateTimedBuffer for playback at the + given timestamp. PTS units a microseconds on the media time timeline. + The media time transform (set with setMediaTimeTransform) set by the + audio producer will handle converting from media time to local time + (perhaps going through the common time timeline in the case of + synchronized multiroom audio case) */ + status_t queueTimedBuffer(const sp<IMemory>& buffer, int64_t pts); + + /* define a transform between media time and either common time or + local time */ + enum TargetTimeline {LOCAL_TIME, COMMON_TIME}; + status_t setMediaTimeTransform(const LinearTransform& xform, + TargetTimeline target); +}; }; // namespace android diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h index 433ce7cf52f1..7a2ada032fb3 100644 --- a/include/media/IAudioFlinger.h +++ b/include/media/IAudioFlinger.h @@ -55,6 +55,7 @@ public: uint32_t flags, const sp<IMemory>& sharedBuffer, audio_io_handle_t output, + bool isTimed, int *sessionId, status_t *status) = 0; diff --git a/include/media/IAudioTrack.h b/include/media/IAudioTrack.h index e4772a17edec..77f3e213c875 100644 --- a/include/media/IAudioTrack.h +++ b/include/media/IAudioTrack.h @@ -24,7 +24,7 @@ #include <utils/Errors.h> #include <binder/IInterface.h> #include <binder/IMemory.h> - +#include <utils/LinearTransform.h> namespace android { @@ -71,6 +71,23 @@ public: */ virtual status_t attachAuxEffect(int effectId) = 0; + + /* Allocate a shared memory buffer suitable for holding timed audio + samples */ + virtual status_t allocateTimedBuffer(size_t size, + sp<IMemory>* buffer) = 0; + + /* Queue a buffer obtained via allocateTimedBuffer for playback at the given + timestamp */ + virtual status_t queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts) = 0; + + /* Define the linear transform that will be applied to the timestamps + given to queueTimedBuffer (which are expressed in media time). + Target specifies whether this transform converts media time to local time + or Tungsten time. The values for target are defined in AudioTrack.h */ + virtual status_t setMediaTimeTransform(const LinearTransform& xform, + int target) = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index 77c82b27ba1b..23a3e4905a63 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -46,6 +46,9 @@ enum player_type { // The shared library with the test player is passed passed as an // argument to the 'test:' url in the setDataSource call. TEST_PLAYER = 5, + + AAH_RX_PLAYER = 100, + AAH_TX_PLAYER = 101, }; diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index db6388ac14c8..0fe7bd88dad1 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -124,7 +124,7 @@ public final class KeyChain { public static final String EXTRA_SENDER = "sender"; /** - * Action to bring up the CertInstaller + * Action to bring up the CertInstaller. */ private static final String ACTION_INSTALL = "android.credentials.INSTALL"; @@ -167,6 +167,22 @@ public final class KeyChain { // Compatible with old android.security.Credentials.PKCS12 public static final String EXTRA_PKCS12 = "PKCS12"; + + /** + * @hide TODO This is temporary and will be removed + * Broadcast Action: Indicates the trusted storage has changed. Sent when + * one of this happens: + * + * <ul> + * <li>a new CA is added, + * <li>an existing CA is removed or disabled, + * <li>a disabled CA is enabled, + * <li>trusted storage is reset (all user certs are cleared), + * <li>when permission to access a private key is changed. + * </ul> + */ + public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED"; + /** * Returns an {@code Intent} that can be used for credential * installation. The intent may be used without any extras, in diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk new file mode 100644 index 000000000000..526f17b44a5c --- /dev/null +++ b/libs/common_time/Android.mk @@ -0,0 +1,21 @@ +LOCAL_PATH:= $(call my-dir) +# +# libcommon_time_client +# (binder marshalers for ICommonClock as well as common clock and local clock +# helper code) +# + +include $(CLEAR_VARS) + +LOCAL_MODULE := libcommon_time_client +LOCAL_MODULE_TAGS := optional +LOCAL_SRC_FILES := cc_helper.cpp \ + local_clock.cpp \ + ICommonClock.cpp \ + ICommonTimeConfig.cpp \ + utils.cpp +LOCAL_SHARED_LIBRARIES := libbinder \ + libhardware \ + libutils + +include $(BUILD_SHARED_LIBRARY) diff --git a/libs/common_time/ICommonClock.cpp b/libs/common_time/ICommonClock.cpp new file mode 100644 index 000000000000..28b43acff16a --- /dev/null +++ b/libs/common_time/ICommonClock.cpp @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <linux/socket.h> + +#include <common_time/ICommonClock.h> +#include <binder/Parcel.h> + +#include "utils.h" + +namespace android { + +/***** ICommonClock *****/ + +enum { + IS_COMMON_TIME_VALID = IBinder::FIRST_CALL_TRANSACTION, + COMMON_TIME_TO_LOCAL_TIME, + LOCAL_TIME_TO_COMMON_TIME, + GET_COMMON_TIME, + GET_COMMON_FREQ, + GET_LOCAL_TIME, + GET_LOCAL_FREQ, + GET_ESTIMATED_ERROR, + GET_TIMELINE_ID, + GET_STATE, + GET_MASTER_ADDRESS, + REGISTER_LISTENER, + UNREGISTER_LISTENER, +}; + +const String16 ICommonClock::kServiceName("common_time.clock"); +const uint64_t ICommonClock::kInvalidTimelineID = 0; +const int32_t ICommonClock::kErrorEstimateUnknown = 0x7FFFFFFF; + +class BpCommonClock : public BpInterface<ICommonClock> +{ + public: + BpCommonClock(const sp<IBinder>& impl) + : BpInterface<ICommonClock>(impl) {} + + virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(IS_COMMON_TIME_VALID, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *valid = reply.readInt32(); + *timelineID = reply.readInt32(); + } + } + return status; + } + + virtual status_t commonTimeToLocalTime(int64_t commonTime, + int64_t* localTime) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + data.writeInt64(commonTime); + status_t status = remote()->transact(COMMON_TIME_TO_LOCAL_TIME, + data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *localTime = reply.readInt64(); + } + } + return status; + } + + virtual status_t localTimeToCommonTime(int64_t localTime, + int64_t* commonTime) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + data.writeInt64(localTime); + status_t status = remote()->transact(LOCAL_TIME_TO_COMMON_TIME, + data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *commonTime = reply.readInt64(); + } + } + return status; + } + + virtual status_t getCommonTime(int64_t* commonTime) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_COMMON_TIME, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *commonTime = reply.readInt64(); + } + } + return status; + } + + virtual status_t getCommonFreq(uint64_t* freq) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_COMMON_FREQ, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *freq = reply.readInt64(); + } + } + return status; + } + + virtual status_t getLocalTime(int64_t* localTime) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_LOCAL_TIME, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *localTime = reply.readInt64(); + } + } + return status; + } + + virtual status_t getLocalFreq(uint64_t* freq) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_LOCAL_FREQ, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *freq = reply.readInt64(); + } + } + return status; + } + + virtual status_t getEstimatedError(int32_t* estimate) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_ESTIMATED_ERROR, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *estimate = reply.readInt32(); + } + } + return status; + } + + virtual status_t getTimelineID(uint64_t* id) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_TIMELINE_ID, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *id = static_cast<uint64_t>(reply.readInt64()); + } + } + return status; + } + + virtual status_t getState(State* state) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_STATE, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *state = static_cast<State>(reply.readInt32()); + } + } + return status; + } + + virtual status_t getMasterAddr(struct sockaddr_storage* addr) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_MASTER_ADDRESS, data, &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) + deserializeSockaddr(&reply, addr); + } + return status; + } + + virtual status_t registerListener( + const sp<ICommonClockListener>& listener) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + data.writeStrongBinder(listener->asBinder()); + + status_t status = remote()->transact(REGISTER_LISTENER, data, &reply); + + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t unregisterListener( + const sp<ICommonClockListener>& listener) { + Parcel data, reply; + data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor()); + data.writeStrongBinder(listener->asBinder()); + status_t status = remote()->transact(UNREGISTER_LISTENER, data, &reply); + + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } +}; + +IMPLEMENT_META_INTERFACE(CommonClock, "android.os.ICommonClock"); + +status_t BnCommonClock::onTransact(uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags) { + switch(code) { + case IS_COMMON_TIME_VALID: { + CHECK_INTERFACE(ICommonClock, data, reply); + bool valid; + uint32_t timelineID; + status_t status = isCommonTimeValid(&valid, &timelineID); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(valid); + reply->writeInt32(timelineID); + } + return OK; + } break; + + case COMMON_TIME_TO_LOCAL_TIME: { + CHECK_INTERFACE(ICommonClock, data, reply); + int64_t commonTime = data.readInt64(); + int64_t localTime; + status_t status = commonTimeToLocalTime(commonTime, &localTime); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(localTime); + } + return OK; + } break; + + case LOCAL_TIME_TO_COMMON_TIME: { + CHECK_INTERFACE(ICommonClock, data, reply); + int64_t localTime = data.readInt64(); + int64_t commonTime; + status_t status = localTimeToCommonTime(localTime, &commonTime); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(commonTime); + } + return OK; + } break; + + case GET_COMMON_TIME: { + CHECK_INTERFACE(ICommonClock, data, reply); + int64_t commonTime; + status_t status = getCommonTime(&commonTime); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(commonTime); + } + return OK; + } break; + + case GET_COMMON_FREQ: { + CHECK_INTERFACE(ICommonClock, data, reply); + uint64_t freq; + status_t status = getCommonFreq(&freq); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(freq); + } + return OK; + } break; + + case GET_LOCAL_TIME: { + CHECK_INTERFACE(ICommonClock, data, reply); + int64_t localTime; + status_t status = getLocalTime(&localTime); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(localTime); + } + return OK; + } break; + + case GET_LOCAL_FREQ: { + CHECK_INTERFACE(ICommonClock, data, reply); + uint64_t freq; + status_t status = getLocalFreq(&freq); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(freq); + } + return OK; + } break; + + case GET_ESTIMATED_ERROR: { + CHECK_INTERFACE(ICommonClock, data, reply); + int32_t error; + status_t status = getEstimatedError(&error); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(error); + } + return OK; + } break; + + case GET_TIMELINE_ID: { + CHECK_INTERFACE(ICommonClock, data, reply); + uint64_t id; + status_t status = getTimelineID(&id); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(static_cast<int64_t>(id)); + } + return OK; + } break; + + case GET_STATE: { + CHECK_INTERFACE(ICommonClock, data, reply); + State state; + status_t status = getState(&state); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(static_cast<int32_t>(state)); + } + return OK; + } break; + + case GET_MASTER_ADDRESS: { + CHECK_INTERFACE(ICommonClock, data, reply); + struct sockaddr_storage addr; + status_t status = getMasterAddr(&addr); + + if ((status == OK) && !canSerializeSockaddr(&addr)) { + status = UNKNOWN_ERROR; + } + + reply->writeInt32(status); + + if (status == OK) { + serializeSockaddr(reply, &addr); + } + + return OK; + } break; + + case REGISTER_LISTENER: { + CHECK_INTERFACE(ICommonClock, data, reply); + sp<ICommonClockListener> listener = + interface_cast<ICommonClockListener>(data.readStrongBinder()); + status_t status = registerListener(listener); + reply->writeInt32(status); + return OK; + } break; + + case UNREGISTER_LISTENER: { + CHECK_INTERFACE(ICommonClock, data, reply); + sp<ICommonClockListener> listener = + interface_cast<ICommonClockListener>(data.readStrongBinder()); + status_t status = unregisterListener(listener); + reply->writeInt32(status); + return OK; + } break; + } + return BBinder::onTransact(code, data, reply, flags); +} + +/***** ICommonClockListener *****/ + +enum { + ON_TIMELINE_CHANGED = IBinder::FIRST_CALL_TRANSACTION, +}; + +class BpCommonClockListener : public BpInterface<ICommonClockListener> +{ + public: + BpCommonClockListener(const sp<IBinder>& impl) + : BpInterface<ICommonClockListener>(impl) {} + + virtual void onTimelineChanged(uint64_t timelineID) { + Parcel data, reply; + data.writeInterfaceToken( + ICommonClockListener::getInterfaceDescriptor()); + data.writeInt64(timelineID); + remote()->transact(ON_TIMELINE_CHANGED, data, &reply); + } +}; + +IMPLEMENT_META_INTERFACE(CommonClockListener, + "android.os.ICommonClockListener"); + +status_t BnCommonClockListener::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { + switch(code) { + case ON_TIMELINE_CHANGED: { + CHECK_INTERFACE(ICommonClockListener, data, reply); + uint32_t timelineID = data.readInt64(); + onTimelineChanged(timelineID); + return NO_ERROR; + } break; + } + + return BBinder::onTransact(code, data, reply, flags); +} + +}; // namespace android diff --git a/libs/common_time/ICommonTimeConfig.cpp b/libs/common_time/ICommonTimeConfig.cpp new file mode 100644 index 000000000000..8eb37cb8064a --- /dev/null +++ b/libs/common_time/ICommonTimeConfig.cpp @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <linux/socket.h> + +#include <common_time/ICommonTimeConfig.h> +#include <binder/Parcel.h> + +#include "utils.h" + +namespace android { + +/***** ICommonTimeConfig *****/ + +enum { + GET_MASTER_ELECTION_PRIORITY = IBinder::FIRST_CALL_TRANSACTION, + SET_MASTER_ELECTION_PRIORITY, + GET_MASTER_ELECTION_ENDPOINT, + SET_MASTER_ELECTION_ENDPOINT, + GET_MASTER_ELECTION_GROUP_ID, + SET_MASTER_ELECTION_GROUP_ID, + GET_INTERFACE_BINDING, + SET_INTERFACE_BINDING, + GET_MASTER_ANNOUNCE_INTERVAL, + SET_MASTER_ANNOUNCE_INTERVAL, + GET_CLIENT_SYNC_INTERVAL, + SET_CLIENT_SYNC_INTERVAL, + GET_PANIC_THRESHOLD, + SET_PANIC_THRESHOLD, + GET_AUTO_DISABLE, + SET_AUTO_DISABLE, + FORCE_NETWORKLESS_MASTER_MODE, +}; + +const String16 ICommonTimeConfig::kServiceName("common_time.config"); + +class BpCommonTimeConfig : public BpInterface<ICommonTimeConfig> +{ + public: + BpCommonTimeConfig(const sp<IBinder>& impl) + : BpInterface<ICommonTimeConfig>(impl) {} + + virtual status_t getMasterElectionPriority(uint8_t *priority) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_MASTER_ELECTION_PRIORITY, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *priority = static_cast<uint8_t>(reply.readInt32()); + } + } + + return status; + } + + virtual status_t setMasterElectionPriority(uint8_t priority) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + data.writeInt32(static_cast<int32_t>(priority)); + status_t status = remote()->transact(SET_MASTER_ELECTION_PRIORITY, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_MASTER_ELECTION_ENDPOINT, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + deserializeSockaddr(&reply, addr); + } + } + + return status; + } + + virtual status_t setMasterElectionEndpoint( + const struct sockaddr_storage *addr) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + if (!canSerializeSockaddr(addr)) + return BAD_VALUE; + if (NULL == addr) { + data.writeInt32(0); + } else { + data.writeInt32(1); + serializeSockaddr(&data, addr); + } + status_t status = remote()->transact(SET_MASTER_ELECTION_ENDPOINT, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t getMasterElectionGroupId(uint64_t *id) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_MASTER_ELECTION_GROUP_ID, + data, + &reply); + + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *id = static_cast<uint64_t>(reply.readInt64()); + } + } + + return status; + } + + virtual status_t setMasterElectionGroupId(uint64_t id) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + data.writeInt64(id); + status_t status = remote()->transact(SET_MASTER_ELECTION_GROUP_ID, + data, + &reply); + + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t getInterfaceBinding(String16& ifaceName) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_INTERFACE_BINDING, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + ifaceName = reply.readString16(); + } + } + + return status; + } + + virtual status_t setInterfaceBinding(const String16& ifaceName) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + data.writeString16(ifaceName); + status_t status = remote()->transact(SET_INTERFACE_BINDING, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t getMasterAnnounceInterval(int *interval) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_MASTER_ANNOUNCE_INTERVAL, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *interval = reply.readInt32(); + } + } + + return status; + } + + virtual status_t setMasterAnnounceInterval(int interval) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + data.writeInt32(interval); + status_t status = remote()->transact(SET_MASTER_ANNOUNCE_INTERVAL, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t getClientSyncInterval(int *interval) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_CLIENT_SYNC_INTERVAL, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *interval = reply.readInt32(); + } + } + + return status; + } + + virtual status_t setClientSyncInterval(int interval) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + data.writeInt32(interval); + status_t status = remote()->transact(SET_CLIENT_SYNC_INTERVAL, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t getPanicThreshold(int *threshold) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_PANIC_THRESHOLD, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *threshold = reply.readInt32(); + } + } + + return status; + } + + virtual status_t setPanicThreshold(int threshold) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + data.writeInt32(threshold); + status_t status = remote()->transact(SET_PANIC_THRESHOLD, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t getAutoDisable(bool *autoDisable) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(GET_AUTO_DISABLE, + data, + &reply); + if (status == OK) { + status = reply.readInt32(); + if (status == OK) { + *autoDisable = (0 != reply.readInt32()); + } + } + + return status; + } + + virtual status_t setAutoDisable(bool autoDisable) { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + data.writeInt32(autoDisable ? 1 : 0); + status_t status = remote()->transact(SET_AUTO_DISABLE, + data, + &reply); + + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } + + virtual status_t forceNetworklessMasterMode() { + Parcel data, reply; + data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor()); + status_t status = remote()->transact(FORCE_NETWORKLESS_MASTER_MODE, + data, + &reply); + + if (status == OK) { + status = reply.readInt32(); + } + + return status; + } +}; + +IMPLEMENT_META_INTERFACE(CommonTimeConfig, "android.os.ICommonTimeConfig"); + +status_t BnCommonTimeConfig::onTransact(uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags) { + switch(code) { + case GET_MASTER_ELECTION_PRIORITY: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + uint8_t priority; + status_t status = getMasterElectionPriority(&priority); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(static_cast<int32_t>(priority)); + } + return OK; + } break; + + case SET_MASTER_ELECTION_PRIORITY: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + uint8_t priority = static_cast<uint8_t>(data.readInt32()); + status_t status = setMasterElectionPriority(priority); + reply->writeInt32(status); + return OK; + } break; + + case GET_MASTER_ELECTION_ENDPOINT: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + struct sockaddr_storage addr; + status_t status = getMasterElectionEndpoint(&addr); + + if ((status == OK) && !canSerializeSockaddr(&addr)) { + status = UNKNOWN_ERROR; + } + + reply->writeInt32(status); + + if (status == OK) { + serializeSockaddr(reply, &addr); + } + + return OK; + } break; + + case SET_MASTER_ELECTION_ENDPOINT: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + struct sockaddr_storage addr; + int hasAddr = data.readInt32(); + + status_t status; + if (hasAddr) { + deserializeSockaddr(&data, &addr); + status = setMasterElectionEndpoint(&addr); + } else { + status = setMasterElectionEndpoint(&addr); + } + + reply->writeInt32(status); + return OK; + } break; + + case GET_MASTER_ELECTION_GROUP_ID: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + uint64_t id; + status_t status = getMasterElectionGroupId(&id); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt64(id); + } + return OK; + } break; + + case SET_MASTER_ELECTION_GROUP_ID: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + uint64_t id = static_cast<uint64_t>(data.readInt64()); + status_t status = setMasterElectionGroupId(id); + reply->writeInt32(status); + return OK; + } break; + + case GET_INTERFACE_BINDING: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + String16 ret; + status_t status = getInterfaceBinding(ret); + reply->writeInt32(status); + if (status == OK) { + reply->writeString16(ret); + } + return OK; + } break; + + case SET_INTERFACE_BINDING: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + String16 ifaceName; + ifaceName = data.readString16(); + status_t status = setInterfaceBinding(ifaceName); + reply->writeInt32(status); + return OK; + } break; + + case GET_MASTER_ANNOUNCE_INTERVAL: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + int interval; + status_t status = getMasterAnnounceInterval(&interval); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(interval); + } + return OK; + } break; + + case SET_MASTER_ANNOUNCE_INTERVAL: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + int interval = data.readInt32(); + status_t status = setMasterAnnounceInterval(interval); + reply->writeInt32(status); + return OK; + } break; + + case GET_CLIENT_SYNC_INTERVAL: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + int interval; + status_t status = getClientSyncInterval(&interval); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(interval); + } + return OK; + } break; + + case SET_CLIENT_SYNC_INTERVAL: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + int interval = data.readInt32(); + status_t status = setClientSyncInterval(interval); + reply->writeInt32(status); + return OK; + } break; + + case GET_PANIC_THRESHOLD: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + int threshold; + status_t status = getPanicThreshold(&threshold); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(threshold); + } + return OK; + } break; + + case SET_PANIC_THRESHOLD: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + int threshold = data.readInt32(); + status_t status = setPanicThreshold(threshold); + reply->writeInt32(status); + return OK; + } break; + + case GET_AUTO_DISABLE: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + bool autoDisable; + status_t status = getAutoDisable(&autoDisable); + reply->writeInt32(status); + if (status == OK) { + reply->writeInt32(autoDisable ? 1 : 0); + } + return OK; + } break; + + case SET_AUTO_DISABLE: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + bool autoDisable = (0 != data.readInt32()); + status_t status = setAutoDisable(autoDisable); + reply->writeInt32(status); + return OK; + } break; + + case FORCE_NETWORKLESS_MASTER_MODE: { + CHECK_INTERFACE(ICommonTimeConfig, data, reply); + status_t status = forceNetworklessMasterMode(); + reply->writeInt32(status); + return OK; + } break; + } + return BBinder::onTransact(code, data, reply, flags); +} + +}; // namespace android + diff --git a/libs/common_time/cc_helper.cpp b/libs/common_time/cc_helper.cpp new file mode 100644 index 000000000000..8d8556cb7f54 --- /dev/null +++ b/libs/common_time/cc_helper.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> + +#include <common_time/cc_helper.h> +#include <common_time/ICommonClock.h> +#include <utils/threads.h> + +namespace android { + +Mutex CCHelper::lock_; +sp<ICommonClock> CCHelper::common_clock_; +sp<ICommonClockListener> CCHelper::common_clock_listener_; +uint32_t CCHelper::ref_count_ = 0; + +bool CCHelper::verifyClock_l() { + bool ret = false; + + if (common_clock_ == NULL) { + common_clock_ = ICommonClock::getInstance(); + if (common_clock_ == NULL) + goto bailout; + } + + if (ref_count_ > 0) { + if (common_clock_listener_ == NULL) { + common_clock_listener_ = new CommonClockListener(); + if (common_clock_listener_ == NULL) + goto bailout; + + if (OK != common_clock_->registerListener(common_clock_listener_)) + goto bailout; + } + } + + ret = true; + +bailout: + if (!ret) { + common_clock_listener_ = NULL; + common_clock_ = NULL; + } + return ret; +} + +CCHelper::CCHelper() { + Mutex::Autolock lock(&lock_); + ref_count_++; + verifyClock_l(); +} + +CCHelper::~CCHelper() { + Mutex::Autolock lock(&lock_); + + assert(ref_count_ > 0); + ref_count_--; + + // If we were the last CCHelper instance in the system, and we had + // previously register a listener, unregister it now so that the common time + // service has the chance to go into auto-disabled mode. + if (!ref_count_ && + (common_clock_ != NULL) && + (common_clock_listener_ != NULL)) { + common_clock_->unregisterListener(common_clock_listener_); + common_clock_listener_ = NULL; + } +} + +void CCHelper::CommonClockListener::onTimelineChanged(uint64_t timelineID) { + // do nothing; listener is only really used as a token so the server can + // find out when clients die. +} + +// Helper methods which attempts to make calls to the common time binder +// service. If the first attempt fails with DEAD_OBJECT, the helpers will +// attempt to make a connection to the service again (assuming that the process +// hosting the service had crashed and the client proxy we are holding is dead) +// If the second attempt fails, or no connection can be made, the we let the +// error propagate up the stack and let the caller deal with the situation as +// best they can. +#define CCHELPER_METHOD(decl, call) \ + status_t CCHelper::decl { \ + Mutex::Autolock lock(&lock_); \ + \ + if (!verifyClock_l()) \ + return DEAD_OBJECT; \ + \ + status_t status = common_clock_->call; \ + if (DEAD_OBJECT == status) { \ + if (!verifyClock_l()) \ + return DEAD_OBJECT; \ + status = common_clock_->call; \ + } \ + \ + return status; \ + } + +#define VERIFY_CLOCK() + +CCHELPER_METHOD(isCommonTimeValid(bool* valid, uint32_t* timelineID), + isCommonTimeValid(valid, timelineID)) +CCHELPER_METHOD(commonTimeToLocalTime(int64_t commonTime, int64_t* localTime), + commonTimeToLocalTime(commonTime, localTime)) +CCHELPER_METHOD(localTimeToCommonTime(int64_t localTime, int64_t* commonTime), + localTimeToCommonTime(localTime, commonTime)) +CCHELPER_METHOD(getCommonTime(int64_t* commonTime), + getCommonTime(commonTime)) +CCHELPER_METHOD(getCommonFreq(uint64_t* freq), + getCommonFreq(freq)) +CCHELPER_METHOD(getLocalTime(int64_t* localTime), + getLocalTime(localTime)) +CCHELPER_METHOD(getLocalFreq(uint64_t* freq), + getLocalFreq(freq)) + +} // namespace android diff --git a/libs/common_time/local_clock.cpp b/libs/common_time/local_clock.cpp new file mode 100644 index 000000000000..a7c61fc76aa5 --- /dev/null +++ b/libs/common_time/local_clock.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <assert.h> +#include <stdint.h> + +#include <common_time/local_clock.h> +#include <hardware/hardware.h> +#include <hardware/local_time_hal.h> +#include <utils/Errors.h> +#include <utils/threads.h> + +namespace android { + +Mutex LocalClock::dev_lock_; +local_time_hw_device_t* LocalClock::dev_ = NULL; + +LocalClock::LocalClock() { + int res; + const hw_module_t* mod; + + AutoMutex lock(&dev_lock_); + + if (dev_ != NULL) + return; + + res = hw_get_module_by_class(LOCAL_TIME_HARDWARE_MODULE_ID, NULL, &mod); + if (res) { + ALOGE("Failed to open local time HAL module (res = %d)", res); + } else { + res = local_time_hw_device_open(mod, &dev_); + if (res) { + ALOGE("Failed to open local time HAL device (res = %d)", res); + dev_ = NULL; + } + } +} + +bool LocalClock::initCheck() { + return (NULL != dev_); +} + +int64_t LocalClock::getLocalTime() { + assert(NULL != dev_); + assert(NULL != dev_->get_local_time); + + return dev_->get_local_time(dev_); +} + +uint64_t LocalClock::getLocalFreq() { + assert(NULL != dev_); + assert(NULL != dev_->get_local_freq); + + return dev_->get_local_freq(dev_); +} + +status_t LocalClock::setLocalSlew(int16_t rate) { + assert(NULL != dev_); + + if (!dev_->set_local_slew) + return INVALID_OPERATION; + + return static_cast<status_t>(dev_->set_local_slew(dev_, rate)); +} + +int32_t LocalClock::getDebugLog(struct local_time_debug_event* records, + int max_records) { + assert(NULL != dev_); + + if (!dev_->get_debug_log) + return INVALID_OPERATION; + + return dev_->get_debug_log(dev_, records, max_records); +} + +} // namespace android diff --git a/libs/common_time/utils.cpp b/libs/common_time/utils.cpp new file mode 100644 index 000000000000..65391711b0bd --- /dev/null +++ b/libs/common_time/utils.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <arpa/inet.h> +#include <linux/socket.h> + +#include <binder/Parcel.h> + +namespace android { + +bool canSerializeSockaddr(const struct sockaddr_storage* addr) { + switch (addr->ss_family) { + case AF_INET: + case AF_INET6: + return true; + default: + return false; + } +} + +void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr) { + switch (addr->ss_family) { + case AF_INET: { + const struct sockaddr_in* s = + reinterpret_cast<const struct sockaddr_in*>(addr); + p->writeInt32(AF_INET); + p->writeInt32(ntohl(s->sin_addr.s_addr)); + p->writeInt32(static_cast<int32_t>(ntohs(s->sin_port))); + } break; + + case AF_INET6: { + const struct sockaddr_in6* s = + reinterpret_cast<const struct sockaddr_in6*>(addr); + const int32_t* a = + reinterpret_cast<const int32_t*>(s->sin6_addr.s6_addr); + p->writeInt32(AF_INET6); + p->writeInt32(ntohl(a[0])); + p->writeInt32(ntohl(a[1])); + p->writeInt32(ntohl(a[2])); + p->writeInt32(ntohl(a[3])); + p->writeInt32(static_cast<int32_t>(ntohs(s->sin6_port))); + p->writeInt32(ntohl(s->sin6_flowinfo)); + p->writeInt32(ntohl(s->sin6_scope_id)); + } break; + } +} + +void deserializeSockaddr(const Parcel* p, struct sockaddr_storage* addr) { + memset(addr, 0, sizeof(addr)); + + addr->ss_family = p->readInt32(); + switch(addr->ss_family) { + case AF_INET: { + struct sockaddr_in* s = + reinterpret_cast<struct sockaddr_in*>(addr); + s->sin_addr.s_addr = htonl(p->readInt32()); + s->sin_port = htons(static_cast<uint16_t>(p->readInt32())); + } break; + + case AF_INET6: { + struct sockaddr_in6* s = + reinterpret_cast<struct sockaddr_in6*>(addr); + int32_t* a = reinterpret_cast<int32_t*>(s->sin6_addr.s6_addr); + + a[0] = htonl(p->readInt32()); + a[1] = htonl(p->readInt32()); + a[2] = htonl(p->readInt32()); + a[3] = htonl(p->readInt32()); + s->sin6_port = htons(static_cast<uint16_t>(p->readInt32())); + s->sin6_flowinfo = htonl(p->readInt32()); + s->sin6_scope_id = htonl(p->readInt32()); + } break; + } +} + +} // namespace android diff --git a/libs/common_time/utils.h b/libs/common_time/utils.h new file mode 100644 index 000000000000..ce79d0d74cec --- /dev/null +++ b/libs/common_time/utils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_LIBCOMMONCLOCK_UTILS_H +#define ANDROID_LIBCOMMONCLOCK_UTILS_H + +#include <linux/socket.h> + +#include <binder/Parcel.h> +#include <utils/Errors.h> + +namespace android { + +extern bool canSerializeSockaddr(const struct sockaddr_storage* addr); +extern void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr); +extern status_t deserializeSockaddr(const Parcel* p, + struct sockaddr_storage* addr); + +}; // namespace android + +#endif // ANDROID_LIBCOMMONCLOCK_UTILS_H diff --git a/libs/rs/driver/rsdAllocation.cpp b/libs/rs/driver/rsdAllocation.cpp index ea921923e745..fb93d822e8a6 100644 --- a/libs/rs/driver/rsdAllocation.cpp +++ b/libs/rs/driver/rsdAllocation.cpp @@ -23,6 +23,11 @@ #include "rsAllocation.h" +#include "system/window.h" +#include "hardware/gralloc.h" +#include "ui/Rect.h" +#include "ui/GraphicBufferMapper.h" + #include <GLES/gl.h> #include <GLES2/gl2.h> #include <GLES/glext.h> @@ -220,7 +225,8 @@ bool rsdAllocationInit(const Context *rsc, Allocation *alloc, bool forceZero) { } void * ptr = alloc->mHal.state.usrPtr; - if (!ptr) { + if (alloc->mHal.state.usageFlags & RS_ALLOCATION_USAGE_IO_OUTPUT) { + } else { ptr = malloc(alloc->mHal.state.type->getSizeBytes()); if (!ptr) { free(drv); @@ -248,7 +254,7 @@ bool rsdAllocationInit(const Context *rsc, Allocation *alloc, bool forceZero) { alloc->mHal.drvState.mallocPtr = ptr; drv->mallocPtr = (uint8_t *)ptr; alloc->mHal.drv = drv; - if (forceZero) { + if (forceZero && ptr) { memset(ptr, 0, alloc->mHal.state.type->getSizeBytes()); } @@ -386,9 +392,96 @@ int32_t rsdAllocationInitSurfaceTexture(const Context *rsc, const Allocation *al return drv->textureID; } +static bool IoGetBuffer(const Context *rsc, Allocation *alloc, ANativeWindow *nw) { + DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv; + + int32_t r = nw->dequeueBuffer(nw, &drv->wndBuffer); + if (r) { + rsc->setError(RS_ERROR_DRIVER, "Error getting next IO output buffer."); + return false; + } + + // This lock is implicitly released by the queue buffer in IoSend + r = nw->lockBuffer(nw, drv->wndBuffer); + if (r) { + rsc->setError(RS_ERROR_DRIVER, "Error locking next IO output buffer."); + return false; + } + + // Must lock the whole surface + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + Rect bounds(drv->wndBuffer->width, drv->wndBuffer->height); + + void *dst = NULL; + mapper.lock(drv->wndBuffer->handle, + GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_OFTEN, + bounds, &dst); + alloc->mHal.drvState.mallocPtr = dst; + return true; +} + +void rsdAllocationSetSurfaceTexture(const Context *rsc, Allocation *alloc, ANativeWindow *nw) { + DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv; + + //ALOGE("rsdAllocationSetSurfaceTexture %p %p", alloc, nw); + + // Cleanup old surface if there is one. + if (alloc->mHal.state.wndSurface) { + ANativeWindow *old = alloc->mHal.state.wndSurface; + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + mapper.unlock(drv->wndBuffer->handle); + old->queueBuffer(old, drv->wndBuffer); + } + + if (nw != NULL) { + int32_t r; + r = native_window_set_usage(nw, GRALLOC_USAGE_SW_READ_RARELY | + GRALLOC_USAGE_SW_WRITE_OFTEN); + if (r) { + rsc->setError(RS_ERROR_DRIVER, "Error setting IO output buffer usage."); + return; + } + + r = native_window_set_buffers_dimensions(nw, alloc->mHal.state.dimensionX, + alloc->mHal.state.dimensionY); + if (r) { + rsc->setError(RS_ERROR_DRIVER, "Error setting IO output buffer dimensions."); + return; + } + + r = native_window_set_buffer_count(nw, 3); + if (r) { + rsc->setError(RS_ERROR_DRIVER, "Error setting IO output buffer count."); + return; + } + + IoGetBuffer(rsc, alloc, nw); + } +} + +void rsdAllocationIoSend(const Context *rsc, Allocation *alloc) { + DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv; + ANativeWindow *nw = alloc->mHal.state.wndSurface; + + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + mapper.unlock(drv->wndBuffer->handle); + int32_t r = nw->queueBuffer(nw, drv->wndBuffer); + if (r) { + rsc->setError(RS_ERROR_DRIVER, "Error sending IO output buffer."); + return; + } + + IoGetBuffer(rsc, alloc, nw); +} + +void rsdAllocationIoReceive(const Context *rsc, Allocation *alloc) { + ALOGE("not implemented"); +} + + void rsdAllocationData1D(const Context *rsc, const Allocation *alloc, uint32_t xoff, uint32_t lod, uint32_t count, - const void *data, uint32_t sizeBytes) { + const void *data, size_t sizeBytes) { DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv; const uint32_t eSize = alloc->mHal.state.type->getElementSizeBytes(); @@ -407,7 +500,7 @@ void rsdAllocationData1D(const Context *rsc, const Allocation *alloc, void rsdAllocationData2D(const Context *rsc, const Allocation *alloc, uint32_t xoff, uint32_t yoff, uint32_t lod, RsAllocationCubemapFace face, - uint32_t w, uint32_t h, const void *data, uint32_t sizeBytes) { + uint32_t w, uint32_t h, const void *data, size_t sizeBytes) { DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv; uint32_t eSize = alloc->mHal.state.elementSizeBytes; diff --git a/libs/rs/driver/rsdAllocation.h b/libs/rs/driver/rsdAllocation.h index 230804b53ae8..e3a5126383d0 100644 --- a/libs/rs/driver/rsdAllocation.h +++ b/libs/rs/driver/rsdAllocation.h @@ -24,6 +24,7 @@ #include <GLES2/gl2.h> class RsdFrameBufferObj; +struct ANativeWindowBuffer; struct DrvAllocation { // Is this a legal structure to be used as a texture source. @@ -47,6 +48,7 @@ struct DrvAllocation { bool uploadDeferred; RsdFrameBufferObj * readBackFBO; + ANativeWindowBuffer *wndBuffer; }; GLenum rsdTypeToGLType(RsDataType t); @@ -69,6 +71,12 @@ void rsdAllocationMarkDirty(const android::renderscript::Context *rsc, const android::renderscript::Allocation *alloc); int32_t rsdAllocationInitSurfaceTexture(const android::renderscript::Context *rsc, const android::renderscript::Allocation *alloc); +void rsdAllocationSetSurfaceTexture(const android::renderscript::Context *rsc, + android::renderscript::Allocation *alloc, ANativeWindow *nw); +void rsdAllocationIoSend(const android::renderscript::Context *rsc, + android::renderscript::Allocation *alloc); +void rsdAllocationIoReceive(const android::renderscript::Context *rsc, + android::renderscript::Allocation *alloc); void rsdAllocationData1D(const android::renderscript::Context *rsc, const android::renderscript::Allocation *alloc, diff --git a/libs/rs/driver/rsdCore.cpp b/libs/rs/driver/rsdCore.cpp index e01195509a87..bf2b62a69cdb 100644 --- a/libs/rs/driver/rsdCore.cpp +++ b/libs/rs/driver/rsdCore.cpp @@ -74,6 +74,9 @@ static RsdHalFunctions FunctionTable = { rsdAllocationSyncAll, rsdAllocationMarkDirty, rsdAllocationInitSurfaceTexture, + rsdAllocationSetSurfaceTexture, + rsdAllocationIoSend, + rsdAllocationIoReceive, rsdAllocationData1D, rsdAllocationData2D, rsdAllocationData3D, diff --git a/libs/rs/driver/rsdProgram.cpp b/libs/rs/driver/rsdProgram.cpp index 54484dfced3a..fa4cb0f4a70f 100644 --- a/libs/rs/driver/rsdProgram.cpp +++ b/libs/rs/driver/rsdProgram.cpp @@ -34,8 +34,11 @@ using namespace android; using namespace android::renderscript; bool rsdProgramVertexInit(const Context *rsc, const ProgramVertex *pv, - const char* shader, uint32_t shaderLen) { - RsdShader *drv = new RsdShader(pv, GL_VERTEX_SHADER, shader, shaderLen); + const char* shader, size_t shaderLen, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength) { + RsdShader *drv = new RsdShader(pv, GL_VERTEX_SHADER, shader, shaderLen, + textureNames, textureNamesCount, textureNamesLength); pv->mHal.drv = drv; return drv->createShader(); @@ -78,8 +81,11 @@ void rsdProgramVertexDestroy(const Context *rsc, const ProgramVertex *pv) { } bool rsdProgramFragmentInit(const Context *rsc, const ProgramFragment *pf, - const char* shader, uint32_t shaderLen) { - RsdShader *drv = new RsdShader(pf, GL_FRAGMENT_SHADER, shader, shaderLen); + const char* shader, size_t shaderLen, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength) { + RsdShader *drv = new RsdShader(pf, GL_FRAGMENT_SHADER, shader, shaderLen, + textureNames, textureNamesCount, textureNamesLength); pf->mHal.drv = drv; return drv->createShader(); diff --git a/libs/rs/driver/rsdProgramFragment.h b/libs/rs/driver/rsdProgramFragment.h index 366cb40d2962..b03a9fe65ac1 100644 --- a/libs/rs/driver/rsdProgramFragment.h +++ b/libs/rs/driver/rsdProgramFragment.h @@ -22,7 +22,9 @@ bool rsdProgramFragmentInit(const android::renderscript::Context *rsc, const android::renderscript::ProgramFragment *, - const char* shader, uint32_t shaderLen); + const char* shader, size_t shaderLen, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength); void rsdProgramFragmentSetActive(const android::renderscript::Context *rsc, const android::renderscript::ProgramFragment *); void rsdProgramFragmentDestroy(const android::renderscript::Context *rsc, diff --git a/libs/rs/driver/rsdProgramVertex.h b/libs/rs/driver/rsdProgramVertex.h index e99857298165..f917a4123747 100644 --- a/libs/rs/driver/rsdProgramVertex.h +++ b/libs/rs/driver/rsdProgramVertex.h @@ -21,7 +21,9 @@ bool rsdProgramVertexInit(const android::renderscript::Context *rsc, const android::renderscript::ProgramVertex *, - const char* shader, uint32_t shaderLen); + const char* shader, size_t shaderLen, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength); void rsdProgramVertexSetActive(const android::renderscript::Context *rsc, const android::renderscript::ProgramVertex *); void rsdProgramVertexDestroy(const android::renderscript::Context *rsc, diff --git a/libs/rs/driver/rsdShader.cpp b/libs/rs/driver/rsdShader.cpp index 3bca794ea9d6..1e73b95b6cd6 100644 --- a/libs/rs/driver/rsdShader.cpp +++ b/libs/rs/driver/rsdShader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2011-2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,14 +30,16 @@ using namespace android; using namespace android::renderscript; RsdShader::RsdShader(const Program *p, uint32_t type, - const char * shaderText, uint32_t shaderLength) { - + const char * shaderText, size_t shaderLength, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength) { mUserShader.setTo(shaderText, shaderLength); mRSProgram = p; mType = type; initMemberVars(); initAttribAndUniformArray(); - init(); + init(textureNames, textureNamesCount, textureNamesLength); + createTexturesString(textureNames, textureNamesCount, textureNamesLength); } RsdShader::~RsdShader() { @@ -65,25 +67,26 @@ void RsdShader::initMemberVars() { mIsValid = false; } -void RsdShader::init() { +void RsdShader::init(const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength) { uint32_t attribCount = 0; uint32_t uniformCount = 0; for (uint32_t ct=0; ct < mRSProgram->mHal.state.inputElementsCount; ct++) { - initAddUserElement(mRSProgram->mHal.state.inputElements[ct], mAttribNames, NULL, &attribCount, RS_SHADER_ATTR); + initAddUserElement(mRSProgram->mHal.state.inputElements[ct], mAttribNames, + NULL, &attribCount, RS_SHADER_ATTR); } for (uint32_t ct=0; ct < mRSProgram->mHal.state.constantsCount; ct++) { - initAddUserElement(mRSProgram->mHal.state.constantTypes[ct]->getElement(), mUniformNames, mUniformArraySizes, &uniformCount, RS_SHADER_UNI); + initAddUserElement(mRSProgram->mHal.state.constantTypes[ct]->getElement(), + mUniformNames, mUniformArraySizes, &uniformCount, RS_SHADER_UNI); } mTextureUniformIndexStart = uniformCount; - char buf[256]; for (uint32_t ct=0; ct < mRSProgram->mHal.state.texturesCount; ct++) { - snprintf(buf, sizeof(buf), "UNI_Tex%i", ct); - mUniformNames[uniformCount].setTo(buf); + mUniformNames[uniformCount].setTo("UNI_"); + mUniformNames[uniformCount].append(textureNames[ct], textureNamesLength[ct]); mUniformArraySizes[uniformCount] = 1; uniformCount++; } - } String8 RsdShader::getGLSLInputString() const { @@ -135,22 +138,25 @@ void RsdShader::appendAttributes() { } } -void RsdShader::appendTextures() { - char buf[256]; - for (uint32_t ct=0; ct < mRSProgram->mHal.state.texturesCount; ct++) { +void RsdShader::createTexturesString(const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength) { + mShaderTextures.setTo(""); + for (uint32_t ct = 0; ct < mRSProgram->mHal.state.texturesCount; ct ++) { if (mRSProgram->mHal.state.textureTargets[ct] == RS_TEXTURE_2D) { Allocation *a = mRSProgram->mHal.state.textures[ct]; if (a && a->mHal.state.surfaceTextureID) { - snprintf(buf, sizeof(buf), "uniform samplerExternalOES UNI_Tex%i;\n", ct); + mShaderTextures.append("uniform samplerExternalOES UNI_"); } else { - snprintf(buf, sizeof(buf), "uniform sampler2D UNI_Tex%i;\n", ct); + mShaderTextures.append("uniform sampler2D UNI_"); } mTextureTargets[ct] = GL_TEXTURE_2D; } else { - snprintf(buf, sizeof(buf), "uniform samplerCube UNI_Tex%i;\n", ct); + mShaderTextures.append("uniform samplerCube UNI_"); mTextureTargets[ct] = GL_TEXTURE_CUBE_MAP; } - mShader.append(buf); + + mShaderTextures.append(textureNames[ct], textureNamesLength[ct]); + mShaderTextures.append(";\n"); } } @@ -161,7 +167,7 @@ bool RsdShader::createShader() { } appendUserConstants(); appendAttributes(); - appendTextures(); + mShader.append(mShaderTextures); mShader.append(mUserShader); @@ -418,7 +424,8 @@ void RsdShader::setupTextures(const Context *rsc, RsdShaderCache *sc) { DrvAllocation *drvTex = (DrvAllocation *)mRSProgram->mHal.state.textures[ct]->mHal.drv; if (drvTex->glTarget != GL_TEXTURE_2D && drvTex->glTarget != GL_TEXTURE_CUBE_MAP) { - ALOGE("Attempting to bind unknown texture to shader id %u, texture unit %u", (uint)this, ct); + ALOGE("Attempting to bind unknown texture to shader id %u, texture unit %u", + (uint)this, ct); rsc->setError(RS_ERROR_BAD_SHADER, "Non-texture allocation bound to a shader"); } RSD_CALL_GL(glBindTexture, drvTex->glTarget, drvTex->textureID); diff --git a/libs/rs/driver/rsdShader.h b/libs/rs/driver/rsdShader.h index 3f0d6eae909e..e32145fad548 100644 --- a/libs/rs/driver/rsdShader.h +++ b/libs/rs/driver/rsdShader.h @@ -39,7 +39,9 @@ class RsdShader { public: RsdShader(const android::renderscript::Program *p, uint32_t type, - const char * shaderText, uint32_t shaderLength); + const char * shaderText, uint32_t shaderLength, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength); virtual ~RsdShader(); bool createShader(); @@ -67,19 +69,27 @@ protected: // Applies to vertex and fragment shaders only void appendUserConstants(); - void setupUserConstants(const android::renderscript::Context *rsc, RsdShaderCache *sc, bool isFragment); - void initAddUserElement(const android::renderscript::Element *e, android::String8 *names, uint32_t *arrayLengths, uint32_t *count, const char *prefix); + void setupUserConstants(const android::renderscript::Context *rsc, + RsdShaderCache *sc, bool isFragment); + void initAddUserElement(const android::renderscript::Element *e, + android::String8 *names, uint32_t *arrayLengths, + uint32_t *count, const char *prefix); void setupTextures(const android::renderscript::Context *rsc, RsdShaderCache *sc); - void setupSampler(const android::renderscript::Context *rsc, const android::renderscript::Sampler *s, const android::renderscript::Allocation *tex); + void setupSampler(const android::renderscript::Context *rsc, + const android::renderscript::Sampler *s, + const android::renderscript::Allocation *tex); void appendAttributes(); void appendTextures(); + void createTexturesString(const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength); void initAttribAndUniformArray(); mutable bool mDirty; android::String8 mShader; android::String8 mUserShader; + android::String8 mShaderTextures; uint32_t mShaderID; uint32_t mType; @@ -93,10 +103,14 @@ protected: int32_t mTextureUniformIndexStart; - void logUniform(const android::renderscript::Element *field, const float *fd, uint32_t arraySize ); - void setUniform(const android::renderscript::Context *rsc, const android::renderscript::Element *field, const float *fd, int32_t slot, uint32_t arraySize ); + void logUniform(const android::renderscript::Element *field, + const float *fd, uint32_t arraySize); + void setUniform(const android::renderscript::Context *rsc, + const android::renderscript::Element *field, + const float *fd, int32_t slot, uint32_t arraySize ); void initMemberVars(); - void init(); + void init(const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength); }; #endif //ANDROID_RSD_SHADER_H diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec index 6759bc7bb271..cf4a3918771c 100644 --- a/libs/rs/rs.spec +++ b/libs/rs/rs.spec @@ -69,168 +69,183 @@ AllocationGetSurfaceTextureID { ret int32_t } +AllocationSetSurface { + param RsAllocation alloc + param RsNativeWindow sur + sync + } + +AllocationIoSend { + param RsAllocation alloc + } + +AllocationIoReceive { + param RsAllocation alloc + } + + ContextFinish { - sync - } + sync + } ContextBindRootScript { - param RsScript sampler - } + param RsScript sampler + } ContextBindProgramStore { - param RsProgramStore pgm - } + param RsProgramStore pgm + } ContextBindProgramFragment { - param RsProgramFragment pgm - } + param RsProgramFragment pgm + } ContextBindProgramVertex { - param RsProgramVertex pgm - } + param RsProgramVertex pgm + } ContextBindProgramRaster { - param RsProgramRaster pgm - } + param RsProgramRaster pgm + } ContextBindFont { - param RsFont pgm - } + param RsFont pgm + } ContextPause { - } + } ContextResume { - } + } ContextSetSurface { - param uint32_t width - param uint32_t height - param RsNativeWindow sur + param uint32_t width + param uint32_t height + param RsNativeWindow sur sync - } + } ContextDump { - param int32_t bits + param int32_t bits } ContextSetPriority { - param int32_t priority - } + param int32_t priority + } ContextDestroyWorker { sync } AssignName { - param RsObjectBase obj - param const char *name - } + param RsObjectBase obj + param const char *name + } ObjDestroy { - param RsAsyncVoidPtr objPtr - } + param RsAsyncVoidPtr objPtr + } ElementCreate { direct - param RsDataType mType - param RsDataKind mKind - param bool mNormalized - param uint32_t mVectorSize - ret RsElement - } + param RsDataType mType + param RsDataKind mKind + param bool mNormalized + param uint32_t mVectorSize + ret RsElement + } ElementCreate2 { direct - param const RsElement * elements - param const char ** names - param const uint32_t * arraySize - ret RsElement - } + param const RsElement * elements + param const char ** names + param const uint32_t * arraySize + ret RsElement + } AllocationCopyToBitmap { - param RsAllocation alloc - param void * data - } + param RsAllocation alloc + param void * data + } Allocation1DData { - param RsAllocation va - param uint32_t xoff - param uint32_t lod - param uint32_t count - param const void *data - } + param RsAllocation va + param uint32_t xoff + param uint32_t lod + param uint32_t count + param const void *data + } Allocation1DElementData { - param RsAllocation va - param uint32_t x - param uint32_t lod - param const void *data - param size_t comp_offset - } + param RsAllocation va + param uint32_t x + param uint32_t lod + param const void *data + param size_t comp_offset + } Allocation2DData { - param RsAllocation va - param uint32_t xoff - param uint32_t yoff - param uint32_t lod - param RsAllocationCubemapFace face - param uint32_t w - param uint32_t h - param const void *data - } + param RsAllocation va + param uint32_t xoff + param uint32_t yoff + param uint32_t lod + param RsAllocationCubemapFace face + param uint32_t w + param uint32_t h + param const void *data + } Allocation2DElementData { - param RsAllocation va - param uint32_t x - param uint32_t y - param uint32_t lod - param RsAllocationCubemapFace face - param const void *data - param size_t element_offset - } + param RsAllocation va + param uint32_t x + param uint32_t y + param uint32_t lod + param RsAllocationCubemapFace face + param const void *data + param size_t element_offset + } AllocationGenerateMipmaps { - param RsAllocation va + param RsAllocation va } AllocationRead { - param RsAllocation va - param void * data - } + param RsAllocation va + param void * data + } AllocationSyncAll { - param RsAllocation va - param RsAllocationUsageType src + param RsAllocation va + param RsAllocationUsageType src } AllocationResize1D { - param RsAllocation va - param uint32_t dimX - } + param RsAllocation va + param uint32_t dimX + } AllocationResize2D { - param RsAllocation va - param uint32_t dimX - param uint32_t dimY - } + param RsAllocation va + param uint32_t dimX + param uint32_t dimY + } AllocationCopy2DRange { - param RsAllocation dest - param uint32_t destXoff - param uint32_t destYoff - param uint32_t destMip - param uint32_t destFace - param uint32_t width - param uint32_t height - param RsAllocation src - param uint32_t srcXoff - param uint32_t srcYoff - param uint32_t srcMip - param uint32_t srcFace - } + param RsAllocation dest + param uint32_t destXoff + param uint32_t destYoff + param uint32_t destMip + param uint32_t destFace + param uint32_t width + param uint32_t height + param RsAllocation src + param uint32_t srcXoff + param uint32_t srcYoff + param uint32_t srcMip + param uint32_t srcFace + } SamplerCreate { direct @@ -244,26 +259,26 @@ SamplerCreate { } ScriptBindAllocation { - param RsScript vtm - param RsAllocation va - param uint32_t slot - } + param RsScript vtm + param RsAllocation va + param uint32_t slot + } ScriptSetTimeZone { - param RsScript s - param const char * timeZone - } + param RsScript s + param const char * timeZone + } ScriptInvoke { - param RsScript s - param uint32_t slot - } + param RsScript s + param uint32_t slot + } ScriptInvokeV { - param RsScript s - param uint32_t slot - param const void * data - } + param RsScript s + param uint32_t slot + param const void * data + } ScriptForEach { param RsScript s @@ -274,125 +289,127 @@ ScriptForEach { } ScriptSetVarI { - param RsScript s - param uint32_t slot - param int value - } + param RsScript s + param uint32_t slot + param int value + } ScriptSetVarObj { - param RsScript s - param uint32_t slot - param RsObjectBase value - } + param RsScript s + param uint32_t slot + param RsObjectBase value + } ScriptSetVarJ { - param RsScript s - param uint32_t slot - param int64_t value - } + param RsScript s + param uint32_t slot + param int64_t value + } ScriptSetVarF { - param RsScript s - param uint32_t slot - param float value - } + param RsScript s + param uint32_t slot + param float value + } ScriptSetVarD { - param RsScript s - param uint32_t slot - param double value - } + param RsScript s + param uint32_t slot + param double value + } ScriptSetVarV { - param RsScript s - param uint32_t slot - param const void * data - } + param RsScript s + param uint32_t slot + param const void * data + } ScriptCCreate { param const char * resName param const char * cacheDir - param const char * text - ret RsScript - } + param const char * text + ret RsScript + } ProgramStoreCreate { - direct - param bool colorMaskR - param bool colorMaskG - param bool colorMaskB - param bool colorMaskA + direct + param bool colorMaskR + param bool colorMaskG + param bool colorMaskB + param bool colorMaskA param bool depthMask param bool ditherEnable - param RsBlendSrcFunc srcFunc - param RsBlendDstFunc destFunc + param RsBlendSrcFunc srcFunc + param RsBlendDstFunc destFunc param RsDepthFunc depthFunc - ret RsProgramStore - } + ret RsProgramStore + } ProgramRasterCreate { - direct - param bool pointSprite - param RsCullMode cull - ret RsProgramRaster + direct + param bool pointSprite + param RsCullMode cull + ret RsProgramRaster } ProgramBindConstants { - param RsProgram vp - param uint32_t slot - param RsAllocation constants - } + param RsProgram vp + param uint32_t slot + param RsAllocation constants + } ProgramBindTexture { - param RsProgramFragment pf - param uint32_t slot - param RsAllocation a - } + param RsProgramFragment pf + param uint32_t slot + param RsAllocation a + } ProgramBindSampler { - param RsProgramFragment pf - param uint32_t slot - param RsSampler s - } + param RsProgramFragment pf + param uint32_t slot + param RsSampler s + } ProgramFragmentCreate { - direct - param const char * shaderText - param const uint32_t * params - ret RsProgramFragment - } + direct + param const char * shaderText + param const char ** textureNames + param const uint32_t * params + ret RsProgramFragment + } ProgramVertexCreate { - direct - param const char * shaderText - param const uint32_t * params - ret RsProgramVertex - } + direct + param const char * shaderText + param const char ** textureNames + param const uint32_t * params + ret RsProgramVertex + } FontCreateFromFile { - param const char *name - param float fontSize - param uint32_t dpi - ret RsFont - } + param const char *name + param float fontSize + param uint32_t dpi + ret RsFont + } FontCreateFromMemory { - param const char *name - param float fontSize - param uint32_t dpi - param const void *data - ret RsFont - } + param const char *name + param float fontSize + param uint32_t dpi + param const void *data + ret RsFont + } MeshCreate { - param RsAllocation *vtx - param RsAllocation *idx - param uint32_t *primType - ret RsMesh - } + param RsAllocation *vtx + param RsAllocation *idx + param uint32_t *primType + ret RsMesh + } PathCreate { param RsPathPrimitive pp @@ -402,4 +419,3 @@ PathCreate { param float quality ret RsPath } - diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp index 02c680939de3..83c88fdffacf 100644 --- a/libs/rs/rsAllocation.cpp +++ b/libs/rs/rsAllocation.cpp @@ -17,6 +17,7 @@ #include "rsContext.h" #include "rs_hal.h" +#include "system/window.h" using namespace android; using namespace android::renderscript; @@ -44,6 +45,7 @@ Allocation * Allocation::createAllocation(Context *rsc, const Type *type, uint32 delete a; return NULL; } + return a; } @@ -420,6 +422,28 @@ int32_t Allocation::getSurfaceTextureID(const Context *rsc) { return id; } +void Allocation::setSurface(const Context *rsc, RsNativeWindow sur) { + ANativeWindow *nw = (ANativeWindow *)sur; + ANativeWindow *old = mHal.state.wndSurface; + if (nw) { + nw->incStrong(NULL); + } + rsc->mHal.funcs.allocation.setSurfaceTexture(rsc, this, nw); + mHal.state.wndSurface = nw; + if (old) { + old->decStrong(NULL); + } +} + +void Allocation::ioSend(const Context *rsc) { + rsc->mHal.funcs.allocation.ioSend(rsc, this); +} + +void Allocation::ioReceive(const Context *rsc) { + rsc->mHal.funcs.allocation.ioReceive(rsc, this); +} + + ///////////////// // @@ -670,6 +694,21 @@ int32_t rsi_AllocationGetSurfaceTextureID(Context *rsc, RsAllocation valloc) { return alloc->getSurfaceTextureID(rsc); } +void rsi_AllocationSetSurface(Context *rsc, RsAllocation valloc, RsNativeWindow sur) { + Allocation *alloc = static_cast<Allocation *>(valloc); + alloc->setSurface(rsc, sur); +} + +void rsi_AllocationIoSend(Context *rsc, RsAllocation valloc) { + Allocation *alloc = static_cast<Allocation *>(valloc); + alloc->ioSend(rsc); +} + +void rsi_AllocationIoReceive(Context *rsc, RsAllocation valloc) { + Allocation *alloc = static_cast<Allocation *>(valloc); + alloc->ioReceive(rsc); +} + } } diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h index 58a582b75866..58a6fcacf6ac 100644 --- a/libs/rs/rsAllocation.h +++ b/libs/rs/rsAllocation.h @@ -19,6 +19,8 @@ #include "rsType.h" +struct ANativeWindow; + // --------------------------------------------------------------------------- namespace android { namespace renderscript { @@ -57,6 +59,7 @@ public: bool hasReferences; void * usrPtr; int32_t surfaceTextureID; + ANativeWindow *wndSurface; }; State state; @@ -127,6 +130,9 @@ public: } int32_t getSurfaceTextureID(const Context *rsc); + void setSurface(const Context *rsc, RsNativeWindow sur); + void ioSend(const Context *rsc); + void ioReceive(const Context *rsc); protected: Vector<const Program *> mToDirtyList; diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp index 4f21b3b0140d..c4276cfe8261 100644 --- a/libs/rs/rsFont.cpp +++ b/libs/rs/rsFont.cpp @@ -490,8 +490,14 @@ void FontState::initRenderState() { shaderString.append(" gl_FragColor = col;\n"); shaderString.append("}\n"); - ObjectBaseRef<const Element> colorElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4); - ObjectBaseRef<const Element> gammaElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 1); + const char *textureNames[] = { "Tex0" }; + const size_t textureNamesLengths[] = { 4 }; + size_t numTextures = sizeof(textureNamesLengths)/sizeof(*textureNamesLengths); + + ObjectBaseRef<const Element> colorElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32, + RS_KIND_USER, false, 4); + ObjectBaseRef<const Element> gammaElem = Element::createRef(mRSC, RS_TYPE_FLOAT_32, + RS_KIND_USER, false, 1); Element::Builder builder; builder.add(colorElem.get(), "Color", 1); builder.add(gammaElem.get(), "Gamma", 1); @@ -506,14 +512,17 @@ void FontState::initRenderState() { tmp[3] = RS_TEXTURE_2D; mFontShaderFConstant.set(Allocation::createAllocation(mRSC, inputType.get(), - RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS)); - ProgramFragment *pf = new ProgramFragment(mRSC, shaderString.string(), - shaderString.length(), tmp, 4); + RS_ALLOCATION_USAGE_SCRIPT | + RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS)); + ProgramFragment *pf = new ProgramFragment(mRSC, shaderString.string(), shaderString.length(), + textureNames, numTextures, textureNamesLengths, + tmp, 4); mFontShaderF.set(pf); mFontShaderF->bindAllocation(mRSC, mFontShaderFConstant.get(), 0); mFontSampler.set(Sampler::getSampler(mRSC, RS_SAMPLER_NEAREST, RS_SAMPLER_NEAREST, - RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP).get()); + RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP, + RS_SAMPLER_CLAMP).get()); mFontShaderF->bindSampler(mRSC, 0, mFontSampler.get()); mFontProgramStore.set(ProgramStore::getProgramStore(mRSC, true, true, true, true, @@ -525,10 +534,12 @@ void FontState::initRenderState() { } void FontState::initTextTexture() { - ObjectBaseRef<const Element> alphaElem = Element::createRef(mRSC, RS_TYPE_UNSIGNED_8, RS_KIND_PIXEL_A, true, 1); + ObjectBaseRef<const Element> alphaElem = Element::createRef(mRSC, RS_TYPE_UNSIGNED_8, + RS_KIND_PIXEL_A, true, 1); // We will allocate a texture to initially hold 32 character bitmaps - ObjectBaseRef<Type> texType = Type::getTypeRef(mRSC, alphaElem.get(), 1024, 256, 0, false, false); + ObjectBaseRef<Type> texType = Type::getTypeRef(mRSC, alphaElem.get(), + 1024, 256, 0, false, false); Allocation *cacheAlloc = Allocation::createAllocation(mRSC, texType.get(), RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE); diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp index 8061515e24fa..7114f29d0509 100644 --- a/libs/rs/rsProgram.cpp +++ b/libs/rs/rsProgram.cpp @@ -20,8 +20,8 @@ using namespace android; using namespace android::renderscript; -Program::Program(Context *rsc, const char * shaderText, uint32_t shaderLength, - const uint32_t * params, uint32_t paramLength) +Program::Program(Context *rsc, const char * shaderText, size_t shaderLength, + const uint32_t * params, size_t paramLength) : ProgramBase(rsc) { initMemberVars(); diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h index 06fc3eca71ab..d03293066803 100644 --- a/libs/rs/rsProgram.h +++ b/libs/rs/rsProgram.h @@ -58,8 +58,8 @@ public: }; Hal mHal; - Program(Context *, const char * shaderText, uint32_t shaderLength, - const uint32_t * params, uint32_t paramLength); + Program(Context *, const char * shaderText, size_t shaderLength, + const uint32_t * params, size_t paramLength); virtual ~Program(); virtual bool freeChildren(); diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp index 4e73ca63e90b..bebde1e0cb53 100644 --- a/libs/rs/rsProgramFragment.cpp +++ b/libs/rs/rsProgramFragment.cpp @@ -20,16 +20,18 @@ using namespace android; using namespace android::renderscript; -ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText, - uint32_t shaderLength, const uint32_t * params, - uint32_t paramLength) +ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText, size_t shaderLength, + const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength, + + const uint32_t * params, size_t paramLength) : Program(rsc, shaderText, shaderLength, params, paramLength) { mConstantColor[0] = 1.f; mConstantColor[1] = 1.f; mConstantColor[2] = 1.f; mConstantColor[3] = 1.f; - mRSC->mHal.funcs.fragment.init(mRSC, this, mUserShader.string(), mUserShader.length()); + mRSC->mHal.funcs.fragment.init(mRSC, this, mUserShader.string(), mUserShader.length(), + textureNames, textureNamesCount, textureNamesLength); } ProgramFragment::~ProgramFragment() { @@ -110,8 +112,8 @@ void ProgramFragmentState::init(Context *rsc) { Allocation *constAlloc = Allocation::createAllocation(rsc, inputType.get(), RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS); - ProgramFragment *pf = new ProgramFragment(rsc, shaderString.string(), - shaderString.length(), tmp, 2); + ProgramFragment *pf = new ProgramFragment(rsc, shaderString.string(), shaderString.length(), + NULL, 0, NULL, tmp, 2); pf->bindAllocation(rsc, constAlloc, 0); pf->setConstantColor(rsc, 1.0f, 1.0f, 1.0f, 1.0f); @@ -127,9 +129,14 @@ namespace android { namespace renderscript { RsProgramFragment rsi_ProgramFragmentCreate(Context *rsc, const char * shaderText, - size_t shaderLength, const uint32_t * params, - size_t paramLength) { - ProgramFragment *pf = new ProgramFragment(rsc, shaderText, shaderLength, params, paramLength); + size_t shaderLength, + const char** textureNames, + size_t textureNamesCount, + const size_t *textureNamesLength, + const uint32_t * params, size_t paramLength) { + ProgramFragment *pf = new ProgramFragment(rsc, shaderText, shaderLength, + textureNames, textureNamesCount, textureNamesLength, + params, paramLength); pf->incUserRef(); //ALOGE("rsi_ProgramFragmentCreate %p", pf); return pf; diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h index d6e20cd192b9..4eb28e7db171 100644 --- a/libs/rs/rsProgramFragment.h +++ b/libs/rs/rsProgramFragment.h @@ -27,9 +27,9 @@ class ProgramFragmentState; class ProgramFragment : public Program { public: - ProgramFragment(Context *rsc, const char * shaderText, - uint32_t shaderLength, const uint32_t * params, - uint32_t paramLength); + ProgramFragment(Context *rsc, const char * shaderText, size_t shaderLength, + const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength, + const uint32_t * params, size_t paramLength); virtual ~ProgramFragment(); virtual void setup(Context *, ProgramFragmentState *); diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp index 871caacd6f66..c8a53eab2012 100644 --- a/libs/rs/rsProgramVertex.cpp +++ b/libs/rs/rsProgramVertex.cpp @@ -21,11 +21,13 @@ using namespace android; using namespace android::renderscript; -ProgramVertex::ProgramVertex(Context *rsc, const char * shaderText, - uint32_t shaderLength, const uint32_t * params, - uint32_t paramLength) +ProgramVertex::ProgramVertex(Context *rsc, const char * shaderText, size_t shaderLength, + const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength, + + const uint32_t * params, size_t paramLength) : Program(rsc, shaderText, shaderLength, params, paramLength) { - mRSC->mHal.funcs.vertex.init(mRSC, this, mUserShader.string(), mUserShader.length()); + mRSC->mHal.funcs.vertex.init(mRSC, this, mUserShader.string(), mUserShader.length(), + textureNames, textureNamesCount, textureNamesLength); } ProgramVertex::~ProgramVertex() { @@ -191,8 +193,8 @@ void ProgramVertexState::init(Context *rsc) { tmp[2] = RS_PROGRAM_PARAM_INPUT; tmp[3] = (uint32_t)attrElem.get(); - ProgramVertex *pv = new ProgramVertex(rsc, shaderString.string(), - shaderString.length(), tmp, 4); + ProgramVertex *pv = new ProgramVertex(rsc, shaderString.string(), shaderString.length(), + NULL, 0, NULL, tmp, 4); Allocation *alloc = Allocation::createAllocation(rsc, inputType.get(), RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS); pv->bindAllocation(rsc, alloc, 0); @@ -229,10 +231,13 @@ void ProgramVertexState::deinit(Context *rsc) { namespace android { namespace renderscript { -RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, const char * shaderText, - size_t shaderLength, const uint32_t * params, - size_t paramLength) { - ProgramVertex *pv = new ProgramVertex(rsc, shaderText, shaderLength, params, paramLength); +RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, const char * shaderText, size_t shaderLength, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength, + const uint32_t * params, size_t paramLength) { + ProgramVertex *pv = new ProgramVertex(rsc, shaderText, shaderLength, + textureNames, textureNamesCount, textureNamesLength, + params, paramLength); pv->incUserRef(); return pv; } diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h index 5cfdd8be355d..67c2a88840f6 100644 --- a/libs/rs/rsProgramVertex.h +++ b/libs/rs/rsProgramVertex.h @@ -27,8 +27,9 @@ class ProgramVertexState; class ProgramVertex : public Program { public: - ProgramVertex(Context *,const char * shaderText, uint32_t shaderLength, - const uint32_t * params, uint32_t paramLength); + ProgramVertex(Context *,const char * shaderText, size_t shaderLength, + const char** textureNames, size_t textureNamesCount, const size_t *textureNamesLength, + const uint32_t * params, size_t paramLength); virtual ~ProgramVertex(); virtual void setup(Context *rsc, ProgramVertexState *state); diff --git a/libs/rs/rs_hal.h b/libs/rs/rs_hal.h index 1e222e1664cf..120112819d8c 100644 --- a/libs/rs/rs_hal.h +++ b/libs/rs/rs_hal.h @@ -19,6 +19,8 @@ #include <RenderScriptDefines.h> +struct ANativeWindow; + namespace android { namespace renderscript { @@ -115,19 +117,23 @@ typedef struct { bool zeroNew); void (*syncAll)(const Context *rsc, const Allocation *alloc, RsAllocationUsageType src); void (*markDirty)(const Context *rsc, const Allocation *alloc); + int32_t (*initSurfaceTexture)(const Context *rsc, const Allocation *alloc); + void (*setSurfaceTexture)(const Context *rsc, Allocation *alloc, ANativeWindow *sur); + void (*ioSend)(const Context *rsc, Allocation *alloc); + void (*ioReceive)(const Context *rsc, Allocation *alloc); void (*data1D)(const Context *rsc, const Allocation *alloc, uint32_t xoff, uint32_t lod, uint32_t count, - const void *data, uint32_t sizeBytes); + const void *data, size_t sizeBytes); void (*data2D)(const Context *rsc, const Allocation *alloc, uint32_t xoff, uint32_t yoff, uint32_t lod, RsAllocationCubemapFace face, uint32_t w, uint32_t h, - const void *data, uint32_t sizeBytes); + const void *data, size_t sizeBytes); void (*data3D)(const Context *rsc, const Allocation *alloc, uint32_t xoff, uint32_t yoff, uint32_t zoff, uint32_t lod, RsAllocationCubemapFace face, - uint32_t w, uint32_t h, uint32_t d, const void *data, uint32_t sizeBytes); + uint32_t w, uint32_t h, uint32_t d, const void *data, size_t sizeBytes); // Allocation to allocation copies void (*allocData1D)(const Context *rsc, @@ -151,9 +157,9 @@ typedef struct { uint32_t srcLod, RsAllocationCubemapFace srcFace); void (*elementData1D)(const Context *rsc, const Allocation *alloc, uint32_t x, - const void *data, uint32_t elementOff, uint32_t sizeBytes); + const void *data, uint32_t elementOff, size_t sizeBytes); void (*elementData2D)(const Context *rsc, const Allocation *alloc, uint32_t x, uint32_t y, - const void *data, uint32_t elementOff, uint32_t sizeBytes); + const void *data, uint32_t elementOff, size_t sizeBytes); } allocation; @@ -172,14 +178,18 @@ typedef struct { struct { bool (*init)(const Context *rsc, const ProgramVertex *pv, - const char* shader, uint32_t shaderLen); + const char* shader, size_t shaderLen, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength); void (*setActive)(const Context *rsc, const ProgramVertex *pv); void (*destroy)(const Context *rsc, const ProgramVertex *pv); } vertex; struct { bool (*init)(const Context *rsc, const ProgramFragment *pf, - const char* shader, uint32_t shaderLen); + const char* shader, size_t shaderLen, + const char** textureNames, size_t textureNamesCount, + const size_t *textureNamesLength); void (*setActive)(const Context *rsc, const ProgramFragment *pf); void (*destroy)(const Context *rsc, const ProgramFragment *pf); } fragment; diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index eae03beae6fc..1c7f57750d02 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -591,7 +591,7 @@ public class AudioService extends IAudioService.Stub { // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, - SENDMSG_REPLACE, + SENDMSG_QUEUE, PERSIST_LAST_AUDIBLE, device, s, @@ -606,7 +606,7 @@ public class AudioService extends IAudioService.Stub { // to persist). Do not change volume if stream is muted. sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, - SENDMSG_NOOP, + SENDMSG_QUEUE, device, 0, streamState, @@ -746,7 +746,7 @@ public class AudioService extends IAudioService.Stub { // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, - SENDMSG_REPLACE, + SENDMSG_QUEUE, PERSIST_LAST_AUDIBLE, device, streamState, @@ -758,7 +758,7 @@ public class AudioService extends IAudioService.Stub { // to persist). sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, - SENDMSG_NOOP, + SENDMSG_QUEUE, device, 0, streamState, @@ -2208,7 +2208,7 @@ public class AudioService extends IAudioService.Stub { } sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, - SENDMSG_NOOP, + SENDMSG_QUEUE, 0, 0, VolumeStreamState.this, 0); @@ -2252,7 +2252,7 @@ public class AudioService extends IAudioService.Stub { } sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, - SENDMSG_NOOP, + SENDMSG_QUEUE, 0, 0, VolumeStreamState.this, 0); @@ -2350,7 +2350,7 @@ public class AudioService extends IAudioService.Stub { // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, - SENDMSG_REPLACE, + SENDMSG_QUEUE, PERSIST_CURRENT|PERSIST_LAST_AUDIBLE, device, streamState, diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index bcf7b89b48a5..91d0addde5f8 100755 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -84,6 +84,7 @@ public class Visualizer { // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp private static final int NATIVE_EVENT_PCM_CAPTURE = 0; private static final int NATIVE_EVENT_FFT_CAPTURE = 1; + private static final int NATIVE_EVENT_SERVER_DIED = 2; // Error codes: /** @@ -147,6 +148,10 @@ public class Visualizer { * PCM and FFT capture listener registered by client */ private OnDataCaptureListener mCaptureListener = null; + /** + * Server Died listener registered by client + */ + private OnServerDiedListener mServerDiedListener = null; // accessed by native methods private int mNativeVisualizer; @@ -396,6 +401,9 @@ public class Visualizer { public interface OnDataCaptureListener { /** * Method called when a new waveform capture is available. + * <p>Data in the waveform buffer is valid only within the scope of the callback. + * Applications which needs access to the waveform data after returning from the callback + * should make a copy of the data instead of holding a reference. * @param visualizer Visualizer object on which the listener is registered. * @param waveform array of bytes containing the waveform representation. * @param samplingRate sampling rate of the audio visualized. @@ -404,6 +412,9 @@ public class Visualizer { /** * Method called when a new frequency capture is available. + * <p>Data in the fft buffer is valid only within the scope of the callback. + * Applications which needs access to the fft data after returning from the callback + * should make a copy of the data instead of holding a reference. * @param visualizer Visualizer object on which the listener is registered. * @param fft array of bytes containing the frequency representation. * @param samplingRate sampling rate of the audio visualized. @@ -452,6 +463,43 @@ public class Visualizer { } /** + * @hide + * + * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that + * the connection to the native media server has been broken and that the Visualizer object will + * need to be released and re-created. + * The client application can implement this interface and register the listener with the + * {@link #setServerDiedListener(OnServerDiedListener)} method. + */ + public interface OnServerDiedListener { + /** + * @hide + * + * Method called when the native media server has died. + * <p>If the native media server encounters a fatal error and needs to restart, the binder + * connection from the {@link #Visualizer} to the media server will be broken. Data capture + * callbacks will stop happening, and client initiated calls to the {@link #Visualizer} + * instance will fail with the error code {@link #DEAD_OBJECT}. To restore functionality, + * clients should {@link #release()} their old visualizer and create a new instance. + */ + void onServerDied(); + } + + /** + * @hide + * + * Registers an OnServerDiedListener interface. + * <p>Call this method with a null listener to stop receiving server death notifications. + * @return {@link #SUCCESS} in case of success, + */ + public int setServerDiedListener(OnServerDiedListener listener) { + synchronized (mListenerLock) { + mServerDiedListener = listener; + } + return SUCCESS; + } + + /** * Helper class to handle the forwarding of native events to the appropriate listeners */ private class NativeEventHandler extends Handler @@ -463,11 +511,7 @@ public class Visualizer { mVisualizer = v; } - @Override - public void handleMessage(Message msg) { - if (mVisualizer == null) { - return; - } + private void handleCaptureMessage(Message msg) { OnDataCaptureListener l = null; synchronized (mListenerLock) { l = mVisualizer.mCaptureListener; @@ -476,6 +520,7 @@ public class Visualizer { if (l != null) { byte[] data = (byte[])msg.obj; int samplingRate = msg.arg1; + switch(msg.what) { case NATIVE_EVENT_PCM_CAPTURE: l.onWaveFormDataCapture(mVisualizer, data, samplingRate); @@ -484,11 +529,41 @@ public class Visualizer { l.onFftDataCapture(mVisualizer, data, samplingRate); break; default: - Log.e(TAG,"Unknown native event: "+msg.what); + Log.e(TAG,"Unknown native event in handleCaptureMessge: "+msg.what); break; } } } + + private void handleServerDiedMessage(Message msg) { + OnServerDiedListener l = null; + synchronized (mListenerLock) { + l = mVisualizer.mServerDiedListener; + } + + if (l != null) + l.onServerDied(); + } + + @Override + public void handleMessage(Message msg) { + if (mVisualizer == null) { + return; + } + + switch(msg.what) { + case NATIVE_EVENT_PCM_CAPTURE: + case NATIVE_EVENT_FFT_CAPTURE: + handleCaptureMessage(msg); + break; + case NATIVE_EVENT_SERVER_DIED: + handleServerDiedMessage(msg); + break; + default: + Log.e(TAG,"Unknown native event: "+msg.what); + break; + } + } } //--------------------------------------------------------- diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index ecd4d076aaed..f015afbaf209 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -23,6 +23,7 @@ #include <nativehelper/jni.h> #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> +#include <utils/threads.h> #include "media/Visualizer.h" using namespace android; @@ -38,6 +39,7 @@ using namespace android; #define NATIVE_EVENT_PCM_CAPTURE 0 #define NATIVE_EVENT_FFT_CAPTURE 1 +#define NATIVE_EVENT_SERVER_DIED 2 // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/audiofx/Visualizer"; @@ -54,6 +56,43 @@ static fields_t fields; struct visualizer_callback_cookie { jclass visualizer_class; // Visualizer class jobject visualizer_ref; // Visualizer object instance + + // Lazily allocated arrays used to hold callback data provided to java + // applications. These arrays are allocated during the first callback and + // reallocated when the size of the callback data changes. Allocating on + // demand and saving the arrays means that applications cannot safely hold a + // reference to the provided data (they need to make a copy if they want to + // hold onto outside of the callback scope), but it avoids GC thrash caused + // by constantly allocating and releasing arrays to hold callback data. + Mutex callback_data_lock; + jbyteArray waveform_data; + jbyteArray fft_data; + + visualizer_callback_cookie() { + waveform_data = NULL; + fft_data = NULL; + } + + ~visualizer_callback_cookie() { + cleanupBuffers(); + } + + void cleanupBuffers() { + AutoMutex lock(&callback_data_lock); + if (waveform_data || fft_data) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + if (waveform_data) { + env->DeleteGlobalRef(waveform_data); + waveform_data = NULL; + } + + if (fft_data) { + env->DeleteGlobalRef(fft_data); + fft_data = NULL; + } + } + } }; // ---------------------------------------------------------------------------- @@ -66,7 +105,6 @@ class visualizerJniStorage { ~visualizerJniStorage() { } - }; @@ -93,6 +131,26 @@ static jint translateError(int code) { // ---------------------------------------------------------------------------- +static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) { + if (NULL != *array) { + uint32_t len = env->GetArrayLength(*array); + if (len == size) + return; + + env->DeleteGlobalRef(*array); + *array = NULL; + } + + jbyteArray localRef = env->NewByteArray(size); + if (NULL != localRef) { + // Promote to global ref. + *array = (jbyteArray)env->NewGlobalRef(localRef); + + // Release our (now pointless) local ref. + env->DeleteLocalRef(localRef); + } +} + static void captureCallback(void* user, uint32_t waveformSize, uint8_t *waveform, @@ -106,6 +164,7 @@ static void captureCallback(void* user, visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user; JNIEnv *env = AndroidRuntime::getJNIEnv(); + AutoMutex lock(&callbackInfo->callback_data_lock); ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p", callbackInfo, @@ -118,7 +177,11 @@ static void captureCallback(void* user, } if (waveformSize != 0 && waveform != NULL) { - jbyteArray jArray = env->NewByteArray(waveformSize); + jbyteArray jArray; + + ensureArraySize(env, &callbackInfo->waveform_data, waveformSize); + jArray = callbackInfo->waveform_data; + if (jArray != NULL) { jbyte *nArray = env->GetByteArrayElements(jArray, NULL); memcpy(nArray, waveform, waveformSize); @@ -131,12 +194,15 @@ static void captureCallback(void* user, samplingrate, 0, jArray); - env->DeleteLocalRef(jArray); } } if (fftSize != 0 && fft != NULL) { - jbyteArray jArray = env->NewByteArray(fftSize); + jbyteArray jArray; + + ensureArraySize(env, &callbackInfo->fft_data, fftSize); + jArray = callbackInfo->fft_data; + if (jArray != NULL) { jbyte *nArray = env->GetByteArrayElements(jArray, NULL); memcpy(nArray, fft, fftSize); @@ -149,7 +215,6 @@ static void captureCallback(void* user, samplingrate, 0, jArray); - env->DeleteLocalRef(jArray); } } @@ -220,6 +285,23 @@ android_media_visualizer_native_init(JNIEnv *env) } +static void android_media_visualizer_effect_callback(int32_t event, + void *user, + void *info) { + if ((event == AudioEffect::EVENT_ERROR) && + (*((status_t*)info) == DEAD_OBJECT)) { + visualizerJniStorage* lpJniStorage = (visualizerJniStorage*)user; + visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData; + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->CallStaticVoidMethod( + callbackInfo->visualizer_class, + fields.midPostNativeEvent, + callbackInfo->visualizer_ref, + NATIVE_EVENT_SERVER_DIED, + 0, 0, 0); + } +} static jint android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, @@ -255,8 +337,8 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th // create the native Visualizer object lpVisualizer = new Visualizer(0, - NULL, - NULL, + android_media_visualizer_effect_callback, + lpJniStorage, sessionId); if (lpVisualizer == NULL) { ALOGE("Error creating Visualizer"); @@ -345,7 +427,17 @@ android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean e return VISUALIZER_ERROR_NO_INIT; } - return translateError(lpVisualizer->setEnabled(enabled)); + jint retVal = translateError(lpVisualizer->setEnabled(enabled)); + + if (!enabled) { + visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField( + thiz, fields.fidJniData); + + if (NULL != lpJniStorage) + lpJniStorage->mCallbackData.cleanupBuffers(); + } + + return retVal; } static jboolean diff --git a/media/libaah_rtp/Android.mk b/media/libaah_rtp/Android.mk new file mode 100644 index 000000000000..54fd9ec10ebd --- /dev/null +++ b/media/libaah_rtp/Android.mk @@ -0,0 +1,40 @@ +LOCAL_PATH:= $(call my-dir) +# +# libaah_rtp +# + +include $(CLEAR_VARS) + +LOCAL_MODULE := libaah_rtp +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := \ + aah_decoder_pump.cpp \ + aah_rx_player.cpp \ + aah_rx_player_core.cpp \ + aah_rx_player_ring_buffer.cpp \ + aah_rx_player_substream.cpp \ + aah_tx_packet.cpp \ + aah_tx_player.cpp \ + aah_tx_sender.cpp \ + pipe_event.cpp + +LOCAL_C_INCLUDES := \ + frameworks/base/include \ + frameworks/base/include/media/stagefright/openmax \ + frameworks/base/media \ + frameworks/base/media/libstagefright + +LOCAL_SHARED_LIBRARIES := \ + libcommon_time_client \ + libbinder \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + libutils + +LOCAL_LDLIBS := \ + -lpthread + +include $(BUILD_SHARED_LIBRARY) + diff --git a/media/libaah_rtp/aah_decoder_pump.cpp b/media/libaah_rtp/aah_decoder_pump.cpp new file mode 100644 index 000000000000..72fe43baf5d9 --- /dev/null +++ b/media/libaah_rtp/aah_decoder_pump.cpp @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include <poll.h> +#include <pthread.h> + +#include <common_time/cc_helper.h> +#include <media/AudioSystem.h> +#include <media/AudioTrack.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXCodec.h> +#include <media/stagefright/Utils.h> +#include <utils/Timers.h> +#include <utils/threads.h> + +#include "aah_decoder_pump.h" + +namespace android { + +static const long long kLongDecodeErrorThreshold = 1000000ll; +static const uint32_t kMaxLongErrorsBeforeFatal = 3; +static const uint32_t kMaxErrorsBeforeFatal = 60; + +AAH_DecoderPump::AAH_DecoderPump(OMXClient& omx) + : omx_(omx) + , thread_status_(OK) + , renderer_(NULL) + , last_queued_pts_valid_(false) + , last_queued_pts_(0) + , last_ts_transform_valid_(false) + , last_volume_(0xFF) { + thread_ = new ThreadWrapper(this); +} + +AAH_DecoderPump::~AAH_DecoderPump() { + shutdown(); +} + +status_t AAH_DecoderPump::initCheck() { + if (thread_ == NULL) { + ALOGE("Failed to allocate thread"); + return NO_MEMORY; + } + + return OK; +} + +status_t AAH_DecoderPump::queueForDecode(MediaBuffer* buf) { + if (NULL == buf) { + return BAD_VALUE; + } + + if (OK != thread_status_) { + return thread_status_; + } + + { // Explicit scope for AutoMutex pattern. + AutoMutex lock(&thread_lock_); + in_queue_.push_back(buf); + } + + thread_cond_.signal(); + + return OK; +} + +void AAH_DecoderPump::queueToRenderer(MediaBuffer* decoded_sample) { + Mutex::Autolock lock(&render_lock_); + sp<MetaData> meta; + int64_t ts; + status_t res; + + // Fetch the metadata and make sure the sample has a timestamp. We + // cannot render samples which are missing PTSs. + meta = decoded_sample->meta_data(); + if ((meta == NULL) || (!meta->findInt64(kKeyTime, &ts))) { + ALOGV("Decoded sample missing timestamp, cannot render."); + CHECK(false); + } else { + // If we currently are not holding on to a renderer, go ahead and + // make one now. + if (NULL == renderer_) { + renderer_ = new TimedAudioTrack(); + if (NULL != renderer_) { + int frameCount; + AudioTrack::getMinFrameCount(&frameCount, + AUDIO_STREAM_DEFAULT, + static_cast<int>(format_sample_rate_)); + int ch_format = (format_channels_ == 1) + ? AUDIO_CHANNEL_OUT_MONO + : AUDIO_CHANNEL_OUT_STEREO; + + res = renderer_->set(AUDIO_STREAM_DEFAULT, + format_sample_rate_, + AUDIO_FORMAT_PCM_16_BIT, + ch_format, + frameCount); + if (res != OK) { + ALOGE("Failed to setup audio renderer. (res = %d)", res); + delete renderer_; + renderer_ = NULL; + } else { + CHECK(last_ts_transform_valid_); + + res = renderer_->setMediaTimeTransform( + last_ts_transform_, TimedAudioTrack::COMMON_TIME); + if (res != NO_ERROR) { + ALOGE("Failed to set media time transform on AudioTrack" + " (res = %d)", res); + delete renderer_; + renderer_ = NULL; + } else { + float volume = static_cast<float>(last_volume_) + / 255.0f; + if (renderer_->setVolume(volume, volume) != OK) { + ALOGW("%s: setVolume failed", __FUNCTION__); + } + + renderer_->start(); + } + } + } else { + ALOGE("Failed to allocate AudioTrack to use as a renderer."); + } + } + + if (NULL != renderer_) { + uint8_t* decoded_data = + reinterpret_cast<uint8_t*>(decoded_sample->data()); + uint32_t decoded_amt = decoded_sample->range_length(); + decoded_data += decoded_sample->range_offset(); + + sp<IMemory> pcm_payload; + res = renderer_->allocateTimedBuffer(decoded_amt, &pcm_payload); + if (res != OK) { + ALOGE("Failed to allocate %d byte audio track buffer." + " (res = %d)", decoded_amt, res); + } else { + memcpy(pcm_payload->pointer(), decoded_data, decoded_amt); + + res = renderer_->queueTimedBuffer(pcm_payload, ts); + if (res != OK) { + ALOGE("Failed to queue %d byte audio track buffer with media" + " PTS %lld. (res = %d)", decoded_amt, ts, res); + } else { + last_queued_pts_valid_ = true; + last_queued_pts_ = ts; + } + } + + } else { + ALOGE("No renderer, dropping audio payload."); + } + } +} + +void AAH_DecoderPump::stopAndCleanupRenderer() { + if (NULL == renderer_) { + return; + } + + renderer_->stop(); + delete renderer_; + renderer_ = NULL; +} + +void AAH_DecoderPump::setRenderTSTransform(const LinearTransform& trans) { + Mutex::Autolock lock(&render_lock_); + + if (last_ts_transform_valid_ && !memcmp(&trans, + &last_ts_transform_, + sizeof(trans))) { + return; + } + + last_ts_transform_ = trans; + last_ts_transform_valid_ = true; + + if (NULL != renderer_) { + status_t res = renderer_->setMediaTimeTransform( + last_ts_transform_, TimedAudioTrack::COMMON_TIME); + if (res != NO_ERROR) { + ALOGE("Failed to set media time transform on AudioTrack" + " (res = %d)", res); + } + } +} + +void AAH_DecoderPump::setRenderVolume(uint8_t volume) { + Mutex::Autolock lock(&render_lock_); + + if (volume == last_volume_) { + return; + } + + last_volume_ = volume; + if (renderer_ != NULL) { + float volume = static_cast<float>(last_volume_) / 255.0f; + if (renderer_->setVolume(volume, volume) != OK) { + ALOGW("%s: setVolume failed", __FUNCTION__); + } + } +} + +// isAboutToUnderflow is something of a hack used to figure out when it might be +// time to give up on trying to fill in a gap in the RTP sequence and simply +// move on with a discontinuity. If we had perfect knowledge of when we were +// going to underflow, it would not be a hack, but unfortunately we do not. +// Right now, we just take the PTS of the last sample queued, and check to see +// if its presentation time is within kAboutToUnderflowThreshold from now. If +// it is, then we say that we are about to underflow. This decision is based on +// two (possibly invalid) assumptions. +// +// 1) The transmitter is leading the clock by more than +// kAboutToUnderflowThreshold. +// 2) The delta between the PTS of the last sample queued and the next sample +// is less than the transmitter's clock lead amount. +// +// Right now, the default transmitter lead time is 1 second, which is a pretty +// large number and greater than the 50mSec that kAboutToUnderflowThreshold is +// currently set to. This should satisfy assumption #1 for now, but changes to +// the transmitter clock lead time could effect this. +// +// For non-sparse streams with a homogeneous sample rate (the vast majority of +// streams in the world), the delta between any two adjacent PTSs will always be +// the homogeneous sample period. It is very uncommon to see a sample period +// greater than the 1 second clock lead we are currently using, and you +// certainly will not see it in an MP3 file which should satisfy assumption #2. +// Sparse audio streams (where no audio is transmitted for long periods of +// silence) and extremely low framerate video stream (like an MPEG-2 slideshow +// or the video stream for a pay TV audio channel) are examples of streams which +// might violate assumption #2. +bool AAH_DecoderPump::isAboutToUnderflow(int64_t threshold) { + Mutex::Autolock lock(&render_lock_); + + // If we have never queued anything to the decoder, we really don't know if + // we are going to underflow or not. + if (!last_queued_pts_valid_ || !last_ts_transform_valid_) { + return false; + } + + // Don't have access to Common Time? If so, then things are Very Bad + // elsewhere in the system; it pretty much does not matter what we do here. + // Since we cannot really tell if we are about to underflow or not, its + // probably best to assume that we are not and proceed accordingly. + int64_t tt_now; + if (OK != cc_helper_.getCommonTime(&tt_now)) { + return false; + } + + // Transform from media time to common time. + int64_t last_queued_pts_tt; + if (!last_ts_transform_.doForwardTransform(last_queued_pts_, + &last_queued_pts_tt)) { + return false; + } + + // Check to see if we are underflowing. + return ((tt_now + threshold - last_queued_pts_tt) > 0); +} + +void* AAH_DecoderPump::workThread() { + // No need to lock when accessing decoder_ from the thread. The + // implementation of init and shutdown ensure that other threads never touch + // decoder_ while the work thread is running. + CHECK(decoder_ != NULL); + CHECK(format_ != NULL); + + // Start the decoder and note its result code. If something goes horribly + // wrong, callers of queueForDecode and getOutput will be able to detect + // that the thread encountered a fatal error and shut down by examining + // thread_status_. + thread_status_ = decoder_->start(format_.get()); + if (OK != thread_status_) { + ALOGE("AAH_DecoderPump's work thread failed to start decoder (res = %d)", + thread_status_); + return NULL; + } + + DurationTimer decode_timer; + uint32_t consecutive_long_errors = 0; + uint32_t consecutive_errors = 0; + + while (!thread_->exitPending()) { + status_t res; + MediaBuffer* bufOut = NULL; + + decode_timer.start(); + res = decoder_->read(&bufOut); + decode_timer.stop(); + + if (res == INFO_FORMAT_CHANGED) { + // Format has changed. Destroy our current renderer so that a new + // one can be created during queueToRenderer with the proper format. + // + // TODO : In order to transition seamlessly, we should change this + // to put the old renderer in a queue to play out completely before + // we destroy it. We can still create a new renderer, the timed + // nature of the renderer should ensure a seamless splice. + stopAndCleanupRenderer(); + res = OK; + } + + // Try to be a little nuanced in our handling of actual decode errors. + // Errors could happen because of minor stream corruption or because of + // transient resource limitations. In these cases, we would rather drop + // a little bit of output and ride out the unpleasantness then throw up + // our hands and abort everything. + // + // OTOH - When things are really bad (like we have a non-transient + // resource or bookkeeping issue, or the stream being fed to us is just + // complete and total garbage) we really want to terminate playback and + // raise an error condition all the way up to the application level so + // they can deal with it. + // + // Unfortunately, the error codes returned by the decoder can be a + // little non-specific. For example, if an OMXCodec times out + // attempting to obtain an output buffer, the error we get back is a + // generic -1. Try to distinguish between this resource timeout error + // and ES corruption error by timing how long the decode operation + // takes. Maintain accounting for both errors and "long errors". If we + // get more than a certain number consecutive errors of either type, + // consider it fatal and shutdown (which will cause the error to + // propagate all of the way up to the application level). The threshold + // for "long errors" is deliberately much lower than that of normal + // decode errors, both because of how long they take to happen and + // because they generally indicate resource limitation errors which are + // unlikely to go away in pathologically bad cases (in contrast to + // stream corruption errors which might happen 20 times in a row and + // then be suddenly OK again) + if (res != OK) { + consecutive_errors++; + if (decode_timer.durationUsecs() >= kLongDecodeErrorThreshold) + consecutive_long_errors++; + + CHECK(NULL == bufOut); + + ALOGW("%s: Failed to decode data (res = %d)", + __PRETTY_FUNCTION__, res); + + if ((consecutive_errors >= kMaxErrorsBeforeFatal) || + (consecutive_long_errors >= kMaxLongErrorsBeforeFatal)) { + ALOGE("%s: Maximum decode error threshold has been reached." + " There have been %d consecutive decode errors, and %d" + " consecutive decode operations which resulted in errors" + " and took more than %lld uSec to process. The last" + " decode operation took %lld uSec.", + __PRETTY_FUNCTION__, + consecutive_errors, consecutive_long_errors, + kLongDecodeErrorThreshold, decode_timer.durationUsecs()); + thread_status_ = res; + break; + } + + continue; + } + + if (NULL == bufOut) { + ALOGW("%s: Successful decode, but no buffer produced", + __PRETTY_FUNCTION__); + continue; + } + + // Successful decode (with actual output produced). Clear the error + // counters. + consecutive_errors = 0; + consecutive_long_errors = 0; + + queueToRenderer(bufOut); + bufOut->release(); + } + + decoder_->stop(); + stopAndCleanupRenderer(); + + return NULL; +} + +status_t AAH_DecoderPump::init(const sp<MetaData>& params) { + Mutex::Autolock lock(&init_lock_); + + if (decoder_ != NULL) { + // already inited + return OK; + } + + if (params == NULL) { + return BAD_VALUE; + } + + if (!params->findInt32(kKeyChannelCount, &format_channels_)) { + return BAD_VALUE; + } + + if (!params->findInt32(kKeySampleRate, &format_sample_rate_)) { + return BAD_VALUE; + } + + CHECK(OK == thread_status_); + CHECK(decoder_ == NULL); + + status_t ret_val = UNKNOWN_ERROR; + + // Cache the format and attempt to create the decoder. + format_ = params; + decoder_ = OMXCodec::Create( + omx_.interface(), // IOMX Handle + format_, // Metadata for substream (indicates codec) + false, // Make a decoder, not an encoder + sp<MediaSource>(this)); // We will be the source for this codec. + + if (decoder_ == NULL) { + ALOGE("Failed to allocate decoder in %s", __PRETTY_FUNCTION__); + goto bailout; + } + + // Fire up the pump thread. It will take care of starting and stopping the + // decoder. + ret_val = thread_->run("aah_decode_pump", ANDROID_PRIORITY_AUDIO); + if (OK != ret_val) { + ALOGE("Failed to start work thread in %s (res = %d)", + __PRETTY_FUNCTION__, ret_val); + goto bailout; + } + +bailout: + if (OK != ret_val) { + decoder_ = NULL; + format_ = NULL; + } + + return OK; +} + +status_t AAH_DecoderPump::shutdown() { + Mutex::Autolock lock(&init_lock_); + return shutdown_l(); +} + +status_t AAH_DecoderPump::shutdown_l() { + thread_->requestExit(); + thread_cond_.signal(); + thread_->requestExitAndWait(); + + for (MBQueue::iterator iter = in_queue_.begin(); + iter != in_queue_.end(); + ++iter) { + (*iter)->release(); + } + in_queue_.clear(); + + last_queued_pts_valid_ = false; + last_ts_transform_valid_ = false; + last_volume_ = 0xFF; + thread_status_ = OK; + + decoder_ = NULL; + format_ = NULL; + + return OK; +} + +status_t AAH_DecoderPump::read(MediaBuffer **buffer, + const ReadOptions *options) { + if (!buffer) { + return BAD_VALUE; + } + + *buffer = NULL; + + // While its not time to shut down, and we have no data to process, wait. + AutoMutex lock(&thread_lock_); + while (!thread_->exitPending() && in_queue_.empty()) + thread_cond_.wait(thread_lock_); + + // At this point, if its not time to shutdown then we must have something to + // process. Go ahead and pop the front of the queue for processing. + if (!thread_->exitPending()) { + CHECK(!in_queue_.empty()); + + *buffer = *(in_queue_.begin()); + in_queue_.erase(in_queue_.begin()); + } + + // If we managed to get a buffer, then everything must be OK. If not, then + // we must be shutting down. + return (NULL == *buffer) ? INVALID_OPERATION : OK; +} + +AAH_DecoderPump::ThreadWrapper::ThreadWrapper(AAH_DecoderPump* owner) + : Thread(false /* canCallJava*/ ) + , owner_(owner) { +} + +bool AAH_DecoderPump::ThreadWrapper::threadLoop() { + CHECK(NULL != owner_); + owner_->workThread(); + return false; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_decoder_pump.h b/media/libaah_rtp/aah_decoder_pump.h new file mode 100644 index 000000000000..f5a6529396be --- /dev/null +++ b/media/libaah_rtp/aah_decoder_pump.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __DECODER_PUMP_H__ +#define __DECODER_PUMP_H__ + +#include <pthread.h> + +#include <common_time/cc_helper.h> +#include <media/stagefright/MediaSource.h> +#include <utils/LinearTransform.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class MetaData; +class OMXClient; +class TimedAudioTrack; + +class AAH_DecoderPump : public MediaSource { + public: + explicit AAH_DecoderPump(OMXClient& omx); + status_t initCheck(); + + status_t queueForDecode(MediaBuffer* buf); + + status_t init(const sp<MetaData>& params); + status_t shutdown(); + + void setRenderTSTransform(const LinearTransform& trans); + void setRenderVolume(uint8_t volume); + bool isAboutToUnderflow(int64_t threshold); + bool getStatus() const { return thread_status_; } + + // MediaSource methods + virtual status_t start(MetaData *params) { return OK; } + virtual sp<MetaData> getFormat() { return format_; } + virtual status_t stop() { return OK; } + virtual status_t read(MediaBuffer **buffer, + const ReadOptions *options); + + protected: + virtual ~AAH_DecoderPump(); + + private: + class ThreadWrapper : public Thread { + public: + friend class AAH_DecoderPump; + explicit ThreadWrapper(AAH_DecoderPump* owner); + + private: + virtual bool threadLoop(); + AAH_DecoderPump* owner_; + + DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper); + }; + + void* workThread(); + virtual status_t shutdown_l(); + void queueToRenderer(MediaBuffer* decoded_sample); + void stopAndCleanupRenderer(); + + sp<MetaData> format_; + int32_t format_channels_; + int32_t format_sample_rate_; + + sp<MediaSource> decoder_; + OMXClient& omx_; + Mutex init_lock_; + + sp<ThreadWrapper> thread_; + Condition thread_cond_; + Mutex thread_lock_; + status_t thread_status_; + + Mutex render_lock_; + TimedAudioTrack* renderer_; + bool last_queued_pts_valid_; + int64_t last_queued_pts_; + bool last_ts_transform_valid_; + LinearTransform last_ts_transform_; + uint8_t last_volume_; + CCHelper cc_helper_; + + // protected by the thread_lock_ + typedef List<MediaBuffer*> MBQueue; + MBQueue in_queue_; + + DISALLOW_EVIL_CONSTRUCTORS(AAH_DecoderPump); +}; + +} // namespace android +#endif // __DECODER_PUMP_H__ diff --git a/media/libaah_rtp/aah_rx_player.cpp b/media/libaah_rtp/aah_rx_player.cpp new file mode 100644 index 000000000000..9dd79fd2dd83 --- /dev/null +++ b/media/libaah_rtp/aah_rx_player.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 + +#include <binder/IServiceManager.h> +#include <media/MediaPlayerInterface.h> +#include <utils/Log.h> + +#include "aah_rx_player.h" + +namespace android { + +const uint32_t AAH_RXPlayer::kRTPRingBufferSize = 1 << 10; + +sp<MediaPlayerBase> createAAH_RXPlayer() { + sp<MediaPlayerBase> ret = new AAH_RXPlayer(); + return ret; +} + +AAH_RXPlayer::AAH_RXPlayer() + : ring_buffer_(kRTPRingBufferSize) + , substreams_(NULL) { + thread_wrapper_ = new ThreadWrapper(*this); + + is_playing_ = false; + multicast_joined_ = false; + transmitter_known_ = false; + current_epoch_known_ = false; + data_source_set_ = false; + sock_fd_ = -1; + + substreams_.setCapacity(4); + + memset(&listen_addr_, 0, sizeof(listen_addr_)); + memset(&transmitter_addr_, 0, sizeof(transmitter_addr_)); + + fetchAudioFlinger(); +} + +AAH_RXPlayer::~AAH_RXPlayer() { + reset_l(); + CHECK(substreams_.size() == 0); + omx_.disconnect(); +} + +status_t AAH_RXPlayer::initCheck() { + if (thread_wrapper_ == NULL) { + ALOGE("Failed to allocate thread wrapper!"); + return NO_MEMORY; + } + + if (!ring_buffer_.initCheck()) { + ALOGE("Failed to allocate reassembly ring buffer!"); + return NO_MEMORY; + } + + // Check for the presense of the common time service by attempting to query + // for CommonTime's frequency. If we get an error back, we cannot talk to + // the service at all and should abort now. + status_t res; + uint64_t freq; + res = cc_helper_.getCommonFreq(&freq); + if (OK != res) { + ALOGE("Failed to connect to common time service!"); + return res; + } + + return omx_.connect(); +} + +status_t AAH_RXPlayer::setDataSource( + const char *url, + const KeyedVector<String8, String8> *headers) { + AutoMutex api_lock(&api_lock_); + uint32_t a, b, c, d; + uint16_t port; + + if (data_source_set_) { + return INVALID_OPERATION; + } + + if (NULL == url) { + return BAD_VALUE; + } + + if (5 != sscanf(url, "%*[^:/]://%u.%u.%u.%u:%hu", &a, &b, &c, &d, &port)) { + ALOGE("Failed to parse URL \"%s\"", url); + return BAD_VALUE; + } + + if ((a > 255) || (b > 255) || (c > 255) || (d > 255) || (port == 0)) { + ALOGE("Bad multicast address \"%s\"", url); + return BAD_VALUE; + } + + ALOGI("setDataSource :: %u.%u.%u.%u:%hu", a, b, c, d, port); + + a = (a << 24) | (b << 16) | (c << 8) | d; + + memset(&listen_addr_, 0, sizeof(listen_addr_)); + listen_addr_.sin_family = AF_INET; + listen_addr_.sin_port = htons(port); + listen_addr_.sin_addr.s_addr = htonl(a); + data_source_set_ = true; + + return OK; +} + +status_t AAH_RXPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + return INVALID_OPERATION; +} + +status_t AAH_RXPlayer::setVideoSurface(const sp<Surface>& surface) { + return OK; +} + +status_t AAH_RXPlayer::setVideoSurfaceTexture( + const sp<ISurfaceTexture>& surfaceTexture) { + return OK; +} + +status_t AAH_RXPlayer::prepare() { + return OK; +} + +status_t AAH_RXPlayer::prepareAsync() { + sendEvent(MEDIA_PREPARED); + return OK; +} + +status_t AAH_RXPlayer::start() { + AutoMutex api_lock(&api_lock_); + + if (is_playing_) { + return OK; + } + + status_t res = startWorkThread(); + is_playing_ = (res == OK); + return res; +} + +status_t AAH_RXPlayer::stop() { + return pause(); +} + +status_t AAH_RXPlayer::pause() { + AutoMutex api_lock(&api_lock_); + stopWorkThread(); + CHECK(sock_fd_ < 0); + is_playing_ = false; + return OK; +} + +bool AAH_RXPlayer::isPlaying() { + AutoMutex api_lock(&api_lock_); + return is_playing_; +} + +status_t AAH_RXPlayer::seekTo(int msec) { + sendEvent(MEDIA_SEEK_COMPLETE); + return OK; +} + +status_t AAH_RXPlayer::getCurrentPosition(int *msec) { + if (NULL != msec) { + *msec = 0; + } + return OK; +} + +status_t AAH_RXPlayer::getDuration(int *msec) { + if (NULL != msec) { + *msec = 1; + } + return OK; +} + +status_t AAH_RXPlayer::reset() { + AutoMutex api_lock(&api_lock_); + reset_l(); + return OK; +} + +void AAH_RXPlayer::reset_l() { + stopWorkThread(); + CHECK(sock_fd_ < 0); + CHECK(!multicast_joined_); + is_playing_ = false; + data_source_set_ = false; + transmitter_known_ = false; + memset(&listen_addr_, 0, sizeof(listen_addr_)); +} + +status_t AAH_RXPlayer::setLooping(int loop) { + return OK; +} + +player_type AAH_RXPlayer::playerType() { + return AAH_RX_PLAYER; +} + +status_t AAH_RXPlayer::setParameter(int key, const Parcel &request) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_RXPlayer::getParameter(int key, Parcel *reply) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_RXPlayer::invoke(const Parcel& request, Parcel *reply) { + if (!reply) { + return BAD_VALUE; + } + + int32_t magic; + status_t err = request.readInt32(&magic); + if (err != OK) { + reply->writeInt32(err); + return OK; + } + + if (magic != 0x12345) { + reply->writeInt32(BAD_VALUE); + return OK; + } + + int32_t methodID; + err = request.readInt32(&methodID); + if (err != OK) { + reply->writeInt32(err); + return OK; + } + + switch (methodID) { + // Get Volume + case INVOKE_GET_MASTER_VOLUME: { + if (audio_flinger_ != NULL) { + reply->writeInt32(OK); + reply->writeFloat(audio_flinger_->masterVolume()); + } else { + reply->writeInt32(UNKNOWN_ERROR); + } + } break; + + // Set Volume + case INVOKE_SET_MASTER_VOLUME: { + float targetVol = request.readFloat(); + reply->writeInt32(audio_flinger_->setMasterVolume(targetVol)); + } break; + + default: return BAD_VALUE; + } + + return OK; +} + +void AAH_RXPlayer::fetchAudioFlinger() { + if (audio_flinger_ == NULL) { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder; + binder = sm->getService(String16("media.audio_flinger")); + + if (binder == NULL) { + ALOGW("AAH_RXPlayer failed to fetch handle to audio flinger." + " Master volume control will not be possible."); + } + + audio_flinger_ = interface_cast<IAudioFlinger>(binder); + } +} + +} // namespace android diff --git a/media/libaah_rtp/aah_rx_player.h b/media/libaah_rtp/aah_rx_player.h new file mode 100644 index 000000000000..7a1b6e3a7efa --- /dev/null +++ b/media/libaah_rtp/aah_rx_player.h @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAH_RX_PLAYER_H__ +#define __AAH_RX_PLAYER_H__ + +#include <common_time/cc_helper.h> +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXClient.h> +#include <netinet/in.h> +#include <utils/KeyedVector.h> +#include <utils/LinearTransform.h> +#include <utils/threads.h> + +#include "aah_decoder_pump.h" +#include "pipe_event.h" + +namespace android { + +class AAH_RXPlayer : public MediaPlayerInterface { + public: + AAH_RXPlayer(); + + virtual status_t initCheck(); + virtual status_t setDataSource(const char *url, + const KeyedVector<String8, String8>* + headers); + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + virtual status_t setVideoSurface(const sp<Surface>& surface); + virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>& + surfaceTexture); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t setParameter(int key, const Parcel &request); + virtual status_t getParameter(int key, Parcel *reply); + virtual status_t invoke(const Parcel& request, Parcel *reply); + + protected: + virtual ~AAH_RXPlayer(); + + private: + class ThreadWrapper : public Thread { + public: + friend class AAH_RXPlayer; + explicit ThreadWrapper(AAH_RXPlayer& player) + : Thread(false /* canCallJava */ ) + , player_(player) { } + + virtual bool threadLoop() { return player_.threadLoop(); } + + private: + AAH_RXPlayer& player_; + + DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper); + }; + +#pragma pack(push, 1) + // PacketBuffers are structures used by the RX ring buffer. The ring buffer + // is a ring of pointers to PacketBuffer structures which act as variable + // length byte arrays and hold the contents of received UDP packets. Rather + // than make this a structure which hold a length and a pointer to another + // allocated structure (which would require two allocations), this struct + // uses a structure overlay pattern where allocation for the byte array + // consists of allocating (arrayLen + sizeof(ssize_t)) bytes of data from + // whatever pool/heap the packet buffer pulls from, and then overlaying the + // packed PacketBuffer structure on top of the allocation. The one-byte + // array at the end of the structure serves as an offset to the the data + // portion of the allocation; packet buffers are never allocated on the + // stack or using the new operator. Instead, the static allocate-byte-array + // and destroy methods handle the allocate and overlay pattern. They also + // allow for a potential future optimization where instead of just + // allocating blocks from the process global heap and overlaying, the + // allocator is replaced with a different implementation (private heap, + // free-list, circular buffer, etc) which reduces potential heap + // fragmentation issues which might arise from the frequent allocation and + // destruction of the received UDP traffic. + struct PacketBuffer { + ssize_t length_; + uint8_t data_[1]; + + // TODO : consider changing this to be some form of ring buffer or free + // pool system instead of just using the heap in order to avoid heap + // fragmentation. + static PacketBuffer* allocate(ssize_t length); + static void destroy(PacketBuffer* pb); + + private: + // Force people to use allocate/destroy instead of new/delete. + PacketBuffer() { } + ~PacketBuffer() { } + }; + + struct RetransRequest { + uint32_t magic_; + uint32_t mcast_ip_; + uint16_t mcast_port_; + uint16_t start_seq_; + uint16_t end_seq_; + }; +#pragma pack(pop) + + enum GapStatus { + kGS_NoGap = 0, + kGS_NormalGap, + kGS_FastStartGap, + }; + + struct SeqNoGap { + uint16_t start_seq_; + uint16_t end_seq_; + }; + + class RXRingBuffer { + public: + explicit RXRingBuffer(uint32_t capacity); + ~RXRingBuffer(); + + bool initCheck() const { return (ring_ != NULL); } + void reset(); + + // Push a packet buffer with a given sequence number into the ring + // buffer. pushBuffer will always consume the buffer pushed to it, + // either destroying it because it was a duplicate or overflow, or + // holding on to it in the ring. Callers should not hold any references + // to PacketBuffers after they have been pushed to the ring. Returns + // false in the case of a serious error (such as ring overflow). + // Callers should consider resetting the pipeline entirely in the event + // of a serious error. + bool pushBuffer(PacketBuffer* buf, uint16_t seq); + + // Fetch the next buffer in the RTP sequence. Returns NULL if there is + // no buffer to fetch. If a non-NULL PacketBuffer is returned, + // is_discon will be set to indicate whether or not this PacketBuffer is + // discontiuous with any previously returned packet buffers. Packet + // buffers returned by fetchBuffer are the caller's responsibility; they + // must be certain to destroy the buffers when they are done. + PacketBuffer* fetchBuffer(bool* is_discon); + + // Returns true and fills out the gap structure if the read pointer of + // the ring buffer is currently pointing to a gap which would stall a + // fetchBuffer operation. Returns false if the read pointer is not + // pointing to a gap in the sequence currently. + GapStatus fetchCurrentGap(SeqNoGap* gap); + + // Causes the read pointer to skip over any portion of a gap indicated + // by nak. If nak is NULL, any gap currently blocking the read pointer + // will be completely skipped. If any portion of a gap is skipped, the + // next successful read from fetch buffer will indicate a discontinuity. + void processNAK(const SeqNoGap* nak = NULL); + + // Compute the number of milliseconds until the inactivity timer for + // this RTP stream. Returns -1 if there is no active timeout, or 0 if + // the system has already timed out. + int computeInactivityTimeout(); + + private: + Mutex lock_; + PacketBuffer** ring_; + uint32_t capacity_; + uint32_t rd_; + uint32_t wr_; + + uint16_t rd_seq_; + bool rd_seq_known_; + bool waiting_for_fast_start_; + bool fetched_first_packet_; + + uint64_t rtp_activity_timeout_; + bool rtp_activity_timeout_valid_; + + DISALLOW_EVIL_CONSTRUCTORS(RXRingBuffer); + }; + + class Substream : public virtual RefBase { + public: + Substream(uint32_t ssrc, OMXClient& omx); + + void cleanupBufferInProgress(); + void shutdown(); + void processPayloadStart(uint8_t* buf, + uint32_t amt, + int32_t ts_lower); + void processPayloadCont (uint8_t* buf, + uint32_t amt); + void processTSTransform(const LinearTransform& trans); + + bool isAboutToUnderflow(); + uint32_t getSSRC() const { return ssrc_; } + uint16_t getProgramID() const { return (ssrc_ >> 5) & 0x1F; } + status_t getStatus() const { return status_; } + + protected: + virtual ~Substream() { + shutdown(); + } + + private: + void cleanupDecoder(); + bool shouldAbort(const char* log_tag); + void processCompletedBuffer(); + bool setupSubstreamType(uint8_t substream_type, + uint8_t codec_type); + + uint32_t ssrc_; + bool waiting_for_rap_; + status_t status_; + + bool substream_details_known_; + uint8_t substream_type_; + uint8_t codec_type_; + sp<MetaData> substream_meta_; + + MediaBuffer* buffer_in_progress_; + uint32_t expected_buffer_size_; + uint32_t buffer_filled_; + + sp<AAH_DecoderPump> decoder_; + + static int64_t kAboutToUnderflowThreshold; + + DISALLOW_EVIL_CONSTRUCTORS(Substream); + }; + + typedef DefaultKeyedVector< uint32_t, sp<Substream> > SubstreamVec; + + status_t startWorkThread(); + void stopWorkThread(); + virtual bool threadLoop(); + bool setupSocket(); + void cleanupSocket(); + void resetPipeline(); + void reset_l(); + bool processRX(PacketBuffer* pb); + void processRingBuffer(); + void processCommandPacket(PacketBuffer* pb); + bool processGaps(); + int computeNextGapRetransmitTimeout(); + void fetchAudioFlinger(); + + PipeEvent wakeup_work_thread_evt_; + sp<ThreadWrapper> thread_wrapper_; + Mutex api_lock_; + bool is_playing_; + bool data_source_set_; + + struct sockaddr_in listen_addr_; + int sock_fd_; + bool multicast_joined_; + + struct sockaddr_in transmitter_addr_; + bool transmitter_known_; + + uint32_t current_epoch_; + bool current_epoch_known_; + + SeqNoGap current_gap_; + GapStatus current_gap_status_; + uint64_t next_retrans_req_time_; + + RXRingBuffer ring_buffer_; + SubstreamVec substreams_; + OMXClient omx_; + CCHelper cc_helper_; + + // Connection to audio flinger used to hack a path to setMasterVolume. + sp<IAudioFlinger> audio_flinger_; + + static const uint32_t kRTPRingBufferSize; + static const uint32_t kRetransRequestMagic; + static const uint32_t kFastStartRequestMagic; + static const uint32_t kRetransNAKMagic; + static const uint32_t kGapRerequestTimeoutUSec; + static const uint32_t kFastStartTimeoutUSec; + static const uint32_t kRTPActivityTimeoutUSec; + + static const uint32_t INVOKE_GET_MASTER_VOLUME = 3; + static const uint32_t INVOKE_SET_MASTER_VOLUME = 4; + + static uint64_t monotonicUSecNow(); + + DISALLOW_EVIL_CONSTRUCTORS(AAH_RXPlayer); +}; + +} // namespace android + +#endif // __AAH_RX_PLAYER_H__ diff --git a/media/libaah_rtp/aah_rx_player_core.cpp b/media/libaah_rtp/aah_rx_player_core.cpp new file mode 100644 index 000000000000..d2b33866cdeb --- /dev/null +++ b/media/libaah_rtp/aah_rx_player_core.cpp @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include <fcntl.h> +#include <poll.h> +#include <sys/socket.h> +#include <time.h> +#include <utils/misc.h> + +#include <media/stagefright/Utils.h> + +#include "aah_rx_player.h" +#include "aah_tx_packet.h" + +namespace android { + +const uint32_t AAH_RXPlayer::kRetransRequestMagic = + FOURCC('T','r','e','q'); +const uint32_t AAH_RXPlayer::kRetransNAKMagic = + FOURCC('T','n','a','k'); +const uint32_t AAH_RXPlayer::kFastStartRequestMagic = + FOURCC('T','f','s','t'); +const uint32_t AAH_RXPlayer::kGapRerequestTimeoutUSec = 75000; +const uint32_t AAH_RXPlayer::kFastStartTimeoutUSec = 800000; +const uint32_t AAH_RXPlayer::kRTPActivityTimeoutUSec = 10000000; + +static inline int16_t fetchInt16(uint8_t* data) { + return static_cast<int16_t>(U16_AT(data)); +} + +static inline int32_t fetchInt32(uint8_t* data) { + return static_cast<int32_t>(U32_AT(data)); +} + +static inline int64_t fetchInt64(uint8_t* data) { + return static_cast<int64_t>(U64_AT(data)); +} + +uint64_t AAH_RXPlayer::monotonicUSecNow() { + struct timespec now; + int res = clock_gettime(CLOCK_MONOTONIC, &now); + CHECK(res >= 0); + + uint64_t ret = static_cast<uint64_t>(now.tv_sec) * 1000000; + ret += now.tv_nsec / 1000; + + return ret; +} + +status_t AAH_RXPlayer::startWorkThread() { + status_t res; + stopWorkThread(); + res = thread_wrapper_->run("TRX_Player", PRIORITY_AUDIO); + + if (res != OK) { + ALOGE("Failed to start work thread (res = %d)", res); + } + + return res; +} + +void AAH_RXPlayer::stopWorkThread() { + thread_wrapper_->requestExit(); // set the exit pending flag + wakeup_work_thread_evt_.setEvent(); + + status_t res; + res = thread_wrapper_->requestExitAndWait(); // block until thread exit. + if (res != OK) { + ALOGE("Failed to stop work thread (res = %d)", res); + } + + wakeup_work_thread_evt_.clearPendingEvents(); +} + +void AAH_RXPlayer::cleanupSocket() { + if (sock_fd_ >= 0) { + if (multicast_joined_) { + int res; + struct ip_mreq mreq; + mreq.imr_multiaddr = listen_addr_.sin_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + res = setsockopt(sock_fd_, + IPPROTO_IP, + IP_DROP_MEMBERSHIP, + &mreq, sizeof(mreq)); + if (res < 0) { + ALOGW("Failed to leave multicast group. (%d, %d)", res, errno); + } + multicast_joined_ = false; + } + + close(sock_fd_); + sock_fd_ = -1; + } + + resetPipeline(); +} + +void AAH_RXPlayer::resetPipeline() { + ring_buffer_.reset(); + + // Explicitly shudown all of the active substreams, then call clear out the + // collection. Failure to clear out a substream can result in its decoder + // holding a reference to itself and therefor not going away when the + // collection is cleared. + for (size_t i = 0; i < substreams_.size(); ++i) + substreams_.valueAt(i)->shutdown(); + + substreams_.clear(); + + current_gap_status_ = kGS_NoGap; +} + +bool AAH_RXPlayer::setupSocket() { + long flags; + int res, buf_size; + socklen_t opt_size; + + cleanupSocket(); + CHECK(sock_fd_ < 0); + + // Make the socket + sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock_fd_ < 0) { + ALOGE("Failed to create listen socket (errno %d)", errno); + goto bailout; + } + + // Set non-blocking operation + flags = fcntl(sock_fd_, F_GETFL); + res = fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK); + if (res < 0) { + ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)", + sock_fd_, errno); + goto bailout; + } + + // Bind to our port + struct sockaddr_in bind_addr; + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = INADDR_ANY; + bind_addr.sin_port = listen_addr_.sin_port; + res = bind(sock_fd_, + reinterpret_cast<const sockaddr*>(&bind_addr), + sizeof(bind_addr)); + if (res < 0) { + uint32_t a = ntohl(bind_addr.sin_addr.s_addr); + uint16_t p = ntohs(bind_addr.sin_port); + ALOGE("Failed to bind socket (%d) to %d.%d.%d.%d:%hd. (errno %d)", + sock_fd_, + (a >> 24) & 0xFF, + (a >> 16) & 0xFF, + (a >> 8) & 0xFF, + (a ) & 0xFF, + p, + errno); + + goto bailout; + } + + buf_size = 1 << 16; // 64k + res = setsockopt(sock_fd_, + SOL_SOCKET, SO_RCVBUF, + &buf_size, sizeof(buf_size)); + if (res < 0) { + ALOGW("Failed to increase socket buffer size to %d. (errno %d)", + buf_size, errno); + } + + buf_size = 0; + opt_size = sizeof(buf_size); + res = getsockopt(sock_fd_, + SOL_SOCKET, SO_RCVBUF, + &buf_size, &opt_size); + if (res < 0) { + ALOGW("Failed to fetch socket buffer size. (errno %d)", errno); + } else { + ALOGI("RX socket buffer size is now %d bytes", buf_size); + } + + if (listen_addr_.sin_addr.s_addr) { + // Join the multicast group and we should be good to go. + struct ip_mreq mreq; + mreq.imr_multiaddr = listen_addr_.sin_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + res = setsockopt(sock_fd_, + IPPROTO_IP, + IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)); + if (res < 0) { + ALOGE("Failed to join multicast group. (errno %d)", errno); + goto bailout; + } + multicast_joined_ = true; + } + + return true; + +bailout: + cleanupSocket(); + return false; +} + +bool AAH_RXPlayer::threadLoop() { + struct pollfd poll_fds[2]; + bool process_more_right_now = false; + + if (!setupSocket()) { + sendEvent(MEDIA_ERROR); + goto bailout; + } + + while (!thread_wrapper_->exitPending()) { + // Step 1: Wait until there is something to do. + int gap_timeout = computeNextGapRetransmitTimeout(); + int ring_timeout = ring_buffer_.computeInactivityTimeout(); + int timeout = -1; + + if (!ring_timeout) { + ALOGW("RTP inactivity timeout reached, resetting pipeline."); + resetPipeline(); + timeout = gap_timeout; + } else { + if (gap_timeout < 0) { + timeout = ring_timeout; + } else if (ring_timeout < 0) { + timeout = gap_timeout; + } else { + timeout = (gap_timeout < ring_timeout) ? gap_timeout + : ring_timeout; + } + } + + if ((0 != timeout) && (!process_more_right_now)) { + // Set up the events to wait on. Start with the wakeup pipe. + memset(&poll_fds, 0, sizeof(poll_fds)); + poll_fds[0].fd = wakeup_work_thread_evt_.getWakeupHandle(); + poll_fds[0].events = POLLIN; + + // Add the RX socket. + poll_fds[1].fd = sock_fd_; + poll_fds[1].events = POLLIN; + + // Wait for something interesing to happen. + int poll_res = poll(poll_fds, NELEM(poll_fds), timeout); + if (poll_res < 0) { + ALOGE("Fatal error (%d,%d) while waiting on events", + poll_res, errno); + sendEvent(MEDIA_ERROR); + goto bailout; + } + } + + if (thread_wrapper_->exitPending()) { + break; + } + + wakeup_work_thread_evt_.clearPendingEvents(); + process_more_right_now = false; + + // Step 2: Do we have data waiting in the socket? If so, drain the + // socket moving valid RTP information into the ring buffer to be + // processed. + if (poll_fds[1].revents) { + struct sockaddr_in from; + socklen_t from_len; + + ssize_t res = 0; + while (!thread_wrapper_->exitPending()) { + // Check the size of any pending packet. + res = recv(sock_fd_, NULL, 0, MSG_PEEK | MSG_TRUNC); + + // Error? + if (res < 0) { + // If the error is anything other than would block, + // something has gone very wrong. + if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) { + ALOGE("Fatal socket error during recvfrom (%d, %d)", + (int)res, errno); + goto bailout; + } + + // Socket is out of data, just break out of processing and + // wait for more. + break; + } + + // Allocate a payload. + PacketBuffer* pb = PacketBuffer::allocate(res); + if (NULL == pb) { + ALOGE("Fatal error, failed to allocate packet buffer of" + " length %u", static_cast<uint32_t>(res)); + goto bailout; + } + + // Fetch the data. + from_len = sizeof(from); + res = recvfrom(sock_fd_, pb->data_, pb->length_, 0, + reinterpret_cast<struct sockaddr*>(&from), + &from_len); + if (res != pb->length_) { + ALOGE("Fatal error, fetched packet length (%d) does not" + " match peeked packet length (%u). This should never" + " happen. (errno = %d)", + static_cast<int>(res), + static_cast<uint32_t>(pb->length_), + errno); + } + + bool drop_packet = false; + if (transmitter_known_) { + if (from.sin_addr.s_addr != + transmitter_addr_.sin_addr.s_addr) { + uint32_t a = ntohl(from.sin_addr.s_addr); + uint16_t p = ntohs(from.sin_port); + ALOGV("Dropping packet from unknown transmitter" + " %u.%u.%u.%u:%hu", + ((a >> 24) & 0xFF), + ((a >> 16) & 0xFF), + ((a >> 8) & 0xFF), + ( a & 0xFF), + p); + + drop_packet = true; + } else { + transmitter_addr_.sin_port = from.sin_port; + } + } else { + memcpy(&transmitter_addr_, &from, sizeof(from)); + transmitter_known_ = true; + } + + if (!drop_packet) { + bool serious_error = !processRX(pb); + + if (serious_error) { + // Something went "seriously wrong". Currently, the + // only trigger for this should be a ring buffer + // overflow. The current failsafe behavior for when + // something goes seriously wrong is to just reset the + // pipeline. The system should behave as if this + // AAH_RXPlayer was just set up for the first time. + ALOGE("Something just went seriously wrong with the" + " pipeline. Resetting."); + resetPipeline(); + } + } else { + PacketBuffer::destroy(pb); + } + } + } + + // Step 3: Process any data we mave have accumulated in the ring buffer + // so far. + if (!thread_wrapper_->exitPending()) { + processRingBuffer(); + } + + // Step 4: At this point in time, the ring buffer should either be + // empty, or stalled in front of a gap caused by some dropped packets. + // Check on the current gap situation and deal with it in an appropriate + // fashion. If processGaps returns true, it means that it has given up + // on a gap and that we should try to process some more data + // immediately. + if (!thread_wrapper_->exitPending()) { + process_more_right_now = processGaps(); + } + + // Step 5: Check for fatal errors. If any of our substreams has + // encountered a fatal, unrecoverable, error, then propagate the error + // up to user level and shut down. + for (size_t i = 0; i < substreams_.size(); ++i) { + status_t status; + CHECK(substreams_.valueAt(i) != NULL); + + status = substreams_.valueAt(i)->getStatus(); + if (OK != status) { + ALOGE("Substream index %d has encountered an unrecoverable" + " error (%d). Signalling application level and shutting" + " down.", i, status); + sendEvent(MEDIA_ERROR); + goto bailout; + } + } + } + +bailout: + cleanupSocket(); + return false; +} + +bool AAH_RXPlayer::processRX(PacketBuffer* pb) { + CHECK(NULL != pb); + + uint8_t* data = pb->data_; + ssize_t amt = pb->length_; + uint32_t nak_magic; + uint16_t seq_no; + uint32_t epoch; + + // Every packet either starts with an RTP header which is at least 12 bytes + // long or is a retry NAK which is 14 bytes long. If there are fewer than + // 12 bytes here, this cannot be a proper RTP packet. + if (amt < 12) { + ALOGV("Dropping packet, too short to contain RTP header (%u bytes)", + static_cast<uint32_t>(amt)); + goto drop_packet; + } + + // Check to see if this is the special case of a NAK packet. + nak_magic = ntohl(*(reinterpret_cast<uint32_t*>(data))); + if (nak_magic == kRetransNAKMagic) { + // Looks like a NAK packet; make sure its long enough. + + if (amt < static_cast<ssize_t>(sizeof(RetransRequest))) { + ALOGV("Dropping packet, too short to contain NAK payload (%u bytes)", + static_cast<uint32_t>(amt)); + goto drop_packet; + } + + SeqNoGap gap; + RetransRequest* rtr = reinterpret_cast<RetransRequest*>(data); + gap.start_seq_ = ntohs(rtr->start_seq_); + gap.end_seq_ = ntohs(rtr->end_seq_); + + ALOGV("Process NAK for gap at [%hu, %hu]", gap.start_seq_, gap.end_seq_); + ring_buffer_.processNAK(&gap); + + return true; + } + + // According to the TRTP spec, version should be 2, padding should be 0, + // extension should be 0 and CSRCCnt should be 0. If any of these tests + // fail, we chuck the packet. + if (data[0] != 0x80) { + ALOGV("Dropping packet, bad V/P/X/CSRCCnt field (0x%02x)", + data[0]); + goto drop_packet; + } + + // Check the payload type. For TRTP, it should always be 100. + if ((data[1] & 0x7F) != 100) { + ALOGV("Dropping packet, bad payload type. (%u)", + data[1] & 0x7F); + goto drop_packet; + } + + // Check whether the transmitter has begun a new epoch. + epoch = (U32_AT(data + 8) >> 10) & 0x3FFFFF; + if (current_epoch_known_) { + if (epoch != current_epoch_) { + ALOGV("%s: new epoch %u", __PRETTY_FUNCTION__, epoch); + current_epoch_ = epoch; + resetPipeline(); + } + } else { + current_epoch_ = epoch; + current_epoch_known_ = true; + } + + // Extract the sequence number and hand the packet off to the ring buffer + // for dropped packet detection and later processing. + seq_no = U16_AT(data + 2); + return ring_buffer_.pushBuffer(pb, seq_no); + +drop_packet: + PacketBuffer::destroy(pb); + return true; +} + +void AAH_RXPlayer::processRingBuffer() { + PacketBuffer* pb; + bool is_discon; + sp<Substream> substream; + LinearTransform trans; + bool foundTrans = false; + + while (NULL != (pb = ring_buffer_.fetchBuffer(&is_discon))) { + if (is_discon) { + // Abort all partially assembled payloads. + for (size_t i = 0; i < substreams_.size(); ++i) { + CHECK(substreams_.valueAt(i) != NULL); + substreams_.valueAt(i)->cleanupBufferInProgress(); + } + } + + uint8_t* data = pb->data_; + ssize_t amt = pb->length_; + + // Should not have any non-RTP packets in the ring buffer. RTP packets + // must be at least 12 bytes long. + CHECK(amt >= 12); + + // Extract the marker bit and the SSRC field. + bool marker = (data[1] & 0x80) != 0; + uint32_t ssrc = U32_AT(data + 8); + + // Is this the start of a new TRTP payload? If so, the marker bit + // should be set and there are some things we should be checking for. + if (marker) { + // TRTP headers need to have at least a byte for version, a byte for + // payload type and flags, and 4 bytes for length. + if (amt < 18) { + ALOGV("Dropping packet, too short to contain TRTP header" + " (%u bytes)", static_cast<uint32_t>(amt)); + goto process_next_packet; + } + + // Check the TRTP version and extract the payload type/flags. + uint8_t trtp_version = data[12]; + uint8_t payload_type = (data[13] >> 4) & 0xF; + uint8_t trtp_flags = data[13] & 0xF; + + if (1 != trtp_version) { + ALOGV("Dropping packet, bad trtp version %hhu", trtp_version); + goto process_next_packet; + } + + // Is there a timestamp transformation present on this packet? If + // so, extract it and pass it to the appropriate substreams. + if (trtp_flags & 0x02) { + ssize_t offset = 18 + ((trtp_flags & 0x01) ? 4 : 0); + if (amt < (offset + 24)) { + ALOGV("Dropping packet, too short to contain TRTP Timestamp" + " Transformation (%u bytes)", + static_cast<uint32_t>(amt)); + goto process_next_packet; + } + + trans.a_zero = fetchInt64(data + offset); + trans.b_zero = fetchInt64(data + offset + 16); + trans.a_to_b_numer = static_cast<int32_t>( + fetchInt32 (data + offset + 8)); + trans.a_to_b_denom = U32_AT(data + offset + 12); + foundTrans = true; + + uint32_t program_id = (ssrc >> 5) & 0x1F; + for (size_t i = 0; i < substreams_.size(); ++i) { + sp<Substream> iter = substreams_.valueAt(i); + CHECK(iter != NULL); + + if (iter->getProgramID() == program_id) { + iter->processTSTransform(trans); + } + } + } + + // Is this a command packet? If so, its not necessarily associate + // with one particular substream. Just give it to the command + // packet handler and then move on. + if (4 == payload_type) { + processCommandPacket(pb); + goto process_next_packet; + } + } + + // If we got to here, then we are a normal packet. Find (or allocate) + // the substream we belong to and send the packet off to be processed. + substream = substreams_.valueFor(ssrc); + if (substream == NULL) { + substream = new Substream(ssrc, omx_); + if (substream == NULL) { + ALOGE("Failed to allocate substream for SSRC 0x%08x", ssrc); + goto process_next_packet; + } + substreams_.add(ssrc, substream); + + if (foundTrans) { + substream->processTSTransform(trans); + } + } + + CHECK(substream != NULL); + + if (marker) { + // Start of a new TRTP payload for this substream. Extract the + // lower 32 bits of the timestamp and hand the buffer to the + // substream for processing. + uint32_t ts_lower = U32_AT(data + 4); + substream->processPayloadStart(data + 12, amt - 12, ts_lower); + } else { + // Continuation of an existing TRTP payload. Just hand it off to + // the substream for processing. + substream->processPayloadCont(data + 12, amt - 12); + } + +process_next_packet: + PacketBuffer::destroy(pb); + } // end of main processing while loop. +} + +void AAH_RXPlayer::processCommandPacket(PacketBuffer* pb) { + CHECK(NULL != pb); + + uint8_t* data = pb->data_; + ssize_t amt = pb->length_; + + // verify that this packet meets the minimum length of a command packet + if (amt < 20) { + return; + } + + uint8_t trtp_version = data[12]; + uint8_t trtp_flags = data[13] & 0xF; + + if (1 != trtp_version) { + ALOGV("Dropping packet, bad trtp version %hhu", trtp_version); + return; + } + + // calculate the start of the command payload + ssize_t offset = 18; + if (trtp_flags & 0x01) { + // timestamp is present (4 bytes) + offset += 4; + } + if (trtp_flags & 0x02) { + // transform is present (24 bytes) + offset += 24; + } + + // the packet must contain 2 bytes of command payload beyond the TRTP header + if (amt < offset + 2) { + return; + } + + uint16_t command_id = U16_AT(data + offset); + + switch (command_id) { + case TRTPControlPacket::kCommandNop: + break; + + case TRTPControlPacket::kCommandEOS: + case TRTPControlPacket::kCommandFlush: { + uint16_t program_id = (U32_AT(data + 8) >> 5) & 0x1F; + ALOGI("*** %s flushing program_id=%d", + __PRETTY_FUNCTION__, program_id); + + Vector<uint32_t> substreams_to_remove; + for (size_t i = 0; i < substreams_.size(); ++i) { + sp<Substream> iter = substreams_.valueAt(i); + if (iter->getProgramID() == program_id) { + iter->shutdown(); + substreams_to_remove.add(iter->getSSRC()); + } + } + + for (size_t i = 0; i < substreams_to_remove.size(); ++i) { + substreams_.removeItem(substreams_to_remove[i]); + } + } break; + } +} + +bool AAH_RXPlayer::processGaps() { + // Deal with the current gap situation. Specifically... + // + // 1) If a new gap has shown up, send a retransmit request to the + // transmitter. + // 2) If a gap we were working on has had a packet in the middle or at + // the end filled in, send another retransmit request for the begining + // portion of the gap. TRTP was designed for LANs where packet + // re-ordering is very unlikely; so see the middle or end of a gap + // filled in before the begining is an almost certain indication that + // a retransmission packet was also dropped. + // 3) If we have been working on a gap for a while and it still has not + // been filled in, send another retransmit request. + // 4) If the are no more gaps in the ring, clear the current_gap_status_ + // flag to indicate that all is well again. + + // Start by fetching the active gap status. + SeqNoGap gap; + bool send_retransmit_request = false; + bool ret_val = false; + GapStatus gap_status; + if (kGS_NoGap != (gap_status = ring_buffer_.fetchCurrentGap(&gap))) { + // Note: checking for a change in the end sequence number should cover + // moving on to an entirely new gap for case #1 as well as resending the + // begining of a gap range for case #2. + send_retransmit_request = (kGS_NoGap == current_gap_status_) || + (current_gap_.end_seq_ != gap.end_seq_); + + // If this is the same gap we have been working on, and it has timed + // out, then check to see if our substreams are about to underflow. If + // so, instead of sending another retransmit request, just give up on + // this gap and move on. + if (!send_retransmit_request && + (kGS_NoGap != current_gap_status_) && + (0 == computeNextGapRetransmitTimeout())) { + + // If out current gap is the fast-start gap, don't bother to skip it + // because substreams look like the are about to underflow. + if ((kGS_FastStartGap != gap_status) || + (current_gap_.end_seq_ != gap.end_seq_)) { + for (size_t i = 0; i < substreams_.size(); ++i) { + if (substreams_.valueAt(i)->isAboutToUnderflow()) { + ALOGV("About to underflow, giving up on gap [%hu, %hu]", + gap.start_seq_, gap.end_seq_); + ring_buffer_.processNAK(); + current_gap_status_ = kGS_NoGap; + return true; + } + } + } + + // Looks like no one is about to underflow. Just go ahead and send + // the request. + send_retransmit_request = true; + } + } else { + current_gap_status_ = kGS_NoGap; + } + + if (send_retransmit_request) { + // If we have been working on a fast start, and it is still not filled + // in, even after the extended retransmit time out, give up and skip it. + // The system should fall back into its normal slow-start behavior. + if ((kGS_FastStartGap == current_gap_status_) && + (current_gap_.end_seq_ == gap.end_seq_)) { + ALOGV("Fast start is taking forever; giving up."); + ring_buffer_.processNAK(); + current_gap_status_ = kGS_NoGap; + return true; + } + + // Send the request. + RetransRequest req; + uint32_t magic = (kGS_FastStartGap == gap_status) + ? kFastStartRequestMagic + : kRetransRequestMagic; + req.magic_ = htonl(magic); + req.mcast_ip_ = listen_addr_.sin_addr.s_addr; + req.mcast_port_ = listen_addr_.sin_port; + req.start_seq_ = htons(gap.start_seq_); + req.end_seq_ = htons(gap.end_seq_); + + { + uint32_t a = ntohl(transmitter_addr_.sin_addr.s_addr); + uint16_t p = ntohs(transmitter_addr_.sin_port); + ALOGV("Sending to transmitter %u.%u.%u.%u:%hu", + ((a >> 24) & 0xFF), + ((a >> 16) & 0xFF), + ((a >> 8) & 0xFF), + ( a & 0xFF), + p); + } + + int res = sendto(sock_fd_, &req, sizeof(req), 0, + reinterpret_cast<struct sockaddr*>(&transmitter_addr_), + sizeof(transmitter_addr_)); + if (res < 0) { + ALOGE("Error when sending retransmit request (%d)", errno); + } else { + ALOGV("%s request for range [%hu, %hu] sent", + (kGS_FastStartGap == gap_status) ? "Fast Start" : "Retransmit", + gap.start_seq_, gap.end_seq_); + } + + // Update the current gap info. + current_gap_ = gap; + current_gap_status_ = gap_status; + next_retrans_req_time_ = monotonicUSecNow() + + ((kGS_FastStartGap == current_gap_status_) + ? kFastStartTimeoutUSec + : kGapRerequestTimeoutUSec); + } + + return false; +} + +// Compute when its time to send the next gap retransmission in milliseconds. +// Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit +// right now. +int AAH_RXPlayer::computeNextGapRetransmitTimeout() { + if (kGS_NoGap == current_gap_status_) { + return -1; + } + + int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow(); + + timeout_delta /= 1000; + if (timeout_delta <= 0) { + return 0; + } + + return static_cast<uint32_t>(timeout_delta); +} + +} // namespace android diff --git a/media/libaah_rtp/aah_rx_player_ring_buffer.cpp b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp new file mode 100644 index 000000000000..0d8b31f5441d --- /dev/null +++ b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +#include "aah_rx_player.h" + +namespace android { + +AAH_RXPlayer::RXRingBuffer::RXRingBuffer(uint32_t capacity) { + capacity_ = capacity; + rd_ = wr_ = 0; + ring_ = new PacketBuffer*[capacity]; + memset(ring_, 0, sizeof(PacketBuffer*) * capacity); + reset(); +} + +AAH_RXPlayer::RXRingBuffer::~RXRingBuffer() { + reset(); + delete[] ring_; +} + +void AAH_RXPlayer::RXRingBuffer::reset() { + AutoMutex lock(&lock_); + + if (NULL != ring_) { + while (rd_ != wr_) { + CHECK(rd_ < capacity_); + if (NULL != ring_[rd_]) { + PacketBuffer::destroy(ring_[rd_]); + ring_[rd_] = NULL; + } + rd_ = (rd_ + 1) % capacity_; + } + } + + rd_ = wr_ = 0; + rd_seq_known_ = false; + waiting_for_fast_start_ = true; + fetched_first_packet_ = false; + rtp_activity_timeout_valid_ = false; +} + +bool AAH_RXPlayer::RXRingBuffer::pushBuffer(PacketBuffer* buf, + uint16_t seq) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + CHECK(NULL != buf); + + rtp_activity_timeout_valid_ = true; + rtp_activity_timeout_ = monotonicUSecNow() + kRTPActivityTimeoutUSec; + + // If the ring buffer is totally reset (we have never received a single + // payload) then we don't know the rd sequence number and this should be + // simple. We just store the payload, advance the wr pointer and record the + // initial sequence number. + if (!rd_seq_known_) { + CHECK(rd_ == wr_); + CHECK(NULL == ring_[wr_]); + CHECK(wr_ < capacity_); + + ring_[wr_] = buf; + wr_ = (wr_ + 1) % capacity_; + rd_seq_ = seq; + rd_seq_known_ = true; + return true; + } + + // Compute the seqence number of this payload and of the write pointer, + // normalized around the read pointer. IOW - transform the payload seq no + // and the wr pointer seq no into a space where the rd pointer seq no is + // zero. This will define 4 cases we can consider... + // + // 1) norm_seq == norm_wr_seq + // This payload is contiguous with the last. All is good. + // + // 2) ((norm_seq < norm_wr_seq) && (norm_seq >= norm_rd_seq) + // aka ((norm_seq < norm_wr_seq) && (norm_seq >= 0) + // This payload is in the past, in the unprocessed region of the ring + // buffer. It is probably a retransmit intended to fill in a dropped + // payload; it may be a duplicate. + // + // 3) ((norm_seq - norm_wr_seq) & 0x8000) != 0 + // This payload is in the past compared to the write pointer (or so very + // far in the future that it has wrapped the seq no space), but not in + // the unprocessed region of the ring buffer. This could be a duplicate + // retransmit; we just drop these payloads unless we are waiting for our + // first fast start packet. If we are waiting for fast start, than this + // packet is probably the first packet of the fast start retransmission. + // If it will fit in the buffer, back up the read pointer to its position + // and clear the fast start flag, otherwise just drop it. + // + // 4) ((norm_seq - norm_wr_seq) & 0x8000) == 0 + // This payload which is ahead of the next write pointer. This indicates + // that we have missed some payloads and need to request a retransmit. + // If norm_seq >= (capacity - 1), then the gap is so large that it would + // overflow the ring buffer and we should probably start to panic. + + uint16_t norm_wr_seq = ((wr_ + capacity_ - rd_) % capacity_); + uint16_t norm_seq = seq - rd_seq_; + + // Check for overflow first. + if ((!(norm_seq & 0x8000)) && (norm_seq >= (capacity_ - 1))) { + ALOGW("Ring buffer overflow; cap = %u, [rd, wr] = [%hu, %hu], seq = %hu", + capacity_, rd_seq_, norm_wr_seq + rd_seq_, seq); + PacketBuffer::destroy(buf); + return false; + } + + // Check for case #1 + if (norm_seq == norm_wr_seq) { + CHECK(wr_ < capacity_); + CHECK(NULL == ring_[wr_]); + + ring_[wr_] = buf; + wr_ = (wr_ + 1) % capacity_; + + CHECK(wr_ != rd_); + return true; + } + + // Check case #2 + uint32_t ring_pos = (rd_ + norm_seq) % capacity_; + if ((norm_seq < norm_wr_seq) && (!(norm_seq & 0x8000))) { + // Do we already have a payload for this slot? If so, then this looks + // like a duplicate retransmit. Just ignore it. + if (NULL != ring_[ring_pos]) { + ALOGD("RXed duplicate retransmit, seq = %hu", seq); + PacketBuffer::destroy(buf); + } else { + // Looks like we were missing this payload. Go ahead and store it. + ring_[ring_pos] = buf; + } + + return true; + } + + // Check case #3 + if ((norm_seq - norm_wr_seq) & 0x8000) { + if (!waiting_for_fast_start_) { + ALOGD("RXed duplicate retransmit from before rd pointer, seq = %hu", + seq); + PacketBuffer::destroy(buf); + } else { + // Looks like a fast start fill-in. Go ahead and store it, assuming + // that we can fit it in the buffer. + uint32_t implied_ring_size = static_cast<uint32_t>(norm_wr_seq) + + (rd_seq_ - seq); + + if (implied_ring_size >= (capacity_ - 1)) { + ALOGD("RXed what looks like a fast start packet (seq = %hu)," + " but packet is too far in the past to fit into the ring" + " buffer. Dropping.", seq); + PacketBuffer::destroy(buf); + } else { + ring_pos = (rd_ + capacity_ + seq - rd_seq_) % capacity_; + rd_seq_ = seq; + rd_ = ring_pos; + waiting_for_fast_start_ = false; + + CHECK(ring_pos < capacity_); + CHECK(NULL == ring_[ring_pos]); + ring_[ring_pos] = buf; + } + + } + return true; + } + + // Must be in case #4 with no overflow. This packet fits in the current + // ring buffer, but is discontiuguous. Advance the write pointer leaving a + // gap behind. + uint32_t gap_len = (ring_pos + capacity_ - wr_) % capacity_; + ALOGD("Drop detected; %u packets, seq_range [%hu, %hu]", + gap_len, + rd_seq_ + norm_wr_seq, + rd_seq_ + norm_wr_seq + gap_len - 1); + + CHECK(NULL == ring_[ring_pos]); + ring_[ring_pos] = buf; + wr_ = (ring_pos + 1) % capacity_; + CHECK(wr_ != rd_); + + return true; +} + +AAH_RXPlayer::PacketBuffer* +AAH_RXPlayer::RXRingBuffer::fetchBuffer(bool* is_discon) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + CHECK(NULL != is_discon); + + // If the read seqence number is not known, then this ring buffer has not + // received a packet since being reset and there cannot be any packets to + // return. If we are still waiting for the first fast start packet to show + // up, we don't want to let any buffer be consumed yet because we expect to + // see a packet before the initial read sequence number show up shortly. + if (!rd_seq_known_ || waiting_for_fast_start_) { + *is_discon = false; + return NULL; + } + + PacketBuffer* ret = NULL; + *is_discon = !fetched_first_packet_; + + while ((rd_ != wr_) && (NULL == ret)) { + CHECK(rd_ < capacity_); + + // If we hit a gap, stall and do not advance the read pointer. Let the + // higher level code deal with requesting retries and/or deciding to + // skip the current gap. + ret = ring_[rd_]; + if (NULL == ret) { + break; + } + + ring_[rd_] = NULL; + rd_ = (rd_ + 1) % capacity_; + ++rd_seq_; + } + + if (NULL != ret) { + fetched_first_packet_ = true; + } + + return ret; +} + +AAH_RXPlayer::GapStatus +AAH_RXPlayer::RXRingBuffer::fetchCurrentGap(SeqNoGap* gap) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + CHECK(NULL != gap); + + // If the read seqence number is not known, then this ring buffer has not + // received a packet since being reset and there cannot be any gaps. + if (!rd_seq_known_) { + return kGS_NoGap; + } + + // If we are waiting for fast start, then the current gap is a fast start + // gap and it includes all packets before the read sequence number. + if (waiting_for_fast_start_) { + gap->start_seq_ = + gap->end_seq_ = rd_seq_ - 1; + return kGS_FastStartGap; + } + + // If rd == wr, then the buffer is empty and there cannot be any gaps. + if (rd_ == wr_) { + return kGS_NoGap; + } + + // If rd_ is currently pointing at an unprocessed packet, then there is no + // current gap. + CHECK(rd_ < capacity_); + if (NULL != ring_[rd_]) { + return kGS_NoGap; + } + + // Looks like there must be a gap here. The start of the gap is the current + // rd sequence number, all we need to do now is determine its length in + // order to compute the end sequence number. + gap->start_seq_ = rd_seq_; + uint16_t end = rd_seq_; + uint32_t tmp = (rd_ + 1) % capacity_; + while ((tmp != wr_) && (NULL == ring_[tmp])) { + ++end; + tmp = (tmp + 1) % capacity_; + } + gap->end_seq_ = end; + + return kGS_NormalGap; +} + +void AAH_RXPlayer::RXRingBuffer::processNAK(const SeqNoGap* nak) { + AutoMutex lock(&lock_); + CHECK(NULL != ring_); + + // If we were waiting for our first fast start fill-in packet, and we + // received a NAK, then apparantly we are not getting our fast start. Just + // clear the waiting flag and go back to normal behavior. + if (waiting_for_fast_start_) { + waiting_for_fast_start_ = false; + } + + // If we have not received a packet since last reset, or there is no data in + // the ring, then there is nothing to skip. + if ((!rd_seq_known_) || (rd_ == wr_)) { + return; + } + + // If rd_ is currently pointing at an unprocessed packet, then there is no + // gap to skip. + CHECK(rd_ < capacity_); + if (NULL != ring_[rd_]) { + return; + } + + // Looks like there must be a gap here. Advance rd until we have passed + // over the portion of it indicated by nak (or all of the gap if nak is + // NULL). Then reset fetched_first_packet_ so that the next read will show + // up as being discontiguous. + uint16_t seq_after_gap = (NULL == nak) ? 0 : nak->end_seq_ + 1; + while ((rd_ != wr_) && + (NULL == ring_[rd_]) && + ((NULL == nak) || (seq_after_gap != rd_seq_))) { + rd_ = (rd_ + 1) % capacity_; + ++rd_seq_; + } + fetched_first_packet_ = false; +} + +int AAH_RXPlayer::RXRingBuffer::computeInactivityTimeout() { + AutoMutex lock(&lock_); + + if (!rtp_activity_timeout_valid_) { + return -1; + } + + uint64_t now = monotonicUSecNow(); + if (rtp_activity_timeout_ <= now) { + return 0; + } + + return (rtp_activity_timeout_ - now) / 1000; +} + +AAH_RXPlayer::PacketBuffer* +AAH_RXPlayer::PacketBuffer::allocate(ssize_t length) { + if (length <= 0) { + return NULL; + } + + uint32_t alloc_len = sizeof(PacketBuffer) + length; + PacketBuffer* ret = reinterpret_cast<PacketBuffer*>( + new uint8_t[alloc_len]); + + if (NULL != ret) { + ret->length_ = length; + } + + return ret; +} + +void AAH_RXPlayer::PacketBuffer::destroy(PacketBuffer* pb) { + uint8_t* kill_me = reinterpret_cast<uint8_t*>(pb); + delete[] kill_me; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_rx_player_substream.cpp b/media/libaah_rtp/aah_rx_player_substream.cpp new file mode 100644 index 000000000000..1e4c784629a2 --- /dev/null +++ b/media/libaah_rtp/aah_rx_player_substream.cpp @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +//#define LOG_NDEBUG 0 + +#include <utils/Log.h> + +#include <include/avc_utils.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXCodec.h> +#include <media/stagefright/Utils.h> + +#include "aah_rx_player.h" + +namespace android { + +int64_t AAH_RXPlayer::Substream::kAboutToUnderflowThreshold = + 50ull * 1000; + +AAH_RXPlayer::Substream::Substream(uint32_t ssrc, OMXClient& omx) { + ssrc_ = ssrc; + substream_details_known_ = false; + buffer_in_progress_ = NULL; + status_ = OK; + + decoder_ = new AAH_DecoderPump(omx); + if (decoder_ == NULL) { + ALOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__); + } + if (OK != decoder_->initCheck()) { + ALOGE("%s failed to initialize decoder pump!", __PRETTY_FUNCTION__); + } + + // cleanupBufferInProgress will reset most of the internal state variables. + // Just need to make sure that buffer_in_progress_ is NULL before calling. + cleanupBufferInProgress(); +} + + +void AAH_RXPlayer::Substream::shutdown() { + substream_meta_ = NULL; + status_ = OK; + cleanupBufferInProgress(); + cleanupDecoder(); +} + +void AAH_RXPlayer::Substream::cleanupBufferInProgress() { + if (NULL != buffer_in_progress_) { + buffer_in_progress_->release(); + buffer_in_progress_ = NULL; + } + + expected_buffer_size_ = 0; + buffer_filled_ = 0; + waiting_for_rap_ = true; +} + +void AAH_RXPlayer::Substream::cleanupDecoder() { + if (decoder_ != NULL) { + decoder_->shutdown(); + } +} + +bool AAH_RXPlayer::Substream::shouldAbort(const char* log_tag) { + // If we have already encountered a fatal error, do nothing. We are just + // waiting for our owner to shut us down now. + if (OK != status_) { + ALOGV("Skipping %s, substream has encountered fatal error (%d).", + log_tag, status_); + return true; + } + + return false; +} + +void AAH_RXPlayer::Substream::processPayloadStart(uint8_t* buf, + uint32_t amt, + int32_t ts_lower) { + uint32_t min_length = 6; + + if (shouldAbort(__PRETTY_FUNCTION__)) { + return; + } + + // Do we have a buffer in progress already? If so, abort the buffer. In + // theory, this should never happen. If there were a discontinutity in the + // stream, the discon in the seq_nos at the RTP level should have already + // triggered a cleanup of the buffer in progress. To see a problem at this + // level is an indication either of a bug in the transmitter, or some form + // of terrible corruption/tampering on the wire. + if (NULL != buffer_in_progress_) { + ALOGE("processPayloadStart is aborting payload already in progress."); + cleanupBufferInProgress(); + } + + // Parse enough of the header to know where we stand. Since this is a + // payload start, it should begin with a TRTP header which has to be at + // least 6 bytes long. + if (amt < min_length) { + ALOGV("Discarding payload too short to contain TRTP header (len = %u)", + amt); + return; + } + + // Check the TRTP version number. + if (0x01 != buf[0]) { + ALOGV("Unexpected TRTP version (%u) in header. Expected %u.", + buf[0], 1); + return; + } + + // Extract the substream type field and make sure its one we understand (and + // one that does not conflict with any previously received substream type. + uint8_t header_type = (buf[1] >> 4) & 0xF; + switch (header_type) { + case 0x01: + // Audio, yay! Just break. We understand audio payloads. + break; + case 0x02: + ALOGV("RXed packet with unhandled TRTP header type (Video)."); + return; + case 0x03: + ALOGV("RXed packet with unhandled TRTP header type (Subpicture)."); + return; + case 0x04: + ALOGV("RXed packet with unhandled TRTP header type (Control)."); + return; + default: + ALOGV("RXed packet with unhandled TRTP header type (%u).", + header_type); + return; + } + + if (substream_details_known_ && (header_type != substream_type_)) { + ALOGV("RXed TRTP Payload for SSRC=0x%08x where header type (%u) does not" + " match previously received header type (%u)", + ssrc_, header_type, substream_type_); + return; + } + + // Check the flags to see if there is another 32 bits of timestamp present. + uint32_t trtp_header_len = 6; + bool ts_valid = buf[1] & 0x1; + if (ts_valid) { + min_length += 4; + trtp_header_len += 4; + if (amt < min_length) { + ALOGV("Discarding payload too short to contain TRTP timestamp" + " (len = %u)", amt); + return; + } + } + + // Extract the TRTP length field and sanity check it. + uint32_t trtp_len; + trtp_len = (static_cast<uint32_t>(buf[2]) << 24) | + (static_cast<uint32_t>(buf[3]) << 16) | + (static_cast<uint32_t>(buf[4]) << 8) | + static_cast<uint32_t>(buf[5]); + if (trtp_len < min_length) { + ALOGV("TRTP length (%u) is too short to be valid. Must be at least %u" + " bytes.", trtp_len, min_length); + return; + } + + // Extract the rest of the timestamp field if valid. + int64_t ts = 0; + uint32_t parse_offset = 6; + if (ts_valid) { + ts = (static_cast<int64_t>(buf[parse_offset ]) << 56) | + (static_cast<int64_t>(buf[parse_offset + 1]) << 48) | + (static_cast<int64_t>(buf[parse_offset + 2]) << 40) | + (static_cast<int64_t>(buf[parse_offset + 3]) << 32); + ts |= ts_lower; + parse_offset += 4; + } + + // Check the flags to see if there is another 24 bytes of timestamp + // transformation present. + if (buf[1] & 0x2) { + min_length += 24; + parse_offset += 24; + trtp_header_len += 24; + if (amt < min_length) { + ALOGV("Discarding payload too short to contain TRTP timestamp" + " transformation (len = %u)", amt); + return; + } + } + + // TODO : break the parsing into individual parsers for the different + // payload types (audio, video, etc). + // + // At this point in time, we know that this is audio. Go ahead and parse + // the basic header, check the codec type, and find the payload portion of + // the packet. + min_length += 3; + if (trtp_len < min_length) { + ALOGV("TRTP length (%u) is too short to be a valid audio payload. Must" + " be at least %u bytes.", trtp_len, min_length); + return; + } + + if (amt < min_length) { + ALOGV("TRTP porttion of RTP payload (%u bytes) too small to contain" + " entire TRTP header. TRTP does not currently support fragmenting" + " TRTP headers across RTP payloads", amt); + return; + } + + uint8_t codec_type = buf[parse_offset ]; + uint8_t flags = buf[parse_offset + 1]; + uint8_t volume = buf[parse_offset + 2]; + parse_offset += 3; + trtp_header_len += 3; + + if (!setupSubstreamType(header_type, codec_type)) { + return; + } + + if (decoder_ != NULL) { + decoder_->setRenderVolume(volume); + } + + // TODO : move all of the constant flag and offset definitions for TRTP up + // into some sort of common header file. + if (waiting_for_rap_ && !(flags & 0x08)) { + ALOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP."); + return; + } + + if (flags & 0x10) { + ALOGV("Dropping TRTP Audio Payload with aux codec data present (only" + " handle MP3 right now, and it has no aux data)"); + return; + } + + // OK - everything left is just payload. Compute the payload size, start + // the buffer in progress and pack as much payload as we can into it. If + // the payload is finished once we are done, go ahead and send the payload + // to the decoder. + expected_buffer_size_ = trtp_len - trtp_header_len; + if (!expected_buffer_size_) { + ALOGV("Dropping TRTP Audio Payload with 0 Access Unit length"); + return; + } + + CHECK(amt >= trtp_header_len); + uint32_t todo = amt - trtp_header_len; + if (expected_buffer_size_ < todo) { + ALOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;" + " dropping payload.", todo, expected_buffer_size_); + return; + } + + buffer_filled_ = 0; + buffer_in_progress_ = new MediaBuffer(expected_buffer_size_); + if ((NULL == buffer_in_progress_) || + (NULL == buffer_in_progress_->data())) { + ALOGV("Failed to allocate MediaBuffer of length %u", + expected_buffer_size_); + cleanupBufferInProgress(); + return; + } + + sp<MetaData> meta = buffer_in_progress_->meta_data(); + if (meta == NULL) { + ALOGV("Missing metadata structure in allocated MediaBuffer; dropping" + " payload"); + cleanupBufferInProgress(); + return; + } + + // TODO : set this based on the codec type indicated in the TRTP stream. + // Right now, we only support MP3, so the choice is obvious. + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); + if (ts_valid) { + meta->setInt64(kKeyTime, ts); + } + + if (amt > 0) { + uint8_t* tgt = + reinterpret_cast<uint8_t*>(buffer_in_progress_->data()); + memcpy(tgt + buffer_filled_, buf + trtp_header_len, todo); + buffer_filled_ += amt; + } + + if (buffer_filled_ >= expected_buffer_size_) { + processCompletedBuffer(); + } +} + +void AAH_RXPlayer::Substream::processPayloadCont(uint8_t* buf, + uint32_t amt) { + if (shouldAbort(__PRETTY_FUNCTION__)) { + return; + } + + if (NULL == buffer_in_progress_) { + ALOGV("TRTP Receiver skipping payload continuation; no buffer currently" + " in progress."); + return; + } + + CHECK(buffer_filled_ < expected_buffer_size_); + uint32_t buffer_left = expected_buffer_size_ - buffer_filled_; + if (amt > buffer_left) { + ALOGV("Extra data (%u > %u) present in continued TRTP Audio Payload;" + " dropping payload.", amt, buffer_left); + cleanupBufferInProgress(); + return; + } + + if (amt > 0) { + uint8_t* tgt = + reinterpret_cast<uint8_t*>(buffer_in_progress_->data()); + memcpy(tgt + buffer_filled_, buf, amt); + buffer_filled_ += amt; + } + + if (buffer_filled_ >= expected_buffer_size_) { + processCompletedBuffer(); + } +} + +void AAH_RXPlayer::Substream::processCompletedBuffer() { + const uint8_t* buffer_data = NULL; + int sample_rate; + int channel_count; + size_t frame_size; + status_t res; + + CHECK(NULL != buffer_in_progress_); + + if (decoder_ == NULL) { + ALOGV("Dropping complete buffer, no decoder pump allocated"); + goto bailout; + } + + buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data()); + if (buffer_in_progress_->size() < 4) { + ALOGV("MP3 payload too short to contain header, dropping payload."); + goto bailout; + } + + // Extract the channel count and the sample rate from the MP3 header. The + // stagefright MP3 requires that these be delivered before decoing can + // begin. + if (!GetMPEGAudioFrameSize(U32_AT(buffer_data), + &frame_size, + &sample_rate, + &channel_count, + NULL, + NULL)) { + ALOGV("Failed to parse MP3 header in payload, droping payload."); + goto bailout; + } + + + // Make sure that our substream metadata is set up properly. If there has + // been a format change, be sure to reset the underlying decoder. In + // stagefright, it seems like the only way to do this is to destroy and + // recreate the decoder. + if (substream_meta_ == NULL) { + substream_meta_ = new MetaData(); + + if (substream_meta_ == NULL) { + ALOGE("Failed to allocate MetaData structure for substream"); + goto bailout; + } + + substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); + substream_meta_->setInt32 (kKeyChannelCount, channel_count); + substream_meta_->setInt32 (kKeySampleRate, sample_rate); + } else { + int32_t prev_sample_rate; + int32_t prev_channel_count; + substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate); + substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count); + + if ((prev_channel_count != channel_count) || + (prev_sample_rate != sample_rate)) { + ALOGW("Format change detected, forcing decoder reset."); + cleanupDecoder(); + + substream_meta_->setInt32(kKeyChannelCount, channel_count); + substream_meta_->setInt32(kKeySampleRate, sample_rate); + } + } + + // If our decoder has not be set up, do so now. + res = decoder_->init(substream_meta_); + if (OK != res) { + ALOGE("Failed to init decoder (res = %d)", res); + cleanupDecoder(); + substream_meta_ = NULL; + goto bailout; + } + + // Queue the payload for decode. + res = decoder_->queueForDecode(buffer_in_progress_); + + if (res != OK) { + ALOGD("Failed to queue payload for decode, resetting decoder pump!" + " (res = %d)", res); + status_ = res; + cleanupDecoder(); + cleanupBufferInProgress(); + } + + // NULL out buffer_in_progress before calling the cleanup helper. + // + // MediaBuffers use something of a hybrid ref-counting pattern which prevent + // the AAH_DecoderPump's input queue from adding their own reference to the + // MediaBuffer. MediaBuffers start life with a reference count of 0, as + // well as an observer which starts as NULL. Before being given an + // observer, the ref count cannot be allowed to become non-zero as it will + // cause calls to release() to assert. Basically, before a MediaBuffer has + // an observer, they behave like non-ref counted obects where release() + // serves the roll of delete. After a MediaBuffer has an observer, they + // become more like ref counted objects where add ref and release can be + // used, and when the ref count hits zero, the MediaBuffer is handed off to + // the observer. + // + // Given all of this, when we give the buffer to the decoder pump to wait in + // the to-be-processed queue, the decoder cannot add a ref to the buffer as + // it would in a traditional ref counting system. Instead it needs to + // "steal" the non-existent ref. In the case of queue failure, we need to + // make certain to release this non-existent reference so that the buffer is + // cleaned up during the cleanupBufferInProgress helper. In the case of a + // successful queue operation, we need to make certain that the + // cleanupBufferInProgress helper does not release the buffer since it needs + // to remain alive in the queue. We acomplish this by NULLing out the + // buffer pointer before calling the cleanup helper. + buffer_in_progress_ = NULL; + +bailout: + cleanupBufferInProgress(); +} + + +void AAH_RXPlayer::Substream::processTSTransform(const LinearTransform& trans) { + if (decoder_ != NULL) { + decoder_->setRenderTSTransform(trans); + } +} + +bool AAH_RXPlayer::Substream::isAboutToUnderflow() { + if (decoder_ == NULL) { + return false; + } + + return decoder_->isAboutToUnderflow(kAboutToUnderflowThreshold); +} + +bool AAH_RXPlayer::Substream::setupSubstreamType(uint8_t substream_type, + uint8_t codec_type) { + // Sanity check the codec type. Right now we only support MP3. Also check + // for conflicts with previously delivered codec types. + if (substream_details_known_ && (codec_type != codec_type_)) { + ALOGV("RXed TRTP Payload for SSRC=0x%08x where codec type (%u) does not" + " match previously received codec type (%u)", + ssrc_, codec_type, codec_type_); + return false; + } + + if (codec_type != 0x03) { + ALOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported codec" + " type (%u)", ssrc_, codec_type); + return false; + } + + if (!substream_details_known_) { + substream_type_ = substream_type; + codec_type_ = codec_type; + substream_details_known_ = true; + } + + return true; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_packet.cpp b/media/libaah_rtp/aah_tx_packet.cpp new file mode 100644 index 000000000000..3f6e0e9520d6 --- /dev/null +++ b/media/libaah_rtp/aah_tx_packet.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <string.h> + +#include <media/stagefright/foundation/ADebug.h> + +#include "aah_tx_packet.h" + +namespace android { + +const int TRTPPacket::kRTPHeaderLen; +const uint32_t TRTPPacket::kTRTPEpochMask; + +TRTPPacket::~TRTPPacket() { + delete mPacket; +} + +/*** TRTP packet properties ***/ + +void TRTPPacket::setSeqNumber(uint16_t val) { + mSeqNumber = val; + + if (mIsPacked) { + const int kTRTPSeqNumberOffset = 2; + uint16_t* buf = reinterpret_cast<uint16_t*>( + mPacket + kTRTPSeqNumberOffset); + *buf = htons(mSeqNumber); + } +} + +uint16_t TRTPPacket::getSeqNumber() const { + return mSeqNumber; +} + +void TRTPPacket::setPTS(int64_t val) { + CHECK(!mIsPacked); + mPTS = val; + mPTSValid = true; +} + +int64_t TRTPPacket::getPTS() const { + return mPTS; +} + +void TRTPPacket::setEpoch(uint32_t val) { + mEpoch = val; + + if (mIsPacked) { + const int kTRTPEpochOffset = 8; + uint32_t* buf = reinterpret_cast<uint32_t*>( + mPacket + kTRTPEpochOffset); + uint32_t val = ntohl(*buf); + val &= ~(kTRTPEpochMask << kTRTPEpochShift); + val |= (mEpoch & kTRTPEpochMask) << kTRTPEpochShift; + *buf = htonl(val); + } +} + +void TRTPPacket::setProgramID(uint16_t val) { + CHECK(!mIsPacked); + mProgramID = val; +} + +void TRTPPacket::setSubstreamID(uint16_t val) { + CHECK(!mIsPacked); + mSubstreamID = val; +} + + +void TRTPPacket::setClockTransform(const LinearTransform& trans) { + CHECK(!mIsPacked); + mClockTranform = trans; + mClockTranformValid = true; +} + +uint8_t* TRTPPacket::getPacket() const { + CHECK(mIsPacked); + return mPacket; +} + +int TRTPPacket::getPacketLen() const { + CHECK(mIsPacked); + return mPacketLen; +} + +void TRTPPacket::setExpireTime(nsecs_t val) { + CHECK(!mIsPacked); + mExpireTime = val; +} + +nsecs_t TRTPPacket::getExpireTime() const { + return mExpireTime; +} + +/*** TRTP audio packet properties ***/ + +void TRTPAudioPacket::setCodecType(TRTPAudioCodecType val) { + CHECK(!mIsPacked); + mCodecType = val; +} + +void TRTPAudioPacket::setRandomAccessPoint(bool val) { + CHECK(!mIsPacked); + mRandomAccessPoint = val; +} + +void TRTPAudioPacket::setDropable(bool val) { + CHECK(!mIsPacked); + mDropable = val; +} + +void TRTPAudioPacket::setDiscontinuity(bool val) { + CHECK(!mIsPacked); + mDiscontinuity = val; +} + +void TRTPAudioPacket::setEndOfStream(bool val) { + CHECK(!mIsPacked); + mEndOfStream = val; +} + +void TRTPAudioPacket::setVolume(uint8_t val) { + CHECK(!mIsPacked); + mVolume = val; +} + +void TRTPAudioPacket::setAccessUnitData(void* data, int len) { + CHECK(!mIsPacked); + mAccessUnitData = data; + mAccessUnitLen = len; +} + +/*** TRTP control packet properties ***/ + +void TRTPControlPacket::setCommandID(TRTPCommandID val) { + CHECK(!mIsPacked); + mCommandID = val; +} + +/*** TRTP packet serializers ***/ + +void TRTPPacket::writeU8(uint8_t*& buf, uint8_t val) { + *buf = val; + buf++; +} + +void TRTPPacket::writeU16(uint8_t*& buf, uint16_t val) { + *reinterpret_cast<uint16_t*>(buf) = htons(val); + buf += 2; +} + +void TRTPPacket::writeU32(uint8_t*& buf, uint32_t val) { + *reinterpret_cast<uint32_t*>(buf) = htonl(val); + buf += 4; +} + +void TRTPPacket::writeU64(uint8_t*& buf, uint64_t val) { + buf[0] = static_cast<uint8_t>(val >> 56); + buf[1] = static_cast<uint8_t>(val >> 48); + buf[2] = static_cast<uint8_t>(val >> 40); + buf[3] = static_cast<uint8_t>(val >> 32); + buf[4] = static_cast<uint8_t>(val >> 24); + buf[5] = static_cast<uint8_t>(val >> 16); + buf[6] = static_cast<uint8_t>(val >> 8); + buf[7] = static_cast<uint8_t>(val); + buf += 8; +} + +void TRTPPacket::writeTRTPHeader(uint8_t*& buf, + bool isFirstFragment, + int totalPacketLen) { + // RTP header + writeU8(buf, + ((mVersion & 0x03) << 6) | + (static_cast<int>(mPadding) << 5) | + (static_cast<int>(mExtension) << 4) | + (mCsrcCount & 0x0F)); + writeU8(buf, + (static_cast<int>(isFirstFragment) << 7) | + (mPayloadType & 0x7F)); + writeU16(buf, mSeqNumber); + if (isFirstFragment && mPTSValid) { + writeU32(buf, mPTS & 0xFFFFFFFF); + } else { + writeU32(buf, 0); + } + writeU32(buf, + ((mEpoch & kTRTPEpochMask) << kTRTPEpochShift) | + ((mProgramID & 0x1F) << 5) | + (mSubstreamID & 0x1F)); + + // TRTP header + writeU8(buf, mTRTPVersion); + writeU8(buf, + ((mTRTPHeaderType & 0x0F) << 4) | + (mClockTranformValid ? 0x02 : 0x00) | + (mPTSValid ? 0x01 : 0x00)); + writeU32(buf, totalPacketLen - kRTPHeaderLen); + if (mPTSValid) { + writeU32(buf, mPTS >> 32); + } + + if (mClockTranformValid) { + writeU64(buf, mClockTranform.a_zero); + writeU32(buf, mClockTranform.a_to_b_numer); + writeU32(buf, mClockTranform.a_to_b_denom); + writeU64(buf, mClockTranform.b_zero); + } +} + +bool TRTPAudioPacket::pack() { + if (mIsPacked) { + return false; + } + + int packetLen = kRTPHeaderLen + + mAccessUnitLen + + TRTPHeaderLen(); + + // TODO : support multiple fragments + const int kMaxUDPPayloadLen = 65507; + if (packetLen > kMaxUDPPayloadLen) { + return false; + } + + mPacket = new uint8_t[packetLen]; + if (!mPacket) { + return false; + } + + mPacketLen = packetLen; + + uint8_t* cur = mPacket; + + writeTRTPHeader(cur, true, packetLen); + writeU8(cur, mCodecType); + writeU8(cur, + (static_cast<int>(mRandomAccessPoint) << 3) | + (static_cast<int>(mDropable) << 2) | + (static_cast<int>(mDiscontinuity) << 1) | + (static_cast<int>(mEndOfStream))); + writeU8(cur, mVolume); + + memcpy(cur, mAccessUnitData, mAccessUnitLen); + + mIsPacked = true; + return true; +} + +int TRTPPacket::TRTPHeaderLen() const { + // 6 bytes for version, payload type, flags and length. An additional 4 if + // there are upper timestamp bits present and another 24 if there is a clock + // transformation present. + return 6 + + (mClockTranformValid ? 24 : 0) + + (mPTSValid ? 4 : 0); +} + +int TRTPAudioPacket::TRTPHeaderLen() const { + // TRTPPacket::TRTPHeaderLen() for the base TRTPHeader. 3 bytes for audio's + // codec type, flags and volume field. Another 5 bytes if the codec type is + // PCM and we are sending sample rate/channel count. as well as however long + // the aux data (if present) is. + + int pcmParamLength; + switch(mCodecType) { + case kCodecPCMBigEndian: + case kCodecPCMLittleEndian: + pcmParamLength = 5; + break; + + default: + pcmParamLength = 0; + break; + } + + + // TODO : properly compute aux data length. Currently, nothing + // uses aux data, so its length is always 0. + int auxDataLength = 0; + return TRTPPacket::TRTPHeaderLen() + + 3 + + auxDataLength + + pcmParamLength; +} + +bool TRTPControlPacket::pack() { + if (mIsPacked) { + return false; + } + + // command packets contain a 2-byte command ID + int packetLen = kRTPHeaderLen + + TRTPHeaderLen() + + 2; + + mPacket = new uint8_t[packetLen]; + if (!mPacket) { + return false; + } + + mPacketLen = packetLen; + + uint8_t* cur = mPacket; + + writeTRTPHeader(cur, true, packetLen); + writeU16(cur, mCommandID); + + mIsPacked = true; + return true; +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_packet.h b/media/libaah_rtp/aah_tx_packet.h new file mode 100644 index 000000000000..833803ea1bef --- /dev/null +++ b/media/libaah_rtp/aah_tx_packet.h @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAH_TX_PACKET_H__ +#define __AAH_TX_PACKET_H__ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/LinearTransform.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> + +namespace android { + +class TRTPPacket : public RefBase { + protected: + enum TRTPHeaderType { + kHeaderTypeAudio = 1, + kHeaderTypeVideo = 2, + kHeaderTypeSubpicture = 3, + kHeaderTypeControl = 4, + }; + + TRTPPacket(TRTPHeaderType headerType) + : mIsPacked(false) + , mVersion(2) + , mPadding(false) + , mExtension(false) + , mCsrcCount(0) + , mPayloadType(100) + , mSeqNumber(0) + , mPTSValid(false) + , mPTS(0) + , mEpoch(0) + , mProgramID(0) + , mSubstreamID(0) + , mClockTranformValid(false) + , mTRTPVersion(1) + , mTRTPLength(0) + , mTRTPHeaderType(headerType) + , mPacket(NULL) + , mPacketLen(0) { } + + public: + virtual ~TRTPPacket(); + + void setSeqNumber(uint16_t val); + uint16_t getSeqNumber() const; + + void setPTS(int64_t val); + int64_t getPTS() const; + + void setEpoch(uint32_t val); + void setProgramID(uint16_t val); + void setSubstreamID(uint16_t val); + void setClockTransform(const LinearTransform& trans); + + uint8_t* getPacket() const; + int getPacketLen() const; + + void setExpireTime(nsecs_t val); + nsecs_t getExpireTime() const; + + virtual bool pack() = 0; + + // mask for the number of bits in a TRTP epoch + static const uint32_t kTRTPEpochMask = (1 << 22) - 1; + static const int kTRTPEpochShift = 10; + + protected: + static const int kRTPHeaderLen = 12; + virtual int TRTPHeaderLen() const; + + void writeTRTPHeader(uint8_t*& buf, + bool isFirstFragment, + int totalPacketLen); + + void writeU8(uint8_t*& buf, uint8_t val); + void writeU16(uint8_t*& buf, uint16_t val); + void writeU32(uint8_t*& buf, uint32_t val); + void writeU64(uint8_t*& buf, uint64_t val); + + bool mIsPacked; + + uint8_t mVersion; + bool mPadding; + bool mExtension; + uint8_t mCsrcCount; + uint8_t mPayloadType; + uint16_t mSeqNumber; + bool mPTSValid; + int64_t mPTS; + uint32_t mEpoch; + uint16_t mProgramID; + uint16_t mSubstreamID; + LinearTransform mClockTranform; + bool mClockTranformValid; + uint8_t mTRTPVersion; + uint32_t mTRTPLength; + TRTPHeaderType mTRTPHeaderType; + + uint8_t* mPacket; + int mPacketLen; + + nsecs_t mExpireTime; + + DISALLOW_EVIL_CONSTRUCTORS(TRTPPacket); +}; + +class TRTPAudioPacket : public TRTPPacket { + public: + TRTPAudioPacket() + : TRTPPacket(kHeaderTypeAudio) + , mCodecType(kCodecInvalid) + , mRandomAccessPoint(false) + , mDropable(false) + , mDiscontinuity(false) + , mEndOfStream(false) + , mVolume(0) + , mAccessUnitData(NULL) { } + + enum TRTPAudioCodecType { + kCodecInvalid = 0, + kCodecPCMBigEndian = 1, + kCodecPCMLittleEndian = 2, + kCodecMPEG1Audio = 3, + }; + + void setCodecType(TRTPAudioCodecType val); + void setRandomAccessPoint(bool val); + void setDropable(bool val); + void setDiscontinuity(bool val); + void setEndOfStream(bool val); + void setVolume(uint8_t val); + void setAccessUnitData(void* data, int len); + + virtual bool pack(); + + protected: + virtual int TRTPHeaderLen() const; + + private: + TRTPAudioCodecType mCodecType; + bool mRandomAccessPoint; + bool mDropable; + bool mDiscontinuity; + bool mEndOfStream; + uint8_t mVolume; + void* mAccessUnitData; + int mAccessUnitLen; + + DISALLOW_EVIL_CONSTRUCTORS(TRTPAudioPacket); +}; + +class TRTPControlPacket : public TRTPPacket { + public: + TRTPControlPacket() + : TRTPPacket(kHeaderTypeControl) + , mCommandID(kCommandNop) {} + + enum TRTPCommandID { + kCommandNop = 1, + kCommandFlush = 2, + kCommandEOS = 3, + }; + + void setCommandID(TRTPCommandID val); + + virtual bool pack(); + + private: + TRTPCommandID mCommandID; + + DISALLOW_EVIL_CONSTRUCTORS(TRTPControlPacket); +}; + +} // namespace android + +#endif // __AAH_TX_PLAYER_H__ diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp new file mode 100644 index 000000000000..a79a9891ef03 --- /dev/null +++ b/media/libaah_rtp/aah_tx_player.cpp @@ -0,0 +1,1139 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <utils/Log.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <netdb.h> +#include <netinet/ip.h> + +#include <common_time/cc_helper.h> +#include <media/IMediaPlayer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MetaData.h> +#include <utils/Timers.h> + +#include "aah_tx_packet.h" +#include "aah_tx_player.h" + +namespace android { + +static int64_t kLowWaterMarkUs = 2000000ll; // 2secs +static int64_t kHighWaterMarkUs = 10000000ll; // 10secs +static const size_t kLowWaterMarkBytes = 40000; +static const size_t kHighWaterMarkBytes = 200000; + +// When we start up, how much lead time should we put on the first access unit? +static const int64_t kAAHStartupLeadTimeUs = 300000LL; + +// How much time do we attempt to lead the clock by in steady state? +static const int64_t kAAHBufferTimeUs = 1000000LL; + +// how long do we keep data in our retransmit buffer after sending it. +const int64_t AAH_TXPlayer::kAAHRetryKeepAroundTimeNs = + kAAHBufferTimeUs * 1100; + +sp<MediaPlayerBase> createAAH_TXPlayer() { + sp<MediaPlayerBase> ret = new AAH_TXPlayer(); + return ret; +} + +template <typename T> static T clamp(T val, T min, T max) { + if (val < min) { + return min; + } else if (val > max) { + return max; + } else { + return val; + } +} + +struct AAH_TXEvent : public TimedEventQueue::Event { + AAH_TXEvent(AAH_TXPlayer *player, + void (AAH_TXPlayer::*method)()) : mPlayer(player) + , mMethod(method) {} + + protected: + virtual ~AAH_TXEvent() {} + + virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) { + (mPlayer->*mMethod)(); + } + + private: + AAH_TXPlayer *mPlayer; + void (AAH_TXPlayer::*mMethod)(); + + AAH_TXEvent(const AAH_TXEvent &); + AAH_TXEvent& operator=(const AAH_TXEvent &); +}; + +AAH_TXPlayer::AAH_TXPlayer() + : mQueueStarted(false) + , mFlags(0) + , mExtractorFlags(0) { + DataSource::RegisterDefaultSniffers(); + + mBufferingEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onBufferingUpdate); + mBufferingEventPending = false; + + mPumpAudioEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onPumpAudio); + mPumpAudioEventPending = false; + + reset_l(); +} + +AAH_TXPlayer::~AAH_TXPlayer() { + if (mQueueStarted) { + mQueue.stop(); + } + + reset_l(); +} + +void AAH_TXPlayer::cancelPlayerEvents(bool keepBufferingGoing) { + if (!keepBufferingGoing) { + mQueue.cancelEvent(mBufferingEvent->eventID()); + mBufferingEventPending = false; + + mQueue.cancelEvent(mPumpAudioEvent->eventID()); + mPumpAudioEventPending = false; + } +} + +status_t AAH_TXPlayer::initCheck() { + // Check for the presense of the common time service by attempting to query + // for CommonTime's frequency. If we get an error back, we cannot talk to + // the service at all and should abort now. + status_t res; + uint64_t freq; + res = mCCHelper.getCommonFreq(&freq); + if (OK != res) { + ALOGE("Failed to connect to common time service! (res %d)", res); + return res; + } + + return OK; +} + +status_t AAH_TXPlayer::setDataSource( + const char *url, + const KeyedVector<String8, String8> *headers) { + Mutex::Autolock autoLock(mLock); + return setDataSource_l(url, headers); +} + +status_t AAH_TXPlayer::setDataSource_l( + const char *url, + const KeyedVector<String8, String8> *headers) { + reset_l(); + + // the URL must consist of "aahTX://" followed by the real URL of + // the data source + const char *kAAHPrefix = "aahTX://"; + if (strncasecmp(url, kAAHPrefix, strlen(kAAHPrefix))) { + return INVALID_OPERATION; + } + + mUri.setTo(url + strlen(kAAHPrefix)); + + if (headers) { + mUriHeaders = *headers; + + ssize_t index = mUriHeaders.indexOfKey(String8("x-hide-urls-from-log")); + if (index >= 0) { + // Browser is in "incognito" mode, suppress logging URLs. + + // This isn't something that should be passed to the server. + mUriHeaders.removeItemsAt(index); + + mFlags |= INCOGNITO; + } + } + + // The URL may optionally contain a "#" character followed by a Skyjam + // cookie. Ideally the cookie header should just be passed in the headers + // argument, but the Java API for supplying headers is apparently not yet + // exposed in the SDK used by application developers. + const char kSkyjamCookieDelimiter = '#'; + char* skyjamCookie = strrchr(mUri.string(), kSkyjamCookieDelimiter); + if (skyjamCookie) { + skyjamCookie++; + mUriHeaders.add(String8("Cookie"), String8(skyjamCookie)); + mUri = String8(mUri.string(), skyjamCookie - mUri.string()); + } + + return OK; +} + +status_t AAH_TXPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + Mutex::Autolock autoLock(mLock); + + reset_l(); + + sp<DataSource> dataSource = new FileSource(dup(fd), offset, length); + + status_t err = dataSource->initCheck(); + + if (err != OK) { + return err; + } + + mFileSource = dataSource; + + sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); + + if (extractor == NULL) { + return UNKNOWN_ERROR; + } + + return setDataSource_l(extractor); +} + +status_t AAH_TXPlayer::setVideoSurface(const sp<Surface>& surface) { + return OK; +} + +status_t AAH_TXPlayer::setVideoSurfaceTexture( + const sp<ISurfaceTexture>& surfaceTexture) { + return OK; +} + +status_t AAH_TXPlayer::prepare() { + return INVALID_OPERATION; +} + +status_t AAH_TXPlayer::prepareAsync() { + Mutex::Autolock autoLock(mLock); + + return prepareAsync_l(); +} + +status_t AAH_TXPlayer::prepareAsync_l() { + if (mFlags & PREPARING) { + return UNKNOWN_ERROR; // async prepare already pending + } + + mAAH_Sender = AAH_TXSender::GetInstance(); + if (mAAH_Sender == NULL) { + return NO_MEMORY; + } + + if (!mQueueStarted) { + mQueue.start(); + mQueueStarted = true; + } + + mFlags |= PREPARING; + mAsyncPrepareEvent = new AAH_TXEvent( + this, &AAH_TXPlayer::onPrepareAsyncEvent); + + mQueue.postEvent(mAsyncPrepareEvent); + + return OK; +} + +status_t AAH_TXPlayer::finishSetDataSource_l() { + sp<DataSource> dataSource; + + if (!strncasecmp("http://", mUri.string(), 7) || + !strncasecmp("https://", mUri.string(), 8)) { + + mConnectingDataSource = HTTPBase::Create( + (mFlags & INCOGNITO) + ? HTTPBase::kFlagIncognito + : 0); + + mLock.unlock(); + status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders); + mLock.lock(); + + if (err != OK) { + mConnectingDataSource.clear(); + + ALOGI("mConnectingDataSource->connect() returned %d", err); + return err; + } + + mCachedSource = new NuCachedSource2(mConnectingDataSource); + mConnectingDataSource.clear(); + + dataSource = mCachedSource; + + // We're going to prefill the cache before trying to instantiate + // the extractor below, as the latter is an operation that otherwise + // could block on the datasource for a significant amount of time. + // During that time we'd be unable to abort the preparation phase + // without this prefill. + + mLock.unlock(); + + for (;;) { + status_t finalStatus; + size_t cachedDataRemaining = + mCachedSource->approxDataRemaining(&finalStatus); + + if (finalStatus != OK || + cachedDataRemaining >= kHighWaterMarkBytes || + (mFlags & PREPARE_CANCELLED)) { + break; + } + + usleep(200000); + } + + mLock.lock(); + + if (mFlags & PREPARE_CANCELLED) { + ALOGI("Prepare cancelled while waiting for initial cache fill."); + return UNKNOWN_ERROR; + } + } else { + dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders); + } + + if (dataSource == NULL) { + return UNKNOWN_ERROR; + } + + sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); + + if (extractor == NULL) { + return UNKNOWN_ERROR; + } + + return setDataSource_l(extractor); +} + +status_t AAH_TXPlayer::setDataSource_l(const sp<MediaExtractor> &extractor) { + // Attempt to approximate overall stream bitrate by summing all + // tracks' individual bitrates, if not all of them advertise bitrate, + // we have to fail. + + int64_t totalBitRate = 0; + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + + int32_t bitrate; + if (!meta->findInt32(kKeyBitRate, &bitrate)) { + totalBitRate = -1; + break; + } + + totalBitRate += bitrate; + } + + mBitrate = totalBitRate; + + ALOGV("mBitrate = %lld bits/sec", mBitrate); + + bool haveAudio = false; + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strncasecmp(mime, "audio/", 6)) { + mAudioSource = extractor->getTrack(i); + CHECK(mAudioSource != NULL); + haveAudio = true; + break; + } + } + + if (!haveAudio) { + return UNKNOWN_ERROR; + } + + mExtractorFlags = extractor->flags(); + + return OK; +} + +void AAH_TXPlayer::abortPrepare(status_t err) { + CHECK(err != OK); + + notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err); + + mPrepareResult = err; + mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED); + mPreparedCondition.broadcast(); +} + +void AAH_TXPlayer::onPrepareAsyncEvent() { + Mutex::Autolock autoLock(mLock); + + if (mFlags & PREPARE_CANCELLED) { + ALOGI("prepare was cancelled before doing anything"); + abortPrepare(UNKNOWN_ERROR); + return; + } + + if (mUri.size() > 0) { + status_t err = finishSetDataSource_l(); + + if (err != OK) { + abortPrepare(err); + return; + } + } + + mAudioSource->getFormat()->findInt64(kKeyDuration, &mDurationUs); + + status_t err = mAudioSource->start(); + if (err != OK) { + ALOGI("failed to start audio source, err=%d", err); + abortPrepare(err); + return; + } + + mFlags |= PREPARING_CONNECTED; + + if (mCachedSource != NULL) { + postBufferingEvent_l(); + } else { + finishAsyncPrepare_l(); + } +} + +void AAH_TXPlayer::finishAsyncPrepare_l() { + notifyListener_l(MEDIA_PREPARED); + + mPrepareResult = OK; + mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED); + mFlags |= PREPARED; + mPreparedCondition.broadcast(); +} + +status_t AAH_TXPlayer::start() { + Mutex::Autolock autoLock(mLock); + + mFlags &= ~CACHE_UNDERRUN; + + return play_l(); +} + +status_t AAH_TXPlayer::play_l() { + if (mFlags & PLAYING) { + return OK; + } + + if (!(mFlags & PREPARED)) { + return INVALID_OPERATION; + } + + { + Mutex::Autolock lock(mEndpointLock); + if (!mEndpointValid) { + return INVALID_OPERATION; + } + if (!mEndpointRegistered) { + mProgramID = mAAH_Sender->registerEndpoint(mEndpoint); + mEndpointRegistered = true; + } + } + + mFlags |= PLAYING; + + updateClockTransform_l(false); + + postPumpAudioEvent_l(-1); + + return OK; +} + +status_t AAH_TXPlayer::stop() { + status_t ret = pause(); + sendEOS_l(); + return ret; +} + +status_t AAH_TXPlayer::pause() { + Mutex::Autolock autoLock(mLock); + + mFlags &= ~CACHE_UNDERRUN; + + return pause_l(); +} + +status_t AAH_TXPlayer::pause_l(bool doClockUpdate) { + if (!(mFlags & PLAYING)) { + return OK; + } + + cancelPlayerEvents(true /* keepBufferingGoing */); + + mFlags &= ~PLAYING; + + if (doClockUpdate) { + updateClockTransform_l(true); + } + + return OK; +} + +void AAH_TXPlayer::updateClockTransform_l(bool pause) { + // record the new pause status so that onPumpAudio knows what rate to apply + // when it initializes the transform + mPlayRateIsPaused = pause; + + // if we haven't yet established a valid clock transform, then we can't + // do anything here + if (!mCurrentClockTransformValid) { + return; + } + + // sample the current common time + int64_t commonTimeNow; + if (OK != mCCHelper.getCommonTime(&commonTimeNow)) { + ALOGE("updateClockTransform_l get common time failed"); + mCurrentClockTransformValid = false; + return; + } + + // convert the current common time to media time using the old + // transform + int64_t mediaTimeNow; + if (!mCurrentClockTransform.doReverseTransform( + commonTimeNow, &mediaTimeNow)) { + ALOGE("updateClockTransform_l reverse transform failed"); + mCurrentClockTransformValid = false; + return; + } + + // calculate a new transform that preserves the old transform's + // result for the current time + mCurrentClockTransform.a_zero = mediaTimeNow; + mCurrentClockTransform.b_zero = commonTimeNow; + mCurrentClockTransform.a_to_b_numer = 1; + mCurrentClockTransform.a_to_b_denom = pause ? 0 : 1; + + // send a packet announcing the new transform + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setClockTransform(mCurrentClockTransform); + packet->setCommandID(TRTPControlPacket::kCommandNop); + queuePacketToSender_l(packet); +} + +void AAH_TXPlayer::sendEOS_l() { + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setCommandID(TRTPControlPacket::kCommandEOS); + queuePacketToSender_l(packet); +} + +bool AAH_TXPlayer::isPlaying() { + return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN); +} + +status_t AAH_TXPlayer::seekTo(int msec) { + if (mExtractorFlags & MediaExtractor::CAN_SEEK) { + Mutex::Autolock autoLock(mLock); + return seekTo_l(static_cast<int64_t>(msec) * 1000); + } + + notifyListener_l(MEDIA_SEEK_COMPLETE); + return OK; +} + +status_t AAH_TXPlayer::seekTo_l(int64_t timeUs) { + mIsSeeking = true; + mSeekTimeUs = timeUs; + + mCurrentClockTransformValid = false; + mLastQueuedMediaTimePTSValid = false; + + // send a flush command packet + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setCommandID(TRTPControlPacket::kCommandFlush); + queuePacketToSender_l(packet); + + return OK; +} + +status_t AAH_TXPlayer::getCurrentPosition(int *msec) { + if (!msec) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mLock); + + int position; + + if (mIsSeeking) { + position = mSeekTimeUs / 1000; + } else if (mCurrentClockTransformValid) { + // sample the current common time + int64_t commonTimeNow; + if (OK != mCCHelper.getCommonTime(&commonTimeNow)) { + ALOGE("getCurrentPosition get common time failed"); + return INVALID_OPERATION; + } + + int64_t mediaTimeNow; + if (!mCurrentClockTransform.doReverseTransform(commonTimeNow, + &mediaTimeNow)) { + ALOGE("getCurrentPosition reverse transform failed"); + return INVALID_OPERATION; + } + + position = static_cast<int>(mediaTimeNow / 1000); + } else { + position = 0; + } + + int duration; + if (getDuration_l(&duration) == OK) { + *msec = clamp(position, 0, duration); + } else { + *msec = (position >= 0) ? position : 0; + } + + return OK; +} + +status_t AAH_TXPlayer::getDuration(int* msec) { + if (!msec) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mLock); + + return getDuration_l(msec); +} + +status_t AAH_TXPlayer::getDuration_l(int* msec) { + if (mDurationUs < 0) { + return UNKNOWN_ERROR; + } + + *msec = (mDurationUs + 500) / 1000; + + return OK; +} + +status_t AAH_TXPlayer::reset() { + Mutex::Autolock autoLock(mLock); + reset_l(); + return OK; +} + +void AAH_TXPlayer::reset_l() { + if (mFlags & PREPARING) { + mFlags |= PREPARE_CANCELLED; + if (mConnectingDataSource != NULL) { + ALOGI("interrupting the connection process"); + mConnectingDataSource->disconnect(); + } + + if (mFlags & PREPARING_CONNECTED) { + // We are basically done preparing, we're just buffering + // enough data to start playback, we can safely interrupt that. + finishAsyncPrepare_l(); + } + } + + while (mFlags & PREPARING) { + mPreparedCondition.wait(mLock); + } + + cancelPlayerEvents(); + + sendEOS_l(); + + mCachedSource.clear(); + + if (mAudioSource != NULL) { + mAudioSource->stop(); + } + mAudioSource.clear(); + + mFlags = 0; + mExtractorFlags = 0; + + mDurationUs = -1; + mIsSeeking = false; + mSeekTimeUs = 0; + + mUri.setTo(""); + mUriHeaders.clear(); + + mFileSource.clear(); + + mBitrate = -1; + + { + Mutex::Autolock lock(mEndpointLock); + if (mAAH_Sender != NULL && mEndpointRegistered) { + mAAH_Sender->unregisterEndpoint(mEndpoint); + } + mEndpointRegistered = false; + mEndpointValid = false; + } + + mProgramID = 0; + + mAAH_Sender.clear(); + mLastQueuedMediaTimePTSValid = false; + mCurrentClockTransformValid = false; + mPlayRateIsPaused = false; + + mTRTPVolume = 255; +} + +status_t AAH_TXPlayer::setLooping(int loop) { + return OK; +} + +player_type AAH_TXPlayer::playerType() { + return AAH_TX_PLAYER; +} + +status_t AAH_TXPlayer::setParameter(int key, const Parcel &request) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_TXPlayer::getParameter(int key, Parcel *reply) { + return ERROR_UNSUPPORTED; +} + +status_t AAH_TXPlayer::invoke(const Parcel& request, Parcel *reply) { + if (!reply) { + return BAD_VALUE; + } + + int32_t methodID; + status_t err = request.readInt32(&methodID); + if (err != android::OK) { + return err; + } + + switch (methodID) { + case kInvokeSetAAHDstIPPort: + case kInvokeSetAAHConfigBlob: { + if (mEndpointValid) { + return INVALID_OPERATION; + } + + String8 addr; + uint16_t port; + + if (methodID == kInvokeSetAAHDstIPPort) { + addr = String8(request.readString16()); + + int32_t port32; + err = request.readInt32(&port32); + if (err != android::OK) { + return err; + } + port = static_cast<uint16_t>(port32); + } else { + String8 blob(request.readString16()); + + char addr_buf[101]; + if (sscanf(blob.string(), "V1:%100s %" SCNu16, + addr_buf, &port) != 2) { + return BAD_VALUE; + } + if (addr.setTo(addr_buf) != OK) { + return NO_MEMORY; + } + } + + struct hostent* ent = gethostbyname(addr.string()); + if (ent == NULL) { + return ERROR_UNKNOWN_HOST; + } + if (!(ent->h_addrtype == AF_INET && ent->h_length == 4)) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mEndpointLock); + mEndpoint = AAH_TXSender::Endpoint( + reinterpret_cast<struct in_addr*>(ent->h_addr)->s_addr, + port); + mEndpointValid = true; + return OK; + }; + + default: + return INVALID_OPERATION; + } +} + +status_t AAH_TXPlayer::getMetadata(const media::Metadata::Filter& ids, + Parcel* records) { + using media::Metadata; + + Metadata metadata(records); + + metadata.appendBool(Metadata::kPauseAvailable, true); + metadata.appendBool(Metadata::kSeekBackwardAvailable, false); + metadata.appendBool(Metadata::kSeekForwardAvailable, false); + metadata.appendBool(Metadata::kSeekAvailable, false); + + return OK; +} + +status_t AAH_TXPlayer::setVolume(float leftVolume, float rightVolume) { + if (leftVolume != rightVolume) { + ALOGE("%s does not support per channel volume: %f, %f", + __PRETTY_FUNCTION__, leftVolume, rightVolume); + } + + float volume = clamp(leftVolume, 0.0f, 1.0f); + + Mutex::Autolock lock(mLock); + mTRTPVolume = static_cast<uint8_t>((leftVolume * 255.0) + 0.5); + + return OK; +} + +status_t AAH_TXPlayer::setAudioStreamType(audio_stream_type_t streamType) { + return OK; +} + +void AAH_TXPlayer::notifyListener_l(int msg, int ext1, int ext2) { + sendEvent(msg, ext1, ext2); +} + +bool AAH_TXPlayer::getBitrate_l(int64_t *bitrate) { + off64_t size; + if (mDurationUs >= 0 && + mCachedSource != NULL && + mCachedSource->getSize(&size) == OK) { + *bitrate = size * 8000000ll / mDurationUs; // in bits/sec + return true; + } + + if (mBitrate >= 0) { + *bitrate = mBitrate; + return true; + } + + *bitrate = 0; + + return false; +} + +// Returns true iff cached duration is available/applicable. +bool AAH_TXPlayer::getCachedDuration_l(int64_t *durationUs, bool *eos) { + int64_t bitrate; + + if (mCachedSource != NULL && getBitrate_l(&bitrate)) { + status_t finalStatus; + size_t cachedDataRemaining = mCachedSource->approxDataRemaining( + &finalStatus); + *durationUs = cachedDataRemaining * 8000000ll / bitrate; + *eos = (finalStatus != OK); + return true; + } + + return false; +} + +void AAH_TXPlayer::ensureCacheIsFetching_l() { + if (mCachedSource != NULL) { + mCachedSource->resumeFetchingIfNecessary(); + } +} + +void AAH_TXPlayer::postBufferingEvent_l() { + if (mBufferingEventPending) { + return; + } + mBufferingEventPending = true; + mQueue.postEventWithDelay(mBufferingEvent, 1000000ll); +} + +void AAH_TXPlayer::postPumpAudioEvent_l(int64_t delayUs) { + if (mPumpAudioEventPending) { + return; + } + mPumpAudioEventPending = true; + mQueue.postEventWithDelay(mPumpAudioEvent, delayUs < 0 ? 10000 : delayUs); +} + +void AAH_TXPlayer::onBufferingUpdate() { + Mutex::Autolock autoLock(mLock); + if (!mBufferingEventPending) { + return; + } + mBufferingEventPending = false; + + if (mCachedSource != NULL) { + status_t finalStatus; + size_t cachedDataRemaining = mCachedSource->approxDataRemaining( + &finalStatus); + bool eos = (finalStatus != OK); + + if (eos) { + if (finalStatus == ERROR_END_OF_STREAM) { + notifyListener_l(MEDIA_BUFFERING_UPDATE, 100); + } + if (mFlags & PREPARING) { + ALOGV("cache has reached EOS, prepare is done."); + finishAsyncPrepare_l(); + } + } else { + int64_t bitrate; + if (getBitrate_l(&bitrate)) { + size_t cachedSize = mCachedSource->cachedSize(); + int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate; + + int percentage = (100.0 * (double) cachedDurationUs) + / mDurationUs; + if (percentage > 100) { + percentage = 100; + } + + notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage); + } else { + // We don't know the bitrate of the stream, use absolute size + // limits to maintain the cache. + + if ((mFlags & PLAYING) && + !eos && + (cachedDataRemaining < kLowWaterMarkBytes)) { + ALOGI("cache is running low (< %d) , pausing.", + kLowWaterMarkBytes); + mFlags |= CACHE_UNDERRUN; + pause_l(); + ensureCacheIsFetching_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START); + } else if (eos || cachedDataRemaining > kHighWaterMarkBytes) { + if (mFlags & CACHE_UNDERRUN) { + ALOGI("cache has filled up (> %d), resuming.", + kHighWaterMarkBytes); + mFlags &= ~CACHE_UNDERRUN; + play_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END); + } else if (mFlags & PREPARING) { + ALOGV("cache has filled up (> %d), prepare is done", + kHighWaterMarkBytes); + finishAsyncPrepare_l(); + } + } + } + } + } + + int64_t cachedDurationUs; + bool eos; + if (getCachedDuration_l(&cachedDurationUs, &eos)) { + ALOGV("cachedDurationUs = %.2f secs, eos=%d", + cachedDurationUs / 1E6, eos); + + if ((mFlags & PLAYING) && + !eos && + (cachedDurationUs < kLowWaterMarkUs)) { + ALOGI("cache is running low (%.2f secs) , pausing.", + cachedDurationUs / 1E6); + mFlags |= CACHE_UNDERRUN; + pause_l(); + ensureCacheIsFetching_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START); + } else if (eos || cachedDurationUs > kHighWaterMarkUs) { + if (mFlags & CACHE_UNDERRUN) { + ALOGI("cache has filled up (%.2f secs), resuming.", + cachedDurationUs / 1E6); + mFlags &= ~CACHE_UNDERRUN; + play_l(); + notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END); + } else if (mFlags & PREPARING) { + ALOGV("cache has filled up (%.2f secs), prepare is done", + cachedDurationUs / 1E6); + finishAsyncPrepare_l(); + } + } + } + + postBufferingEvent_l(); +} + +void AAH_TXPlayer::onPumpAudio() { + while (true) { + Mutex::Autolock autoLock(mLock); + // If this flag is clear, its because someone has externally canceled + // this pump operation (probably because we a resetting/shutting down). + // Get out immediately, do not reschedule ourselves. + if (!mPumpAudioEventPending) { + return; + } + + // Start by checking if there is still work to be doing. If we have + // never queued a payload (so we don't know what the last queued PTS is) + // or we have never established a MediaTime->CommonTime transformation, + // then we have work to do (one time through this loop should establish + // both). Otherwise, we want to keep a fixed amt of presentation time + // worth of data buffered. If we cannot get common time (service is + // unavailable, or common time is undefined)) then we don't have a lot + // of good options here. For now, signal an error up to the app level + // and shut down the transmission pump. + int64_t commonTimeNow; + if (OK != mCCHelper.getCommonTime(&commonTimeNow)) { + // Failed to get common time; either the service is down or common + // time is not synced. Raise an error and shutdown the player. + ALOGE("*** Cannot pump audio, unable to fetch common time." + " Shutting down."); + notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, UNKNOWN_ERROR); + mPumpAudioEventPending = false; + break; + } + + if (mCurrentClockTransformValid && mLastQueuedMediaTimePTSValid) { + int64_t mediaTimeNow; + bool conversionResult = mCurrentClockTransform.doReverseTransform( + commonTimeNow, + &mediaTimeNow); + CHECK(conversionResult); + + if ((mediaTimeNow + + kAAHBufferTimeUs - + mLastQueuedMediaTimePTS) <= 0) { + break; + } + } + + MediaSource::ReadOptions options; + if (mIsSeeking) { + options.setSeekTo(mSeekTimeUs); + } + + MediaBuffer* mediaBuffer; + status_t err = mAudioSource->read(&mediaBuffer, &options); + if (err != NO_ERROR) { + if (err == ERROR_END_OF_STREAM) { + ALOGI("*** %s reached end of stream", __PRETTY_FUNCTION__); + notifyListener_l(MEDIA_BUFFERING_UPDATE, 100); + notifyListener_l(MEDIA_PLAYBACK_COMPLETE); + pause_l(false); + sendEOS_l(); + } else { + ALOGE("*** %s read failed err=%d", __PRETTY_FUNCTION__, err); + } + return; + } + + if (mIsSeeking) { + mIsSeeking = false; + notifyListener_l(MEDIA_SEEK_COMPLETE); + } + + uint8_t* data = (static_cast<uint8_t*>(mediaBuffer->data()) + + mediaBuffer->range_offset()); + ALOGV("*** %s got media buffer data=[%02hhx %02hhx %02hhx %02hhx]" + " offset=%d length=%d", __PRETTY_FUNCTION__, + data[0], data[1], data[2], data[3], + mediaBuffer->range_offset(), mediaBuffer->range_length()); + + int64_t mediaTimeUs; + CHECK(mediaBuffer->meta_data()->findInt64(kKeyTime, &mediaTimeUs)); + ALOGV("*** timeUs=%lld", mediaTimeUs); + + if (!mCurrentClockTransformValid) { + if (OK == mCCHelper.getCommonTime(&commonTimeNow)) { + mCurrentClockTransform.a_zero = mediaTimeUs; + mCurrentClockTransform.b_zero = commonTimeNow + + kAAHStartupLeadTimeUs; + mCurrentClockTransform.a_to_b_numer = 1; + mCurrentClockTransform.a_to_b_denom = mPlayRateIsPaused ? 0 : 1; + mCurrentClockTransformValid = true; + } else { + // Failed to get common time; either the service is down or + // common time is not synced. Raise an error and shutdown the + // player. + ALOGE("*** Cannot begin transmission, unable to fetch common" + " time. Dropping sample with pts=%lld", mediaTimeUs); + notifyListener_l(MEDIA_ERROR, + MEDIA_ERROR_UNKNOWN, + UNKNOWN_ERROR); + mPumpAudioEventPending = false; + break; + } + } + + ALOGV("*** transmitting packet with pts=%lld", mediaTimeUs); + + sp<TRTPAudioPacket> packet = new TRTPAudioPacket(); + packet->setPTS(mediaTimeUs); + packet->setSubstreamID(1); + + packet->setCodecType(TRTPAudioPacket::kCodecMPEG1Audio); + packet->setVolume(mTRTPVolume); + // TODO : introduce a throttle for this so we can control the + // frequency with which transforms get sent. + packet->setClockTransform(mCurrentClockTransform); + packet->setAccessUnitData(data, mediaBuffer->range_length()); + packet->setRandomAccessPoint(true); + + queuePacketToSender_l(packet); + mediaBuffer->release(); + + mLastQueuedMediaTimePTSValid = true; + mLastQueuedMediaTimePTS = mediaTimeUs; + } + + { // Explicit scope for the autolock pattern. + Mutex::Autolock autoLock(mLock); + + // If someone externally has cleared this flag, its because we should be + // shutting down. Do not reschedule ourselves. + if (!mPumpAudioEventPending) { + return; + } + + // Looks like no one canceled us explicitly. Clear our flag and post a + // new event to ourselves. + mPumpAudioEventPending = false; + postPumpAudioEvent_l(10000); + } +} + +void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) { + if (mAAH_Sender == NULL) { + return; + } + + sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket, + mAAH_Sender->handlerID()); + + { + Mutex::Autolock lock(mEndpointLock); + if (!mEndpointValid) { + return; + } + + message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr); + message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port); + } + + packet->setProgramID(mProgramID); + packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs); + packet->pack(); + + message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet); + + message->post(); +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_player.h b/media/libaah_rtp/aah_tx_player.h new file mode 100644 index 000000000000..64cf5dc11a03 --- /dev/null +++ b/media/libaah_rtp/aah_tx_player.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAH_TX_PLAYER_H__ +#define __AAH_TX_PLAYER_H__ + +#include <common_time/cc_helper.h> +#include <libstagefright/include/HTTPBase.h> +#include <libstagefright/include/NuCachedSource2.h> +#include <libstagefright/include/TimedEventQueue.h> +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <utils/LinearTransform.h> +#include <utils/String8.h> +#include <utils/threads.h> + +#include "aah_tx_sender.h" + +namespace android { + +class AAH_TXPlayer : public MediaPlayerHWInterface { + public: + AAH_TXPlayer(); + + virtual status_t initCheck(); + virtual status_t setDataSource(const char *url, + const KeyedVector<String8, String8>* + headers); + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + virtual status_t setVideoSurface(const sp<Surface>& surface); + virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>& + surfaceTexture); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t setParameter(int key, const Parcel &request); + virtual status_t getParameter(int key, Parcel *reply); + virtual status_t invoke(const Parcel& request, Parcel *reply); + virtual status_t getMetadata(const media::Metadata::Filter& ids, + Parcel* records); + virtual status_t setVolume(float leftVolume, float rightVolume); + virtual status_t setAudioStreamType(audio_stream_type_t streamType); + + // invoke method IDs + enum { + // set the IP address and port of the A@H receiver + kInvokeSetAAHDstIPPort = 1, + + // set the destination IP address and port (and perhaps any additional + // parameters added in the future) packaged in one string + kInvokeSetAAHConfigBlob, + }; + + static const int64_t kAAHRetryKeepAroundTimeNs; + + protected: + virtual ~AAH_TXPlayer(); + + private: + friend struct AwesomeEvent; + + enum { + PLAYING = 1, + PREPARING = 8, + PREPARED = 16, + PREPARE_CANCELLED = 64, + CACHE_UNDERRUN = 128, + + // We are basically done preparing but are currently buffering + // sufficient data to begin playback and finish the preparation + // phase for good. + PREPARING_CONNECTED = 2048, + + INCOGNITO = 32768, + }; + + status_t setDataSource_l(const char *url, + const KeyedVector<String8, String8> *headers); + status_t setDataSource_l(const sp<MediaExtractor>& extractor); + status_t finishSetDataSource_l(); + status_t prepareAsync_l(); + void onPrepareAsyncEvent(); + void finishAsyncPrepare_l(); + void abortPrepare(status_t err); + status_t play_l(); + status_t pause_l(bool doClockUpdate = true); + status_t seekTo_l(int64_t timeUs); + void updateClockTransform_l(bool pause); + void sendEOS_l(); + void cancelPlayerEvents(bool keepBufferingGoing = false); + void reset_l(); + void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0); + bool getBitrate_l(int64_t* bitrate); + status_t getDuration_l(int* msec); + bool getCachedDuration_l(int64_t* durationUs, bool* eos); + void ensureCacheIsFetching_l(); + void postBufferingEvent_l(); + void postPumpAudioEvent_l(int64_t delayUs); + void onBufferingUpdate(); + void onPumpAudio(); + void queuePacketToSender_l(const sp<TRTPPacket>& packet); + + Mutex mLock; + + TimedEventQueue mQueue; + bool mQueueStarted; + + sp<TimedEventQueue::Event> mBufferingEvent; + bool mBufferingEventPending; + + uint32_t mFlags; + uint32_t mExtractorFlags; + + String8 mUri; + KeyedVector<String8, String8> mUriHeaders; + + sp<DataSource> mFileSource; + + sp<TimedEventQueue::Event> mAsyncPrepareEvent; + Condition mPreparedCondition; + status_t mPrepareResult; + + bool mIsSeeking; + int64_t mSeekTimeUs; + + sp<TimedEventQueue::Event> mPumpAudioEvent; + bool mPumpAudioEventPending; + + sp<HTTPBase> mConnectingDataSource; + sp<NuCachedSource2> mCachedSource; + + sp<MediaSource> mAudioSource; + int64_t mDurationUs; + int64_t mBitrate; + + sp<AAH_TXSender> mAAH_Sender; + LinearTransform mCurrentClockTransform; + bool mCurrentClockTransformValid; + int64_t mLastQueuedMediaTimePTS; + bool mLastQueuedMediaTimePTSValid; + bool mPlayRateIsPaused; + CCHelper mCCHelper; + + Mutex mEndpointLock; + AAH_TXSender::Endpoint mEndpoint; + bool mEndpointValid; + bool mEndpointRegistered; + uint16_t mProgramID; + uint8_t mTRTPVolume; + + DISALLOW_EVIL_CONSTRUCTORS(AAH_TXPlayer); +}; + +} // namespace android + +#endif // __AAH_TX_PLAYER_H__ diff --git a/media/libaah_rtp/aah_tx_sender.cpp b/media/libaah_rtp/aah_tx_sender.cpp new file mode 100644 index 000000000000..d991ea76afe2 --- /dev/null +++ b/media/libaah_rtp/aah_tx_sender.cpp @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <media/stagefright/foundation/ADebug.h> + +#include <netinet/in.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <media/stagefright/foundation/AMessage.h> +#include <utils/misc.h> + +#include "aah_tx_player.h" +#include "aah_tx_sender.h" + +namespace android { + +const char* AAH_TXSender::kSendPacketIPAddr = "ipaddr"; +const char* AAH_TXSender::kSendPacketPort = "port"; +const char* AAH_TXSender::kSendPacketTRTPPacket = "trtp"; + +const int AAH_TXSender::kRetryTrimIntervalUs = 100000; +const int AAH_TXSender::kHeartbeatIntervalUs = 1000000; +const int AAH_TXSender::kRetryBufferCapacity = 100; +const nsecs_t AAH_TXSender::kHeartbeatTimeout = 600ull * 1000000000ull; + +Mutex AAH_TXSender::sLock; +wp<AAH_TXSender> AAH_TXSender::sInstance; +uint32_t AAH_TXSender::sNextEpoch; +bool AAH_TXSender::sNextEpochValid = false; + +AAH_TXSender::AAH_TXSender() : mSocket(-1) { + mLastSentPacketTime = systemTime(); +} + +sp<AAH_TXSender> AAH_TXSender::GetInstance() { + Mutex::Autolock autoLock(sLock); + + sp<AAH_TXSender> sender = sInstance.promote(); + + if (sender == NULL) { + sender = new AAH_TXSender(); + if (sender == NULL) { + return NULL; + } + + sender->mLooper = new ALooper(); + if (sender->mLooper == NULL) { + return NULL; + } + + sender->mReflector = new AHandlerReflector<AAH_TXSender>(sender.get()); + if (sender->mReflector == NULL) { + return NULL; + } + + sender->mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sender->mSocket == -1) { + ALOGW("%s unable to create socket", __PRETTY_FUNCTION__); + return NULL; + } + + struct sockaddr_in bind_addr; + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + if (bind(sender->mSocket, + reinterpret_cast<const sockaddr*>(&bind_addr), + sizeof(bind_addr)) < 0) { + ALOGW("%s unable to bind socket (errno %d)", + __PRETTY_FUNCTION__, errno); + return NULL; + } + + sender->mRetryReceiver = new RetryReceiver(sender.get()); + if (sender->mRetryReceiver == NULL) { + return NULL; + } + + sender->mLooper->setName("AAH_TXSender"); + sender->mLooper->registerHandler(sender->mReflector); + sender->mLooper->start(false, false, PRIORITY_AUDIO); + + if (sender->mRetryReceiver->run("AAH_TXSenderRetry", PRIORITY_AUDIO) + != OK) { + ALOGW("%s unable to start retry thread", __PRETTY_FUNCTION__); + return NULL; + } + + sInstance = sender; + } + + return sender; +} + +AAH_TXSender::~AAH_TXSender() { + mLooper->stop(); + mLooper->unregisterHandler(mReflector->id()); + + if (mRetryReceiver != NULL) { + mRetryReceiver->requestExit(); + mRetryReceiver->mWakeupEvent.setEvent(); + if (mRetryReceiver->requestExitAndWait() != OK) { + ALOGW("%s shutdown of retry receiver failed", __PRETTY_FUNCTION__); + } + mRetryReceiver->mSender = NULL; + mRetryReceiver.clear(); + } + + if (mSocket != -1) { + close(mSocket); + } +} + +// Return the next epoch number usable for a newly instantiated endpoint. +uint32_t AAH_TXSender::getNextEpoch() { + Mutex::Autolock autoLock(sLock); + + if (sNextEpochValid) { + sNextEpoch = (sNextEpoch + 1) & TRTPPacket::kTRTPEpochMask; + } else { + sNextEpoch = ns2ms(systemTime()) & TRTPPacket::kTRTPEpochMask; + sNextEpochValid = true; + } + + return sNextEpoch; +} + +// Notify the sender that a player has started sending to this endpoint. +// Returns a program ID for use by the calling player. +uint16_t AAH_TXSender::registerEndpoint(const Endpoint& endpoint) { + Mutex::Autolock lock(mEndpointLock); + + EndpointState* eps = mEndpointMap.valueFor(endpoint); + if (eps) { + eps->playerRefCount++; + } else { + eps = new EndpointState(getNextEpoch()); + mEndpointMap.add(endpoint, eps); + } + + // if this is the first registered endpoint, then send a message to start + // trimming retry buffers and a message to start sending heartbeats. + if (mEndpointMap.size() == 1) { + sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers, + handlerID()); + trimMessage->post(kRetryTrimIntervalUs); + + sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats, + handlerID()); + heartbeatMessage->post(kHeartbeatIntervalUs); + } + + eps->nextProgramID++; + return eps->nextProgramID; +} + +// Notify the sender that a player has ceased sending to this endpoint. +// An endpoint's state can not be deleted until all of the endpoint's +// registered players have called unregisterEndpoint. +void AAH_TXSender::unregisterEndpoint(const Endpoint& endpoint) { + Mutex::Autolock lock(mEndpointLock); + + EndpointState* eps = mEndpointMap.valueFor(endpoint); + if (eps) { + eps->playerRefCount--; + CHECK(eps->playerRefCount >= 0); + } +} + +void AAH_TXSender::onMessageReceived(const sp<AMessage>& msg) { + switch (msg->what()) { + case kWhatSendPacket: + onSendPacket(msg); + break; + + case kWhatTrimRetryBuffers: + trimRetryBuffers(); + break; + + case kWhatSendHeartbeats: + sendHeartbeats(); + break; + + default: + TRESPASS(); + break; + } +} + +void AAH_TXSender::onSendPacket(const sp<AMessage>& msg) { + sp<RefBase> obj; + CHECK(msg->findObject(kSendPacketTRTPPacket, &obj)); + sp<TRTPPacket> packet = static_cast<TRTPPacket*>(obj.get()); + + uint32_t ipAddr; + CHECK(msg->findInt32(kSendPacketIPAddr, + reinterpret_cast<int32_t*>(&ipAddr))); + + int32_t port32; + CHECK(msg->findInt32(kSendPacketPort, &port32)); + uint16_t port = port32; + + Mutex::Autolock lock(mEndpointLock); + doSendPacket_l(packet, Endpoint(ipAddr, port)); + mLastSentPacketTime = systemTime(); +} + +void AAH_TXSender::doSendPacket_l(const sp<TRTPPacket>& packet, + const Endpoint& endpoint) { + EndpointState* eps = mEndpointMap.valueFor(endpoint); + if (!eps) { + // the endpoint state has disappeared, so the player that sent this + // packet must be dead. + return; + } + + // assign the packet's sequence number + packet->setEpoch(eps->epoch); + packet->setSeqNumber(eps->trtpSeqNumber++); + + // add the packet to the retry buffer + RetryBuffer& retry = eps->retry; + retry.push_back(packet); + + // send the packet + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = endpoint.addr; + addr.sin_port = htons(endpoint.port); + + ssize_t result = sendto(mSocket, + packet->getPacket(), + packet->getPacketLen(), + 0, + (const struct sockaddr *) &addr, + sizeof(addr)); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } +} + +void AAH_TXSender::trimRetryBuffers() { + Mutex::Autolock lock(mEndpointLock); + + nsecs_t localTimeNow = systemTime(); + + Vector<Endpoint> endpointsToRemove; + + for (size_t i = 0; i < mEndpointMap.size(); i++) { + EndpointState* eps = mEndpointMap.editValueAt(i); + RetryBuffer& retry = eps->retry; + + while (!retry.isEmpty()) { + if (retry[0]->getExpireTime() < localTimeNow) { + retry.pop_front(); + } else { + break; + } + } + + if (retry.isEmpty() && eps->playerRefCount == 0) { + endpointsToRemove.add(mEndpointMap.keyAt(i)); + } + } + + // remove the state for any endpoints that are no longer in use + for (size_t i = 0; i < endpointsToRemove.size(); i++) { + Endpoint& e = endpointsToRemove.editItemAt(i); + ALOGD("*** %s removing endpoint addr=%08x", __PRETTY_FUNCTION__, e.addr); + size_t index = mEndpointMap.indexOfKey(e); + delete mEndpointMap.valueAt(index); + mEndpointMap.removeItemsAt(index); + } + + // schedule the next trim + if (mEndpointMap.size()) { + sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers, + handlerID()); + trimMessage->post(kRetryTrimIntervalUs); + } +} + +void AAH_TXSender::sendHeartbeats() { + Mutex::Autolock lock(mEndpointLock); + + if (shouldSendHeartbeats_l()) { + for (size_t i = 0; i < mEndpointMap.size(); i++) { + EndpointState* eps = mEndpointMap.editValueAt(i); + const Endpoint& ep = mEndpointMap.keyAt(i); + + sp<TRTPControlPacket> packet = new TRTPControlPacket(); + packet->setCommandID(TRTPControlPacket::kCommandNop); + + packet->setExpireTime(systemTime() + + AAH_TXPlayer::kAAHRetryKeepAroundTimeNs); + packet->pack(); + + doSendPacket_l(packet, ep); + } + } + + // schedule the next heartbeat + if (mEndpointMap.size()) { + sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats, + handlerID()); + heartbeatMessage->post(kHeartbeatIntervalUs); + } +} + +bool AAH_TXSender::shouldSendHeartbeats_l() { + // assert(holding endpoint lock) + return (systemTime() < (mLastSentPacketTime + kHeartbeatTimeout)); +} + +// Receiver + +// initial 4-byte ID of a retry request packet +const uint32_t AAH_TXSender::RetryReceiver::kRetryRequestID = 'Treq'; + +// initial 4-byte ID of a retry NAK packet +const uint32_t AAH_TXSender::RetryReceiver::kRetryNakID = 'Tnak'; + +// initial 4-byte ID of a fast start request packet +const uint32_t AAH_TXSender::RetryReceiver::kFastStartRequestID = 'Tfst'; + +AAH_TXSender::RetryReceiver::RetryReceiver(AAH_TXSender* sender) + : Thread(false), + mSender(sender) {} + + AAH_TXSender::RetryReceiver::~RetryReceiver() { + mWakeupEvent.clearPendingEvents(); + } + +// Returns true if val is within the interval bounded inclusively by +// start and end. Also handles the case where there is a rollover of the +// range between start and end. +template <typename T> +static inline bool withinIntervalWithRollover(T val, T start, T end) { + return ((start <= end && val >= start && val <= end) || + (start > end && (val >= start || val <= end))); +} + +bool AAH_TXSender::RetryReceiver::threadLoop() { + struct pollfd pollFds[2]; + pollFds[0].fd = mSender->mSocket; + pollFds[0].events = POLLIN; + pollFds[0].revents = 0; + pollFds[1].fd = mWakeupEvent.getWakeupHandle(); + pollFds[1].events = POLLIN; + pollFds[1].revents = 0; + + int pollResult = poll(pollFds, NELEM(pollFds), -1); + if (pollResult == -1) { + ALOGE("%s poll failed", __PRETTY_FUNCTION__); + return false; + } + + if (exitPending()) { + ALOGI("*** %s exiting", __PRETTY_FUNCTION__); + return false; + } + + if (pollFds[0].revents) { + handleRetryRequest(); + } + + return true; +} + +void AAH_TXSender::RetryReceiver::handleRetryRequest() { + ALOGV("*** RX %s start", __PRETTY_FUNCTION__); + + RetryPacket request; + struct sockaddr requestSrcAddr; + socklen_t requestSrcAddrLen = sizeof(requestSrcAddr); + + ssize_t result = recvfrom(mSender->mSocket, &request, sizeof(request), 0, + &requestSrcAddr, &requestSrcAddrLen); + if (result == -1) { + ALOGE("%s recvfrom failed, errno=%d", __PRETTY_FUNCTION__, errno); + return; + } + + if (static_cast<size_t>(result) < sizeof(RetryPacket)) { + ALOGW("%s short packet received", __PRETTY_FUNCTION__); + return; + } + + uint32_t host_request_id = ntohl(request.id); + if ((host_request_id != kRetryRequestID) && + (host_request_id != kFastStartRequestID)) { + ALOGW("%s received retry request with bogus ID (%08x)", + __PRETTY_FUNCTION__, host_request_id); + return; + } + + Endpoint endpoint(request.endpointIP, ntohs(request.endpointPort)); + + Mutex::Autolock lock(mSender->mEndpointLock); + + EndpointState* eps = mSender->mEndpointMap.valueFor(endpoint); + + if (eps == NULL || eps->retry.isEmpty()) { + // we have no retry buffer or an empty retry buffer for this endpoint, + // so NAK the entire request + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + return; + } + + RetryBuffer& retry = eps->retry; + + uint16_t startSeq = ntohs(request.seqStart); + uint16_t endSeq = ntohs(request.seqEnd); + + uint16_t retryFirstSeq = retry[0]->getSeqNumber(); + uint16_t retryLastSeq = retry[retry.size() - 1]->getSeqNumber(); + + // If this is a fast start, then force the start of the retry to match the + // start of the retransmit ring buffer (unless the end of the retransmit + // ring buffer is already past the point of fast start) + if ((host_request_id == kFastStartRequestID) && + !((startSeq - retryFirstSeq) & 0x8000)) { + startSeq = retryFirstSeq; + } + + int startIndex; + if (withinIntervalWithRollover(startSeq, retryFirstSeq, retryLastSeq)) { + startIndex = static_cast<uint16_t>(startSeq - retryFirstSeq); + } else { + startIndex = -1; + } + + int endIndex; + if (withinIntervalWithRollover(endSeq, retryFirstSeq, retryLastSeq)) { + endIndex = static_cast<uint16_t>(endSeq - retryFirstSeq); + } else { + endIndex = -1; + } + + if (startIndex == -1 && endIndex == -1) { + // no part of the request range is found in the retry buffer + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + return; + } + + if (startIndex == -1) { + // NAK a subrange at the front of the request range + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + nak.seqEnd = htons(retryFirstSeq - 1); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + + startIndex = 0; + } else if (endIndex == -1) { + // NAK a subrange at the back of the request range + RetryPacket nak = request; + nak.id = htonl(kRetryNakID); + nak.seqStart = htons(retryLastSeq + 1); + result = sendto(mSender->mSocket, &nak, sizeof(nak), 0, + &requestSrcAddr, requestSrcAddrLen); + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + + endIndex = retry.size() - 1; + } + + // send the retry packets + for (int i = startIndex; i <= endIndex; i++) { + const sp<TRTPPacket>& replyPacket = retry[i]; + + result = sendto(mSender->mSocket, + replyPacket->getPacket(), + replyPacket->getPacketLen(), + 0, + &requestSrcAddr, + requestSrcAddrLen); + + if (result == -1) { + ALOGW("%s sendto failed", __PRETTY_FUNCTION__); + } + } +} + +// Endpoint + +AAH_TXSender::Endpoint::Endpoint() + : addr(0) + , port(0) { } + +AAH_TXSender::Endpoint::Endpoint(uint32_t a, uint16_t p) + : addr(a) + , port(p) {} + +bool AAH_TXSender::Endpoint::operator<(const Endpoint& other) const { + return ((addr < other.addr) || + (addr == other.addr && port < other.port)); +} + +// EndpointState + +AAH_TXSender::EndpointState::EndpointState(uint32_t _epoch) + : retry(kRetryBufferCapacity) + , playerRefCount(1) + , trtpSeqNumber(0) + , nextProgramID(0) + , epoch(_epoch) { } + +// CircularBuffer + +template <typename T> +CircularBuffer<T>::CircularBuffer(size_t capacity) + : mCapacity(capacity) + , mHead(0) + , mTail(0) + , mFillCount(0) { + mBuffer = new T[capacity]; +} + +template <typename T> +CircularBuffer<T>::~CircularBuffer() { + delete [] mBuffer; +} + +template <typename T> +void CircularBuffer<T>::push_back(const T& item) { + if (this->isFull()) { + this->pop_front(); + } + mBuffer[mHead] = item; + mHead = (mHead + 1) % mCapacity; + mFillCount++; +} + +template <typename T> +void CircularBuffer<T>::pop_front() { + CHECK(!isEmpty()); + mBuffer[mTail] = T(); + mTail = (mTail + 1) % mCapacity; + mFillCount--; +} + +template <typename T> +size_t CircularBuffer<T>::size() const { + return mFillCount; +} + +template <typename T> +bool CircularBuffer<T>::isFull() const { + return (mFillCount == mCapacity); +} + +template <typename T> +bool CircularBuffer<T>::isEmpty() const { + return (mFillCount == 0); +} + +template <typename T> +const T& CircularBuffer<T>::itemAt(size_t index) const { + CHECK(index < mFillCount); + return mBuffer[(mTail + index) % mCapacity]; +} + +template <typename T> +const T& CircularBuffer<T>::operator[](size_t index) const { + return itemAt(index); +} + +} // namespace android diff --git a/media/libaah_rtp/aah_tx_sender.h b/media/libaah_rtp/aah_tx_sender.h new file mode 100644 index 000000000000..74206c49b28b --- /dev/null +++ b/media/libaah_rtp/aah_tx_sender.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAH_TX_SENDER_H__ +#define __AAH_TX_SENDER_H__ + +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AHandlerReflector.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +#include "aah_tx_packet.h" +#include "pipe_event.h" + +namespace android { + +template <typename T> class CircularBuffer { + public: + CircularBuffer(size_t capacity); + ~CircularBuffer(); + void push_back(const T& item);; + void pop_front(); + size_t size() const; + bool isFull() const; + bool isEmpty() const; + const T& itemAt(size_t index) const; + const T& operator[](size_t index) const; + + private: + T* mBuffer; + size_t mCapacity; + size_t mHead; + size_t mTail; + size_t mFillCount; + + DISALLOW_EVIL_CONSTRUCTORS(CircularBuffer); +}; + +class AAH_TXSender : public virtual RefBase { + public: + ~AAH_TXSender(); + + static sp<AAH_TXSender> GetInstance(); + + ALooper::handler_id handlerID() { return mReflector->id(); } + + // an IP address and port + struct Endpoint { + Endpoint(); + Endpoint(uint32_t a, uint16_t p); + bool operator<(const Endpoint& other) const; + + uint32_t addr; + uint16_t port; + }; + + uint16_t registerEndpoint(const Endpoint& endpoint); + void unregisterEndpoint(const Endpoint& endpoint); + + enum { + kWhatSendPacket, + kWhatTrimRetryBuffers, + kWhatSendHeartbeats, + }; + + // fields for SendPacket messages + static const char* kSendPacketIPAddr; + static const char* kSendPacketPort; + static const char* kSendPacketTRTPPacket; + + private: + AAH_TXSender(); + + static Mutex sLock; + static wp<AAH_TXSender> sInstance; + static uint32_t sNextEpoch; + static bool sNextEpochValid; + + static uint32_t getNextEpoch(); + + typedef CircularBuffer<sp<TRTPPacket> > RetryBuffer; + + // state maintained on a per-endpoint basis + struct EndpointState { + EndpointState(uint32_t epoch); + RetryBuffer retry; + int playerRefCount; + uint16_t trtpSeqNumber; + uint16_t nextProgramID; + uint32_t epoch; + }; + + friend class AHandlerReflector<AAH_TXSender>; + void onMessageReceived(const sp<AMessage>& msg); + void onSendPacket(const sp<AMessage>& msg); + void doSendPacket_l(const sp<TRTPPacket>& packet, + const Endpoint& endpoint); + void trimRetryBuffers(); + void sendHeartbeats(); + bool shouldSendHeartbeats_l(); + + sp<ALooper> mLooper; + sp<AHandlerReflector<AAH_TXSender> > mReflector; + + int mSocket; + nsecs_t mLastSentPacketTime; + + DefaultKeyedVector<Endpoint, EndpointState*> mEndpointMap; + Mutex mEndpointLock; + + static const int kRetryTrimIntervalUs; + static const int kHeartbeatIntervalUs; + static const int kRetryBufferCapacity; + static const nsecs_t kHeartbeatTimeout; + + class RetryReceiver : public Thread { + private: + friend class AAH_TXSender; + + RetryReceiver(AAH_TXSender* sender); + virtual ~RetryReceiver(); + virtual bool threadLoop(); + void handleRetryRequest(); + + static const int kMaxReceiverPacketLen; + static const uint32_t kRetryRequestID; + static const uint32_t kFastStartRequestID; + static const uint32_t kRetryNakID; + + AAH_TXSender* mSender; + PipeEvent mWakeupEvent; + }; + + sp<RetryReceiver> mRetryReceiver; + + DISALLOW_EVIL_CONSTRUCTORS(AAH_TXSender); +}; + +struct RetryPacket { + uint32_t id; + uint32_t endpointIP; + uint16_t endpointPort; + uint16_t seqStart; + uint16_t seqEnd; +} __attribute__((packed)); + +} // namespace android + +#endif // __AAH_TX_SENDER_H__ diff --git a/media/libaah_rtp/pipe_event.cpp b/media/libaah_rtp/pipe_event.cpp new file mode 100644 index 000000000000..b8e696080ca9 --- /dev/null +++ b/media/libaah_rtp/pipe_event.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibAAH_RTP" +#include <utils/Log.h> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +#include "pipe_event.h" + +namespace android { + +PipeEvent::PipeEvent() { + pipe_[0] = -1; + pipe_[1] = -1; + + // Create the pipe. + if (pipe(pipe_) >= 0) { + // Set non-blocking mode on the read side of the pipe so we can + // easily drain it whenever we wakeup. + fcntl(pipe_[0], F_SETFL, O_NONBLOCK); + } else { + ALOGE("Failed to create pipe event %d %d %d", + pipe_[0], pipe_[1], errno); + pipe_[0] = -1; + pipe_[1] = -1; + } +} + +PipeEvent::~PipeEvent() { + if (pipe_[0] >= 0) { + close(pipe_[0]); + } + + if (pipe_[1] >= 0) { + close(pipe_[1]); + } +} + +void PipeEvent::clearPendingEvents() { + char drain_buffer[16]; + while (read(pipe_[0], drain_buffer, sizeof(drain_buffer)) > 0) { + // No body. + } +} + +bool PipeEvent::wait(int timeout) { + struct pollfd wait_fd; + + wait_fd.fd = getWakeupHandle(); + wait_fd.events = POLLIN; + wait_fd.revents = 0; + + int res = poll(&wait_fd, 1, timeout); + + if (res < 0) { + ALOGE("Wait error in PipeEvent; sleeping to prevent overload!"); + usleep(1000); + } + + return (res > 0); +} + +void PipeEvent::setEvent() { + char foo = 'q'; + write(pipe_[1], &foo, 1); +} + +} // namespace android + diff --git a/media/libaah_rtp/pipe_event.h b/media/libaah_rtp/pipe_event.h new file mode 100644 index 000000000000..e53b0fdb74c0 --- /dev/null +++ b/media/libaah_rtp/pipe_event.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __PIPE_EVENT_H__ +#define __PIPE_EVENT_H__ + +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +class PipeEvent { + public: + PipeEvent(); + ~PipeEvent(); + + bool initCheck() const { + return ((pipe_[0] >= 0) && (pipe_[1] >= 0)); + } + + int getWakeupHandle() const { return pipe_[0]; } + + // block until the event fires; returns true if the event fired and false if + // the wait timed out. Timeout is expressed in milliseconds; negative + // values mean wait forever. + bool wait(int timeout = -1); + + void clearPendingEvents(); + void setEvent(); + + private: + int pipe_[2]; + + DISALLOW_EVIL_CONSTRUCTORS(PipeEvent); +}; + +} // namespace android + +#endif // __PIPE_EVENT_H__ diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index f9f997fe6a32..19b7e324b32b 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -202,7 +202,7 @@ bool AudioEffect::getEnabled() const status_t AudioEffect::setEnabled(bool enabled) { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus; } status_t status = NO_ERROR; @@ -231,7 +231,7 @@ status_t AudioEffect::command(uint32_t cmdCode, { if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) { ALOGV("command() bad status %d", mStatus); - return INVALID_OPERATION; + return mStatus; } if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) { @@ -263,7 +263,7 @@ status_t AudioEffect::command(uint32_t cmdCode, status_t AudioEffect::setParameter(effect_param_t *param) { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus; } if (param == NULL || param->psize == 0 || param->vsize == 0) { @@ -281,7 +281,7 @@ status_t AudioEffect::setParameter(effect_param_t *param) status_t AudioEffect::setParameterDeferred(effect_param_t *param) { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus; } if (param == NULL || param->psize == 0 || param->vsize == 0) { @@ -307,7 +307,7 @@ status_t AudioEffect::setParameterDeferred(effect_param_t *param) status_t AudioEffect::setParameterCommit() { if (mStatus != NO_ERROR) { - return INVALID_OPERATION; + return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus; } Mutex::Autolock _l(mCblk->lock); @@ -321,7 +321,7 @@ status_t AudioEffect::setParameterCommit() status_t AudioEffect::getParameter(effect_param_t *param) { if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) { - return INVALID_OPERATION; + return mStatus; } if (param == NULL || param->psize == 0 || param->vsize == 0) { @@ -341,7 +341,7 @@ status_t AudioEffect::getParameter(effect_param_t *param) void AudioEffect::binderDied() { ALOGW("IEffect died"); - mStatus = NO_INIT; + mStatus = DEAD_OBJECT; if (mCbf != NULL) { status_t status = DEAD_OBJECT; mCbf(EVENT_ERROR, mUserData, &status); diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index aead9a1654be..74c97ed932cf 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -80,7 +80,9 @@ status_t AudioTrack::getMinFrameCount( AudioTrack::AudioTrack() : mStatus(NO_INIT), - mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) + mIsTimed(false), + mPreviousPriority(ANDROID_PRIORITY_NORMAL), + mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) { } @@ -96,7 +98,9 @@ AudioTrack::AudioTrack( int notificationFrames, int sessionId) : mStatus(NO_INIT), - mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) + mIsTimed(false), + mPreviousPriority(ANDROID_PRIORITY_NORMAL), + mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) { mStatus = set(streamType, sampleRate, format, channelMask, frameCount, flags, cbf, user, notificationFrames, @@ -134,7 +138,9 @@ AudioTrack::AudioTrack( int notificationFrames, int sessionId) : mStatus(NO_INIT), - mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) + mIsTimed(false), + mPreviousPriority(ANDROID_PRIORITY_NORMAL), + mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT) { mStatus = set(streamType, sampleRate, format, channelMask, 0, flags, cbf, user, notificationFrames, @@ -540,6 +546,10 @@ status_t AudioTrack::setSampleRate(int rate) { int afSamplingRate; + if (mIsTimed) { + return INVALID_OPERATION; + } + if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) { return NO_INIT; } @@ -553,6 +563,10 @@ status_t AudioTrack::setSampleRate(int rate) uint32_t AudioTrack::getSampleRate() const { + if (mIsTimed) { + return INVALID_OPERATION; + } + AutoMutex lock(mLock); return mCblk->sampleRate; } @@ -578,6 +592,10 @@ status_t AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCou return NO_ERROR; } + if (mIsTimed) { + return INVALID_OPERATION; + } + if (loopStart >= loopEnd || loopEnd - loopStart > cblk->frameCount || cblk->server > loopStart) { @@ -641,6 +659,8 @@ status_t AudioTrack::getPositionUpdatePeriod(uint32_t *updatePeriod) const status_t AudioTrack::setPosition(uint32_t position) { + if (mIsTimed) return INVALID_OPERATION; + AutoMutex lock(mLock); if (!stopped_l()) return INVALID_OPERATION; @@ -791,6 +811,7 @@ status_t AudioTrack::createTrack_l( ((uint16_t)flags) << 16, sharedBuffer, output, + mIsTimed, &mSessionId, &status); @@ -957,6 +978,7 @@ ssize_t AudioTrack::write(const void* buffer, size_t userSize) { if (mSharedBuffer != 0) return INVALID_OPERATION; + if (mIsTimed) return INVALID_OPERATION; if (ssize_t(userSize) < 0) { // Sanity-check: user is most-likely passing an error code, and it would @@ -1013,6 +1035,59 @@ ssize_t AudioTrack::write(const void* buffer, size_t userSize) // ------------------------------------------------------------------------- +TimedAudioTrack::TimedAudioTrack() { + mIsTimed = true; +} + +status_t TimedAudioTrack::allocateTimedBuffer(size_t size, sp<IMemory>* buffer) +{ + status_t result = UNKNOWN_ERROR; + + // If the track is not invalid already, try to allocate a buffer. alloc + // fails indicating that the server is dead, flag the track as invalid so + // we can attempt to restore in in just a bit. + if (!(mCblk->flags & CBLK_INVALID_MSK)) { + result = mAudioTrack->allocateTimedBuffer(size, buffer); + if (result == DEAD_OBJECT) { + android_atomic_or(CBLK_INVALID_ON, &mCblk->flags); + } + } + + // If the track is invalid at this point, attempt to restore it. and try the + // allocation one more time. + if (mCblk->flags & CBLK_INVALID_MSK) { + mCblk->lock.lock(); + result = restoreTrack_l(mCblk, false); + mCblk->lock.unlock(); + + if (result == OK) + result = mAudioTrack->allocateTimedBuffer(size, buffer); + } + + return result; +} + +status_t TimedAudioTrack::queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts) +{ + // restart track if it was disabled by audioflinger due to previous underrun + if (mActive && (mCblk->flags & CBLK_DISABLED_MSK)) { + android_atomic_and(~CBLK_DISABLED_ON, &mCblk->flags); + ALOGW("queueTimedBuffer() track %p disabled, restarting", this); + mAudioTrack->start(0); + } + + return mAudioTrack->queueTimedBuffer(buffer, pts); +} + +status_t TimedAudioTrack::setMediaTimeTransform(const LinearTransform& xform, + TargetTimeline target) +{ + return mAudioTrack->setMediaTimeTransform(xform, target); +} + +// ------------------------------------------------------------------------- + bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread) { Buffer audioBuffer; diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp index 4507e5db7683..ebadbfa5499c 100644 --- a/media/libmedia/IAudioFlinger.cpp +++ b/media/libmedia/IAudioFlinger.cpp @@ -90,6 +90,7 @@ public: uint32_t flags, const sp<IMemory>& sharedBuffer, audio_io_handle_t output, + bool isTimed, int *sessionId, status_t *status) { @@ -105,6 +106,7 @@ public: data.writeInt32(flags); data.writeStrongBinder(sharedBuffer->asBinder()); data.writeInt32((int32_t) output); + data.writeInt32(isTimed); int lSessionId = 0; if (sessionId != NULL) { lSessionId = *sessionId; @@ -689,11 +691,12 @@ status_t BnAudioFlinger::onTransact( uint32_t flags = data.readInt32(); sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder()); audio_io_handle_t output = (audio_io_handle_t) data.readInt32(); + bool isTimed = data.readInt32(); int sessionId = data.readInt32(); status_t status; sp<IAudioTrack> track = createTrack(pid, (audio_stream_type_t) streamType, sampleRate, format, - channelCount, bufferCount, flags, buffer, output, &sessionId, &status); + channelCount, bufferCount, flags, buffer, output, isTimed, &sessionId, &status); reply->writeInt32(sessionId); reply->writeInt32(status); reply->writeStrongBinder(track->asBinder()); diff --git a/media/libmedia/IAudioTrack.cpp b/media/libmedia/IAudioTrack.cpp index a7958debe1ae..28ebbbfc73be 100644 --- a/media/libmedia/IAudioTrack.cpp +++ b/media/libmedia/IAudioTrack.cpp @@ -35,7 +35,10 @@ enum { FLUSH, MUTE, PAUSE, - ATTACH_AUX_EFFECT + ATTACH_AUX_EFFECT, + ALLOCATE_TIMED_BUFFER, + QUEUE_TIMED_BUFFER, + SET_MEDIA_TIME_TRANSFORM, }; class BpAudioTrack : public BpInterface<IAudioTrack> @@ -114,6 +117,52 @@ public: } return status; } + + virtual status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer) { + Parcel data, reply; + data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor()); + data.writeInt32(size); + status_t status = remote()->transact(ALLOCATE_TIMED_BUFFER, + data, &reply); + if (status == NO_ERROR) { + status = reply.readInt32(); + if (status == NO_ERROR) { + *buffer = interface_cast<IMemory>(reply.readStrongBinder()); + } + } + return status; + } + + virtual status_t queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts) { + Parcel data, reply; + data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor()); + data.writeStrongBinder(buffer->asBinder()); + data.writeInt64(pts); + status_t status = remote()->transact(QUEUE_TIMED_BUFFER, + data, &reply); + if (status == NO_ERROR) { + status = reply.readInt32(); + } + return status; + } + + virtual status_t setMediaTimeTransform(const LinearTransform& xform, + int target) { + Parcel data, reply; + data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor()); + data.writeInt64(xform.a_zero); + data.writeInt64(xform.b_zero); + data.writeInt32(xform.a_to_b_numer); + data.writeInt32(xform.a_to_b_denom); + data.writeInt32(target); + status_t status = remote()->transact(SET_MEDIA_TIME_TRANSFORM, + data, &reply); + if (status == NO_ERROR) { + status = reply.readInt32(); + } + return status; + } }; IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack"); @@ -159,10 +208,38 @@ status_t BnAudioTrack::onTransact( reply->writeInt32(attachAuxEffect(data.readInt32())); return NO_ERROR; } break; + case ALLOCATE_TIMED_BUFFER: { + CHECK_INTERFACE(IAudioTrack, data, reply); + sp<IMemory> buffer; + status_t status = allocateTimedBuffer(data.readInt32(), &buffer); + reply->writeInt32(status); + if (status == NO_ERROR) { + reply->writeStrongBinder(buffer->asBinder()); + } + return NO_ERROR; + } break; + case QUEUE_TIMED_BUFFER: { + CHECK_INTERFACE(IAudioTrack, data, reply); + sp<IMemory> buffer = interface_cast<IMemory>( + data.readStrongBinder()); + uint64_t pts = data.readInt64(); + reply->writeInt32(queueTimedBuffer(buffer, pts)); + return NO_ERROR; + } break; + case SET_MEDIA_TIME_TRANSFORM: { + CHECK_INTERFACE(IAudioTrack, data, reply); + LinearTransform xform; + xform.a_zero = data.readInt64(); + xform.b_zero = data.readInt64(); + xform.a_to_b_numer = data.readInt32(); + xform.a_to_b_denom = data.readInt32(); + int target = data.readInt32(); + reply->writeInt32(setMediaTimeTransform(xform, target)); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } } }; // namespace android - diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp index d469e280f647..5d40cc82af48 100644 --- a/media/libmedia/IEffect.cpp +++ b/media/libmedia/IEffect.cpp @@ -83,8 +83,15 @@ public: size = *pReplySize; } data.writeInt32(size); - remote()->transact(COMMAND, data, &reply); - status_t status = reply.readInt32(); + + status_t status = remote()->transact(COMMAND, data, &reply); + if (status != NO_ERROR) { + if (pReplySize != NULL) + *pReplySize = 0; + return status; + } + + status = reply.readInt32(); size = reply.readInt32(); if (size != 0 && pReplyData != NULL && pReplySize != NULL) { reply.read(pReplyData, size); diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp index 13b64e9f2917..70f8c0c28e8e 100644 --- a/media/libmedia/Visualizer.cpp +++ b/media/libmedia/Visualizer.cpp @@ -168,7 +168,7 @@ status_t Visualizer::getWaveForm(uint8_t *waveform) uint32_t replySize = mCaptureSize; status = command(VISUALIZER_CMD_CAPTURE, 0, NULL, &replySize, waveform); ALOGV("getWaveForm() command returned %d", status); - if (replySize == 0) { + if ((status == NO_ERROR) && (replySize == 0)) { status = NOT_ENOUGH_DATA; } } else { diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index a3e2517b37d2..e521648fde9f 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -29,7 +29,8 @@ LOCAL_SHARED_LIBRARIES := \ libstagefright_omx \ libstagefright_foundation \ libgui \ - libdl + libdl \ + libaah_rtp LOCAL_STATIC_LIBRARIES := \ libstagefright_nuplayer \ diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 4df7f3d654c4..764eddc840a8 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -70,6 +70,11 @@ #include <OMX.h> +namespace android { +sp<MediaPlayerBase> createAAH_TXPlayer(); +sp<MediaPlayerBase> createAAH_RXPlayer(); +} + namespace { using android::media::Metadata; using android::status_t; @@ -593,6 +598,14 @@ player_type getPlayerType(const char* url) return NU_PLAYER; } + if (!strncasecmp("aahRX://", url, 8)) { + return AAH_RX_PLAYER; + } + + if (!strncasecmp("aahTX://", url, 8)) { + return AAH_TX_PLAYER; + } + // use MidiFile for MIDI extensions int lenURL = strlen(url); for (int i = 0; i < NELEM(FILE_EXTS); ++i) { @@ -629,6 +642,14 @@ static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie, ALOGV("Create Test Player stub"); p = new TestPlayerStub(); break; + case AAH_RX_PLAYER: + ALOGV(" create A@H RX Player"); + p = createAAH_RXPlayer(); + break; + case AAH_TX_PLAYER: + ALOGV(" create A@H TX Player"); + p = createAAH_TXPlayer(); + break; default: ALOGE("Unknown player type: %d", playerType); return NULL; @@ -1031,9 +1052,21 @@ status_t MediaPlayerService::Client::setLooping(int loop) status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume) { ALOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume); - // TODO: for hardware output, call player instead - Mutex::Autolock l(mLock); - if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume); + + // for hardware output, call player instead + sp<MediaPlayerBase> p = getPlayer(); + { + Mutex::Autolock l(mLock); + if (p != 0 && p->hardwareOutput()) { + MediaPlayerHWInterface* hwp = + reinterpret_cast<MediaPlayerHWInterface*>(p.get()); + return hwp->setVolume(leftVolume, rightVolume); + } else { + if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume); + return NO_ERROR; + } + } + return NO_ERROR; } diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp index 4a56dcffde22..eec5ce1a0677 100644 --- a/opengl/libs/EGL/egl.cpp +++ b/opengl/libs/EGL/egl.cpp @@ -202,34 +202,6 @@ egl_connection_t* validate_display_config(EGLDisplay dpy, EGLConfig, // ---------------------------------------------------------------------------- -EGLImageKHR egl_get_image_for_current_context(EGLImageKHR image) -{ - EGLContext context = egl_tls_t::getContext(); - if (context == EGL_NO_CONTEXT || image == EGL_NO_IMAGE_KHR) - return EGL_NO_IMAGE_KHR; - - egl_context_t const * const c = get_context(context); - if (c == NULL) // this should never happen, by construction - return EGL_NO_IMAGE_KHR; - - egl_display_t* display = egl_display_t::get(c->dpy); - if (display == NULL) // this should never happen, by construction - return EGL_NO_IMAGE_KHR; - - ImageRef _i(display, image); - if (!_i.get()) - return EGL_NO_IMAGE_KHR; - - // here we don't validate the context because if it's been marked for - // termination, this call should still succeed since it's internal to - // EGL. - - egl_image_t const * const i = get_image(image); - return i->image; -} - -// ---------------------------------------------------------------------------- - const GLubyte * egl_get_string_for_current_context(GLenum name) { // NOTE: returning NULL here will fall-back to the default // implementation. diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp index bb2783dc26bc..a5dc832bf6de 100644 --- a/opengl/libs/EGL/eglApi.cpp +++ b/opengl/libs/EGL/eglApi.cpp @@ -627,29 +627,6 @@ EGLint eglGetError(void) return err; } -// Note: Similar implementations of these functions also exist in -// gl2.cpp and gl.cpp, and are used by applications that call the -// exported entry points directly. -typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image); -typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC) (GLenum target, GLeglImageOES image); - -static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES_impl = NULL; -static PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES_impl = NULL; - -static void glEGLImageTargetTexture2DOES_wrapper(GLenum target, GLeglImageOES image) -{ - GLeglImageOES implImage = - (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image); - glEGLImageTargetTexture2DOES_impl(target, implImage); -} - -static void glEGLImageTargetRenderbufferStorageOES_wrapper(GLenum target, GLeglImageOES image) -{ - GLeglImageOES implImage = - (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image); - glEGLImageTargetRenderbufferStorageOES_impl(target, implImage); -} - __eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname) { // eglGetProcAddress() could be the very first function called @@ -724,16 +701,6 @@ __eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname) if (found) { addr = gExtensionForwarders[slot]; - - if (!strcmp(procname, "glEGLImageTargetTexture2DOES")) { - glEGLImageTargetTexture2DOES_impl = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)addr; - addr = (__eglMustCastToProperFunctionPointerType)glEGLImageTargetTexture2DOES_wrapper; - } - if (!strcmp(procname, "glEGLImageTargetRenderbufferStorageOES")) { - glEGLImageTargetRenderbufferStorageOES_impl = (PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC)addr; - addr = (__eglMustCastToProperFunctionPointerType)glEGLImageTargetRenderbufferStorageOES_wrapper; - } - sGLExtentionMap.add(name, addr); sGLExtentionSlot++; } @@ -1024,57 +991,18 @@ EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, egl_display_t const * const dp = validate_display(dpy); if (!dp) return EGL_NO_IMAGE_KHR; - if (ctx != EGL_NO_CONTEXT) { - ContextRef _c(dp, ctx); - if (!_c.get()) - return setError(EGL_BAD_CONTEXT, EGL_NO_IMAGE_KHR); - egl_context_t * const c = get_context(ctx); - // since we have an EGLContext, we know which implementation to use - EGLImageKHR image = c->cnx->egl.eglCreateImageKHR( - dp->disp.dpy, c->context, target, buffer, attrib_list); - if (image == EGL_NO_IMAGE_KHR) - return image; - - egl_image_t* result = new egl_image_t(dpy, ctx); - result->image = image; - return (EGLImageKHR)result; - } else { - // EGL_NO_CONTEXT is a valid parameter - - /* Since we don't have a way to know which implementation to call, - * we're calling all of them. If at least one of the implementation - * succeeded, this is a success. - */ - - EGLint currentError = eglGetError(); - - EGLImageKHR implImage = EGL_NO_IMAGE_KHR; - egl_connection_t* const cnx = &gEGLImpl; - if (cnx->dso && cnx->egl.eglCreateImageKHR) { - implImage = cnx->egl.eglCreateImageKHR( - dp->disp.dpy, ctx, target, buffer, attrib_list); - } - - if (implImage == EGL_NO_IMAGE_KHR) { - // failure, if there was an error when we entered this function, - // the error flag must not be updated. - // Otherwise, the error is whatever happened in the implementation - // that faulted. - if (currentError != EGL_SUCCESS) { - setError(currentError, EGL_NO_IMAGE_KHR); - } - return EGL_NO_IMAGE_KHR; - } else { - // In case of success, we need to clear all error flags - // (especially those caused by the implementation that didn't - // succeed). - eglGetError(); - } + ContextRef _c(dp, ctx); + egl_context_t * const c = _c.get(); - egl_image_t* result = new egl_image_t(dpy, ctx); - result->image = implImage; - return (EGLImageKHR)result; + EGLImageKHR result = EGL_NO_IMAGE_KHR; + egl_connection_t* const cnx = &gEGLImpl; + if (cnx->dso && cnx->egl.eglCreateImageKHR) { + result = cnx->egl.eglCreateImageKHR( + dp->disp.dpy, + c ? c->context : EGL_NO_CONTEXT, + target, buffer, attrib_list); } + return result; } EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img) @@ -1084,27 +1012,10 @@ EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img) egl_display_t const * const dp = validate_display(dpy); if (!dp) return EGL_FALSE; - ImageRef _i(dp, img); - if (!_i.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE); - - egl_image_t* image = get_image(img); - bool success = false; - egl_connection_t* const cnx = &gEGLImpl; - if (image->image != EGL_NO_IMAGE_KHR) { - if (cnx->dso && cnx->egl.eglDestroyImageKHR) { - if (cnx->egl.eglDestroyImageKHR( - dp->disp.dpy, image->image)) { - success = true; - } - } + if (cnx->dso && cnx->egl.eglDestroyImageKHR) { + cnx->egl.eglDestroyImageKHR(dp->disp.dpy, img); } - - if (!success) - return EGL_FALSE; - - _i.terminate(); - return EGL_TRUE; } @@ -1120,21 +1031,12 @@ EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_l egl_display_t const * const dp = validate_display(dpy); if (!dp) return EGL_NO_SYNC_KHR; - EGLContext ctx = eglGetCurrentContext(); - ContextRef _c(dp, ctx); - if (!_c.get()) - return setError(EGL_BAD_CONTEXT, EGL_NO_SYNC_KHR); - - egl_context_t * const c = get_context(ctx); EGLSyncKHR result = EGL_NO_SYNC_KHR; - if (c->cnx->egl.eglCreateSyncKHR) { - EGLSyncKHR sync = c->cnx->egl.eglCreateSyncKHR( - dp->disp.dpy, type, attrib_list); - if (sync == EGL_NO_SYNC_KHR) - return sync; - result = (egl_sync_t*)new egl_sync_t(dpy, ctx, sync); + egl_connection_t* const cnx = &gEGLImpl; + if (cnx->dso && cnx->egl.eglCreateSyncKHR) { + result = cnx->egl.eglCreateSyncKHR(dp->disp.dpy, type, attrib_list); } - return (EGLSyncKHR)result; + return result; } EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync) @@ -1144,75 +1046,46 @@ EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync) egl_display_t const * const dp = validate_display(dpy); if (!dp) return EGL_FALSE; - SyncRef _s(dp, sync); - if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE); - egl_sync_t* syncObject = get_sync(sync); - - EGLContext ctx = syncObject->context; - ContextRef _c(dp, ctx); - if (!_c.get()) - return setError(EGL_BAD_CONTEXT, EGL_FALSE); - EGLBoolean result = EGL_FALSE; - egl_context_t * const c = get_context(ctx); - if (c->cnx->egl.eglDestroySyncKHR) { - result = c->cnx->egl.eglDestroySyncKHR( - dp->disp.dpy, syncObject->sync); - if (result) - _s.terminate(); + egl_connection_t* const cnx = &gEGLImpl; + if (cnx->dso && cnx->egl.eglDestroySyncKHR) { + result = cnx->egl.eglDestroySyncKHR(dp->disp.dpy, sync); } return result; } -EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout) +EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, + EGLint flags, EGLTimeKHR timeout) { clearError(); egl_display_t const * const dp = validate_display(dpy); if (!dp) return EGL_FALSE; - SyncRef _s(dp, sync); - if (!_s.get()) return setError(EGL_BAD_PARAMETER, EGL_FALSE); - egl_sync_t* syncObject = get_sync(sync); - - EGLContext ctx = syncObject->context; - ContextRef _c(dp, ctx); - if (!_c.get()) - return setError(EGL_BAD_CONTEXT, EGL_FALSE); - - egl_context_t * const c = get_context(ctx); - if (c->cnx->egl.eglClientWaitSyncKHR) { - return c->cnx->egl.eglClientWaitSyncKHR( - dp->disp.dpy, syncObject->sync, flags, timeout); + EGLBoolean result = EGL_FALSE; + egl_connection_t* const cnx = &gEGLImpl; + if (cnx->dso && cnx->egl.eglClientWaitSyncKHR) { + result = cnx->egl.eglClientWaitSyncKHR( + dp->disp.dpy, sync, flags, timeout); } - - return EGL_FALSE; + return result; } -EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value) +EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, + EGLint attribute, EGLint *value) { clearError(); egl_display_t const * const dp = validate_display(dpy); if (!dp) return EGL_FALSE; - SyncRef _s(dp, sync); - if (!_s.get()) - return setError(EGL_BAD_PARAMETER, EGL_FALSE); - - egl_sync_t* syncObject = get_sync(sync); - EGLContext ctx = syncObject->context; - ContextRef _c(dp, ctx); - if (!_c.get()) - return setError(EGL_BAD_CONTEXT, EGL_FALSE); - - egl_context_t * const c = get_context(ctx); - if (c->cnx->egl.eglGetSyncAttribKHR) { - return c->cnx->egl.eglGetSyncAttribKHR( - dp->disp.dpy, syncObject->sync, attribute, value); + EGLBoolean result = EGL_FALSE; + egl_connection_t* const cnx = &gEGLImpl; + if (cnx->dso && cnx->egl.eglGetSyncAttribKHR) { + result = cnx->egl.eglGetSyncAttribKHR( + dp->disp.dpy, sync, attribute, value); } - - return EGL_FALSE; + return result; } // ---------------------------------------------------------------------------- diff --git a/opengl/libs/EGL/egl_object.h b/opengl/libs/EGL/egl_object.h index f137badd1190..4d91f5417b0b 100644 --- a/opengl/libs/EGL/egl_object.h +++ b/opengl/libs/EGL/egl_object.h @@ -173,40 +173,10 @@ public: String8 gl_extensions; }; -class egl_image_t: public egl_object_t { -protected: - ~egl_image_t() {} -public: - typedef egl_object_t::LocalRef<egl_image_t, EGLImageKHR> Ref; - - egl_image_t(EGLDisplay dpy, EGLContext context) : - egl_object_t(get_display(dpy)), - dpy(dpy), context(context), image(EGL_NO_IMAGE_KHR) { } - EGLDisplay dpy; - EGLContext context; - EGLImageKHR image; -}; - -class egl_sync_t: public egl_object_t { -protected: - ~egl_sync_t() {} -public: - typedef egl_object_t::LocalRef<egl_sync_t, EGLSyncKHR> Ref; - - egl_sync_t(EGLDisplay dpy, EGLContext context, EGLSyncKHR sync) : - egl_object_t(get_display(dpy)), dpy(dpy), context(context), sync(sync) { - } - EGLDisplay dpy; - EGLContext context; - EGLSyncKHR sync; -}; - // ---------------------------------------------------------------------------- typedef egl_surface_t::Ref SurfaceRef; typedef egl_context_t::Ref ContextRef; -typedef egl_image_t::Ref ImageRef; -typedef egl_sync_t::Ref SyncRef; // ---------------------------------------------------------------------------- @@ -225,16 +195,6 @@ egl_context_t* get_context(EGLContext context) { return egl_to_native_cast<egl_context_t>(context); } -static inline -egl_image_t* get_image(EGLImageKHR image) { - return egl_to_native_cast<egl_image_t>(image); -} - -static inline -egl_sync_t* get_sync(EGLSyncKHR sync) { - return egl_to_native_cast<egl_sync_t>(sync); -} - // ---------------------------------------------------------------------------- }; // namespace android // ---------------------------------------------------------------------------- diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp index 79aa3cd59eeb..4345c2b89463 100644 --- a/opengl/libs/GLES2/gl2.cpp +++ b/opengl/libs/GLES2/gl2.cpp @@ -124,27 +124,3 @@ const GLubyte * glGetString(GLenum name) } return ret; } - -/* - * These GL calls are special because they need to EGL to retrieve some - * informations before they can execute. - */ - -extern "C" void __glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image); -extern "C" void __glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image); - - -void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) -{ - GLeglImageOES implImage = - (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image); - __glEGLImageTargetTexture2DOES(target, implImage); -} - -void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image) -{ - GLeglImageOES implImage = - (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image); - __glEGLImageTargetRenderbufferStorageOES(target, implImage); -} - diff --git a/opengl/libs/GLES2/gl2ext_api.in b/opengl/libs/GLES2/gl2ext_api.in index a8907fd40bbe..c381075b9862 100644 --- a/opengl/libs/GLES2/gl2ext_api.in +++ b/opengl/libs/GLES2/gl2ext_api.in @@ -1,7 +1,7 @@ -void API_ENTRY(__glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) { +void API_ENTRY(glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) { CALL_GL_API(glEGLImageTargetTexture2DOES, target, image); } -void API_ENTRY(__glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) { +void API_ENTRY(glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) { CALL_GL_API(glEGLImageTargetRenderbufferStorageOES, target, image); } void API_ENTRY(glGetProgramBinaryOES)(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary) { diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp index adeaa5b8233d..adcb60d711f2 100644 --- a/opengl/libs/GLES_CM/gl.cpp +++ b/opengl/libs/GLES_CM/gl.cpp @@ -179,27 +179,3 @@ const GLubyte * glGetString(GLenum name) } return ret; } - -/* - * These GL calls are special because they need to EGL to retrieve some - * informations before they can execute. - */ - -extern "C" void __glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image); -extern "C" void __glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image); - - -void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) -{ - GLeglImageOES implImage = - (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image); - __glEGLImageTargetTexture2DOES(target, implImage); -} - -void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image) -{ - GLeglImageOES implImage = - (GLeglImageOES)egl_get_image_for_current_context((EGLImageKHR)image); - __glEGLImageTargetRenderbufferStorageOES(target, implImage); -} - diff --git a/opengl/libs/GLES_CM/glext_api.in b/opengl/libs/GLES_CM/glext_api.in index 268a5352bab5..7cd6cb539251 100644 --- a/opengl/libs/GLES_CM/glext_api.in +++ b/opengl/libs/GLES_CM/glext_api.in @@ -31,10 +31,10 @@ void API_ENTRY(glDrawTexfOES)(GLfloat x, GLfloat y, GLfloat z, GLfloat width, GL void API_ENTRY(glDrawTexfvOES)(const GLfloat *coords) { CALL_GL_API(glDrawTexfvOES, coords); } -void API_ENTRY(__glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) { +void API_ENTRY(glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) { CALL_GL_API(glEGLImageTargetTexture2DOES, target, image); } -void API_ENTRY(__glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) { +void API_ENTRY(glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) { CALL_GL_API(glEGLImageTargetRenderbufferStorageOES, target, image); } void API_ENTRY(glAlphaFuncxOES)(GLenum func, GLclampx ref) { diff --git a/opengl/libs/egl_impl.h b/opengl/libs/egl_impl.h index 8ff51eca4b7a..cb0e908d3140 100644 --- a/opengl/libs/egl_impl.h +++ b/opengl/libs/egl_impl.h @@ -30,7 +30,6 @@ namespace android { // ---------------------------------------------------------------------------- EGLAPI const GLubyte * egl_get_string_for_current_context(GLenum name); -EGLAPI EGLImageKHR egl_get_image_for_current_context(EGLImageKHR image); // ---------------------------------------------------------------------------- }; // namespace android diff --git a/opengl/libs/tools/glapigen b/opengl/libs/tools/glapigen index 9be40cf67d48..4d8334f90f0c 100755 --- a/opengl/libs/tools/glapigen +++ b/opengl/libs/tools/glapigen @@ -37,12 +37,6 @@ while (my $line = <>) { #printf("%s", $line); my $prefix = ""; - if ($name eq "glEGLImageTargetTexture2DOES") { - $prefix = "__"; - } - if ($name eq "glEGLImageTargetRenderbufferStorageOES") { - $prefix = "__"; - } if ($name eq "glGetString") { $prefix = "__"; } diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 3beaac998b75..3a8e3fc6fcff 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -148,4 +148,7 @@ <!-- Development settings --> <bool name="def_stay_on_while_plugged_in">false</bool> + <!-- Number of retries for connecting to DHCP. + Value here is the same as WifiStateMachine.DEFAULT_MAX_DHCP_RETRIES --> + <integer name="def_max_dhcp_retries">9</integer> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 882aa6624a28..330a1899a0b0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -1602,6 +1602,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadBooleanSetting(stmt, Settings.Secure.NETSTATS_ENABLED, R.bool.def_netstats_enabled); + + loadIntegerSetting(stmt, Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT, + R.integer.def_max_dhcp_retries); } finally { if (stmt != null) stmt.close(); } diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java index f8a45923349b..dc2f0be40542 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java +++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java @@ -134,7 +134,9 @@ import android.view.View; void jumpTo(boolean appearing) { mContentView.setTranslationY(appearing ? 0 : mPanelHeight); - mScrimView.getBackground().setAlpha(appearing ? 255 : 0); + if (mScrimView.getBackground() != null) { + mScrimView.getBackground().setAlpha(appearing ? 255 : 0); + } } public void setPanelHeight(int h) { diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index 8706f10128b3..78967203e889 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -239,6 +239,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener public void show(boolean show, boolean animate) { if (show) { + refreshRecentTasksList(null, true); mWaitingToShow = true; mWaitingToShowAnimated = animate; showIfReady(); diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk index 157405a2168c..22fa7520ee1a 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -7,6 +7,7 @@ LOCAL_SRC_FILES:= \ AudioMixer.cpp.arm \ AudioResampler.cpp.arm \ AudioPolicyService.cpp \ + AudioBufferProvider.cpp \ ServiceUtilities.cpp # AudioResamplerSinc.cpp.arm # AudioResamplerCubic.cpp.arm @@ -17,6 +18,7 @@ LOCAL_C_INCLUDES := \ LOCAL_SHARED_LIBRARIES := \ libaudioutils \ + libcommon_time_client \ libcutils \ libutils \ libbinder \ diff --git a/services/audioflinger/AudioBufferProvider.cpp b/services/audioflinger/AudioBufferProvider.cpp new file mode 100644 index 000000000000..678fd58b4e47 --- /dev/null +++ b/services/audioflinger/AudioBufferProvider.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef __STRICT_ANSI__ +#define __STDINT_LIMITS +#define __STDC_LIMIT_MACROS +#include <stdint.h> + +#include "AudioBufferProvider.h" + +namespace android { + +const int64_t AudioBufferProvider::kInvalidPTS = INT64_MAX; + +}; // namespace android diff --git a/services/audioflinger/AudioBufferProvider.h b/services/audioflinger/AudioBufferProvider.h index 81c5c3959930..62ad6bd727b8 100644 --- a/services/audioflinger/AudioBufferProvider.h +++ b/services/audioflinger/AudioBufferProvider.h @@ -38,8 +38,15 @@ public: }; virtual ~AudioBufferProvider() {} - - virtual status_t getNextBuffer(Buffer* buffer) = 0; + + // value representing an invalid presentation timestamp + static const int64_t kInvalidPTS; + + // pts is the local time when the next sample yielded by getNextBuffer + // will be rendered. + // Pass kInvalidPTS if the PTS is unknown or not applicable. + virtual status_t getNextBuffer(Buffer* buffer, int64_t pts) = 0; + virtual void releaseBuffer(Buffer* buffer) = 0; }; diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 024868729adb..29d63de1bf3c 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -61,6 +61,9 @@ #include <powermanager/PowerManager.h> // #define DEBUG_CPU_USAGE 10 // log statistics every n wall clock seconds +#include <common_time/cc_helper.h> +#include <common_time/local_clock.h> + // ---------------------------------------------------------------------------- @@ -69,7 +72,6 @@ namespace android { static const char kDeadlockedString[] = "AudioFlinger may be deadlocked\n"; static const char kHardwareLockedString[] = "Hardware lock is taken\n"; -//static const nsecs_t kStandbyTimeInNsecs = seconds(3); static const float MAX_GAIN = 4096.0f; static const uint32_t MAX_GAIN_INT = 0x1000; @@ -99,6 +101,7 @@ static const uint32_t kMinThreadSleepTimeUs = 5000; // maximum divider applied to the active sleep time in the mixer thread loop static const uint32_t kMaxThreadSleepTimeShift = 2; +nsecs_t AudioFlinger::mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs; // ---------------------------------------------------------------------------- @@ -147,11 +150,14 @@ static const char * const audio_interfaces[] = { AudioFlinger::AudioFlinger() : BnAudioFlinger(), - mPrimaryHardwareDev(NULL), - mHardwareStatus(AUDIO_HW_IDLE), // see also onFirstRef() - mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1), - mMode(AUDIO_MODE_INVALID), - mBtNrecIsOff(false) + mPrimaryHardwareDev(NULL), + mHardwareStatus(AUDIO_HW_IDLE), // see also onFirstRef() + mMasterVolume(1.0f), + mMasterVolumeSupportLvl(MVS_NONE), + mMasterMute(false), + mNextUniqueId(1), + mMode(AUDIO_MODE_INVALID), + mBtNrecIsOff(false) { } @@ -162,6 +168,18 @@ void AudioFlinger::onFirstRef() Mutex::Autolock _l(mLock); /* TODO: move all this work into an Init() function */ + char val_str[PROPERTY_VALUE_MAX] = { 0 }; + if (property_get("ro.audio.flinger_standbytime_ms", val_str, NULL) >= 0) { + uint32_t int_val; + if (1 == sscanf(val_str, "%u", &int_val)) { + mStandbyTimeInNsecs = milliseconds(int_val); + ALOGI("Using %u mSec as standby time.", int_val); + } else { + mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs; + ALOGI("Using default %u mSec as standby time.", + (uint32_t)(mStandbyTimeInNsecs / 1000000)); + } + } for (size_t i = 0; i < ARRAY_SIZE(audio_interfaces); i++) { const hw_module_t *mod; @@ -193,6 +211,32 @@ void AudioFlinger::onFirstRef() AutoMutex lock(mHardwareLock); + // Determine the level of master volume support the primary audio HAL has, + // and set the initial master volume at the same time. + float initialVolume = 1.0; + mMasterVolumeSupportLvl = MVS_NONE; + if (0 == mPrimaryHardwareDev->init_check(mPrimaryHardwareDev)) { + audio_hw_device_t *dev = mPrimaryHardwareDev; + + mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME; + if ((NULL != dev->get_master_volume) && + (NO_ERROR == dev->get_master_volume(dev, &initialVolume))) { + mMasterVolumeSupportLvl = MVS_FULL; + } else { + mMasterVolumeSupportLvl = MVS_SETONLY; + initialVolume = 1.0; + } + + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + if ((NULL == dev->set_master_volume) || + (NO_ERROR != dev->set_master_volume(dev, initialVolume))) { + mMasterVolumeSupportLvl = MVS_NONE; + } + mHardwareStatus = AUDIO_HW_INIT; + } + + // Set the mode for each audio HAL, and try to set the initial volume (if + // supported) for all of the non-primary audio HALs. for (size_t i = 0; i < mAudioHwDevs.size(); i++) { audio_hw_device_t *dev = mAudioHwDevs[i]; @@ -203,11 +247,22 @@ void AudioFlinger::onFirstRef() mMode = AUDIO_MODE_NORMAL; // assigned multiple times with same value mHardwareStatus = AUDIO_HW_SET_MODE; dev->set_mode(dev, mMode); - mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; - dev->set_master_volume(dev, 1.0f); - mHardwareStatus = AUDIO_HW_IDLE; + + if ((dev != mPrimaryHardwareDev) && + (NULL != dev->set_master_volume)) { + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + dev->set_master_volume(dev, initialVolume); + } + + mHardwareStatus = AUDIO_HW_INIT; } } + + mMasterVolumeSW = (MVS_NONE == mMasterVolumeSupportLvl) + ? initialVolume + : 1.0; + mMasterVolume = initialVolume; + mHardwareStatus = AUDIO_HW_IDLE; } AudioFlinger::~AudioFlinger() @@ -273,7 +328,10 @@ status_t AudioFlinger::dumpInternals(int fd, const Vector<String16>& args) String8 result; hardware_call_state hardwareStatus = mHardwareStatus; - snprintf(buffer, SIZE, "Hardware status: %d\n", hardwareStatus); + snprintf(buffer, SIZE, "Hardware status: %d\n" + "Standby Time mSec: %u\n", + hardwareStatus, + (uint32_t)(mStandbyTimeInNsecs / 1000000)); result.append(buffer); write(fd, result.string(), result.size()); return NO_ERROR; @@ -377,6 +435,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( uint32_t flags, const sp<IMemory>& sharedBuffer, audio_io_handle_t output, + bool isTimed, int *sessionId, status_t *status) { @@ -435,7 +494,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( ALOGV("createTrack() lSessionId: %d", lSessionId); track = thread->createTrack_l(client, streamType, sampleRate, format, - channelMask, frameCount, sharedBuffer, lSessionId, &lStatus); + channelMask, frameCount, sharedBuffer, lSessionId, isTimed, &lStatus); // move effect chain to this output thread if an effect on same session was waiting // for a track to be created @@ -528,20 +587,29 @@ status_t AudioFlinger::setMasterVolume(float value) return PERMISSION_DENIED; } + float swmv = value; + // when hw supports master volume, don't scale in sw mixer - { // scope for the lock - AutoMutex lock(mHardwareLock); - mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; - if (mPrimaryHardwareDev->set_master_volume(mPrimaryHardwareDev, value) == NO_ERROR) { - value = 1.0f; + if (MVS_NONE != mMasterVolumeSupportLvl) { + for (size_t i = 0; i < mAudioHwDevs.size(); i++) { + AutoMutex lock(mHardwareLock); + audio_hw_device_t *dev = mAudioHwDevs[i]; + + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + if (NULL != dev->set_master_volume) { + dev->set_master_volume(dev, value); + } + mHardwareStatus = AUDIO_HW_IDLE; } - mHardwareStatus = AUDIO_HW_IDLE; + + swmv = 1.0; } Mutex::Autolock _l(mLock); - mMasterVolume = value; + mMasterVolume = value; + mMasterVolumeSW = swmv; for (size_t i = 0; i < mPlaybackThreads.size(); i++) - mPlaybackThreads.valueAt(i)->setMasterVolume(value); + mPlaybackThreads.valueAt(i)->setMasterVolume(swmv); return NO_ERROR; } @@ -635,12 +703,36 @@ float AudioFlinger::masterVolume() const return masterVolume_l(); } +float AudioFlinger::masterVolumeSW() const +{ + Mutex::Autolock _l(mLock); + return masterVolumeSW_l(); +} + bool AudioFlinger::masterMute() const { Mutex::Autolock _l(mLock); return masterMute_l(); } +float AudioFlinger::masterVolume_l() const +{ + if (MVS_FULL == mMasterVolumeSupportLvl) { + float ret_val; + AutoMutex lock(mHardwareLock); + + mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME; + assert(NULL != mPrimaryHardwareDev); + assert(NULL != mPrimaryHardwareDev->get_master_volume); + + mPrimaryHardwareDev->get_master_volume(mPrimaryHardwareDev, &ret_val); + mHardwareStatus = AUDIO_HW_IDLE; + return ret_val; + } + + return mMasterVolume; +} + status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value, audio_io_handle_t output) { @@ -814,7 +906,7 @@ String8 AudioFlinger::getParameters(audio_io_handle_t ioHandle, const String8& k for (size_t i = 0; i < mAudioHwDevs.size(); i++) { audio_hw_device_t *dev = mAudioHwDevs[i]; char *s = dev->get_parameters(dev, keys.string()); - out_s8 += String8(s); + out_s8 += String8(s ? s : ""); free(s); } return out_s8; @@ -1367,7 +1459,7 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge mOutput(output), // Assumes constructor is called by AudioFlinger with it's mLock held, // but it would be safer to explicitly pass initial masterVolume as parameter - mMasterVolume(audioFlinger->masterVolume_l()), + mMasterVolume(audioFlinger->masterVolumeSW_l()), mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false) { snprintf(mName, kNameLength, "AudioOut_%d", id); @@ -1485,6 +1577,7 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra int frameCount, const sp<IMemory>& sharedBuffer, int sessionId, + bool isTimed, status_t *status) { sp<Track> track; @@ -1535,9 +1628,14 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra } } - track = new Track(this, client, streamType, sampleRate, format, - channelMask, frameCount, sharedBuffer, sessionId); - if (track->getCblk() == NULL || track->name() < 0) { + if (!isTimed) { + track = new Track(this, client, streamType, sampleRate, format, + channelMask, frameCount, sharedBuffer, sessionId); + } else { + track = TimedTrack::create(this, client, streamType, sampleRate, format, + channelMask, frameCount, sharedBuffer, sessionId); + } + if (track == NULL || track->getCblk() == NULL || track->name() < 0) { lStatus = NO_MEMORY; goto Exit; } @@ -1941,7 +2039,7 @@ bool AudioFlinger::MixerThread::threadLoop() } } - standbyTime = systemTime() + kStandbyTimeInNsecs; + standbyTime = systemTime() + mStandbyTimeInNsecs; sleepTime = idleSleepTime; sleepTimeShift = 0; continue; @@ -1957,8 +2055,21 @@ bool AudioFlinger::MixerThread::threadLoop() } if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) { + // obtain the presentation timestamp of the next output buffer + int64_t pts; + status_t status = INVALID_OPERATION; + + if (NULL != mOutput->stream->get_next_write_timestamp) { + status = mOutput->stream->get_next_write_timestamp( + mOutput->stream, &pts); + } + + if (status != NO_ERROR) { + pts = AudioBufferProvider::kInvalidPTS; + } + // mix buffers... - mAudioMixer->process(); + mAudioMixer->process(pts); // increase sleep time progressively when application underrun condition clears. // Only increase sleep time if the mixer is ready for two consecutive times to avoid // that a steady state of alternating ready/not ready conditions keeps the sleep time @@ -1967,7 +2078,7 @@ bool AudioFlinger::MixerThread::threadLoop() sleepTimeShift--; } sleepTime = 0; - standbyTime = systemTime() + kStandbyTimeInNsecs; + standbyTime = systemTime() + mStandbyTimeInNsecs; //TODO: delay standby when effects have a tail } else { // If no tracks are ready, sleep once for the duration of an output @@ -2114,7 +2225,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac ALOG_ASSERT(minFrames <= cblk->frameCount); } } - if ((cblk->framesReady() >= minFrames) && track->isReady() && + if ((track->framesReady() >= minFrames) && track->isReady() && !track->isPaused() && !track->isTerminated()) { //ALOGV("track %d u=%08x, s=%08x [OK] on thread %p", name, cblk->user, cblk->server, this); @@ -2785,7 +2896,8 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // output audio to hardware while (frameCount) { buffer.frameCount = frameCount; - activeTrack->getNextBuffer(&buffer); + activeTrack->getNextBuffer(&buffer, + AudioBufferProvider::kInvalidPTS); if (CC_UNLIKELY(buffer.raw == NULL)) { memset(curBuf, 0, frameCount * mFrameSize); break; @@ -3038,7 +3150,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop() } } - standbyTime = systemTime() + kStandbyTimeInNsecs; + standbyTime = systemTime() + mStandbyTimeInNsecs; sleepTime = idleSleepTime; continue; } @@ -3055,7 +3167,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop() if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) { // mix buffers... if (outputsReady(outputTracks)) { - mAudioMixer->process(); + mAudioMixer->process(AudioBufferProvider::kInvalidPTS); } else { memset(mMixBuffer, 0, mixBufferSize); } @@ -3092,7 +3204,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop() // enable changes in effect chain unlockEffectChains(effectChains); - standbyTime = systemTime() + kStandbyTimeInNsecs; + standbyTime = systemTime() + mStandbyTimeInNsecs; for (size_t i = 0; i < outputTracks.size(); i++) { outputTracks[i]->write(mMixBuffer, writeFrames); } @@ -3443,7 +3555,8 @@ void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size) (int)mAuxBuffer); } -status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer) +status_t AudioFlinger::PlaybackThread::Track::getNextBuffer( + AudioBufferProvider::Buffer* buffer, int64_t pts) { audio_track_cblk_t* cblk = this->cblk(); uint32_t framesReady; @@ -3484,10 +3597,14 @@ getNextBuffer_exit: return NOT_ENOUGH_DATA; } +uint32_t AudioFlinger::PlaybackThread::Track::framesReady() const{ + return mCblk->framesReady(); +} + bool AudioFlinger::PlaybackThread::Track::isReady() const { if (mFillingUpStatus != FS_FILLING || isStopped() || isPausing()) return true; - if (mCblk->framesReady() >= mCblk->frameCount || + if (framesReady() >= mCblk->frameCount || (mCblk->flags & CBLK_FORCEREADY_MSK)) { mFillingUpStatus = FS_FILLED; android_atomic_and(~CBLK_FORCEREADY_MSK, &mCblk->flags); @@ -3644,6 +3761,393 @@ void AudioFlinger::PlaybackThread::Track::setAuxBuffer(int EffectId, int32_t *bu mAuxBuffer = buffer; } +// timed audio tracks + +sp<AudioFlinger::PlaybackThread::TimedTrack> +AudioFlinger::PlaybackThread::TimedTrack::create( + const wp<ThreadBase>& thread, + const sp<Client>& client, + audio_stream_type_t streamType, + uint32_t sampleRate, + audio_format_t format, + uint32_t channelMask, + int frameCount, + const sp<IMemory>& sharedBuffer, + int sessionId) { + if (!client->reserveTimedTrack()) + return NULL; + + sp<TimedTrack> track = new TimedTrack( + thread, client, streamType, sampleRate, format, channelMask, frameCount, + sharedBuffer, sessionId); + + if (track == NULL) { + client->releaseTimedTrack(); + return NULL; + } + + return track; +} + +AudioFlinger::PlaybackThread::TimedTrack::TimedTrack( + const wp<ThreadBase>& thread, + const sp<Client>& client, + audio_stream_type_t streamType, + uint32_t sampleRate, + audio_format_t format, + uint32_t channelMask, + int frameCount, + const sp<IMemory>& sharedBuffer, + int sessionId) + : Track(thread, client, streamType, sampleRate, format, channelMask, + frameCount, sharedBuffer, sessionId), + mTimedSilenceBuffer(NULL), + mTimedSilenceBufferSize(0), + mTimedAudioOutputOnTime(false), + mMediaTimeTransformValid(false) +{ + LocalClock lc; + mLocalTimeFreq = lc.getLocalFreq(); + + mLocalTimeToSampleTransform.a_zero = 0; + mLocalTimeToSampleTransform.b_zero = 0; + mLocalTimeToSampleTransform.a_to_b_numer = sampleRate; + mLocalTimeToSampleTransform.a_to_b_denom = mLocalTimeFreq; + LinearTransform::reduce(&mLocalTimeToSampleTransform.a_to_b_numer, + &mLocalTimeToSampleTransform.a_to_b_denom); +} + +AudioFlinger::PlaybackThread::TimedTrack::~TimedTrack() { + mClient->releaseTimedTrack(); + delete [] mTimedSilenceBuffer; +} + +status_t AudioFlinger::PlaybackThread::TimedTrack::allocateTimedBuffer( + size_t size, sp<IMemory>* buffer) { + + Mutex::Autolock _l(mTimedBufferQueueLock); + + trimTimedBufferQueue_l(); + + // lazily initialize the shared memory heap for timed buffers + if (mTimedMemoryDealer == NULL) { + const int kTimedBufferHeapSize = 512 << 10; + + mTimedMemoryDealer = new MemoryDealer(kTimedBufferHeapSize, + "AudioFlingerTimed"); + if (mTimedMemoryDealer == NULL) + return NO_MEMORY; + } + + sp<IMemory> newBuffer = mTimedMemoryDealer->allocate(size); + if (newBuffer == NULL) { + newBuffer = mTimedMemoryDealer->allocate(size); + if (newBuffer == NULL) + return NO_MEMORY; + } + + *buffer = newBuffer; + return NO_ERROR; +} + +// caller must hold mTimedBufferQueueLock +void AudioFlinger::PlaybackThread::TimedTrack::trimTimedBufferQueue_l() { + int64_t mediaTimeNow; + { + Mutex::Autolock mttLock(mMediaTimeTransformLock); + if (!mMediaTimeTransformValid) + return; + + int64_t targetTimeNow; + status_t res = (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME) + ? mCCHelper.getCommonTime(&targetTimeNow) + : mCCHelper.getLocalTime(&targetTimeNow); + + if (OK != res) + return; + + if (!mMediaTimeTransform.doReverseTransform(targetTimeNow, + &mediaTimeNow)) { + return; + } + } + + size_t trimIndex; + for (trimIndex = 0; trimIndex < mTimedBufferQueue.size(); trimIndex++) { + if (mTimedBufferQueue[trimIndex].pts() > mediaTimeNow) + break; + } + + if (trimIndex) { + mTimedBufferQueue.removeItemsAt(0, trimIndex); + } +} + +status_t AudioFlinger::PlaybackThread::TimedTrack::queueTimedBuffer( + const sp<IMemory>& buffer, int64_t pts) { + + { + Mutex::Autolock mttLock(mMediaTimeTransformLock); + if (!mMediaTimeTransformValid) + return INVALID_OPERATION; + } + + Mutex::Autolock _l(mTimedBufferQueueLock); + + mTimedBufferQueue.add(TimedBuffer(buffer, pts)); + + return NO_ERROR; +} + +status_t AudioFlinger::PlaybackThread::TimedTrack::setMediaTimeTransform( + const LinearTransform& xform, TimedAudioTrack::TargetTimeline target) { + + ALOGV("%s az=%lld bz=%lld n=%d d=%u tgt=%d", __PRETTY_FUNCTION__, + xform.a_zero, xform.b_zero, xform.a_to_b_numer, xform.a_to_b_denom, + target); + + if (!(target == TimedAudioTrack::LOCAL_TIME || + target == TimedAudioTrack::COMMON_TIME)) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mMediaTimeTransformLock); + mMediaTimeTransform = xform; + mMediaTimeTransformTarget = target; + mMediaTimeTransformValid = true; + + return NO_ERROR; +} + +#define min(a, b) ((a) < (b) ? (a) : (b)) + +// implementation of getNextBuffer for tracks whose buffers have timestamps +status_t AudioFlinger::PlaybackThread::TimedTrack::getNextBuffer( + AudioBufferProvider::Buffer* buffer, int64_t pts) +{ + if (pts == AudioBufferProvider::kInvalidPTS) { + buffer->raw = 0; + buffer->frameCount = 0; + return INVALID_OPERATION; + } + + // get ahold of the output stream that these samples will be written to + sp<ThreadBase> thread = mThread.promote(); + if (thread == NULL) { + buffer->raw = 0; + buffer->frameCount = 0; + return INVALID_OPERATION; + } + PlaybackThread* playbackThread = static_cast<PlaybackThread*>(thread.get()); + + Mutex::Autolock _l(mTimedBufferQueueLock); + + while (true) { + + // if we have no timed buffers, then fail + if (mTimedBufferQueue.isEmpty()) { + buffer->raw = 0; + buffer->frameCount = 0; + return NOT_ENOUGH_DATA; + } + + TimedBuffer& head = mTimedBufferQueue.editItemAt(0); + + // calculate the PTS of the head of the timed buffer queue expressed in + // local time + int64_t headLocalPTS; + { + Mutex::Autolock mttLock(mMediaTimeTransformLock); + + assert(mMediaTimeTransformValid); + + if (mMediaTimeTransform.a_to_b_denom == 0) { + // the transform represents a pause, so yield silence + timedYieldSilence(buffer->frameCount, buffer); + return NO_ERROR; + } + + int64_t transformedPTS; + if (!mMediaTimeTransform.doForwardTransform(head.pts(), + &transformedPTS)) { + // the transform failed. this shouldn't happen, but if it does + // then just drop this buffer + ALOGW("timedGetNextBuffer transform failed"); + buffer->raw = 0; + buffer->frameCount = 0; + mTimedBufferQueue.removeAt(0); + return NO_ERROR; + } + + if (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME) { + if (OK != mCCHelper.commonTimeToLocalTime(transformedPTS, + &headLocalPTS)) { + buffer->raw = 0; + buffer->frameCount = 0; + return INVALID_OPERATION; + } + } else { + headLocalPTS = transformedPTS; + } + } + + // adjust the head buffer's PTS to reflect the portion of the head buffer + // that has already been consumed + int64_t effectivePTS = headLocalPTS + + ((head.position() / mCblk->frameSize) * mLocalTimeFreq / sampleRate()); + + // Calculate the delta in samples between the head of the input buffer + // queue and the start of the next output buffer that will be written. + // If the transformation fails because of over or underflow, it means + // that the sample's position in the output stream is so far out of + // whack that it should just be dropped. + int64_t sampleDelta; + if (llabs(effectivePTS - pts) >= (static_cast<int64_t>(1) << 31)) { + ALOGV("*** head buffer is too far from PTS: dropped buffer"); + mTimedBufferQueue.removeAt(0); + continue; + } + if (!mLocalTimeToSampleTransform.doForwardTransform( + (effectivePTS - pts) << 32, &sampleDelta)) { + ALOGV("*** too late during sample rate transform: dropped buffer"); + mTimedBufferQueue.removeAt(0); + continue; + } + + ALOGV("*** %s head.pts=%lld head.pos=%d pts=%lld sampleDelta=[%d.%08x]", + __PRETTY_FUNCTION__, head.pts(), head.position(), pts, + static_cast<int32_t>((sampleDelta >= 0 ? 0 : 1) + (sampleDelta >> 32)), + static_cast<uint32_t>(sampleDelta & 0xFFFFFFFF)); + + // if the delta between the ideal placement for the next input sample and + // the current output position is within this threshold, then we will + // concatenate the next input samples to the previous output + const int64_t kSampleContinuityThreshold = + (static_cast<int64_t>(sampleRate()) << 32) / 10; + + // if this is the first buffer of audio that we're emitting from this track + // then it should be almost exactly on time. + const int64_t kSampleStartupThreshold = 1LL << 32; + + if ((mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleContinuityThreshold) || + (!mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleStartupThreshold)) { + // the next input is close enough to being on time, so concatenate it + // with the last output + timedYieldSamples(buffer); + + ALOGV("*** on time: head.pos=%d frameCount=%u", head.position(), buffer->frameCount); + return NO_ERROR; + } else if (sampleDelta > 0) { + // the gap between the current output position and the proper start of + // the next input sample is too big, so fill it with silence + uint32_t framesUntilNextInput = (sampleDelta + 0x80000000) >> 32; + + timedYieldSilence(framesUntilNextInput, buffer); + ALOGV("*** silence: frameCount=%u", buffer->frameCount); + return NO_ERROR; + } else { + // the next input sample is late + uint32_t lateFrames = static_cast<uint32_t>(-((sampleDelta + 0x80000000) >> 32)); + size_t onTimeSamplePosition = + head.position() + lateFrames * mCblk->frameSize; + + if (onTimeSamplePosition > head.buffer()->size()) { + // all the remaining samples in the head are too late, so + // drop it and move on + ALOGV("*** too late: dropped buffer"); + mTimedBufferQueue.removeAt(0); + continue; + } else { + // skip over the late samples + head.setPosition(onTimeSamplePosition); + + // yield the available samples + timedYieldSamples(buffer); + + ALOGV("*** late: head.pos=%d frameCount=%u", head.position(), buffer->frameCount); + return NO_ERROR; + } + } + } +} + +// Yield samples from the timed buffer queue head up to the given output +// buffer's capacity. +// +// Caller must hold mTimedBufferQueueLock +void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSamples( + AudioBufferProvider::Buffer* buffer) { + + const TimedBuffer& head = mTimedBufferQueue[0]; + + buffer->raw = (static_cast<uint8_t*>(head.buffer()->pointer()) + + head.position()); + + uint32_t framesLeftInHead = ((head.buffer()->size() - head.position()) / + mCblk->frameSize); + size_t framesRequested = buffer->frameCount; + buffer->frameCount = min(framesLeftInHead, framesRequested); + + mTimedAudioOutputOnTime = true; +} + +// Yield samples of silence up to the given output buffer's capacity +// +// Caller must hold mTimedBufferQueueLock +void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSilence( + uint32_t numFrames, AudioBufferProvider::Buffer* buffer) { + + // lazily allocate a buffer filled with silence + if (mTimedSilenceBufferSize < numFrames * mCblk->frameSize) { + delete [] mTimedSilenceBuffer; + mTimedSilenceBufferSize = numFrames * mCblk->frameSize; + mTimedSilenceBuffer = new uint8_t[mTimedSilenceBufferSize]; + memset(mTimedSilenceBuffer, 0, mTimedSilenceBufferSize); + } + + buffer->raw = mTimedSilenceBuffer; + size_t framesRequested = buffer->frameCount; + buffer->frameCount = min(numFrames, framesRequested); + + mTimedAudioOutputOnTime = false; +} + +void AudioFlinger::PlaybackThread::TimedTrack::releaseBuffer( + AudioBufferProvider::Buffer* buffer) { + + Mutex::Autolock _l(mTimedBufferQueueLock); + + if (buffer->raw != mTimedSilenceBuffer) { + TimedBuffer& head = mTimedBufferQueue.editItemAt(0); + head.setPosition(head.position() + buffer->frameCount * mCblk->frameSize); + if (static_cast<size_t>(head.position()) >= head.buffer()->size()) { + mTimedBufferQueue.removeAt(0); + } + } + + buffer->raw = 0; + buffer->frameCount = 0; +} + +uint32_t AudioFlinger::PlaybackThread::TimedTrack::framesReady() const { + Mutex::Autolock _l(mTimedBufferQueueLock); + + uint32_t frames = 0; + for (size_t i = 0; i < mTimedBufferQueue.size(); i++) { + const TimedBuffer& tb = mTimedBufferQueue[i]; + frames += (tb.buffer()->size() - tb.position()) / mCblk->frameSize; + } + + return frames; +} + +AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer() + : mPTS(0), mPosition(0) {} + +AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer( + const sp<IMemory>& buffer, int64_t pts) + : mBuffer(buffer), mPTS(pts), mPosition(0) {} + // ---------------------------------------------------------------------------- // RecordTrack constructor must be called with AudioFlinger::mLock held @@ -3680,7 +4184,7 @@ AudioFlinger::RecordThread::RecordTrack::~RecordTrack() } } -status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer) +status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts) { audio_track_cblk_t* cblk = this->cblk(); uint32_t framesAvail; @@ -4002,7 +4506,8 @@ AudioFlinger::Client::Client(const sp<AudioFlinger>& audioFlinger, pid_t pid) mAudioFlinger(audioFlinger), // FIXME should be a "k" constant not hard-coded, in .h or ro. property, see 4 lines below mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")), - mPid(pid) + mPid(pid), + mTimedTrackCount(0) { // 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer } @@ -4018,6 +4523,31 @@ sp<MemoryDealer> AudioFlinger::Client::heap() const return mMemoryDealer; } +// Reserve one of the limited slots for a timed audio track associated +// with this client +bool AudioFlinger::Client::reserveTimedTrack() +{ + const int kMaxTimedTracksPerClient = 4; + + Mutex::Autolock _l(mTimedTrackLock); + + if (mTimedTrackCount >= kMaxTimedTracksPerClient) { + ALOGW("can not create timed track - pid %d has exceeded the limit", + mPid); + return false; + } + + mTimedTrackCount++; + return true; +} + +// Release a slot for a timed audio track +void AudioFlinger::Client::releaseTimedTrack() +{ + Mutex::Autolock _l(mTimedTrackLock); + mTimedTrackCount--; +} + // ---------------------------------------------------------------------------- AudioFlinger::NotificationClient::NotificationClient(const sp<AudioFlinger>& audioFlinger, @@ -4084,6 +4614,38 @@ status_t AudioFlinger::TrackHandle::attachAuxEffect(int EffectId) return mTrack->attachAuxEffect(EffectId); } +status_t AudioFlinger::TrackHandle::allocateTimedBuffer(size_t size, + sp<IMemory>* buffer) { + if (!mTrack->isTimedTrack()) + return INVALID_OPERATION; + + PlaybackThread::TimedTrack* tt = + reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get()); + return tt->allocateTimedBuffer(size, buffer); +} + +status_t AudioFlinger::TrackHandle::queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts) { + if (!mTrack->isTimedTrack()) + return INVALID_OPERATION; + + PlaybackThread::TimedTrack* tt = + reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get()); + return tt->queueTimedBuffer(buffer, pts); +} + +status_t AudioFlinger::TrackHandle::setMediaTimeTransform( + const LinearTransform& xform, int target) { + + if (!mTrack->isTimedTrack()) + return INVALID_OPERATION; + + PlaybackThread::TimedTrack* tt = + reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get()); + return tt->setMediaTimeTransform( + xform, static_cast<TimedAudioTrack::TargetTimeline>(target)); +} + status_t AudioFlinger::TrackHandle::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { @@ -4313,7 +4875,8 @@ bool AudioFlinger::RecordThread::threadLoop() } buffer.frameCount = mFrameCount; - if (CC_LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) { + if (CC_LIKELY(mActiveTrack->getNextBuffer( + &buffer, AudioBufferProvider::kInvalidPTS) == NO_ERROR)) { size_t framesOut = buffer.frameCount; if (mResampler == NULL) { // no resampling @@ -4591,7 +5154,7 @@ status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args) return NO_ERROR; } -status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer) +status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts) { size_t framesReq = buffer->frameCount; size_t framesReady = mFrameCount - mRsmpInIndex; diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index aa0b8f8575ac..9396a0bf3acc 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -22,11 +22,14 @@ #include <sys/types.h> #include <limits.h> +#include <common_time/cc_helper.h> + #include <media/IAudioFlinger.h> #include <media/IAudioFlingerClient.h> #include <media/IAudioTrack.h> #include <media/IAudioRecord.h> #include <media/AudioSystem.h> +#include <media/AudioTrack.h> #include <utils/Atomic.h> #include <utils/Errors.h> @@ -55,7 +58,7 @@ class AudioResampler; // ---------------------------------------------------------------------------- -static const nsecs_t kStandbyTimeInNsecs = seconds(3); +static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3); class AudioFlinger : public BinderService<AudioFlinger>, @@ -78,6 +81,7 @@ public: uint32_t flags, const sp<IMemory>& sharedBuffer, audio_io_handle_t output, + bool isTimed, int *sessionId, status_t *status); @@ -102,6 +106,7 @@ public: virtual status_t setMasterMute(bool muted); virtual float masterVolume() const; + virtual float masterVolumeSW() const; virtual bool masterMute() const; virtual status_t setStreamVolume(audio_stream_type_t stream, float value, @@ -206,6 +211,8 @@ private: audio_hw_device_t* findSuitableHwDev_l(uint32_t devices); void purgeStaleEffects_l(); + static nsecs_t mStandbyTimeInNsecs; + // Internal dump utilites. status_t dumpPermissionDenial(int fd, const Vector<String16>& args); status_t dumpClients(int fd, const Vector<String16>& args); @@ -220,12 +227,18 @@ private: pid_t pid() const { return mPid; } sp<AudioFlinger> audioFlinger() const { return mAudioFlinger; } + bool reserveTimedTrack(); + void releaseTimedTrack(); + private: Client(const Client&); Client& operator = (const Client&); const sp<AudioFlinger> mAudioFlinger; const sp<MemoryDealer> mMemoryDealer; const pid_t mPid; + + Mutex mTimedTrackLock; + int mTimedTrackCount; }; // --- Notification Client --- @@ -333,7 +346,9 @@ private: TrackBase(const TrackBase&); TrackBase& operator = (const TrackBase&); - virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0; + virtual status_t getNextBuffer( + AudioBufferProvider::Buffer* buffer, + int64_t pts) = 0; virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); audio_format_t format() const { @@ -609,7 +624,6 @@ private: int16_t *mainBuffer() const { return mMainBuffer; } int auxEffectId() const { return mAuxEffectId; } - protected: friend class ThreadBase; friend class TrackHandle; @@ -620,7 +634,11 @@ private: Track(const Track&); Track& operator = (const Track&); - virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + virtual status_t getNextBuffer( + AudioBufferProvider::Buffer* buffer, + int64_t pts); + virtual uint32_t framesReady() const; + bool isMuted() const { return mMute; } bool isPausing() const { return mState == PAUSING; @@ -636,6 +654,8 @@ private: return (mStreamType == AUDIO_STREAM_CNT); } + virtual bool isTimedTrack() const { return false; } + // we don't really need a lock for these volatile bool mMute; // FILLED state is used for suppressing volume ramp at begin of playing @@ -652,6 +672,79 @@ private: bool mHasVolumeController; }; // end of Track + class TimedTrack : public Track { + public: + static sp<TimedTrack> create(const wp<ThreadBase>& thread, + const sp<Client>& client, + audio_stream_type_t streamType, + uint32_t sampleRate, + audio_format_t format, + uint32_t channelMask, + int frameCount, + const sp<IMemory>& sharedBuffer, + int sessionId); + ~TimedTrack(); + + class TimedBuffer { + public: + TimedBuffer(); + TimedBuffer(const sp<IMemory>& buffer, int64_t pts); + const sp<IMemory>& buffer() const { return mBuffer; } + int64_t pts() const { return mPTS; } + int position() const { return mPosition; } + void setPosition(int pos) { mPosition = pos; } + private: + sp<IMemory> mBuffer; + int64_t mPTS; + int mPosition; + }; + + virtual bool isTimedTrack() const { return true; } + + virtual uint32_t framesReady() const; + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer, + int64_t pts); + virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); + void timedYieldSamples(AudioBufferProvider::Buffer* buffer); + void timedYieldSilence(uint32_t numFrames, + AudioBufferProvider::Buffer* buffer); + + status_t allocateTimedBuffer(size_t size, + sp<IMemory>* buffer); + status_t queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts); + status_t setMediaTimeTransform(const LinearTransform& xform, + TimedAudioTrack::TargetTimeline target); + void trimTimedBufferQueue_l(); + + private: + TimedTrack(const wp<ThreadBase>& thread, + const sp<Client>& client, + audio_stream_type_t streamType, + uint32_t sampleRate, + audio_format_t format, + uint32_t channelMask, + int frameCount, + const sp<IMemory>& sharedBuffer, + int sessionId); + + uint64_t mLocalTimeFreq; + LinearTransform mLocalTimeToSampleTransform; + sp<MemoryDealer> mTimedMemoryDealer; + Vector<TimedBuffer> mTimedBufferQueue; + uint8_t* mTimedSilenceBuffer; + uint32_t mTimedSilenceBufferSize; + mutable Mutex mTimedBufferQueueLock; + bool mTimedAudioOutputOnTime; + CCHelper mCCHelper; + + Mutex mMediaTimeTransformLock; + LinearTransform mMediaTimeTransform; + bool mMediaTimeTransformValid; + TimedAudioTrack::TargetTimeline mMediaTimeTransformTarget; + }; + // playback track class OutputTrack : public Track { @@ -726,6 +819,7 @@ private: int frameCount, const sp<IMemory>& sharedBuffer, int sessionId, + bool isTimed, status_t *status); AudioStreamOut* getOutput() const; @@ -920,6 +1014,12 @@ private: virtual void mute(bool); virtual void pause(); virtual status_t attachAuxEffect(int effectId); + virtual status_t allocateTimedBuffer(size_t size, + sp<IMemory>* buffer); + virtual status_t queueTimedBuffer(const sp<IMemory>& buffer, + int64_t pts); + virtual status_t setMediaTimeTransform(const LinearTransform& xform, + int target); virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); private: @@ -967,7 +1067,9 @@ private: RecordTrack(const RecordTrack&); RecordTrack& operator = (const RecordTrack&); - virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + virtual status_t getNextBuffer( + AudioBufferProvider::Buffer* buffer, + int64_t pts); bool mOverflow; }; @@ -1004,7 +1106,8 @@ private: AudioStreamIn* clearInput(); virtual audio_stream_t* stream(); - virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer, + int64_t pts); virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); virtual bool checkForNewParameters_l(); virtual String8 getParameters(const String8& keys); @@ -1401,6 +1504,28 @@ mutable Mutex mLock; // mutex for process, commands and handl friend class RecordThread; friend class PlaybackThread; + enum master_volume_support { + // MVS_NONE: + // Audio HAL has no support for master volume, either setting or + // getting. All master volume control must be implemented in SW by the + // AudioFlinger mixing core. + MVS_NONE, + + // MVS_SETONLY: + // Audio HAL has support for setting master volume, but not for getting + // master volume (original HAL design did not include a getter). + // AudioFlinger needs to keep track of the last set master volume in + // addition to needing to set an initial, default, master volume at HAL + // load time. + MVS_SETONLY, + + // MVS_FULL: + // Audio HAL has support both for setting and getting master volume. + // AudioFlinger should send all set and get master volume requests + // directly to the HAL. + MVS_FULL, + }; + mutable Mutex mLock; DefaultKeyedVector< pid_t, wp<Client> > mClients; // see ~Client() @@ -1429,6 +1554,7 @@ mutable Mutex mLock; // mutex for process, commands and handl AUDIO_SET_VOICE_VOLUME, AUDIO_SET_PARAMETER, AUDIO_HW_GET_INPUT_BUFFER_SIZE, + AUDIO_HW_GET_MASTER_VOLUME, }; mutable hardware_call_state mHardwareStatus; // for dump only @@ -1439,6 +1565,8 @@ mutable Mutex mLock; // mutex for process, commands and handl // both are protected by mLock float mMasterVolume; + float mMasterVolumeSW; + master_volume_support mMasterVolumeSupportLvl; bool mMasterMute; DefaultKeyedVector< audio_io_handle_t, sp<RecordThread> > mRecordThreads; @@ -1451,7 +1579,8 @@ mutable Mutex mLock; // mutex for process, commands and handl // protected by mLock Vector<AudioSessionRef*> mAudioSessionRefs; - float masterVolume_l() const { return mMasterVolume; } + float masterVolume_l() const; + float masterVolumeSW_l() const { return mMasterVolumeSW; } bool masterMute_l() const { return mMasterMute; } private: diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp index cb7678b23e71..c399691828c9 100644 --- a/services/audioflinger/AudioMixer.cpp +++ b/services/audioflinger/AudioMixer.cpp @@ -33,6 +33,8 @@ #include <system/audio.h> #include <audio_utils/primitives.h> +#include <common_time/local_clock.h> +#include <common_time/cc_helper.h> #include "AudioMixer.h" @@ -45,6 +47,9 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) { // AudioMixer is not yet capable of multi-channel beyond stereo assert(2 == MAX_NUM_CHANNELS); + + LocalClock lc; + mState.enabledTracks= 0; mState.needsChanged = 0; mState.frameCount = frameCount; @@ -80,6 +85,7 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) t->sampleRate = mSampleRate; t->mainBuffer = NULL; t->auxBuffer = NULL; + t->localTimeFreq = lc.getLocalFreq(); t++; } } @@ -289,6 +295,7 @@ bool AudioMixer::track_t::setResampler(uint32_t value, uint32_t devSampleRate) if (resampler == NULL) { resampler = AudioResampler::create( format, channelCount, devSampleRate); + resampler->setLocalTimeFreq(localTimeFreq); } return true; } @@ -333,13 +340,13 @@ void AudioMixer::setBufferProvider(int name, AudioBufferProvider* buffer) -void AudioMixer::process() +void AudioMixer::process(int64_t pts) { - mState.hook(&mState); + mState.hook(&mState, pts); } -void AudioMixer::process__validate(state_t* state) +void AudioMixer::process__validate(state_t* state, int64_t pts) { ALOGW_IF(!state->needsChanged, "in process__validate() but nothing's invalid"); @@ -443,7 +450,7 @@ void AudioMixer::process__validate(state_t* state) countActiveTracks, state->enabledTracks, all16BitsStereoNoResample, resampling, volumeRamp); - state->hook(state); + state->hook(state, pts); // Now that the volume ramp has been done, set optimal state and // track hooks for subsequent mixer process @@ -757,7 +764,7 @@ void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount, } // no-op case -void AudioMixer::process__nop(state_t* state) +void AudioMixer::process__nop(state_t* state, int64_t pts) { uint32_t e0 = state->enabledTracks; size_t bufSize = state->frameCount * sizeof(int16_t) * MAX_NUM_CHANNELS; @@ -787,7 +794,9 @@ void AudioMixer::process__nop(state_t* state) size_t outFrames = state->frameCount; while (outFrames) { t1.buffer.frameCount = outFrames; - t1.bufferProvider->getNextBuffer(&t1.buffer); + int64_t outputPTS = calculateOutputPTS( + t1, pts, state->frameCount - outFrames); + t1.bufferProvider->getNextBuffer(&t1.buffer, outputPTS); if (t1.buffer.raw == NULL) break; outFrames -= t1.buffer.frameCount; t1.bufferProvider->releaseBuffer(&t1.buffer); @@ -797,7 +806,7 @@ void AudioMixer::process__nop(state_t* state) } // generic code without resampling -void AudioMixer::process__genericNoResampling(state_t* state) +void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts) { int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32))); @@ -809,7 +818,7 @@ void AudioMixer::process__genericNoResampling(state_t* state) e0 &= ~(1<<i); track_t& t = state->tracks[i]; t.buffer.frameCount = state->frameCount; - t.bufferProvider->getNextBuffer(&t.buffer); + t.bufferProvider->getNextBuffer(&t.buffer, pts); t.frameCount = t.buffer.frameCount; t.in = t.buffer.raw; // t.in == NULL can happen if the track was flushed just after having @@ -863,7 +872,9 @@ void AudioMixer::process__genericNoResampling(state_t* state) if (t.frameCount == 0 && outFrames) { t.bufferProvider->releaseBuffer(&t.buffer); t.buffer.frameCount = (state->frameCount - numFrames) - (BLOCKSIZE - outFrames); - t.bufferProvider->getNextBuffer(&t.buffer); + int64_t outputPTS = calculateOutputPTS( + t, pts, numFrames + (BLOCKSIZE - outFrames)); + t.bufferProvider->getNextBuffer(&t.buffer, outputPTS); t.in = t.buffer.raw; if (t.in == NULL) { enabledTracks &= ~(1<<i); @@ -892,7 +903,7 @@ void AudioMixer::process__genericNoResampling(state_t* state) // generic code with resampling -void AudioMixer::process__genericResampling(state_t* state) +void AudioMixer::process__genericResampling(state_t* state, int64_t pts) { // this const just means that local variable outTemp doesn't change int32_t* const outTemp = state->outputTemp; @@ -932,6 +943,7 @@ void AudioMixer::process__genericResampling(state_t* state) // acquire/release the buffers because it's done by // the resampler. if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) { + t.resampler->setPTS(pts); (t.hook)(&t, outTemp, numFrames, state->resampleTemp, aux); } else { @@ -939,7 +951,8 @@ void AudioMixer::process__genericResampling(state_t* state) while (outFrames < numFrames) { t.buffer.frameCount = numFrames - outFrames; - t.bufferProvider->getNextBuffer(&t.buffer); + int64_t outputPTS = calculateOutputPTS(t, pts, outFrames); + t.bufferProvider->getNextBuffer(&t.buffer, outputPTS); t.in = t.buffer.raw; // t.in == NULL can happen if the track was flushed just after having // been enabled for mixing. @@ -959,7 +972,8 @@ void AudioMixer::process__genericResampling(state_t* state) } // one track, 16 bits stereo without resampling is the most common case -void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) +void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state, + int64_t pts) { // This method is only called when state->enabledTracks has exactly // one bit set. The asserts below would verify this, but are commented out @@ -979,7 +993,8 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) const uint32_t vrl = t.volumeRL; while (numFrames) { b.frameCount = numFrames; - t.bufferProvider->getNextBuffer(&b); + int64_t outputPTS = calculateOutputPTS(t, pts, out - t.mainBuffer); + t.bufferProvider->getNextBuffer(&b, outputPTS); const int16_t *in = b.i16; // in == NULL can happen if the track was flushed just after having @@ -1023,7 +1038,8 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) // 2 tracks is also a common case // NEVER used in current implementation of process__validate() // only use if the 2 tracks have the same output buffer -void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) +void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state, + int64_t pts) { int i; uint32_t en = state->enabledTracks; @@ -1057,7 +1073,9 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) if (frameCount0 == 0) { b0.frameCount = numFrames; - t0.bufferProvider->getNextBuffer(&b0); + int64_t outputPTS = calculateOutputPTS(t0, pts, + out - t0.mainBuffer); + t0.bufferProvider->getNextBuffer(&b0, outputPTS); if (b0.i16 == NULL) { if (buff == NULL) { buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount]; @@ -1071,7 +1089,9 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) } if (frameCount1 == 0) { b1.frameCount = numFrames; - t1.bufferProvider->getNextBuffer(&b1); + int64_t outputPTS = calculateOutputPTS(t1, pts, + out - t0.mainBuffer); + t1.bufferProvider->getNextBuffer(&b1, outputPTS); if (b1.i16 == NULL) { if (buff == NULL) { buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount]; @@ -1117,5 +1137,14 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) } #endif +int64_t AudioMixer::calculateOutputPTS(const track_t& t, int64_t basePTS, + int outputFrameIndex) +{ + if (AudioBufferProvider::kInvalidPTS == basePTS) + return AudioBufferProvider::kInvalidPTS; + + return basePTS + ((outputFrameIndex * t.localTimeFreq) / t.sampleRate); +} + // ---------------------------------------------------------------------------- }; // namespace android diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h index c95691865c8c..f442671c8258 100644 --- a/services/audioflinger/AudioMixer.h +++ b/services/audioflinger/AudioMixer.h @@ -79,7 +79,7 @@ public: void setParameter(int name, int target, int param, void *value); void setBufferProvider(int name, AudioBufferProvider* bufferProvider); - void process(); + void process(int64_t pts); uint32_t trackNames() const { return mTrackNames; } @@ -114,7 +114,7 @@ private: struct state_t; struct track_t; - typedef void (*mix_t)(state_t* state); + typedef void (*mix_t)(state_t* state, int64_t pts); typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux); static const int BLOCKSIZE = 16; // 4 cache lines @@ -152,6 +152,8 @@ private: int32_t* mainBuffer; int32_t* auxBuffer; + uint64_t localTimeFreq; + bool setResampler(uint32_t sampleRate, uint32_t devSampleRate); bool doesResample() const { return resampler != NULL; } void resetResampler() { if (resampler != NULL) resampler->reset(); } @@ -187,14 +189,19 @@ private: static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux); static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux); - static void process__validate(state_t* state); - static void process__nop(state_t* state); - static void process__genericNoResampling(state_t* state); - static void process__genericResampling(state_t* state); - static void process__OneTrack16BitsStereoNoResampling(state_t* state); + static void process__validate(state_t* state, int64_t pts); + static void process__nop(state_t* state, int64_t pts); + static void process__genericNoResampling(state_t* state, int64_t pts); + static void process__genericResampling(state_t* state, int64_t pts); + static void process__OneTrack16BitsStereoNoResampling(state_t* state, + int64_t pts); #if 0 - static void process__TwoTracks16BitsStereoNoResampling(state_t* state); + static void process__TwoTracks16BitsStereoNoResampling(state_t* state, + int64_t pts); #endif + + static int64_t calculateOutputPTS(const track_t& t, int64_t basePTS, + int outputFrameIndex); }; // ---------------------------------------------------------------------------- diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp index 9486b9c9efe0..398ba0b6004d 100644 --- a/services/audioflinger/AudioResampler.cpp +++ b/services/audioflinger/AudioResampler.cpp @@ -122,7 +122,8 @@ AudioResampler::AudioResampler(int bitDepth, int inChannelCount, int32_t sampleRate) : mBitDepth(bitDepth), mChannelCount(inChannelCount), mSampleRate(sampleRate), mInSampleRate(sampleRate), mInputIndex(0), - mPhaseFraction(0) { + mPhaseFraction(0), mLocalTimeFreq(0), + mPTS(AudioBufferProvider::kInvalidPTS) { // sanity check on format if ((bitDepth != 16) ||(inChannelCount < 1) || (inChannelCount > 2)) { ALOGE("Unsupported sample format, %d bits, %d channels", bitDepth, @@ -150,6 +151,23 @@ void AudioResampler::setVolume(int16_t left, int16_t right) { mVolume[1] = right; } +void AudioResampler::setLocalTimeFreq(uint64_t freq) { + mLocalTimeFreq = freq; +} + +void AudioResampler::setPTS(int64_t pts) { + mPTS = pts; +} + +int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) { + + if (mPTS == AudioBufferProvider::kInvalidPTS) { + return AudioBufferProvider::kInvalidPTS; + } else { + return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate); + } +} + void AudioResampler::reset() { mInputIndex = 0; mPhaseFraction = 0; @@ -196,7 +214,8 @@ void AudioResamplerOrder1::resampleStereo16(int32_t* out, size_t outFrameCount, // buffer is empty, fetch a new one while (mBuffer.frameCount == 0) { mBuffer.frameCount = inFrameCount; - provider->getNextBuffer(&mBuffer); + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); if (mBuffer.raw == NULL) { goto resampleStereo16_exit; } @@ -290,7 +309,8 @@ void AudioResamplerOrder1::resampleMono16(int32_t* out, size_t outFrameCount, // buffer is empty, fetch a new one while (mBuffer.frameCount == 0) { mBuffer.frameCount = inFrameCount; - provider->getNextBuffer(&mBuffer); + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); if (mBuffer.raw == NULL) { mInputIndex = inputIndex; mPhaseFraction = phaseFraction; diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h index c23016e02ca5..9deb7963d215 100644 --- a/services/audioflinger/AudioResampler.h +++ b/services/audioflinger/AudioResampler.h @@ -49,6 +49,10 @@ public: virtual void init() = 0; virtual void setSampleRate(int32_t inSampleRate); virtual void setVolume(int16_t left, int16_t right); + virtual void setLocalTimeFreq(uint64_t freq); + + // set the PTS of the next buffer output by the resampler + virtual void setPTS(int64_t pts); virtual void resample(int32_t* out, size_t outFrameCount, AudioBufferProvider* provider) = 0; @@ -72,6 +76,8 @@ protected: AudioResampler(const AudioResampler&); AudioResampler& operator=(const AudioResampler&); + int64_t calculateOutputPTS(int outputFrameIndex); + const int32_t mBitDepth; const int32_t mChannelCount; const int32_t mSampleRate; @@ -85,6 +91,8 @@ protected: size_t mInputIndex; int32_t mPhaseIncrement; uint32_t mPhaseFraction; + uint64_t mLocalTimeFreq; + int64_t mPTS; }; // ---------------------------------------------------------------------------- diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp index c0e760e643ce..18e59e93ee2e 100644 --- a/services/audioflinger/AudioResamplerCubic.cpp +++ b/services/audioflinger/AudioResamplerCubic.cpp @@ -65,7 +65,7 @@ void AudioResamplerCubic::resampleStereo16(int32_t* out, size_t outFrameCount, // fetch first buffer if (mBuffer.frameCount == 0) { mBuffer.frameCount = inFrameCount; - provider->getNextBuffer(&mBuffer); + provider->getNextBuffer(&mBuffer, mPTS); if (mBuffer.raw == NULL) return; // ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount); @@ -95,7 +95,8 @@ void AudioResamplerCubic::resampleStereo16(int32_t* out, size_t outFrameCount, inputIndex = 0; provider->releaseBuffer(&mBuffer); mBuffer.frameCount = inFrameCount; - provider->getNextBuffer(&mBuffer); + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); if (mBuffer.raw == NULL) goto save_state; // ugly, but efficient in = mBuffer.i16; @@ -130,7 +131,7 @@ void AudioResamplerCubic::resampleMono16(int32_t* out, size_t outFrameCount, // fetch first buffer if (mBuffer.frameCount == 0) { mBuffer.frameCount = inFrameCount; - provider->getNextBuffer(&mBuffer); + provider->getNextBuffer(&mBuffer, mPTS); if (mBuffer.raw == NULL) return; // ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount); @@ -160,7 +161,8 @@ void AudioResamplerCubic::resampleMono16(int32_t* out, size_t outFrameCount, inputIndex = 0; provider->releaseBuffer(&mBuffer); mBuffer.frameCount = inFrameCount; - provider->getNextBuffer(&mBuffer); + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); if (mBuffer.raw == NULL) goto save_state; // ugly, but efficient // ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount); @@ -181,4 +183,3 @@ save_state: // ---------------------------------------------------------------------------- } ; // namespace android - diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp index 7a27b810afd0..d373c08385b0 100644 --- a/services/audioflinger/AudioResamplerSinc.cpp +++ b/services/audioflinger/AudioResamplerSinc.cpp @@ -203,7 +203,8 @@ void AudioResamplerSinc::resample(int32_t* out, size_t outFrameCount, // buffer is empty, fetch a new one while (mBuffer.frameCount == 0) { mBuffer.frameCount = inFrameCount; - provider->getNextBuffer(&mBuffer); + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); if (mBuffer.raw == NULL) { goto resample_exit; } @@ -354,4 +355,3 @@ void AudioResamplerSinc::interpolate( // ---------------------------------------------------------------------------- }; // namespace android - diff --git a/services/common_time/Android.mk b/services/common_time/Android.mk new file mode 100644 index 000000000000..aabe5728cb05 --- /dev/null +++ b/services/common_time/Android.mk @@ -0,0 +1,32 @@ +LOCAL_PATH:= $(call my-dir) + +# +# common_time_service +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + common_clock_service.cpp \ + common_time_config_service.cpp \ + common_time_server.cpp \ + common_time_server_api.cpp \ + common_time_server_packets.cpp \ + clock_recovery.cpp \ + common_clock.cpp \ + main.cpp + +ifeq ($(TIME_SERVICE_DEBUG), true) +LOCAL_SRC_FILES += diag_thread.cpp +LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG +endif + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libcommon_time_client \ + libutils + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := common_time + +include $(BUILD_EXECUTABLE) diff --git a/services/common_time/clock_recovery.cpp b/services/common_time/clock_recovery.cpp new file mode 100644 index 000000000000..be054fb9f3a1 --- /dev/null +++ b/services/common_time/clock_recovery.cpp @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A service that exchanges time synchronization information between + * a master that defines a timeline and clients that follow the timeline. + */ + +#define __STDC_LIMIT_MACROS +#define LOG_TAG "common_time" +#include <utils/Log.h> +#include <stdint.h> + +#include <common_time/local_clock.h> +#include <assert.h> + +#include "clock_recovery.h" +#include "common_clock.h" +#ifdef TIME_SERVICE_DEBUG +#include "diag_thread.h" +#endif + +namespace android { + +ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock, + CommonClock* common_clock) { + assert(NULL != local_clock); + assert(NULL != common_clock); + + local_clock_ = local_clock; + common_clock_ = common_clock; + + local_clock_can_slew_ = local_clock_->initCheck() && + (local_clock_->setLocalSlew(0) == OK); + + computePIDParams(); + reset(true, true); + +#ifdef TIME_SERVICE_DEBUG + diag_thread_ = new DiagThread(common_clock_, local_clock_); + if (diag_thread_ != NULL) { + status_t res = diag_thread_->startWorkThread(); + if (res != OK) + ALOGW("Failed to start A@H clock recovery diagnostic thread."); + } else + ALOGW("Failed to allocate diagnostic thread."); +#endif +} + +ClockRecoveryLoop::~ClockRecoveryLoop() { +#ifdef TIME_SERVICE_DEBUG + diag_thread_->stopWorkThread(); +#endif +} + +void ClockRecoveryLoop::reset(bool position, bool frequency) { + Mutex::Autolock lock(&lock_); + reset_l(position, frequency); +} + +uint32_t ClockRecoveryLoop::findMinRTTNdx(DisciplineDataPoint* data, + uint32_t count) { + uint32_t min_rtt = 0; + for (uint32_t i = 1; i < count; ++i) + if (data[min_rtt].rtt > data[i].rtt) + min_rtt = i; + + return min_rtt; +} + +bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time, + int64_t nominal_common_time, + int64_t rtt) { + Mutex::Autolock lock(&lock_); + + // If we have not defined a basis for common time, then we need to use these + // initial points to do so. In order to avoid significant initial error + // from a particularly bad startup data point, we collect the first N data + // points and choose the best of them before moving on. + if (!common_clock_->isValid()) { + if (startup_filter_wr_ < kStartupFilterSize) { + DisciplineDataPoint& d = startup_filter_data_[startup_filter_wr_]; + d.local_time = local_time; + d.nominal_common_time = nominal_common_time; + d.rtt = rtt; + startup_filter_wr_++; + } + + if (startup_filter_wr_ == kStartupFilterSize) { + uint32_t min_rtt = findMinRTTNdx(startup_filter_data_, + kStartupFilterSize); + + common_clock_->setBasis( + startup_filter_data_[min_rtt].local_time, + startup_filter_data_[min_rtt].nominal_common_time); + } + + return true; + } + + int64_t observed_common; + int64_t delta; + int32_t delta32; + int32_t correction_cur; + int32_t correction_cur_P = 0; + int32_t correction_cur_I = 0; + int32_t correction_cur_D = 0; + + if (OK != common_clock_->localToCommon(local_time, &observed_common)) { + // Since we just checked to make certain that this conversion was valid, + // and no one else in the system should be messing with it, if this + // conversion is suddenly invalid, it is a good reason to panic. + ALOGE("Failed to convert local time to common time in %s:%d", + __PRETTY_FUNCTION__, __LINE__); + return false; + } + + // Implement a filter which should match NTP filtering behavior when a + // client is associated with only one peer of lower stratum. Basically, + // always use the best of the N last data points, where best is defined as + // lowest round trip time. NTP uses an N of 8; we use a value of 6. + // + // TODO(johngro) : experiment with other filter strategies. The goal here + // is to mitigate the effects of high RTT data points which typically have + // large asymmetries in the TX/RX legs. Downside of the existing NTP + // approach (particularly because of the PID controller we are using to + // produce the control signal from the filtered data) are that the rate at + // which discipline events are actually acted upon becomes irregular and can + // become drawn out (the time between actionable event can go way up). If + // the system receives a strong high quality data point, the proportional + // component of the controller can produce a strong correction which is left + // in place for too long causing overshoot. In addition, the integral + // component of the system currently is an approximation based on the + // assumption of a more or less homogeneous sampling of the error. Its + // unclear what the effect of undermining this assumption would be right + // now. + + // Two ideas which come to mind immediately would be to... + // 1) Keep a history of more data points (32 or so) and ignore data points + // whose RTT is more than a certain number of standard deviations outside + // of the norm. + // 2) Eliminate the PID controller portion of this system entirely. + // Instead, move to a system which uses a very wide filter (128 data + // points or more) with a sum-of-least-squares line fitting approach to + // tracking the long term drift. This would take the place of the I + // component in the current PID controller. Also use a much more narrow + // outlier-rejector filter (as described in #1) to drive a short term + // correction factor similar to the P component of the PID controller. + assert(filter_wr_ < kFilterSize); + filter_data_[filter_wr_].local_time = local_time; + filter_data_[filter_wr_].observed_common_time = observed_common; + filter_data_[filter_wr_].nominal_common_time = nominal_common_time; + filter_data_[filter_wr_].rtt = rtt; + filter_data_[filter_wr_].point_used = false; + filter_wr_ = (filter_wr_ + 1) % kFilterSize; + if (!filter_wr_) + filter_full_ = true; + + // Scan the accumulated data for the point with the minimum RTT. If that + // point has never been used before, go ahead and use it now, otherwise just + // do nothing. + uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_; + uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end); + if (filter_data_[min_rtt].point_used) + return true; + + local_time = filter_data_[min_rtt].local_time; + observed_common = filter_data_[min_rtt].observed_common_time; + nominal_common_time = filter_data_[min_rtt].nominal_common_time; + filter_data_[min_rtt].point_used = true; + + // Compute the error then clamp to the panic threshold. If we ever exceed + // this amt of error, its time to panic and reset the system. Given that + // the error in the measurement of the error could be as high as the RTT of + // the data point, we don't actually panic until the implied error (delta) + // is greater than the absolute panic threashold plus the RTT. IOW - we + // don't panic until we are absoluely sure that our best case sync is worse + // than the absolute panic threshold. + int64_t effective_panic_thresh = panic_thresh_ + filter_data_[min_rtt].rtt; + delta = nominal_common_time - observed_common; + if ((delta > effective_panic_thresh) || (delta < -effective_panic_thresh)) { + // PANIC!!! + // + // TODO(johngro) : need to report this to the upper levels of + // code. + reset_l(false, true); + return false; + } else + delta32 = delta; + + // Accumulate error into the integrated error, then clamp. + integrated_error_ += delta32; + if (integrated_error_ > pid_params_.integrated_delta_max) + integrated_error_ = pid_params_.integrated_delta_max; + else if (integrated_error_ < pid_params_.integrated_delta_min) + integrated_error_ = pid_params_.integrated_delta_min; + + // Compute the difference in error between last time and this time, then + // update last_delta_ + int32_t input_D = last_delta_valid_ ? delta32 - last_delta_ : 0; + last_delta_valid_ = true; + last_delta_ = delta32; + + // Compute the various components of the correction value. + correction_cur_P = doGainScale(pid_params_.gain_P, delta32); + correction_cur_I = doGainScale(pid_params_.gain_I, integrated_error_); + + // TODO(johngro) : the differential portion of this code used to rely + // upon a completely homogeneous discipline frequency. Now that the + // discipline frequency may not be homogeneous, its probably important + // to divide by the amt of time between discipline events during the + // gain calculation. + correction_cur_D = doGainScale(pid_params_.gain_D, input_D); + + // Compute the final correction value and clamp. + correction_cur = correction_cur_P + correction_cur_I + correction_cur_D; + if (correction_cur < pid_params_.correction_min) + correction_cur = pid_params_.correction_min; + else if (correction_cur > pid_params_.correction_max) + correction_cur = pid_params_.correction_max; + + // If there was a change in the amt of correction to use, update the + // system. + if (correction_cur_ != correction_cur) { + correction_cur_ = correction_cur; + applySlew(); + } + + ALOGV("rtt %lld observed %lld nominal %lld delta = %5lld " + "int = %7d correction %5d (P %5d, I %5d, D %5d)\n", + filter_data_[min_rtt].rtt, + observed_common, + nominal_common_time, + nominal_common_time - observed_common, + integrated_error_, + correction_cur, + correction_cur_P, + correction_cur_I, + correction_cur_D); + +#ifdef TIME_SERVICE_DEBUG + diag_thread_->pushDisciplineEvent( + local_time, + observed_common, + nominal_common_time, + correction_cur, + correction_cur_P, + correction_cur_I, + correction_cur_D); +#endif + + return true; +} + +int32_t ClockRecoveryLoop::getLastErrorEstimate() { + Mutex::Autolock lock(&lock_); + + if (last_delta_valid_) + return last_delta_; + else + return ICommonClock::kErrorEstimateUnknown; +} + +void ClockRecoveryLoop::computePIDParams() { + // TODO(johngro) : add the ability to fetch parameters from the driver/board + // level in case they have a HW clock discipline solution with parameters + // tuned specifically for it. + + // Correction factor is limited to MIN/MAX_INT_16 + pid_params_.correction_min = -0x8000; + pid_params_.correction_max = 0x7FFF; + + // Default proportional gain to 2^15:1000. (max proportional drive at 1mSec + // of instantaneous error) + memset(&pid_params_.gain_P, 0, sizeof(pid_params_.gain_P)); + pid_params_.gain_P.a_to_b_numer = 0x8000; + pid_params_.gain_P.a_to_b_denom = 1000; + + // Set the integral gain to 2^15:5000 + memset(&pid_params_.gain_I, 0, sizeof(pid_params_.gain_I)); + pid_params_.gain_I.a_to_b_numer = 0x8000; + pid_params_.gain_I.a_to_b_denom = 5000; + + // Default controller is just a PI controller. Right now, the network based + // measurements of the error are way to noisy to feed into the differential + // component of a PID controller. Someday we might come back and add some + // filtering of the error channel, but until then leave the controller as a + // simple PI controller. + memset(&pid_params_.gain_D, 0, sizeof(pid_params_.gain_D)); + + // Don't let the integral component of the controller wind up to + // the point where it would want to drive the correction factor + // past saturation. + int64_t tmp; + pid_params_.gain_I.doReverseTransform(pid_params_.correction_min, &tmp); + pid_params_.integrated_delta_min = static_cast<int32_t>(tmp); + pid_params_.gain_I.doReverseTransform(pid_params_.correction_max, &tmp); + pid_params_.integrated_delta_max = static_cast<int32_t>(tmp); + + // By default, panic when are certain that the sync error is > 20mSec; + panic_thresh_ = 20000; +} + +void ClockRecoveryLoop::reset_l(bool position, bool frequency) { + assert(NULL != common_clock_); + + if (position) { + common_clock_->resetBasis(); + startup_filter_wr_ = 0; + } + + if (frequency) { + last_delta_valid_ = false; + last_delta_ = 0; + integrated_error_ = 0; + correction_cur_ = 0; + applySlew(); + } + + filter_wr_ = 0; + filter_full_ = false; +} + +int32_t ClockRecoveryLoop::doGainScale(const LinearTransform& gain, + int32_t val) { + if (!gain.a_to_b_numer || !gain.a_to_b_denom || !val) + return 0; + + int64_t tmp; + int64_t val64 = static_cast<int64_t>(val); + if (!gain.doForwardTransform(val64, &tmp)) { + ALOGW("Overflow/Underflow while scaling %d in %s", + val, __PRETTY_FUNCTION__); + return (val < 0) ? INT32_MIN : INT32_MAX; + } + + if (tmp > INT32_MAX) { + ALOGW("Overflow while scaling %d in %s", val, __PRETTY_FUNCTION__); + return INT32_MAX; + } + + if (tmp < INT32_MIN) { + ALOGW("Underflow while scaling %d in %s", val, __PRETTY_FUNCTION__); + return INT32_MIN; + } + + return static_cast<int32_t>(tmp); +} + +void ClockRecoveryLoop::applySlew() { + if (local_clock_can_slew_) { + local_clock_->setLocalSlew(correction_cur_); + } else { + // The SW clock recovery implemented by the common clock class expects + // values expressed in PPM. Map the MIN/MAX_INT_16 drive range to +/- + // 100ppm. + int sw_correction; + sw_correction = correction_cur_ - pid_params_.correction_min; + sw_correction *= 200; + sw_correction /= (pid_params_.correction_max - + pid_params_.correction_min); + sw_correction -= 100; + + common_clock_->setSlew(local_clock_->getLocalTime(), sw_correction); + } +} + +} // namespace android diff --git a/services/common_time/clock_recovery.h b/services/common_time/clock_recovery.h new file mode 100644 index 000000000000..5c35c381f5e0 --- /dev/null +++ b/services/common_time/clock_recovery.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CLOCK_RECOVERY_H__ +#define __CLOCK_RECOVERY_H__ + +#include <stdint.h> +#include <common_time/ICommonClock.h> +#include <utils/LinearTransform.h> +#include <utils/threads.h> + +#ifdef TIME_SERVICE_DEBUG +#include "diag_thread.h" +#endif + +namespace android { + +class CommonClock; +class LocalClock; + +class ClockRecoveryLoop { + public: + ClockRecoveryLoop(LocalClock* local_clock, CommonClock* common_clock); + ~ClockRecoveryLoop(); + + void reset(bool position, bool frequency); + bool pushDisciplineEvent(int64_t local_time, + int64_t nominal_common_time, + int64_t data_point_rtt); + int32_t getLastErrorEstimate(); + + private: + typedef struct { + // Limits for the correction factor supplied to set_counter_slew_rate. + // The controller will always clamp its output to the range expressed by + // correction_(min|max) + int32_t correction_min; + int32_t correction_max; + + // Limits for the internal integration accumulator in the PID + // controller. The value of the accumulator is scaled by gain_I to + // produce the integral component of the PID controller output. + // Platforms can use these limits to prevent windup in the system + // if/when the correction factor needs to be driven to saturation for + // extended periods of time. + int32_t integrated_delta_min; + int32_t integrated_delta_max; + + // Gain for the P, I and D components of the controller. + LinearTransform gain_P; + LinearTransform gain_I; + LinearTransform gain_D; + } PIDParams; + + typedef struct { + int64_t local_time; + int64_t observed_common_time; + int64_t nominal_common_time; + int64_t rtt; + bool point_used; + } DisciplineDataPoint; + + static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count); + + void computePIDParams(); + void reset_l(bool position, bool frequency); + static int32_t doGainScale(const LinearTransform& gain, int32_t val); + void applySlew(); + + // The local clock HW abstraction we use as the basis for common time. + LocalClock* local_clock_; + bool local_clock_can_slew_; + + // The common clock we end up controlling along with the lock used to + // serialize operations. + CommonClock* common_clock_; + Mutex lock_; + + // The parameters computed to be used for the PID Controller. + PIDParams pid_params_; + + // The maximum allowed error (as indicated by a pushDisciplineEvent) before + // we panic. + int32_t panic_thresh_; + + // parameters maintained while running and reset during a reset + // of the frequency correction. + bool last_delta_valid_; + int32_t last_delta_; + int32_t integrated_error_; + int32_t correction_cur_; + + // State kept for filtering the discipline data. + static const uint32_t kFilterSize = 6; + DisciplineDataPoint filter_data_[kFilterSize]; + uint32_t filter_wr_; + bool filter_full_; + + static const uint32_t kStartupFilterSize = 4; + DisciplineDataPoint startup_filter_data_[kStartupFilterSize]; + uint32_t startup_filter_wr_; + +#ifdef TIME_SERVICE_DEBUG + sp<DiagThread> diag_thread_; +#endif +}; + +} // namespace android + +#endif // __CLOCK_RECOVERY_H__ diff --git a/services/common_time/common_clock.cpp b/services/common_time/common_clock.cpp new file mode 100644 index 000000000000..c9eb3884440d --- /dev/null +++ b/services/common_time/common_clock.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define __STDC_LIMIT_MACROS + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <stdint.h> + +#include <utils/Errors.h> +#include <utils/LinearTransform.h> + +#include "common_clock.h" + +namespace android { + +CommonClock::CommonClock() { + cur_slew_ = 0; + cur_trans_valid_ = false; + + cur_trans_.a_zero = 0; + cur_trans_.b_zero = 0; + cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = 1; + cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = 1; + duration_trans_ = cur_trans_; +} + +bool CommonClock::init(uint64_t local_freq) { + Mutex::Autolock lock(&lock_); + + if (!local_freq) + return false; + + uint64_t numer = kCommonFreq; + uint64_t denom = local_freq; + + LinearTransform::reduce(&numer, &denom); + if ((numer > UINT32_MAX) || (denom > UINT32_MAX)) { + ALOGE("Overflow in CommonClock::init while trying to reduce %lld/%lld", + kCommonFreq, local_freq); + return false; + } + + cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = + static_cast<uint32_t>(numer); + cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = + static_cast<uint32_t>(denom); + duration_trans_ = cur_trans_; + + return true; +} + +status_t CommonClock::localToCommon(int64_t local, int64_t *common_out) const { + Mutex::Autolock lock(&lock_); + + if (!cur_trans_valid_) + return INVALID_OPERATION; + + if (!cur_trans_.doForwardTransform(local, common_out)) + return INVALID_OPERATION; + + return OK; +} + +status_t CommonClock::commonToLocal(int64_t common, int64_t *local_out) const { + Mutex::Autolock lock(&lock_); + + if (!cur_trans_valid_) + return INVALID_OPERATION; + + if (!cur_trans_.doReverseTransform(common, local_out)) + return INVALID_OPERATION; + + return OK; +} + +int64_t CommonClock::localDurationToCommonDuration(int64_t localDur) const { + int64_t ret; + duration_trans_.doForwardTransform(localDur, &ret); + return ret; +} + +void CommonClock::setBasis(int64_t local, int64_t common) { + Mutex::Autolock lock(&lock_); + + cur_trans_.a_zero = local; + cur_trans_.b_zero = common; + cur_trans_valid_ = true; +} + +void CommonClock::resetBasis() { + Mutex::Autolock lock(&lock_); + + cur_trans_.a_zero = 0; + cur_trans_.b_zero = 0; + cur_trans_valid_ = false; +} + +status_t CommonClock::setSlew(int64_t change_time, int32_t ppm) { + Mutex::Autolock lock(&lock_); + + int64_t new_local_basis; + int64_t new_common_basis; + + if (cur_trans_valid_) { + new_local_basis = change_time; + if (!cur_trans_.doForwardTransform(change_time, &new_common_basis)) { + ALOGE("Overflow when attempting to set slew rate to %d", ppm); + return INVALID_OPERATION; + } + } else { + new_local_basis = 0; + new_common_basis = 0; + } + + cur_slew_ = ppm; + uint32_t n1 = local_to_common_freq_numer_; + uint32_t n2 = 1000000 + cur_slew_; + + uint32_t d1 = local_to_common_freq_denom_; + uint32_t d2 = 1000000; + + // n1/d1 has already been reduced, no need to do so here. + LinearTransform::reduce(&n1, &d2); + LinearTransform::reduce(&n2, &d1); + LinearTransform::reduce(&n2, &d2); + + cur_trans_.a_zero = new_local_basis; + cur_trans_.b_zero = new_common_basis; + cur_trans_.a_to_b_numer = n1 * n2; + cur_trans_.a_to_b_denom = d1 * d2; + + return OK; +} + +} // namespace android diff --git a/services/common_time/common_clock.h b/services/common_time/common_clock.h new file mode 100644 index 000000000000..b786fdceba5f --- /dev/null +++ b/services/common_time/common_clock.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __COMMON_CLOCK_H__ +#define __COMMON_CLOCK_H__ + +#include <stdint.h> + +#include <utils/Errors.h> +#include <utils/LinearTransform.h> +#include <utils/threads.h> + +namespace android { + +class CommonClock { + public: + CommonClock(); + + bool init(uint64_t local_freq); + + status_t localToCommon(int64_t local, int64_t *common_out) const; + status_t commonToLocal(int64_t common, int64_t *local_out) const; + int64_t localDurationToCommonDuration(int64_t localDur) const; + uint64_t getCommonFreq() const { return kCommonFreq; } + bool isValid() const { return cur_trans_valid_; } + status_t setSlew(int64_t change_time, int32_t ppm); + void setBasis(int64_t local, int64_t common); + void resetBasis(); + private: + mutable Mutex lock_; + + int32_t cur_slew_; + uint32_t local_to_common_freq_numer_; + uint32_t local_to_common_freq_denom_; + + LinearTransform duration_trans_; + LinearTransform cur_trans_; + bool cur_trans_valid_; + + static const uint64_t kCommonFreq = 1000000ull; +}; + +} // namespace android +#endif // __COMMON_CLOCK_H__ diff --git a/services/common_time/common_clock_service.cpp b/services/common_time/common_clock_service.cpp new file mode 100644 index 000000000000..9ca6f3523084 --- /dev/null +++ b/services/common_time/common_clock_service.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <common_time/local_clock.h> +#include <utils/String8.h> + +#include "common_clock_service.h" +#include "common_clock.h" +#include "common_time_server.h" + +namespace android { + +sp<CommonClockService> CommonClockService::instantiate( + CommonTimeServer& timeServer) { + sp<CommonClockService> tcc = new CommonClockService(timeServer); + if (tcc == NULL) + return NULL; + + defaultServiceManager()->addService(ICommonClock::kServiceName, tcc); + return tcc; +} + +status_t CommonClockService::dump(int fd, const Vector<String16>& args) { + Mutex::Autolock lock(mRegistrationLock); + return mTimeServer.dumpClockInterface(fd, args, mListeners.size()); +} + +status_t CommonClockService::isCommonTimeValid(bool* valid, + uint32_t* timelineID) { + return mTimeServer.isCommonTimeValid(valid, timelineID); +} + +status_t CommonClockService::commonTimeToLocalTime(int64_t commonTime, + int64_t* localTime) { + return mTimeServer.getCommonClock().commonToLocal(commonTime, localTime); +} + +status_t CommonClockService::localTimeToCommonTime(int64_t localTime, + int64_t* commonTime) { + return mTimeServer.getCommonClock().localToCommon(localTime, commonTime); +} + +status_t CommonClockService::getCommonTime(int64_t* commonTime) { + return localTimeToCommonTime(mTimeServer.getLocalClock().getLocalTime(), commonTime); +} + +status_t CommonClockService::getCommonFreq(uint64_t* freq) { + *freq = mTimeServer.getCommonClock().getCommonFreq(); + return OK; +} + +status_t CommonClockService::getLocalTime(int64_t* localTime) { + *localTime = mTimeServer.getLocalClock().getLocalTime(); + return OK; +} + +status_t CommonClockService::getLocalFreq(uint64_t* freq) { + *freq = mTimeServer.getLocalClock().getLocalFreq(); + return OK; +} + +status_t CommonClockService::getEstimatedError(int32_t* estimate) { + *estimate = mTimeServer.getEstimatedError(); + return OK; +} + +status_t CommonClockService::getTimelineID(uint64_t* id) { + *id = mTimeServer.getTimelineID(); + return OK; +} + +status_t CommonClockService::getState(State* state) { + *state = mTimeServer.getState(); + return OK; +} + +status_t CommonClockService::getMasterAddr(struct sockaddr_storage* addr) { + return mTimeServer.getMasterAddr(addr); +} + +status_t CommonClockService::registerListener( + const sp<ICommonClockListener>& listener) { + Mutex::Autolock lock(mRegistrationLock); + + { // scoping for autolock pattern + Mutex::Autolock lock(mCallbackLock); + // check whether this is a duplicate + for (size_t i = 0; i < mListeners.size(); i++) { + if (mListeners[i]->asBinder() == listener->asBinder()) + return ALREADY_EXISTS; + } + } + + mListeners.add(listener); + mTimeServer.reevaluateAutoDisableState(0 != mListeners.size()); + return listener->asBinder()->linkToDeath(this); +} + +status_t CommonClockService::unregisterListener( + const sp<ICommonClockListener>& listener) { + Mutex::Autolock lock(mRegistrationLock); + status_t ret_val = NAME_NOT_FOUND; + + { // scoping for autolock pattern + Mutex::Autolock lock(mCallbackLock); + for (size_t i = 0; i < mListeners.size(); i++) { + if (mListeners[i]->asBinder() == listener->asBinder()) { + mListeners[i]->asBinder()->unlinkToDeath(this); + mListeners.removeAt(i); + ret_val = OK; + break; + } + } + } + + mTimeServer.reevaluateAutoDisableState(0 != mListeners.size()); + return ret_val; +} + +void CommonClockService::binderDied(const wp<IBinder>& who) { + Mutex::Autolock lock(mRegistrationLock); + + { // scoping for autolock pattern + Mutex::Autolock lock(mCallbackLock); + for (size_t i = 0; i < mListeners.size(); i++) { + if (mListeners[i]->asBinder() == who) { + mListeners.removeAt(i); + break; + } + } + } + + mTimeServer.reevaluateAutoDisableState(0 != mListeners.size()); +} + +void CommonClockService::notifyOnTimelineChanged(uint64_t timelineID) { + Mutex::Autolock lock(mCallbackLock); + + for (size_t i = 0; i < mListeners.size(); i++) { + mListeners[i]->onTimelineChanged(timelineID); + } +} + +}; // namespace android diff --git a/services/common_time/common_clock_service.h b/services/common_time/common_clock_service.h new file mode 100644 index 000000000000..a65e398d276f --- /dev/null +++ b/services/common_time/common_clock_service.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <common_time/ICommonClock.h> + +#ifndef ANDROID_COMMON_CLOCK_SERVICE_H +#define ANDROID_COMMON_CLOCK_SERVICE_H + +namespace android { + +class CommonTimeServer; + +class CommonClockService : public BnCommonClock, + public android::IBinder::DeathRecipient { + public: + static sp<CommonClockService> instantiate(CommonTimeServer& timeServer); + + virtual status_t dump(int fd, const Vector<String16>& args); + + virtual status_t isCommonTimeValid(bool* valid, uint32_t *timelineID); + virtual status_t commonTimeToLocalTime(int64_t common_time, + int64_t* local_time); + virtual status_t localTimeToCommonTime(int64_t local_time, + int64_t* common_time); + virtual status_t getCommonTime(int64_t* common_time); + virtual status_t getCommonFreq(uint64_t* freq); + virtual status_t getLocalTime(int64_t* local_time); + virtual status_t getLocalFreq(uint64_t* freq); + virtual status_t getEstimatedError(int32_t* estimate); + virtual status_t getTimelineID(uint64_t* id); + virtual status_t getState(ICommonClock::State* state); + virtual status_t getMasterAddr(struct sockaddr_storage* addr); + + virtual status_t registerListener( + const sp<ICommonClockListener>& listener); + virtual status_t unregisterListener( + const sp<ICommonClockListener>& listener); + + void notifyOnTimelineChanged(uint64_t timelineID); + + private: + CommonClockService(CommonTimeServer& timeServer) + : mTimeServer(timeServer) { }; + + virtual void binderDied(const wp<IBinder>& who); + + CommonTimeServer& mTimeServer; + + // locks used to synchronize access to the list of registered listeners. + // The callback lock is held whenever the list is used to perform callbacks + // or while the list is being modified. The registration lock used to + // serialize access across registerListener, unregisterListener, and + // binderDied. + // + // The reason for two locks is that registerListener, unregisterListener, + // and binderDied each call into the core service and obtain the core + // service thread lock when they call reevaluateAutoDisableState. The core + // service thread obtains the main thread lock whenever its thread is + // running, and sometimes needs to call notifyOnTimelineChanged which then + // obtains the callback lock. If callers of registration functions were + // holding the callback lock when they called into the core service, we + // would have a classic A/B, B/A ordering deadlock. To avoid this, the + // registration functions hold the registration lock for the duration of + // their call, but hold the callback lock only while they mutate the list. + // This way, the list's size cannot change (because of the registration + // lock) during the call into reevaluateAutoDisableState, but the core work + // thread can still safely call notifyOnTimelineChanged while holding the + // main thread lock. + Mutex mCallbackLock; + Mutex mRegistrationLock; + + Vector<sp<ICommonClockListener> > mListeners; +}; + +}; // namespace android + +#endif // ANDROID_COMMON_CLOCK_SERVICE_H diff --git a/services/common_time/common_time_config_service.cpp b/services/common_time/common_time_config_service.cpp new file mode 100644 index 000000000000..958561818423 --- /dev/null +++ b/services/common_time/common_time_config_service.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/String8.h> + +#include "common_time_config_service.h" +#include "common_time_server.h" + +namespace android { + +sp<CommonTimeConfigService> CommonTimeConfigService::instantiate( + CommonTimeServer& timeServer) { + sp<CommonTimeConfigService> ctcs = new CommonTimeConfigService(timeServer); + if (ctcs == NULL) + return NULL; + + defaultServiceManager()->addService(ICommonTimeConfig::kServiceName, ctcs); + return ctcs; +} + +status_t CommonTimeConfigService::dump(int fd, const Vector<String16>& args) { + return mTimeServer.dumpConfigInterface(fd, args); +} + +status_t CommonTimeConfigService::getMasterElectionPriority(uint8_t *priority) { + return mTimeServer.getMasterElectionPriority(priority); +} + +status_t CommonTimeConfigService::setMasterElectionPriority(uint8_t priority) { + return mTimeServer.setMasterElectionPriority(priority); +} + +status_t CommonTimeConfigService::getMasterElectionEndpoint( + struct sockaddr_storage *addr) { + return mTimeServer.getMasterElectionEndpoint(addr); +} + +status_t CommonTimeConfigService::setMasterElectionEndpoint( + const struct sockaddr_storage *addr) { + return mTimeServer.setMasterElectionEndpoint(addr); +} + +status_t CommonTimeConfigService::getMasterElectionGroupId(uint64_t *id) { + return mTimeServer.getMasterElectionGroupId(id); +} + +status_t CommonTimeConfigService::setMasterElectionGroupId(uint64_t id) { + return mTimeServer.setMasterElectionGroupId(id); +} + +status_t CommonTimeConfigService::getInterfaceBinding(String16& ifaceName) { + String8 tmp; + status_t ret = mTimeServer.getInterfaceBinding(tmp); + ifaceName = String16(tmp); + return ret; +} + +status_t CommonTimeConfigService::setInterfaceBinding(const String16& ifaceName) { + String8 tmp(ifaceName); + return mTimeServer.setInterfaceBinding(tmp); +} + +status_t CommonTimeConfigService::getMasterAnnounceInterval(int *interval) { + return mTimeServer.getMasterAnnounceInterval(interval); +} + +status_t CommonTimeConfigService::setMasterAnnounceInterval(int interval) { + return mTimeServer.setMasterAnnounceInterval(interval); +} + +status_t CommonTimeConfigService::getClientSyncInterval(int *interval) { + return mTimeServer.getClientSyncInterval(interval); +} + +status_t CommonTimeConfigService::setClientSyncInterval(int interval) { + return mTimeServer.setClientSyncInterval(interval); +} + +status_t CommonTimeConfigService::getPanicThreshold(int *threshold) { + return mTimeServer.getPanicThreshold(threshold); +} + +status_t CommonTimeConfigService::setPanicThreshold(int threshold) { + return mTimeServer.setPanicThreshold(threshold); +} + +status_t CommonTimeConfigService::getAutoDisable(bool *autoDisable) { + return mTimeServer.getAutoDisable(autoDisable); +} + +status_t CommonTimeConfigService::setAutoDisable(bool autoDisable) { + return mTimeServer.setAutoDisable(autoDisable); +} + +status_t CommonTimeConfigService::forceNetworklessMasterMode() { + return mTimeServer.forceNetworklessMasterMode(); +} + +}; // namespace android diff --git a/services/common_time/common_time_config_service.h b/services/common_time/common_time_config_service.h new file mode 100644 index 000000000000..8283c24e0f4c --- /dev/null +++ b/services/common_time/common_time_config_service.h @@ -0,0 +1,59 @@ +/* * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <common_time/ICommonTimeConfig.h> + +#ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H +#define ANDROID_COMMON_TIME_CONFIG_SERVICE_H + +namespace android { + +class String16; +class CommonTimeServer; + +class CommonTimeConfigService : public BnCommonTimeConfig { + public: + static sp<CommonTimeConfigService> instantiate(CommonTimeServer& timeServer); + + virtual status_t dump(int fd, const Vector<String16>& args); + + virtual status_t getMasterElectionPriority(uint8_t *priority); + virtual status_t setMasterElectionPriority(uint8_t priority); + virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr); + virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr); + virtual status_t getMasterElectionGroupId(uint64_t *id); + virtual status_t setMasterElectionGroupId(uint64_t id); + virtual status_t getInterfaceBinding(String16& ifaceName); + virtual status_t setInterfaceBinding(const String16& ifaceName); + virtual status_t getMasterAnnounceInterval(int *interval); + virtual status_t setMasterAnnounceInterval(int interval); + virtual status_t getClientSyncInterval(int *interval); + virtual status_t setClientSyncInterval(int interval); + virtual status_t getPanicThreshold(int *threshold); + virtual status_t setPanicThreshold(int threshold); + virtual status_t getAutoDisable(bool *autoDisable); + virtual status_t setAutoDisable(bool autoDisable); + virtual status_t forceNetworklessMasterMode(); + + private: + CommonTimeConfigService(CommonTimeServer& timeServer) + : mTimeServer(timeServer) { } + CommonTimeServer& mTimeServer; + +}; + +}; // namespace android + +#endif // ANDROID_COMMON_TIME_CONFIG_SERVICE_H diff --git a/services/common_time/common_time_server.cpp b/services/common_time/common_time_server.cpp new file mode 100644 index 000000000000..4fed0d0a2470 --- /dev/null +++ b/services/common_time/common_time_server.cpp @@ -0,0 +1,1380 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A service that exchanges time synchronization information between + * a master that defines a timeline and clients that follow the timeline. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <assert.h> +#include <fcntl.h> +#include <linux/if_ether.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/ip.h> +#include <poll.h> +#include <stdio.h> +#include <sys/eventfd.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <common_time/local_clock.h> +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <utils/Timers.h> + +#include "common_clock_service.h" +#include "common_time_config_service.h" +#include "common_time_server.h" +#include "common_time_server_packets.h" +#include "clock_recovery.h" +#include "common_clock.h" + +#define MAX_INT ((int)0x7FFFFFFF) + +namespace android { + +const char* CommonTimeServer::kDefaultMasterElectionAddr = "239.195.128.88"; +const uint16_t CommonTimeServer::kDefaultMasterElectionPort = 8887; +const uint64_t CommonTimeServer::kDefaultSyncGroupID = 0; +const uint8_t CommonTimeServer::kDefaultMasterPriority = 1; +const uint32_t CommonTimeServer::kDefaultMasterAnnounceIntervalMs = 10000; +const uint32_t CommonTimeServer::kDefaultSyncRequestIntervalMs = 1000; +const uint32_t CommonTimeServer::kDefaultPanicThresholdUsec = 50000; +const bool CommonTimeServer::kDefaultAutoDisable = true; +const int CommonTimeServer::kSetupRetryTimeoutMs = 30000; +const int64_t CommonTimeServer::kNoGoodDataPanicThresholdUsec = 600000000ll; +const uint32_t CommonTimeServer::kRTTDiscardPanicThreshMultiplier = 5; + +// timeout value representing an infinite timeout +const int CommonTimeServer::kInfiniteTimeout = -1; + +/*** Initial state constants ***/ + +// number of WhoIsMaster attempts sent before giving up +const int CommonTimeServer::kInitial_NumWhoIsMasterRetries = 6; + +// timeout used when waiting for a response to a WhoIsMaster request +const int CommonTimeServer::kInitial_WhoIsMasterTimeoutMs = 500; + +/*** Client state constants ***/ + +// number of sync requests that can fail before a client assumes its master +// is dead +const int CommonTimeServer::kClient_NumSyncRequestRetries = 5; + +/*** Master state constants ***/ + +/*** Ronin state constants ***/ + +// number of WhoIsMaster attempts sent before declaring ourselves master +const int CommonTimeServer::kRonin_NumWhoIsMasterRetries = 4; + +// timeout used when waiting for a response to a WhoIsMaster request +const int CommonTimeServer::kRonin_WhoIsMasterTimeoutMs = 500; + +/*** WaitForElection state constants ***/ + +// how long do we wait for an announcement from a master before +// trying another election? +const int CommonTimeServer::kWaitForElection_TimeoutMs = 5000; + +CommonTimeServer::CommonTimeServer() + : Thread(false) + , mState(ICommonClock::STATE_INITIAL) + , mClockRecovery(&mLocalClock, &mCommonClock) + , mSocket(-1) + , mLastPacketRxLocalTime(0) + , mTimelineID(ICommonClock::kInvalidTimelineID) + , mClockSynced(false) + , mCommonClockHasClients(false) + , mInitial_WhoIsMasterRequestTimeouts(0) + , mClient_MasterDeviceID(0) + , mClient_MasterDevicePriority(0) + , mRonin_WhoIsMasterRequestTimeouts(0) { + // zero out sync stats + resetSyncStats(); + + // Setup the master election endpoint to use the default. + struct sockaddr_in* meep = + reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP); + memset(&mMasterElectionEP, 0, sizeof(mMasterElectionEP)); + inet_aton(kDefaultMasterElectionAddr, &meep->sin_addr); + meep->sin_family = AF_INET; + meep->sin_port = htons(kDefaultMasterElectionPort); + + // Zero out the master endpoint. + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + mBindIfaceValid = false; + setForceLowPriority(false); + + // Set all remaining configuration parameters to their defaults. + mDeviceID = 0; + mSyncGroupID = kDefaultSyncGroupID; + mMasterPriority = kDefaultMasterPriority; + mMasterAnnounceIntervalMs = kDefaultMasterAnnounceIntervalMs; + mSyncRequestIntervalMs = kDefaultSyncRequestIntervalMs; + mPanicThresholdUsec = kDefaultPanicThresholdUsec; + mAutoDisable = kDefaultAutoDisable; + + // Create the eventfd we will use to signal our thread to wake up when + // needed. + mWakeupThreadFD = eventfd(0, EFD_NONBLOCK); + + // seed the random number generator (used to generated timeline IDs) + srand48(static_cast<unsigned int>(systemTime())); +} + +CommonTimeServer::~CommonTimeServer() { + shutdownThread(); + + // No need to grab the lock here. We are in the destructor; if the the user + // has a thread in any of the APIs while the destructor is being called, + // there is a threading problem a the application level we cannot reasonably + // do anything about. + cleanupSocket_l(); + + if (mWakeupThreadFD >= 0) { + close(mWakeupThreadFD); + mWakeupThreadFD = -1; + } +} + +bool CommonTimeServer::startServices() { + // start the ICommonClock service + mICommonClock = CommonClockService::instantiate(*this); + if (mICommonClock == NULL) + return false; + + // start the ICommonTimeConfig service + mICommonTimeConfig = CommonTimeConfigService::instantiate(*this); + if (mICommonTimeConfig == NULL) + return false; + + return true; +} + +bool CommonTimeServer::threadLoop() { + // Register our service interfaces. + if (!startServices()) + return false; + + // Hold the lock while we are in the main thread loop. It will release the + // lock when it blocks, and hold the lock at all other times. + mLock.lock(); + runStateMachine_l(); + mLock.unlock(); + + IPCThreadState::self()->stopProcess(); + return false; +} + +bool CommonTimeServer::runStateMachine_l() { + if (!mLocalClock.initCheck()) + return false; + + if (!mCommonClock.init(mLocalClock.getLocalFreq())) + return false; + + // Enter the initial state. + becomeInitial("startup"); + + // run the state machine + while (!exitPending()) { + struct pollfd pfds[2]; + int rc; + int eventCnt = 0; + int64_t wakeupTime; + + // We are always interested in our wakeup FD. + pfds[eventCnt].fd = mWakeupThreadFD; + pfds[eventCnt].events = POLLIN; + pfds[eventCnt].revents = 0; + eventCnt++; + + // If we have a valid socket, then we are interested in what it has to + // say as well. + if (mSocket >= 0) { + pfds[eventCnt].fd = mSocket; + pfds[eventCnt].events = POLLIN; + pfds[eventCnt].revents = 0; + eventCnt++; + } + + // Note, we were holding mLock when this function was called. We + // release it only while we are blocking and hold it at all other times. + mLock.unlock(); + rc = poll(pfds, eventCnt, mCurTimeout.msecTillTimeout()); + wakeupTime = mLocalClock.getLocalTime(); + mLock.lock(); + + // Is it time to shutdown? If so, don't hesitate... just do it. + if (exitPending()) + break; + + // Did the poll fail? This should never happen and is fatal if it does. + if (rc < 0) { + ALOGE("%s:%d poll failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + if (rc == 0) + mCurTimeout.setTimeout(kInfiniteTimeout); + + // Were we woken up on purpose? If so, clear the eventfd with a read. + if (pfds[0].revents) + clearPendingWakeupEvents_l(); + + // Is out bind address dirty? If so, clean up our socket (if any). + // Alternatively, do we have an active socket but should be auto + // disabled? If so, release the socket and enter the proper sync state. + bool droppedSocket = false; + if (mBindIfaceDirty || ((mSocket >= 0) && shouldAutoDisable())) { + cleanupSocket_l(); + mBindIfaceDirty = false; + droppedSocket = true; + } + + // Do we not have a socket but should have one? If so, try to set one + // up. + if ((mSocket < 0) && mBindIfaceValid && !shouldAutoDisable()) { + if (setupSocket_l()) { + // Success! We are now joining a new network (either coming + // from no network, or coming from a potentially different + // network). Force our priority to be lower so that we defer to + // any other masters which may already be on the network we are + // joining. Later, when we enter either the client or the + // master state, we will clear this flag and go back to our + // normal election priority. + setForceLowPriority(true); + switch (mState) { + // If we were in initial (whether we had a immediately + // before this network or not) we want to simply reset the + // system and start again. Forcing a transition from + // INITIAL to INITIAL should do the job. + case CommonClockService::STATE_INITIAL: + becomeInitial("bound interface"); + break; + + // If we were in the master state, then either we were the + // master in a no-network situation, or we were the master + // of a different network and have moved to a new interface. + // In either case, immediately send out a master + // announcement at low priority. + case CommonClockService::STATE_MASTER: + sendMasterAnnouncement(); + break; + + // If we were in any other state (CLIENT, RONIN, or + // WAIT_FOR_ELECTION) then we must be moving from one + // network to another. We have lost our old master; + // transition to RONIN in an attempt to find a new master. + // If there are none out there, we will just assume + // responsibility for the timeline we used to be a client + // of. + default: + becomeRonin("bound interface"); + break; + } + } else { + // That's odd... we failed to set up our socket. This could be + // due to some transient network change which will work itself + // out shortly; schedule a retry attempt in the near future. + mCurTimeout.setTimeout(kSetupRetryTimeoutMs); + } + + // One way or the other, we don't have any data to process at this + // point (since we just tried to bulid a new socket). Loop back + // around and wait for the next thing to do. + continue; + } else if (droppedSocket) { + // We just lost our socket, and for whatever reason (either no + // config, or auto disable engaged) we are not supposed to rebuild + // one at this time. We are not going to rebuild our socket until + // something about our config/auto-disabled status changes, so we + // are basically in network-less mode. If we are already in either + // INITIAL or MASTER, just stay there until something changes. If + // we are in any other state (CLIENT, RONIN or WAIT_FOR_ELECTION), + // then transition to either INITIAL or MASTER depending on whether + // or not our timeline is valid. + ALOGI("Entering networkless mode interface is %s, " + "shouldAutoDisable = %s", + mBindIfaceValid ? "valid" : "invalid", + shouldAutoDisable() ? "true" : "false"); + if ((mState != ICommonClock::STATE_INITIAL) && + (mState != ICommonClock::STATE_MASTER)) { + if (mTimelineID == ICommonClock::kInvalidTimelineID) + becomeInitial("network-less mode"); + else + becomeMaster("network-less mode"); + } + + continue; + } + + // Did we wakeup with no signalled events across all of our FDs? If so, + // we must have hit our timeout. + if (rc == 0) { + if (!handleTimeout()) + ALOGE("handleTimeout failed"); + continue; + } + + // Does our socket have data for us (assuming we still have one, we + // may have RXed a packet at the same time as a config change telling us + // to shut our socket down)? If so, process its data. + if ((mSocket >= 0) && (eventCnt > 1) && (pfds[1].revents)) { + mLastPacketRxLocalTime = wakeupTime; + if (!handlePacket()) + ALOGE("handlePacket failed"); + } + } + + cleanupSocket_l(); + return true; +} + +void CommonTimeServer::clearPendingWakeupEvents_l() { + int64_t tmp; + read(mWakeupThreadFD, &tmp, sizeof(tmp)); +} + +void CommonTimeServer::wakeupThread_l() { + int64_t tmp = 1; + write(mWakeupThreadFD, &tmp, sizeof(tmp)); +} + +void CommonTimeServer::cleanupSocket_l() { + if (mSocket >= 0) { + close(mSocket); + mSocket = -1; + } +} + +void CommonTimeServer::shutdownThread() { + // Flag the work thread for shutdown. + this->requestExit(); + + // Signal the thread in case its sleeping. + mLock.lock(); + wakeupThread_l(); + mLock.unlock(); + + // Wait for the thread to exit. + this->join(); +} + +bool CommonTimeServer::setupSocket_l() { + int rc; + bool ret_val = false; + struct sockaddr_in* ipv4_addr = NULL; + char masterElectionEPStr[64]; + const int one = 1; + + // This should never be needed, but if we happened to have an old socket + // lying around, be sure to not leak it before proceeding. + cleanupSocket_l(); + + // If we don't have a valid endpoint to bind to, then how did we get here in + // the first place? Regardless, we know that we are going to fail to bind, + // so don't even try. + if (!mBindIfaceValid) + return false; + + sockaddrToString(mMasterElectionEP, true, masterElectionEPStr, + sizeof(masterElectionEPStr)); + ALOGI("Building socket :: bind = %s master election = %s", + mBindIface.string(), masterElectionEPStr); + + // TODO: add proper support for IPv6. Right now, we block IPv6 addresses at + // the configuration interface level. + if (AF_INET != mMasterElectionEP.ss_family) { + ALOGW("TODO: add proper IPv6 support"); + goto bailout; + } + + // open a UDP socket for the timeline serivce + mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (mSocket < 0) { + ALOGE("Failed to create socket (errno = %d)", errno); + goto bailout; + } + + // Bind to the selected interface using Linux's spiffy SO_BINDTODEVICE. + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", mBindIface.string()); + ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0; + rc = setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE, + (void *)&ifr, sizeof(ifr)); + if (rc) { + ALOGE("Failed to bind socket at to interface %s (errno = %d)", + ifr.ifr_name, errno); + goto bailout; + } + + // Bind our socket to INADDR_ANY and the master election port. The + // interface binding we made using SO_BINDTODEVICE should limit us to + // traffic only on the interface we are interested in. We need to bind to + // INADDR_ANY and the specific master election port in order to be able to + // receive both unicast traffic and master election multicast traffic with + // just a single socket. + struct sockaddr_in bindAddr; + ipv4_addr = reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP); + memcpy(&bindAddr, ipv4_addr, sizeof(bindAddr)); + bindAddr.sin_addr.s_addr = INADDR_ANY; + rc = bind(mSocket, + reinterpret_cast<const sockaddr *>(&bindAddr), + sizeof(bindAddr)); + if (rc) { + ALOGE("Failed to bind socket to port %hu (errno = %d)", + ntohs(bindAddr.sin_port), errno); + goto bailout; + } + + if (0xE0000000 == (ntohl(ipv4_addr->sin_addr.s_addr) & 0xF0000000)) { + // If our master election endpoint is a multicast address, be sure to join + // the multicast group. + struct ip_mreq mreq; + mreq.imr_multiaddr = ipv4_addr->sin_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + rc = setsockopt(mSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)); + if (rc == -1) { + ALOGE("Failed to join multicast group at %s. (errno = %d)", + masterElectionEPStr, errno); + goto bailout; + } + + // disable loopback of multicast packets + const int zero = 0; + rc = setsockopt(mSocket, IPPROTO_IP, IP_MULTICAST_LOOP, + &zero, sizeof(zero)); + if (rc == -1) { + ALOGE("Failed to disable multicast loopback (errno = %d)", errno); + goto bailout; + } + } else + if (ntohl(ipv4_addr->sin_addr.s_addr) != 0xFFFFFFFF) { + // If the master election address is neither broadcast, nor multicast, + // then we are misconfigured. The config API layer should prevent this + // from ever happening. + goto bailout; + } + + // Set the TTL of sent packets to 1. (Time protocol sync should never leave + // the local subnet) + rc = setsockopt(mSocket, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (rc == -1) { + ALOGE("Failed to set TTL to %d (errno = %d)", one, errno); + goto bailout; + } + + // get the device's unique ID + if (!assignDeviceID()) + goto bailout; + + ret_val = true; + +bailout: + if (!ret_val) + cleanupSocket_l(); + return ret_val; +} + +// generate a unique device ID that can be used for arbitration +bool CommonTimeServer::assignDeviceID() { + if (!mBindIfaceValid) + return false; + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_addr.sa_family = AF_INET; + strlcpy(ifr.ifr_name, mBindIface.string(), IFNAMSIZ); + + int rc = ioctl(mSocket, SIOCGIFHWADDR, &ifr); + if (rc) { + ALOGE("%s:%d ioctl failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) { + ALOGE("%s:%d got non-Ethernet address", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + mDeviceID = 0; + for (int i = 0; i < ETH_ALEN; i++) { + mDeviceID = (mDeviceID << 8) | ifr.ifr_hwaddr.sa_data[i]; + } + + return true; +} + +// generate a new timeline ID +void CommonTimeServer::assignTimelineID() { + do { + mTimelineID = (static_cast<uint64_t>(lrand48()) << 32) + | static_cast<uint64_t>(lrand48()); + } while (mTimelineID == ICommonClock::kInvalidTimelineID); +} + +// Select a preference between the device IDs of two potential masters. +// Returns true if the first ID wins, or false if the second ID wins. +bool CommonTimeServer::arbitrateMaster( + uint64_t deviceID1, uint8_t devicePrio1, + uint64_t deviceID2, uint8_t devicePrio2) { + return ((devicePrio1 > devicePrio2) || + ((devicePrio1 == devicePrio2) && (deviceID1 > deviceID2))); +} + +bool CommonTimeServer::handlePacket() { + uint8_t buf[256]; + struct sockaddr_storage srcAddr; + socklen_t srcAddrLen = sizeof(srcAddr); + + ssize_t recvBytes = recvfrom( + mSocket, buf, sizeof(buf), 0, + reinterpret_cast<const sockaddr *>(&srcAddr), &srcAddrLen); + + if (recvBytes < 0) { + ALOGE("%s:%d recvfrom failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + UniversalTimeServicePacket pkt; + recvBytes = pkt.deserializePacket(buf, recvBytes, mSyncGroupID); + if (recvBytes < 0) + return false; + + bool result; + switch (pkt.packetType) { + case TIME_PACKET_WHO_IS_MASTER_REQUEST: + result = handleWhoIsMasterRequest(&pkt.p.who_is_master_request, + srcAddr); + break; + + case TIME_PACKET_WHO_IS_MASTER_RESPONSE: + result = handleWhoIsMasterResponse(&pkt.p.who_is_master_response, + srcAddr); + break; + + case TIME_PACKET_SYNC_REQUEST: + result = handleSyncRequest(&pkt.p.sync_request, srcAddr); + break; + + case TIME_PACKET_SYNC_RESPONSE: + result = handleSyncResponse(&pkt.p.sync_response, srcAddr); + break; + + case TIME_PACKET_MASTER_ANNOUNCEMENT: + result = handleMasterAnnouncement(&pkt.p.master_announcement, + srcAddr); + break; + + default: { + ALOGD("%s:%d unknown packet type(%d)", + __PRETTY_FUNCTION__, __LINE__, pkt.packetType); + result = false; + } break; + } + + return result; +} + +bool CommonTimeServer::handleTimeout() { + // If we have no socket, then this must be a timeout to retry socket setup. + if (mSocket < 0) + return true; + + switch (mState) { + case ICommonClock::STATE_INITIAL: + return handleTimeoutInitial(); + case ICommonClock::STATE_CLIENT: + return handleTimeoutClient(); + case ICommonClock::STATE_MASTER: + return handleTimeoutMaster(); + case ICommonClock::STATE_RONIN: + return handleTimeoutRonin(); + case ICommonClock::STATE_WAIT_FOR_ELECTION: + return handleTimeoutWaitForElection(); + } + + return false; +} + +bool CommonTimeServer::handleTimeoutInitial() { + if (++mInitial_WhoIsMasterRequestTimeouts == + kInitial_NumWhoIsMasterRetries) { + // none of our attempts to discover a master succeeded, so make + // this device the master + return becomeMaster("initial timeout"); + } else { + // retry the WhoIsMaster request + return sendWhoIsMasterRequest(); + } +} + +bool CommonTimeServer::handleTimeoutClient() { + if (shouldPanicNotGettingGoodData()) + return becomeInitial("timeout panic, no good data"); + + if (mClient_SyncRequestPending) { + mClient_SyncRequestPending = false; + + if (++mClient_SyncRequestTimeouts < kClient_NumSyncRequestRetries) { + // a sync request has timed out, so retry + return sendSyncRequest(); + } else { + // The master has failed to respond to a sync request for too many + // times in a row. Assume the master is dead and start electing + // a new master. + return becomeRonin("master not responding"); + } + } else { + // initiate the next sync request + return sendSyncRequest(); + } +} + +bool CommonTimeServer::handleTimeoutMaster() { + // send another announcement from the master + return sendMasterAnnouncement(); +} + +bool CommonTimeServer::handleTimeoutRonin() { + if (++mRonin_WhoIsMasterRequestTimeouts == kRonin_NumWhoIsMasterRetries) { + // no other master is out there, so we won the election + return becomeMaster("no better masters detected"); + } else { + return sendWhoIsMasterRequest(); + } +} + +bool CommonTimeServer::handleTimeoutWaitForElection() { + return becomeRonin("timeout waiting for election conclusion"); +} + +bool CommonTimeServer::handleWhoIsMasterRequest( + const WhoIsMasterRequestPacket* request, + const sockaddr_storage& srcAddr) { + if (mState == ICommonClock::STATE_MASTER) { + // is this request related to this master's timeline? + if (request->timelineID != ICommonClock::kInvalidTimelineID && + request->timelineID != mTimelineID) + return true; + + WhoIsMasterResponsePacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + pkt.deviceID = mDeviceID; + pkt.devicePriority = effectivePriority(); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz < 0) + return false; + + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&srcAddr), + sizeof(srcAddr)); + if (sendBytes == -1) { + ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + } else if (mState == ICommonClock::STATE_RONIN) { + // if we hear a WhoIsMaster request from another device following + // the same timeline and that device wins arbitration, then we will stop + // trying to elect ourselves master and will instead wait for an + // announcement from the election winner + if (request->timelineID != mTimelineID) + return true; + + if (arbitrateMaster(request->senderDeviceID, + request->senderDevicePriority, + mDeviceID, + effectivePriority())) + return becomeWaitForElection("would lose election"); + + return true; + } else if (mState == ICommonClock::STATE_INITIAL) { + // If a group of devices booted simultaneously (e.g. after a power + // outage) and all of them are in the initial state and there is no + // master, then each device may time out and declare itself master at + // the same time. To avoid this, listen for + // WhoIsMaster(InvalidTimeline) requests from peers. If we would lose + // arbitration against that peer, reset our timeout count so that the + // peer has a chance to become master before we time out. + if (request->timelineID == ICommonClock::kInvalidTimelineID && + arbitrateMaster(request->senderDeviceID, + request->senderDevicePriority, + mDeviceID, + effectivePriority())) { + mInitial_WhoIsMasterRequestTimeouts = 0; + } + } + + return true; +} + +bool CommonTimeServer::handleWhoIsMasterResponse( + const WhoIsMasterResponsePacket* response, + const sockaddr_storage& srcAddr) { + if (mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN) { + return becomeClient(srcAddr, + response->deviceID, + response->devicePriority, + response->timelineID, + "heard whois response"); + } else if (mState == ICommonClock::STATE_CLIENT) { + // if we get multiple responses because there are multiple devices + // who believe that they are master, then follow the master that + // wins arbitration + if (arbitrateMaster(response->deviceID, + response->devicePriority, + mClient_MasterDeviceID, + mClient_MasterDevicePriority)) { + return becomeClient(srcAddr, + response->deviceID, + response->devicePriority, + response->timelineID, + "heard whois response"); + } + } + + return true; +} + +bool CommonTimeServer::handleSyncRequest(const SyncRequestPacket* request, + const sockaddr_storage& srcAddr) { + SyncResponsePacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + + if ((mState == ICommonClock::STATE_MASTER) && + (mTimelineID == request->timelineID)) { + int64_t rxLocalTime = mLastPacketRxLocalTime; + int64_t rxCommonTime; + + // If we are master on an actual network and have actual clients, then + // we are no longer low priority. + setForceLowPriority(false); + + if (OK != mCommonClock.localToCommon(rxLocalTime, &rxCommonTime)) { + return false; + } + + int64_t txLocalTime = mLocalClock.getLocalTime();; + int64_t txCommonTime; + if (OK != mCommonClock.localToCommon(txLocalTime, &txCommonTime)) { + return false; + } + + pkt.nak = 0; + pkt.clientTxLocalTime = request->clientTxLocalTime; + pkt.masterRxCommonTime = rxCommonTime; + pkt.masterTxCommonTime = txCommonTime; + } else { + pkt.nak = 1; + pkt.clientTxLocalTime = 0; + pkt.masterRxCommonTime = 0; + pkt.masterTxCommonTime = 0; + } + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz < 0) + return false; + + ssize_t sendBytes = sendto( + mSocket, &buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&srcAddr), + sizeof(srcAddr)); + if (sendBytes == -1) { + ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__); + return false; + } + + return true; +} + +bool CommonTimeServer::handleSyncResponse( + const SyncResponsePacket* response, + const sockaddr_storage& srcAddr) { + if (mState != ICommonClock::STATE_CLIENT) + return true; + + assert(mMasterEPValid); + if (!sockaddrMatch(srcAddr, mMasterEP, true)) { + char srcEP[64], expectedEP[64]; + sockaddrToString(srcAddr, true, srcEP, sizeof(srcEP)); + sockaddrToString(mMasterEP, true, expectedEP, sizeof(expectedEP)); + ALOGI("Dropping sync response from unexpected address." + " Expected %s Got %s", expectedEP, srcEP); + return true; + } + + if (response->nak) { + // if our master is no longer accepting requests, then we need to find + // a new master + return becomeRonin("master NAK'ed"); + } + + mClient_SyncRequestPending = 0; + mClient_SyncRequestTimeouts = 0; + mClient_PacketRTTLog.logRX(response->clientTxLocalTime, + mLastPacketRxLocalTime); + + bool result; + if (!(mClient_SyncRespsRXedFromCurMaster++)) { + // the first request/response exchange between a client and a master + // may take unusually long due to ARP, so discard it. + result = true; + } else { + int64_t clientTxLocalTime = response->clientTxLocalTime; + int64_t clientRxLocalTime = mLastPacketRxLocalTime; + int64_t masterTxCommonTime = response->masterTxCommonTime; + int64_t masterRxCommonTime = response->masterRxCommonTime; + + int64_t rtt = (clientRxLocalTime - clientTxLocalTime); + int64_t avgLocal = (clientTxLocalTime + clientRxLocalTime) >> 1; + int64_t avgCommon = (masterTxCommonTime + masterRxCommonTime) >> 1; + + // if the RTT of the packet is significantly larger than the panic + // threshold, we should simply discard it. Its better to do nothing + // than to take cues from a packet like that. + int rttCommon = mCommonClock.localDurationToCommonDuration(rtt); + if (rttCommon > (static_cast<int64_t>(mPanicThresholdUsec) * + kRTTDiscardPanicThreshMultiplier)) { + ALOGV("Dropping sync response with RTT of %lld uSec", rttCommon); + mClient_ExpiredSyncRespsRXedFromCurMaster++; + if (shouldPanicNotGettingGoodData()) + return becomeInitial("RX panic, no good data"); + } else { + result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rtt); + mClient_LastGoodSyncRX = clientRxLocalTime; + + if (result) { + // indicate to listeners that we've synced to the common timeline + notifyClockSync(); + } else { + ALOGE("Panic! Observed clock sync error is too high to tolerate," + " resetting state machine and starting over."); + notifyClockSyncLoss(); + return becomeInitial("panic"); + } + } + } + + mCurTimeout.setTimeout(mSyncRequestIntervalMs); + return result; +} + +bool CommonTimeServer::handleMasterAnnouncement( + const MasterAnnouncementPacket* packet, + const sockaddr_storage& srcAddr) { + uint64_t newDeviceID = packet->deviceID; + uint8_t newDevicePrio = packet->devicePriority; + uint64_t newTimelineID = packet->timelineID; + + if (mState == ICommonClock::STATE_INITIAL || + mState == ICommonClock::STATE_RONIN || + mState == ICommonClock::STATE_WAIT_FOR_ELECTION) { + // if we aren't currently following a master, then start following + // this new master + return becomeClient(srcAddr, + newDeviceID, + newDevicePrio, + newTimelineID, + "heard master announcement"); + } else if (mState == ICommonClock::STATE_CLIENT) { + // if the new master wins arbitration against our current master, + // then become a client of the new master + if (arbitrateMaster(newDeviceID, + newDevicePrio, + mClient_MasterDeviceID, + mClient_MasterDevicePriority)) + return becomeClient(srcAddr, + newDeviceID, + newDevicePrio, + newTimelineID, + "heard master announcement"); + } else if (mState == ICommonClock::STATE_MASTER) { + // two masters are competing - if the new one wins arbitration, then + // cease acting as master + if (arbitrateMaster(newDeviceID, newDevicePrio, + mDeviceID, effectivePriority())) + return becomeClient(srcAddr, newDeviceID, + newDevicePrio, newTimelineID, + "heard master announcement"); + } + + return true; +} + +bool CommonTimeServer::sendWhoIsMasterRequest() { + assert(mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN); + + // If we have no socket, then we must be in the unconfigured initial state. + // Don't report any errors, just don't try to send the initial who-is-master + // query. Eventually, our network will either become configured, or we will + // be forced into network-less master mode by higher level code. + if (mSocket < 0) { + assert(mState == ICommonClock::STATE_INITIAL); + return true; + } + + bool ret = false; + WhoIsMasterRequestPacket pkt; + pkt.initHeader(mSyncGroupID); + pkt.senderDeviceID = mDeviceID; + pkt.senderDevicePriority = effectivePriority(); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz >= 0) { + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&mMasterElectionEP), + sizeof(mMasterElectionEP)); + if (sendBytes < 0) + ALOGE("WhoIsMaster sendto failed (errno %d)", errno); + ret = true; + } + + if (mState == ICommonClock::STATE_INITIAL) { + mCurTimeout.setTimeout(kInitial_WhoIsMasterTimeoutMs); + } else { + mCurTimeout.setTimeout(kRonin_WhoIsMasterTimeoutMs); + } + + return ret; +} + +bool CommonTimeServer::sendSyncRequest() { + // If we are sending sync requests, then we must be in the client state and + // we must have a socket (when we have no network, we are only supposed to + // be in INITIAL or MASTER) + assert(mState == ICommonClock::STATE_CLIENT); + assert(mSocket >= 0); + + bool ret = false; + SyncRequestPacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + pkt.clientTxLocalTime = mLocalClock.getLocalTime(); + + if (!mClient_FirstSyncTX) + mClient_FirstSyncTX = pkt.clientTxLocalTime; + + mClient_PacketRTTLog.logTX(pkt.clientTxLocalTime); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz >= 0) { + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&mMasterEP), + sizeof(mMasterEP)); + if (sendBytes < 0) + ALOGE("SyncRequest sendto failed (errno %d)", errno); + ret = true; + } + + mClient_SyncsSentToCurMaster++; + mCurTimeout.setTimeout(mSyncRequestIntervalMs); + mClient_SyncRequestPending = true; + + return ret; +} + +bool CommonTimeServer::sendMasterAnnouncement() { + bool ret = false; + assert(mState == ICommonClock::STATE_MASTER); + + // If we are being asked to send a master announcement, but we have no + // socket, we must be in network-less master mode. Don't bother to send the + // announcement, and don't bother to schedule a timeout. When the network + // comes up, the work thread will get poked and start the process of + // figuring out who the current master should be. + if (mSocket < 0) { + mCurTimeout.setTimeout(kInfiniteTimeout); + return true; + } + + MasterAnnouncementPacket pkt; + pkt.initHeader(mTimelineID, mSyncGroupID); + pkt.deviceID = mDeviceID; + pkt.devicePriority = effectivePriority(); + + uint8_t buf[256]; + ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf)); + if (bufSz >= 0) { + ssize_t sendBytes = sendto( + mSocket, buf, bufSz, 0, + reinterpret_cast<const sockaddr *>(&mMasterElectionEP), + sizeof(mMasterElectionEP)); + if (sendBytes < 0) + ALOGE("MasterAnnouncement sendto failed (errno %d)", errno); + ret = true; + } + + mCurTimeout.setTimeout(mMasterAnnounceIntervalMs); + return ret; +} + +bool CommonTimeServer::becomeClient(const sockaddr_storage& masterEP, + uint64_t masterDeviceID, + uint8_t masterDevicePriority, + uint64_t timelineID, + const char* cause) { + char newEPStr[64], oldEPStr[64]; + sockaddrToString(masterEP, true, newEPStr, sizeof(newEPStr)); + sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr)); + + ALOGI("%s --> CLIENT (%s) :%s" + " OldMaster: %02x-%014llx::%016llx::%s" + " NewMaster: %02x-%014llx::%016llx::%s", + stateToString(mState), cause, + (mTimelineID != timelineID) ? " (new timeline)" : "", + mClient_MasterDevicePriority, mClient_MasterDeviceID, + mTimelineID, oldEPStr, + masterDevicePriority, masterDeviceID, + timelineID, newEPStr); + + if (mTimelineID != timelineID) { + // start following a new timeline + mTimelineID = timelineID; + mClockRecovery.reset(true, true); + notifyClockSyncLoss(); + } else { + // start following a new master on the existing timeline + mClockRecovery.reset(false, true); + } + + mMasterEP = masterEP; + mMasterEPValid = true; + setForceLowPriority(false); + + mClient_MasterDeviceID = masterDeviceID; + mClient_MasterDevicePriority = masterDevicePriority; + resetSyncStats(); + + setState(ICommonClock::STATE_CLIENT); + + // add some jitter to when the various clients send their requests + // in order to reduce the likelihood that a group of clients overload + // the master after receiving a master announcement + usleep((lrand48() % 100) * 1000); + + return sendSyncRequest(); +} + +bool CommonTimeServer::becomeMaster(const char* cause) { + uint64_t oldTimelineID = mTimelineID; + if (mTimelineID == ICommonClock::kInvalidTimelineID) { + // this device has not been following any existing timeline, + // so it will create a new timeline and declare itself master + assert(!mCommonClock.isValid()); + + // set the common time basis + mCommonClock.setBasis(mLocalClock.getLocalTime(), 0); + + // assign an arbitrary timeline iD + assignTimelineID(); + + // notify listeners that we've created a common timeline + notifyClockSync(); + } + + ALOGI("%s --> MASTER (%s) : %s timeline %016llx", + stateToString(mState), cause, + (oldTimelineID == mTimelineID) ? "taking ownership of" + : "creating new", + mTimelineID); + + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + setForceLowPriority(false); + mClient_MasterDevicePriority = effectivePriority(); + mClient_MasterDeviceID = mDeviceID; + mClockRecovery.reset(false, true); + resetSyncStats(); + + setState(ICommonClock::STATE_MASTER); + return sendMasterAnnouncement(); +} + +bool CommonTimeServer::becomeRonin(const char* cause) { + // If we were the client of a given timeline, but had never received even a + // single time sync packet, then we transition back to Initial instead of + // Ronin. If we transition to Ronin and end up becoming the new Master, we + // will be unable to service requests for other clients because we never + // actually knew what time it was. By going to initial, we ensure that + // other clients who know what time it is, but would lose master arbitration + // in the Ronin case, will step up and become the proper new master of the + // old timeline. + + char oldEPStr[64]; + sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr)); + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + + if (mCommonClock.isValid()) { + ALOGI("%s --> RONIN (%s) : lost track of previously valid timeline " + "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)", + stateToString(mState), cause, + mClient_MasterDevicePriority, mClient_MasterDeviceID, + mTimelineID, oldEPStr, + mClient_SyncsSentToCurMaster, + mClient_SyncRespsRXedFromCurMaster, + mClient_ExpiredSyncRespsRXedFromCurMaster); + + mRonin_WhoIsMasterRequestTimeouts = 0; + setState(ICommonClock::STATE_RONIN); + return sendWhoIsMasterRequest(); + } else { + ALOGI("%s --> INITIAL (%s) : never synced timeline " + "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)", + stateToString(mState), cause, + mClient_MasterDevicePriority, mClient_MasterDeviceID, + mTimelineID, oldEPStr, + mClient_SyncsSentToCurMaster, + mClient_SyncRespsRXedFromCurMaster, + mClient_ExpiredSyncRespsRXedFromCurMaster); + + return becomeInitial("ronin, no timeline"); + } +} + +bool CommonTimeServer::becomeWaitForElection(const char* cause) { + ALOGI("%s --> WAIT_FOR_ELECTION (%s) : dropping out of election," + " waiting %d mSec for completion.", + stateToString(mState), cause, kWaitForElection_TimeoutMs); + + setState(ICommonClock::STATE_WAIT_FOR_ELECTION); + mCurTimeout.setTimeout(kWaitForElection_TimeoutMs); + return true; +} + +bool CommonTimeServer::becomeInitial(const char* cause) { + ALOGI("Entering INITIAL (%s), total reset.", cause); + + setState(ICommonClock::STATE_INITIAL); + + // reset clock recovery + mClockRecovery.reset(true, true); + + // reset internal state bookkeeping. + mCurTimeout.setTimeout(kInfiniteTimeout); + memset(&mMasterEP, 0, sizeof(mMasterEP)); + mMasterEPValid = false; + mLastPacketRxLocalTime = 0; + mTimelineID = ICommonClock::kInvalidTimelineID; + mClockSynced = false; + mInitial_WhoIsMasterRequestTimeouts = 0; + mClient_MasterDeviceID = 0; + mClient_MasterDevicePriority = 0; + mRonin_WhoIsMasterRequestTimeouts = 0; + resetSyncStats(); + + // send the first request to discover the master + return sendWhoIsMasterRequest(); +} + +void CommonTimeServer::notifyClockSync() { + if (!mClockSynced) { + mClockSynced = true; + mICommonClock->notifyOnTimelineChanged(mTimelineID); + } +} + +void CommonTimeServer::notifyClockSyncLoss() { + if (mClockSynced) { + mClockSynced = false; + mICommonClock->notifyOnTimelineChanged( + ICommonClock::kInvalidTimelineID); + } +} + +void CommonTimeServer::setState(ICommonClock::State s) { + mState = s; +} + +const char* CommonTimeServer::stateToString(ICommonClock::State s) { + switch(s) { + case ICommonClock::STATE_INITIAL: + return "INITIAL"; + case ICommonClock::STATE_CLIENT: + return "CLIENT"; + case ICommonClock::STATE_MASTER: + return "MASTER"; + case ICommonClock::STATE_RONIN: + return "RONIN"; + case ICommonClock::STATE_WAIT_FOR_ELECTION: + return "WAIT_FOR_ELECTION"; + default: + return "unknown"; + } +} + +void CommonTimeServer::sockaddrToString(const sockaddr_storage& addr, + bool addrValid, + char* buf, size_t bufLen) { + if (!bufLen || !buf) + return; + + if (addrValid) { + switch (addr.ss_family) { + case AF_INET: { + const struct sockaddr_in* sa = + reinterpret_cast<const struct sockaddr_in*>(&addr); + unsigned long a = ntohl(sa->sin_addr.s_addr); + uint16_t p = ntohs(sa->sin_port); + snprintf(buf, bufLen, "%lu.%lu.%lu.%lu:%hu", + ((a >> 24) & 0xFF), ((a >> 16) & 0xFF), + ((a >> 8) & 0xFF), (a & 0xFF), p); + } break; + + case AF_INET6: { + const struct sockaddr_in6* sa = + reinterpret_cast<const struct sockaddr_in6*>(&addr); + const uint8_t* a = sa->sin6_addr.s6_addr; + uint16_t p = ntohs(sa->sin6_port); + snprintf(buf, bufLen, + "%02X%02X:%02X%02X:%02X%02X:%02X%02X:" + "%02X%02X:%02X%02X:%02X%02X:%02X%02X port %hd", + a[0], a[1], a[ 2], a[ 3], a[ 4], a[ 5], a[ 6], a[ 7], + a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], + p); + } break; + + default: + snprintf(buf, bufLen, + "<unknown sockaddr family %d>", addr.ss_family); + break; + } + } else { + snprintf(buf, bufLen, "<none>"); + } + + buf[bufLen - 1] = 0; +} + +bool CommonTimeServer::sockaddrMatch(const sockaddr_storage& a1, + const sockaddr_storage& a2, + bool matchAddressOnly) { + if (a1.ss_family != a2.ss_family) + return false; + + switch (a1.ss_family) { + case AF_INET: { + const struct sockaddr_in* sa1 = + reinterpret_cast<const struct sockaddr_in*>(&a1); + const struct sockaddr_in* sa2 = + reinterpret_cast<const struct sockaddr_in*>(&a2); + + if (sa1->sin_addr.s_addr != sa2->sin_addr.s_addr) + return false; + + return (matchAddressOnly || (sa1->sin_port == sa2->sin_port)); + } break; + + case AF_INET6: { + const struct sockaddr_in6* sa1 = + reinterpret_cast<const struct sockaddr_in6*>(&a1); + const struct sockaddr_in6* sa2 = + reinterpret_cast<const struct sockaddr_in6*>(&a2); + + if (memcmp(&sa1->sin6_addr, &sa2->sin6_addr, sizeof(sa2->sin6_addr))) + return false; + + return (matchAddressOnly || (sa1->sin6_port == sa2->sin6_port)); + } break; + + // Huh? We don't deal in non-IPv[46] addresses. Not sure how we got + // here, but we don't know how to comapre these addresses and simply + // default to a no-match decision. + default: return false; + } +} + +void CommonTimeServer::TimeoutHelper::setTimeout(int msec) { + mTimeoutValid = (msec >= 0); + if (mTimeoutValid) + mEndTime = systemTime() + + (static_cast<nsecs_t>(msec) * 1000000); +} + +int CommonTimeServer::TimeoutHelper::msecTillTimeout() { + if (!mTimeoutValid) + return kInfiniteTimeout; + + nsecs_t now = systemTime(); + if (now >= mEndTime) + return 0; + + uint64_t deltaMsec = (((mEndTime - now) + 999999) / 1000000); + + if (deltaMsec > static_cast<uint64_t>(MAX_INT)) + return MAX_INT; + + return static_cast<int>(deltaMsec); +} + +bool CommonTimeServer::shouldPanicNotGettingGoodData() { + if (mClient_FirstSyncTX) { + int64_t now = mLocalClock.getLocalTime(); + int64_t delta = now - (mClient_LastGoodSyncRX + ? mClient_LastGoodSyncRX + : mClient_FirstSyncTX); + int64_t deltaUsec = mCommonClock.localDurationToCommonDuration(delta); + + if (deltaUsec >= kNoGoodDataPanicThresholdUsec) + return true; + } + + return false; +} + +void CommonTimeServer::PacketRTTLog::logTX(int64_t txTime) { + txTimes[wrPtr] = txTime; + rxTimes[wrPtr] = 0; + wrPtr = (wrPtr + 1) % RTT_LOG_SIZE; + if (!wrPtr) + logFull = true; +} + +void CommonTimeServer::PacketRTTLog::logRX(int64_t txTime, int64_t rxTime) { + if (!logFull && !wrPtr) + return; + + uint32_t i = logFull ? wrPtr : 0; + do { + if (txTimes[i] == txTime) { + rxTimes[i] = rxTime; + break; + } + i = (i + 1) % RTT_LOG_SIZE; + } while (i != wrPtr); +} + +} // namespace android diff --git a/services/common_time/common_time_server.h b/services/common_time/common_time_server.h new file mode 100644 index 000000000000..1b5520286b43 --- /dev/null +++ b/services/common_time/common_time_server.h @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_COMMON_TIME_SERVER_H +#define ANDROID_COMMON_TIME_SERVER_H + +#include <arpa/inet.h> +#include <stdint.h> +#include <linux/socket.h> + +#include <common_time/ICommonClock.h> +#include <common_time/local_clock.h> +#include <utils/String8.h> + +#include "clock_recovery.h" +#include "common_clock.h" +#include "common_time_server_packets.h" + +#define RTT_LOG_SIZE 30 + +namespace android { + +class CommonClockService; +class CommonTimeConfigService; + +/***** time service implementation *****/ + +class CommonTimeServer : public Thread { + public: + CommonTimeServer(); + ~CommonTimeServer(); + + bool startServices(); + + // Common Clock API methods + CommonClock& getCommonClock() { return mCommonClock; } + LocalClock& getLocalClock() { return mLocalClock; } + uint64_t getTimelineID(); + int32_t getEstimatedError(); + ICommonClock::State getState(); + status_t getMasterAddr(struct sockaddr_storage* addr); + status_t isCommonTimeValid(bool* valid, uint32_t* timelineID); + + // Config API methods + status_t getMasterElectionPriority(uint8_t *priority); + status_t setMasterElectionPriority(uint8_t priority); + status_t getMasterElectionEndpoint(struct sockaddr_storage *addr); + status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr); + status_t getMasterElectionGroupId(uint64_t *id); + status_t setMasterElectionGroupId(uint64_t id); + status_t getInterfaceBinding(String8& ifaceName); + status_t setInterfaceBinding(const String8& ifaceName); + status_t getMasterAnnounceInterval(int *interval); + status_t setMasterAnnounceInterval(int interval); + status_t getClientSyncInterval(int *interval); + status_t setClientSyncInterval(int interval); + status_t getPanicThreshold(int *threshold); + status_t setPanicThreshold(int threshold); + status_t getAutoDisable(bool *autoDisable); + status_t setAutoDisable(bool autoDisable); + status_t forceNetworklessMasterMode(); + + // Method used by the CommonClockService to notify the core service about + // changes in the number of active common clock clients. + void reevaluateAutoDisableState(bool commonClockHasClients); + + status_t dumpClockInterface(int fd, const Vector<String16>& args, + size_t activeClients); + status_t dumpConfigInterface(int fd, const Vector<String16>& args); + + private: + class PacketRTTLog { + public: + PacketRTTLog() { + resetLog(); + } + + void resetLog() { + wrPtr = 0; + logFull = 0; + } + + void logTX(int64_t txTime); + void logRX(int64_t txTime, int64_t rxTime); + void dumpLog(int fd, const CommonClock& cclk); + + private: + uint32_t wrPtr; + bool logFull; + int64_t txTimes[RTT_LOG_SIZE]; + int64_t rxTimes[RTT_LOG_SIZE]; + }; + + class TimeoutHelper { + public: + TimeoutHelper() : mTimeoutValid(false) { } + + void setTimeout(int msec); + int msecTillTimeout(); + + private: + bool mTimeoutValid; + nsecs_t mEndTime; + }; + + bool threadLoop(); + + bool runStateMachine_l(); + bool setupSocket_l(); + + void assignTimelineID(); + bool assignDeviceID(); + + static bool arbitrateMaster(uint64_t deviceID1, uint8_t devicePrio1, + uint64_t deviceID2, uint8_t devicePrio2); + + bool handlePacket(); + bool handleWhoIsMasterRequest (const WhoIsMasterRequestPacket* request, + const sockaddr_storage& srcAddr); + bool handleWhoIsMasterResponse(const WhoIsMasterResponsePacket* response, + const sockaddr_storage& srcAddr); + bool handleSyncRequest (const SyncRequestPacket* request, + const sockaddr_storage& srcAddr); + bool handleSyncResponse (const SyncResponsePacket* response, + const sockaddr_storage& srcAddr); + bool handleMasterAnnouncement (const MasterAnnouncementPacket* packet, + const sockaddr_storage& srcAddr); + + bool handleTimeout(); + bool handleTimeoutInitial(); + bool handleTimeoutClient(); + bool handleTimeoutMaster(); + bool handleTimeoutRonin(); + bool handleTimeoutWaitForElection(); + + bool sendWhoIsMasterRequest(); + bool sendSyncRequest(); + bool sendMasterAnnouncement(); + + bool becomeClient(const sockaddr_storage& masterAddr, + uint64_t masterDeviceID, + uint8_t masterDevicePriority, + uint64_t timelineID, + const char* cause); + bool becomeMaster(const char* cause); + bool becomeRonin(const char* cause); + bool becomeWaitForElection(const char* cause); + bool becomeInitial(const char* cause); + + void notifyClockSync(); + void notifyClockSyncLoss(); + + ICommonClock::State mState; + void setState(ICommonClock::State s); + + void clearPendingWakeupEvents_l(); + void wakeupThread_l(); + void cleanupSocket_l(); + void shutdownThread(); + + inline uint8_t effectivePriority() const { + return (mMasterPriority & 0x7F) | + (mForceLowPriority ? 0x00 : 0x80); + } + + inline bool shouldAutoDisable() const { + return (mAutoDisable && !mCommonClockHasClients); + } + + inline void resetSyncStats() { + mClient_SyncRequestPending = false; + mClient_SyncRequestTimeouts = 0; + mClient_SyncsSentToCurMaster = 0; + mClient_SyncRespsRXedFromCurMaster = 0; + mClient_ExpiredSyncRespsRXedFromCurMaster = 0; + mClient_FirstSyncTX = 0; + mClient_LastGoodSyncRX = 0; + mClient_PacketRTTLog.resetLog(); + } + + bool shouldPanicNotGettingGoodData(); + + // Helper to keep track of the state machine's current timeout + TimeoutHelper mCurTimeout; + + // common clock, local clock abstraction, and clock recovery loop + CommonClock mCommonClock; + LocalClock mLocalClock; + ClockRecoveryLoop mClockRecovery; + + // implementation of ICommonClock + sp<CommonClockService> mICommonClock; + + // implementation of ICommonTimeConfig + sp<CommonTimeConfigService> mICommonTimeConfig; + + // UDP socket for the time sync protocol + int mSocket; + + // eventfd used to wakeup the work thread in response to configuration + // changes. + int mWakeupThreadFD; + + // timestamp captured when a packet is received + int64_t mLastPacketRxLocalTime; + + // ID of the timeline that this device is following + uint64_t mTimelineID; + + // flag for whether the clock has been synced to a timeline + bool mClockSynced; + + // flag used to indicate that clients should be considered to be lower + // priority than all of their peers during elections. This flag is set and + // cleared by the state machine. It is set when the client joins a new + // network. If the client had been a master in the old network (or an + // isolated master with no network connectivity) it should defer to any + // masters which may already be on the network. It will be cleared whenever + // the state machine transitions to the master state. + bool mForceLowPriority; + inline void setForceLowPriority(bool val) { + mForceLowPriority = val; + if (mState == ICommonClock::STATE_MASTER) + mClient_MasterDevicePriority = effectivePriority(); + } + + // Lock to synchronize access to internal state and configuration. + Mutex mLock; + + // Flag updated by the common clock service to indicate that it does or does + // not currently have registered clients. When the the auto disable flag is + // cleared on the common time service, the service will participate in + // network synchronization whenever it has a valid network interface to bind + // to. When the auto disable flag is set on the common time service, it + // will only participate in network synchronization when it has both a valid + // interface AND currently active common clock clients. + bool mCommonClockHasClients; + + // Configuration info + struct sockaddr_storage mMasterElectionEP; // Endpoint over which we conduct master election + String8 mBindIface; // Endpoint for the service to bind to. + bool mBindIfaceValid; // whether or not the bind Iface is valid. + bool mBindIfaceDirty; // whether or not the bind Iface is valid. + struct sockaddr_storage mMasterEP; // Endpoint of our current master (if any) + bool mMasterEPValid; + uint64_t mDeviceID; // unique ID of this device + uint64_t mSyncGroupID; // synchronization group ID of this device. + uint8_t mMasterPriority; // Priority of this device in master election. + uint32_t mMasterAnnounceIntervalMs; + uint32_t mSyncRequestIntervalMs; + uint32_t mPanicThresholdUsec; + bool mAutoDisable; + + // Config defaults. + static const char* kDefaultMasterElectionAddr; + static const uint16_t kDefaultMasterElectionPort; + static const uint64_t kDefaultSyncGroupID; + static const uint8_t kDefaultMasterPriority; + static const uint32_t kDefaultMasterAnnounceIntervalMs; + static const uint32_t kDefaultSyncRequestIntervalMs; + static const uint32_t kDefaultPanicThresholdUsec; + static const bool kDefaultAutoDisable; + + // Priority mask and shift fields. + static const uint64_t kDeviceIDMask; + static const uint8_t kDevicePriorityMask; + static const uint8_t kDevicePriorityHiLowBit; + static const uint32_t kDevicePriorityShift; + + // Unconfgurable constants + static const int kSetupRetryTimeoutMs; + static const int64_t kNoGoodDataPanicThresholdUsec; + static const uint32_t kRTTDiscardPanicThreshMultiplier; + + /*** status while in the Initial state ***/ + int mInitial_WhoIsMasterRequestTimeouts; + static const int kInitial_NumWhoIsMasterRetries; + static const int kInitial_WhoIsMasterTimeoutMs; + + /*** status while in the Client state ***/ + uint64_t mClient_MasterDeviceID; + uint8_t mClient_MasterDevicePriority; + bool mClient_SyncRequestPending; + int mClient_SyncRequestTimeouts; + uint32_t mClient_SyncsSentToCurMaster; + uint32_t mClient_SyncRespsRXedFromCurMaster; + uint32_t mClient_ExpiredSyncRespsRXedFromCurMaster; + int64_t mClient_FirstSyncTX; + int64_t mClient_LastGoodSyncRX; + PacketRTTLog mClient_PacketRTTLog; + static const int kClient_NumSyncRequestRetries; + + + /*** status while in the Master state ***/ + static const uint32_t kDefaultMaster_AnnouncementIntervalMs; + + /*** status while in the Ronin state ***/ + int mRonin_WhoIsMasterRequestTimeouts; + static const int kRonin_NumWhoIsMasterRetries; + static const int kRonin_WhoIsMasterTimeoutMs; + + /*** status while in the WaitForElection state ***/ + static const int kWaitForElection_TimeoutMs; + + static const int kInfiniteTimeout; + + static const char* stateToString(ICommonClock::State s); + static void sockaddrToString(const sockaddr_storage& addr, bool addrValid, + char* buf, size_t bufLen); + static bool sockaddrMatch(const sockaddr_storage& a1, + const sockaddr_storage& a2, + bool matchAddressOnly); +}; + +} // namespace android + +#endif // ANDROID_COMMON_TIME_SERVER_H + diff --git a/services/common_time/common_time_server_api.cpp b/services/common_time/common_time_server_api.cpp new file mode 100644 index 000000000000..fb8c2615e1c0 --- /dev/null +++ b/services/common_time/common_time_server_api.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A service that exchanges time synchronization information between + * a master that defines a timeline and clients that follow the timeline. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> + +#include "common_time_server.h" + +namespace android { + +// +// Clock API +// +uint64_t CommonTimeServer::getTimelineID() { + AutoMutex _lock(&mLock); + return mTimelineID; +} + +ICommonClock::State CommonTimeServer::getState() { + AutoMutex _lock(&mLock); + return mState; +} + +status_t CommonTimeServer::getMasterAddr(struct sockaddr_storage* addr) { + AutoMutex _lock(&mLock); + if (mMasterEPValid) { + memcpy(addr, &mMasterEP, sizeof(*addr)); + return OK; + } + + return UNKNOWN_ERROR; +} + +int32_t CommonTimeServer::getEstimatedError() { + AutoMutex _lock(&mLock); + + if (ICommonClock::STATE_MASTER == mState) + return 0; + + if (!mClockSynced) + return ICommonClock::kErrorEstimateUnknown; + + return mClockRecovery.getLastErrorEstimate(); +} + +status_t CommonTimeServer::isCommonTimeValid(bool* valid, + uint32_t* timelineID) { + AutoMutex _lock(&mLock); + *valid = mCommonClock.isValid(); + *timelineID = mTimelineID; + return OK; +} + +// +// Config API +// +status_t CommonTimeServer::getMasterElectionPriority(uint8_t *priority) { + AutoMutex _lock(&mLock); + *priority = mMasterPriority; + return OK; +} + +status_t CommonTimeServer::setMasterElectionPriority(uint8_t priority) { + AutoMutex _lock(&mLock); + + if (priority > 0x7F) + return BAD_VALUE; + + mMasterPriority = priority; + return OK; +} + +status_t CommonTimeServer::getMasterElectionEndpoint( + struct sockaddr_storage *addr) { + AutoMutex _lock(&mLock); + memcpy(addr, &mMasterElectionEP, sizeof(*addr)); + return OK; +} + +status_t CommonTimeServer::setMasterElectionEndpoint( + const struct sockaddr_storage *addr) { + AutoMutex _lock(&mLock); + + if (!addr) + return BAD_VALUE; + + // TODO: add proper support for IPv6 + if (addr->ss_family != AF_INET) + return BAD_VALUE; + + // Only multicast and broadcast endpoints with explicit ports are allowed. + uint16_t ipv4Port = ntohs( + reinterpret_cast<const struct sockaddr_in*>(addr)->sin_port); + if (!ipv4Port) + return BAD_VALUE; + + uint32_t ipv4Addr = ntohl( + reinterpret_cast<const struct sockaddr_in*>(addr)->sin_addr.s_addr); + if ((ipv4Addr != 0xFFFFFFFF) && (0xE0000000 != (ipv4Addr & 0xF0000000))) + return BAD_VALUE; + + memcpy(&mMasterElectionEP, addr, sizeof(mMasterElectionEP)); + + // Force a rebind in order to change election enpoints. + mBindIfaceDirty = true; + wakeupThread_l(); + return OK; +} + +status_t CommonTimeServer::getMasterElectionGroupId(uint64_t *id) { + AutoMutex _lock(&mLock); + *id = mSyncGroupID; + return OK; +} + +status_t CommonTimeServer::setMasterElectionGroupId(uint64_t id) { + AutoMutex _lock(&mLock); + mSyncGroupID = id; + return OK; +} + +status_t CommonTimeServer::getInterfaceBinding(String8& ifaceName) { + AutoMutex _lock(&mLock); + if (!mBindIfaceValid) + return INVALID_OPERATION; + ifaceName = mBindIface; + return OK; +} + +status_t CommonTimeServer::setInterfaceBinding(const String8& ifaceName) { + AutoMutex _lock(&mLock); + + mBindIfaceDirty = true; + if (ifaceName.size()) { + mBindIfaceValid = true; + mBindIface = ifaceName; + } else { + mBindIfaceValid = false; + mBindIface.clear(); + } + + wakeupThread_l(); + return OK; +} + +status_t CommonTimeServer::getMasterAnnounceInterval(int *interval) { + AutoMutex _lock(&mLock); + *interval = mMasterAnnounceIntervalMs; + return OK; +} + +status_t CommonTimeServer::setMasterAnnounceInterval(int interval) { + AutoMutex _lock(&mLock); + + if (interval > (6 *3600000)) // Max interval is once every 6 hrs + return BAD_VALUE; + + if (interval < 500) // Min interval is once per 0.5 seconds + return BAD_VALUE; + + mMasterAnnounceIntervalMs = interval; + if (ICommonClock::STATE_MASTER == mState) { + int pendingTimeout = mCurTimeout.msecTillTimeout(); + if ((kInfiniteTimeout == pendingTimeout) || + (pendingTimeout > interval)) { + mCurTimeout.setTimeout(mMasterAnnounceIntervalMs); + wakeupThread_l(); + } + } + + return OK; +} + +status_t CommonTimeServer::getClientSyncInterval(int *interval) { + AutoMutex _lock(&mLock); + *interval = mSyncRequestIntervalMs; + return OK; +} + +status_t CommonTimeServer::setClientSyncInterval(int interval) { + AutoMutex _lock(&mLock); + + if (interval > (3600000)) // Max interval is once every 60 min + return BAD_VALUE; + + if (interval < 250) // Min interval is once per 0.25 seconds + return BAD_VALUE; + + mSyncRequestIntervalMs = interval; + if (ICommonClock::STATE_CLIENT == mState) { + int pendingTimeout = mCurTimeout.msecTillTimeout(); + if ((kInfiniteTimeout == pendingTimeout) || + (pendingTimeout > interval)) { + mCurTimeout.setTimeout(mSyncRequestIntervalMs); + wakeupThread_l(); + } + } + + return OK; +} + +status_t CommonTimeServer::getPanicThreshold(int *threshold) { + AutoMutex _lock(&mLock); + *threshold = mPanicThresholdUsec; + return OK; +} + +status_t CommonTimeServer::setPanicThreshold(int threshold) { + AutoMutex _lock(&mLock); + + if (threshold < 1000) // Min threshold is 1mSec + return BAD_VALUE; + + mPanicThresholdUsec = threshold; + return OK; +} + +status_t CommonTimeServer::getAutoDisable(bool *autoDisable) { + AutoMutex _lock(&mLock); + *autoDisable = mAutoDisable; + return OK; +} + +status_t CommonTimeServer::setAutoDisable(bool autoDisable) { + AutoMutex _lock(&mLock); + mAutoDisable = autoDisable; + wakeupThread_l(); + return OK; +} + +status_t CommonTimeServer::forceNetworklessMasterMode() { + AutoMutex _lock(&mLock); + + // Can't force networkless master mode if we are currently bound to a + // network. + if (mSocket >= 0) + return INVALID_OPERATION; + + becomeMaster("force networkless"); + + return OK; +} + +void CommonTimeServer::reevaluateAutoDisableState(bool commonClockHasClients) { + AutoMutex _lock(&mLock); + bool needWakeup = (mAutoDisable && mMasterEPValid && + (commonClockHasClients != mCommonClockHasClients)); + + mCommonClockHasClients = commonClockHasClients; + + if (needWakeup) { + ALOGI("Waking up service, auto-disable is engaged and service now has%s" + " clients", mCommonClockHasClients ? "" : " no"); + wakeupThread_l(); + } +} + +#define dump_printf(a, b...) do { \ + int res; \ + res = snprintf(buffer, sizeof(buffer), a, b); \ + buffer[sizeof(buffer) - 1] = 0; \ + if (res > 0) \ + write(fd, buffer, res); \ +} while (0) +#define checked_percentage(a, b) ((0 == b) ? 0.0f : ((100.0f * a) / b)) + +status_t CommonTimeServer::dumpClockInterface(int fd, + const Vector<String16>& args, + size_t activeClients) { + AutoMutex _lock(&mLock); + const size_t SIZE = 256; + char buffer[SIZE]; + + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump CommonClockService from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + write(fd, buffer, strlen(buffer)); + } else { + int64_t commonTime; + int64_t localTime; + bool synced; + char maStr[64]; + + localTime = mLocalClock.getLocalTime(); + synced = (OK == mCommonClock.localToCommon(localTime, &commonTime)); + sockaddrToString(mMasterEP, mMasterEPValid, maStr, sizeof(maStr)); + + dump_printf("Common Clock Service Status\nLocal time : %lld\n", + localTime); + + if (synced) + dump_printf("Common time : %lld\n", commonTime); + else + dump_printf("Common time : %s\n", "not synced"); + + dump_printf("Timeline ID : %016llx\n", mTimelineID); + dump_printf("State : %s\n", stateToString(mState)); + dump_printf("Master Addr : %s\n", maStr); + + + if (synced) { + int32_t est = (ICommonClock::STATE_MASTER != mState) + ? mClockRecovery.getLastErrorEstimate() + : 0; + dump_printf("Error Est. : %.3f msec\n", + static_cast<float>(est) / 1000.0); + } else { + dump_printf("Error Est. : %s\n", "unknown"); + } + + dump_printf("Syncs TXes : %u\n", mClient_SyncsSentToCurMaster); + dump_printf("Syncs RXes : %u (%.2f%%)\n", + mClient_SyncRespsRXedFromCurMaster, + checked_percentage( + mClient_SyncRespsRXedFromCurMaster, + mClient_SyncsSentToCurMaster)); + dump_printf("RXs Expired : %u (%.2f%%)\n", + mClient_ExpiredSyncRespsRXedFromCurMaster, + checked_percentage( + mClient_ExpiredSyncRespsRXedFromCurMaster, + mClient_SyncsSentToCurMaster)); + + if (!mClient_LastGoodSyncRX) { + dump_printf("Last Good RX : %s\n", "unknown"); + } else { + int64_t localDelta, usecDelta; + localDelta = localTime - mClient_LastGoodSyncRX; + usecDelta = mCommonClock.localDurationToCommonDuration(localDelta); + dump_printf("Last Good RX : %lld uSec ago\n", usecDelta); + } + + dump_printf("Active Clients : %u\n", activeClients); + mClient_PacketRTTLog.dumpLog(fd, mCommonClock); + } + + return NO_ERROR; +} + +status_t CommonTimeServer::dumpConfigInterface(int fd, + const Vector<String16>& args) { + AutoMutex _lock(&mLock); + const size_t SIZE = 256; + char buffer[SIZE]; + + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump CommonTimeConfigService from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + write(fd, buffer, strlen(buffer)); + } else { + char meStr[64]; + + sockaddrToString(mMasterElectionEP, true, meStr, sizeof(meStr)); + + dump_printf("Common Time Config Service Status\n" + "Bound Interface : %s\n", + mBindIfaceValid ? mBindIface.string() : "<unbound>"); + dump_printf("Master Election Endpoint : %s\n", meStr); + dump_printf("Master Election Group ID : %016llx\n", mSyncGroupID); + dump_printf("Master Announce Interval : %d mSec\n", + mMasterAnnounceIntervalMs); + dump_printf("Client Sync Interval : %d mSec\n", + mSyncRequestIntervalMs); + dump_printf("Panic Threshold : %d uSec\n", + mPanicThresholdUsec); + dump_printf("Base ME Prio : 0x%02x\n", + static_cast<uint32_t>(mMasterPriority)); + dump_printf("Effective ME Prio : 0x%02x\n", + static_cast<uint32_t>(effectivePriority())); + dump_printf("Auto Disable Allowed : %s\n", + mAutoDisable ? "yes" : "no"); + dump_printf("Auto Disable Engaged : %s\n", + shouldAutoDisable() ? "yes" : "no"); + } + + return NO_ERROR; +} + +void CommonTimeServer::PacketRTTLog::dumpLog(int fd, const CommonClock& cclk) { + const size_t SIZE = 256; + char buffer[SIZE]; + uint32_t avail = !logFull ? wrPtr : RTT_LOG_SIZE; + + if (!avail) + return; + + dump_printf("\nPacket Log (%d entries)\n", avail); + + uint32_t ndx = 0; + uint32_t i = logFull ? wrPtr : 0; + do { + if (rxTimes[i]) { + int64_t delta = rxTimes[i] - txTimes[i]; + int64_t deltaUsec = cclk.localDurationToCommonDuration(delta); + dump_printf("pkt[%2d] : localTX %12lld localRX %12lld " + "(%.3f msec RTT)\n", + ndx, txTimes[i], rxTimes[i], + static_cast<float>(deltaUsec) / 1000.0); + } else { + dump_printf("pkt[%2d] : localTX %12lld localRX never\n", + ndx, txTimes[i]); + } + i = (i + 1) % RTT_LOG_SIZE; + ndx++; + } while (i != wrPtr); +} + +#undef dump_printf +#undef checked_percentage + +} // namespace android diff --git a/services/common_time/common_time_server_packets.cpp b/services/common_time/common_time_server_packets.cpp new file mode 100644 index 000000000000..9833c37f2519 --- /dev/null +++ b/services/common_time/common_time_server_packets.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A service that exchanges time synchronization information between + * a master that defines a timeline and clients that follow the timeline. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <stdint.h> + +#include "common_time_server_packets.h" + +namespace android { + +const uint32_t TimeServicePacketHeader::kMagic = + (static_cast<uint32_t>('c') << 24) | + (static_cast<uint32_t>('c') << 16) | + (static_cast<uint32_t>('l') << 8) | + static_cast<uint32_t>('k'); + +const uint16_t TimeServicePacketHeader::kCurVersion = 1; + +#define SERIALIZE_FIELD(field_name, type, converter) \ + do { \ + if ((offset + sizeof(field_name)) > length) \ + return -1; \ + *((type*)(data + offset)) = converter(field_name); \ + offset += sizeof(field_name); \ + } while (0) +#define SERIALIZE_INT16(field_name) SERIALIZE_FIELD(field_name, int16_t, htons) +#define SERIALIZE_INT32(field_name) SERIALIZE_FIELD(field_name, int32_t, htonl) +#define SERIALIZE_INT64(field_name) SERIALIZE_FIELD(field_name, int64_t, htonq) + +#define DESERIALIZE_FIELD(field_name, type, converter) \ + do { \ + if ((offset + sizeof(field_name)) > length) \ + return -1; \ + field_name = converter(*((type*)(data + offset))); \ + offset += sizeof(field_name); \ + } while (0) +#define DESERIALIZE_INT16(field_name) DESERIALIZE_FIELD(field_name, int16_t, ntohs) +#define DESERIALIZE_INT32(field_name) DESERIALIZE_FIELD(field_name, int32_t, ntohl) +#define DESERIALIZE_INT64(field_name) DESERIALIZE_FIELD(field_name, int64_t, ntohq) + +#define kDevicePriorityShift 56 +#define kDeviceIDMask ((static_cast<uint64_t>(1) << kDevicePriorityShift) - 1) + +inline uint64_t packDeviceID(uint64_t devID, uint8_t prio) { + return (devID & kDeviceIDMask) | + (static_cast<uint64_t>(prio) << kDevicePriorityShift); +} + +inline uint64_t unpackDeviceID(uint64_t packed) { + return (packed & kDeviceIDMask); +} + +inline uint8_t unpackDevicePriority(uint64_t packed) { + return static_cast<uint8_t>(packed >> kDevicePriorityShift); +} + +ssize_t TimeServicePacketHeader::serializeHeader(uint8_t* data, + uint32_t length) { + ssize_t offset = 0; + int16_t pktType = static_cast<int16_t>(packetType); + SERIALIZE_INT32(magic); + SERIALIZE_INT16(version); + SERIALIZE_INT16(pktType); + SERIALIZE_INT64(timelineID); + SERIALIZE_INT64(syncGroupID); + return offset; +} + +ssize_t TimeServicePacketHeader::deserializeHeader(const uint8_t* data, + uint32_t length) { + ssize_t offset = 0; + int16_t tmp; + DESERIALIZE_INT32(magic); + DESERIALIZE_INT16(version); + DESERIALIZE_INT16(tmp); + DESERIALIZE_INT64(timelineID); + DESERIALIZE_INT64(syncGroupID); + packetType = static_cast<TimeServicePacketType>(tmp); + return offset; +} + +ssize_t TimeServicePacketHeader::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t ret, tmp; + + ret = serializeHeader(data, length); + if (ret < 0) + return ret; + + data += ret; + length -= ret; + + switch (packetType) { + case TIME_PACKET_WHO_IS_MASTER_REQUEST: + tmp =((WhoIsMasterRequestPacket*)(this))->serializePacket(data, + length); + break; + case TIME_PACKET_WHO_IS_MASTER_RESPONSE: + tmp =((WhoIsMasterResponsePacket*)(this))->serializePacket(data, + length); + break; + case TIME_PACKET_SYNC_REQUEST: + tmp =((SyncRequestPacket*)(this))->serializePacket(data, length); + break; + case TIME_PACKET_SYNC_RESPONSE: + tmp =((SyncResponsePacket*)(this))->serializePacket(data, length); + break; + case TIME_PACKET_MASTER_ANNOUNCEMENT: + tmp =((MasterAnnouncementPacket*)(this))->serializePacket(data, + length); + break; + default: + return -1; + } + + if (tmp < 0) + return tmp; + + return ret + tmp; +} + +ssize_t UniversalTimeServicePacket::deserializePacket( + const uint8_t* data, + uint32_t length, + uint64_t expectedSyncGroupID) { + ssize_t ret; + TimeServicePacketHeader* header; + if (length < 8) + return -1; + + packetType = ntohs(*((uint16_t*)(data + 6))); + switch (packetType) { + case TIME_PACKET_WHO_IS_MASTER_REQUEST: + ret = p.who_is_master_request.deserializePacket(data, length); + header = &p.who_is_master_request; + break; + case TIME_PACKET_WHO_IS_MASTER_RESPONSE: + ret = p.who_is_master_response.deserializePacket(data, length); + header = &p.who_is_master_response; + break; + case TIME_PACKET_SYNC_REQUEST: + ret = p.sync_request.deserializePacket(data, length); + header = &p.sync_request; + break; + case TIME_PACKET_SYNC_RESPONSE: + ret = p.sync_response.deserializePacket(data, length); + header = &p.sync_response; + break; + case TIME_PACKET_MASTER_ANNOUNCEMENT: + ret = p.master_announcement.deserializePacket(data, length); + header = &p.master_announcement; + break; + default: + return -1; + } + + if ((ret >= 0) && !header->checkPacket(expectedSyncGroupID)) + ret = -1; + + return ret; +} + +ssize_t WhoIsMasterRequestPacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + uint64_t packed = packDeviceID(senderDeviceID, senderDevicePriority); + SERIALIZE_INT64(packed); + } + return offset; +} + +ssize_t WhoIsMasterRequestPacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + uint64_t packed; + DESERIALIZE_INT64(packed); + senderDeviceID = unpackDeviceID(packed); + senderDevicePriority = unpackDevicePriority(packed); + } + return offset; +} + +ssize_t WhoIsMasterResponsePacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + uint64_t packed = packDeviceID(deviceID, devicePriority); + SERIALIZE_INT64(packed); + } + return offset; +} + +ssize_t WhoIsMasterResponsePacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + uint64_t packed; + DESERIALIZE_INT64(packed); + deviceID = unpackDeviceID(packed); + devicePriority = unpackDevicePriority(packed); + } + return offset; +} + +ssize_t SyncRequestPacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + SERIALIZE_INT64(clientTxLocalTime); + } + return offset; +} + +ssize_t SyncRequestPacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + DESERIALIZE_INT64(clientTxLocalTime); + } + return offset; +} + +ssize_t SyncResponsePacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + SERIALIZE_INT64(clientTxLocalTime); + SERIALIZE_INT64(masterRxCommonTime); + SERIALIZE_INT64(masterTxCommonTime); + SERIALIZE_INT32(nak); + } + return offset; +} + +ssize_t SyncResponsePacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + DESERIALIZE_INT64(clientTxLocalTime); + DESERIALIZE_INT64(masterRxCommonTime); + DESERIALIZE_INT64(masterTxCommonTime); + DESERIALIZE_INT32(nak); + } + return offset; +} + +ssize_t MasterAnnouncementPacket::serializePacket(uint8_t* data, + uint32_t length) { + ssize_t offset = serializeHeader(data, length); + if (offset > 0) { + uint64_t packed = packDeviceID(deviceID, devicePriority); + SERIALIZE_INT64(packed); + } + return offset; +} + +ssize_t MasterAnnouncementPacket::deserializePacket(const uint8_t* data, + uint32_t length) { + ssize_t offset = deserializeHeader(data, length); + if (offset > 0) { + uint64_t packed; + DESERIALIZE_INT64(packed); + deviceID = unpackDeviceID(packed); + devicePriority = unpackDevicePriority(packed); + } + return offset; +} + +} // namespace android + diff --git a/services/common_time/common_time_server_packets.h b/services/common_time/common_time_server_packets.h new file mode 100644 index 000000000000..57ba8a256308 --- /dev/null +++ b/services/common_time/common_time_server_packets.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_COMMON_TIME_SERVER_PACKETS_H +#define ANDROID_COMMON_TIME_SERVER_PACKETS_H + +#include <stdint.h> +#include <common_time/ICommonClock.h> + +namespace android { + +/***** time sync protocol packets *****/ + +enum TimeServicePacketType { + TIME_PACKET_WHO_IS_MASTER_REQUEST = 1, + TIME_PACKET_WHO_IS_MASTER_RESPONSE, + TIME_PACKET_SYNC_REQUEST, + TIME_PACKET_SYNC_RESPONSE, + TIME_PACKET_MASTER_ANNOUNCEMENT, +}; + +class TimeServicePacketHeader { + public: + friend class UniversalTimeServicePacket; + // magic number identifying the protocol + uint32_t magic; + + // protocol version of the packet + uint16_t version; + + // type of the packet + TimeServicePacketType packetType; + + // the timeline ID + uint64_t timelineID; + + // synchronization group this packet belongs to (used to operate multiple + // synchronization domains which all use the same master election endpoint) + uint64_t syncGroupID; + + ssize_t serializePacket(uint8_t* data, uint32_t length); + + protected: + void initHeader(TimeServicePacketType type, + const uint64_t tlID, + const uint64_t groupID) { + magic = kMagic; + version = kCurVersion; + packetType = type; + timelineID = tlID; + syncGroupID = groupID; + } + + bool checkPacket(uint64_t expectedSyncGroupID) const { + return ((magic == kMagic) && + (version == kCurVersion) && + (!expectedSyncGroupID || (syncGroupID == expectedSyncGroupID))); + } + + ssize_t serializeHeader(uint8_t* data, uint32_t length); + ssize_t deserializeHeader(const uint8_t* data, uint32_t length); + + private: + static const uint32_t kMagic; + static const uint16_t kCurVersion; +}; + +// packet querying for a suitable master +class WhoIsMasterRequestPacket : public TimeServicePacketHeader { + public: + uint64_t senderDeviceID; + uint8_t senderDevicePriority; + + void initHeader(const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_REQUEST, + ICommonClock::kInvalidTimelineID, + groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// response to a WhoIsMaster request +class WhoIsMasterResponsePacket : public TimeServicePacketHeader { + public: + uint64_t deviceID; + uint8_t devicePriority; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_RESPONSE, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// packet sent by a client requesting correspondence between local +// and common time +class SyncRequestPacket : public TimeServicePacketHeader { + public: + // local time when this request was transmitted + int64_t clientTxLocalTime; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_REQUEST, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// response to a sync request sent by the master +class SyncResponsePacket : public TimeServicePacketHeader { + public: + // local time when this request was transmitted by the client + int64_t clientTxLocalTime; + + // common time when the master received the request + int64_t masterRxCommonTime; + + // common time when the master transmitted the response + int64_t masterTxCommonTime; + + // flag that is set if the recipient of the sync request is not acting + // as a master for the requested timeline + uint32_t nak; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_RESPONSE, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +// announcement of the master's presence +class MasterAnnouncementPacket : public TimeServicePacketHeader { + public: + // the master's device ID + uint64_t deviceID; + uint8_t devicePriority; + + void initHeader(const uint64_t tlID, const uint64_t groupID) { + TimeServicePacketHeader::initHeader(TIME_PACKET_MASTER_ANNOUNCEMENT, + tlID, groupID); + } + + ssize_t serializePacket(uint8_t* data, uint32_t length); + ssize_t deserializePacket(const uint8_t* data, uint32_t length); +}; + +class UniversalTimeServicePacket { + public: + uint16_t packetType; + union { + WhoIsMasterRequestPacket who_is_master_request; + WhoIsMasterResponsePacket who_is_master_response; + SyncRequestPacket sync_request; + SyncResponsePacket sync_response; + MasterAnnouncementPacket master_announcement; + } p; + + ssize_t deserializePacket(const uint8_t* data, + uint32_t length, + uint64_t expectedSyncGroupID); +}; + +}; // namespace android + +#endif // ANDROID_COMMON_TIME_SERVER_PACKETS_H + + diff --git a/services/common_time/diag_thread.cpp b/services/common_time/diag_thread.cpp new file mode 100644 index 000000000000..c8e605316867 --- /dev/null +++ b/services/common_time/diag_thread.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <fcntl.h> +#include <linux/in.h> +#include <linux/tcp.h> +#include <poll.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <utils/Errors.h> +#include <utils/misc.h> + +#include <common_time/local_clock.h> + +#include "common_clock.h" +#include "diag_thread.h" + +#define kMaxEvents 16 +#define kListenPort 9876 + +static bool setNonblocking(int fd) { + int flags = fcntl(fd, F_GETFL); + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)", + fd, errno); + return false; + } + + return true; +} + +static bool setNodelay(int fd) { + int tmp = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) { + ALOGE("Failed to set socket (%d) to no-delay mode (errno %d)", + fd, errno); + return false; + } + + return true; +} + +namespace android { + +DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) { + common_clock_ = common_clock; + local_clock_ = local_clock; + listen_fd_ = -1; + data_fd_ = -1; + kernel_logID_basis_known_ = false; + discipline_log_ID_ = 0; +} + +DiagThread::~DiagThread() { +} + +status_t DiagThread::startWorkThread() { + status_t res; + stopWorkThread(); + res = run("Diag"); + + if (res != OK) + ALOGE("Failed to start work thread (res = %d)", res); + + return res; +} + +void DiagThread::stopWorkThread() { + status_t res; + res = requestExitAndWait(); // block until thread exit. + if (res != OK) + ALOGE("Failed to stop work thread (res = %d)", res); +} + +bool DiagThread::openListenSocket() { + bool ret = false; + int flags; + cleanupListenSocket(); + + if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + ALOGE("Socket failed."); + goto bailout; + } + + // Set non-blocking operation + if (!setNonblocking(listen_fd_)) + goto bailout; + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(kListenPort); + + if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + ALOGE("Bind failed."); + goto bailout; + } + + if (listen(listen_fd_, 1) < 0) { + ALOGE("Listen failed."); + goto bailout; + } + + ret = true; +bailout: + if (!ret) + cleanupListenSocket(); + + return ret; +} + +void DiagThread::cleanupListenSocket() { + if (listen_fd_ >= 0) { + int res; + + struct linger l; + l.l_onoff = 1; + l.l_linger = 0; + + setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + shutdown(listen_fd_, SHUT_RDWR); + close(listen_fd_); + listen_fd_ = -1; + } +} + +void DiagThread::cleanupDataSocket() { + if (data_fd_ >= 0) { + int res; + + struct linger l; + l.l_onoff = 1; + l.l_linger = 0; + + setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + shutdown(data_fd_, SHUT_RDWR); + close(data_fd_); + data_fd_ = -1; + } +} + +void DiagThread::resetLogIDs() { + // Drain and discard all of the events from the kernel + struct local_time_debug_event events[kMaxEvents]; + while(local_clock_->getDebugLog(events, kMaxEvents) > 0) + ; + + { + Mutex::Autolock lock(&discipline_log_lock_); + discipline_log_.clear(); + discipline_log_ID_ = 0; + } + + kernel_logID_basis_known_ = false; +} + +void DiagThread::pushDisciplineEvent(int64_t observed_local_time, + int64_t observed_common_time, + int64_t nominal_common_time, + int32_t total_correction, + int32_t P_correction, + int32_t I_correction, + int32_t D_correction) { + Mutex::Autolock lock(&discipline_log_lock_); + + DisciplineEventRecord evt; + + evt.event_id = discipline_log_ID_++; + + evt.action_local_time = local_clock_->getLocalTime(); + common_clock_->localToCommon(evt.action_local_time, + &evt.action_common_time); + + evt.observed_local_time = observed_local_time; + evt.observed_common_time = observed_common_time; + evt.nominal_common_time = nominal_common_time; + evt.total_correction = total_correction; + evt.P_correction = P_correction; + evt.I_correction = I_correction; + evt.D_correction = D_correction; + + discipline_log_.push_back(evt); + while (discipline_log_.size() > kMaxDisciplineLogSize) + discipline_log_.erase(discipline_log_.begin()); +} + +bool DiagThread::threadLoop() { + struct pollfd poll_fds[1]; + + if (!openListenSocket()) { + ALOGE("Failed to open listen socket"); + goto bailout; + } + + while (!exitPending()) { + memset(&poll_fds, 0, sizeof(poll_fds)); + + if (data_fd_ < 0) { + poll_fds[0].fd = listen_fd_; + poll_fds[0].events = POLLIN; + } else { + poll_fds[0].fd = data_fd_; + poll_fds[0].events = POLLRDHUP | POLLIN; + } + + int poll_res = poll(poll_fds, NELEM(poll_fds), 50); + if (poll_res < 0) { + ALOGE("Fatal error (%d,%d) while waiting on events", + poll_res, errno); + goto bailout; + } + + if (exitPending()) + break; + + if (poll_fds[0].revents) { + if (poll_fds[0].fd == listen_fd_) { + data_fd_ = accept(listen_fd_, NULL, NULL); + + if (data_fd_ < 0) { + ALOGW("Failed accept on socket %d with err %d", + listen_fd_, errno); + } else { + if (!setNonblocking(data_fd_)) + cleanupDataSocket(); + if (!setNodelay(data_fd_)) + cleanupDataSocket(); + } + } else + if (poll_fds[0].fd == data_fd_) { + if (poll_fds[0].revents & POLLRDHUP) { + // Connection hung up; time to clean up. + cleanupDataSocket(); + } else + if (poll_fds[0].revents & POLLIN) { + uint8_t cmd; + if (read(data_fd_, &cmd, sizeof(cmd)) > 0) { + switch(cmd) { + case 'r': + case 'R': + resetLogIDs(); + break; + } + } + } + } + } + + struct local_time_debug_event events[kMaxEvents]; + int amt = local_clock_->getDebugLog(events, kMaxEvents); + + if (amt > 0) { + for (int i = 0; i < amt; i++) { + struct local_time_debug_event& e = events[i]; + + if (!kernel_logID_basis_known_) { + kernel_logID_basis_ = e.local_timesync_event_id; + kernel_logID_basis_known_ = true; + } + + char buf[1024]; + int64_t common_time; + status_t res = common_clock_->localToCommon(e.local_time, + &common_time); + snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n", + e.local_timesync_event_id - kernel_logID_basis_, + e.local_time, + common_time, + (OK == res) ? 1 : 0); + buf[sizeof(buf) - 1] = 0; + + if (data_fd_ >= 0) + write(data_fd_, buf, strlen(buf)); + } + } + + { // scope for autolock pattern + Mutex::Autolock lock(&discipline_log_lock_); + + while (discipline_log_.size() > 0) { + char buf[1024]; + DisciplineEventRecord& e = *discipline_log_.begin(); + snprintf(buf, sizeof(buf), + "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d,%d,%d\n", + e.event_id, + e.action_local_time, + e.action_common_time, + e.observed_local_time, + e.observed_common_time, + e.nominal_common_time, + e.total_correction, + e.P_correction, + e.I_correction, + e.D_correction); + buf[sizeof(buf) - 1] = 0; + + if (data_fd_ >= 0) + write(data_fd_, buf, strlen(buf)); + + discipline_log_.erase(discipline_log_.begin()); + } + } + } + +bailout: + cleanupDataSocket(); + cleanupListenSocket(); + return false; +} + +} // namespace android diff --git a/services/common_time/diag_thread.h b/services/common_time/diag_thread.h new file mode 100644 index 000000000000..6ebe8295b327 --- /dev/null +++ b/services/common_time/diag_thread.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __DIAG_THREAD_H__ +#define __DIAG_THREAD_H__ + +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class CommonClock; +class LocalClock; + +class DiagThread : public Thread { + public: + DiagThread(CommonClock* common_clock, LocalClock* local_clock); + ~DiagThread(); + + status_t startWorkThread(); + void stopWorkThread(); + virtual bool threadLoop(); + + void pushDisciplineEvent(int64_t observed_local_time, + int64_t observed_common_time, + int64_t nominal_common_time, + int32_t total_correction, + int32_t P_correction, + int32_t I_correction, + int32_t D_correction); + + private: + typedef struct { + int64_t event_id; + int64_t action_local_time; + int64_t action_common_time; + int64_t observed_local_time; + int64_t observed_common_time; + int64_t nominal_common_time; + int32_t total_correction; + int32_t P_correction; + int32_t I_correction; + int32_t D_correction; + } DisciplineEventRecord; + + bool openListenSocket(); + void cleanupListenSocket(); + void cleanupDataSocket(); + void resetLogIDs(); + + CommonClock* common_clock_; + LocalClock* local_clock_; + int listen_fd_; + int data_fd_; + + int64_t kernel_logID_basis_; + bool kernel_logID_basis_known_; + + static const size_t kMaxDisciplineLogSize = 16; + Mutex discipline_log_lock_; + List<DisciplineEventRecord> discipline_log_; + int64_t discipline_log_ID_; +}; + +} // namespace android + +#endif //__ DIAG_THREAD_H__ diff --git a/services/common_time/main.cpp b/services/common_time/main.cpp new file mode 100644 index 000000000000..49eb30abab66 --- /dev/null +++ b/services/common_time/main.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A service that exchanges time synchronization information between + * a master that defines a timeline and clients that follow the timeline. + */ + +#define LOG_TAG "common_time" +#include <utils/Log.h> + +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> + +#include "common_time_server.h" + +int main(int argc, char *argv[]) { + using namespace android; + + sp<CommonTimeServer> service = new CommonTimeServer(); + if (service == NULL) + return 1; + + ProcessState::self()->startThreadPool(); + service->run("CommonTimeServer", ANDROID_PRIORITY_NORMAL); + + IPCThreadState::self()->joinThreadPool(); + return 0; +} + diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java index 41ede2e4b608..9c408c40826b 100644 --- a/services/java/com/android/server/AppWidgetServiceImpl.java +++ b/services/java/com/android/server/AppWidgetServiceImpl.java @@ -463,7 +463,7 @@ class AppWidgetServiceImpl { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); intent.setComponent(p.info.provider); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); - mContext.sendBroadcast(intent); + mContext.sendBroadcast(intent, mUserId); if (p.instances.size() == 0) { // cancel the future updates cancelBroadcasts(p); @@ -471,7 +471,7 @@ class AppWidgetServiceImpl { // send the broacast saying that the provider is not in use any more intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + mContext.sendBroadcast(intent, mUserId); } } } @@ -515,8 +515,6 @@ class AppWidgetServiceImpl { + " safe mode: " + provider); } - Binder.restoreCallingIdentity(ident); - id.provider = p; p.instances.add(id); int instancesSize = p.instances.size(); @@ -1066,7 +1064,7 @@ class AppWidgetServiceImpl { void sendEnableIntentLocked(Provider p) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + mContext.sendBroadcast(intent, mUserId); } void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { @@ -1074,7 +1072,7 @@ class AppWidgetServiceImpl { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + mContext.sendBroadcast(intent, mUserId); } } @@ -1477,12 +1475,11 @@ class AppWidgetServiceImpl { } AtomicFile savedStateFile() { - int userId = UserId.getCallingUserId(); - File dir = new File("/data/system/users/" + userId); + File dir = new File("/data/system/users/" + mUserId); File settingsFile = new File(dir, SETTINGS_FILENAME); if (!dir.exists()) { dir.mkdirs(); - if (userId == 0) { + if (mUserId == 0) { // Migrate old data File oldFile = new File("/data/system/" + SETTINGS_FILENAME); // Method doesn't throw an exception on failure. Ignore any errors diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java new file mode 100644 index 000000000000..9a25d2ea5f39 --- /dev/null +++ b/services/java/com/android/server/CommonTimeManagementService.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.InetAddress; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.INetworkManagementEventObserver; +import android.net.InterfaceConfiguration; +import android.net.NetworkInfo; +import android.os.Binder; +import android.os.CommonTimeConfig; +import android.os.Handler; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.util.Log; + +/** + * @hide + * <p>CommonTimeManagementService manages the configuration of the native Common Time service, + * reconfiguring the native service as appropriate in response to changes in network configuration. + */ +class CommonTimeManagementService extends Binder { + /* + * Constants and globals. + */ + private static final String TAG = CommonTimeManagementService.class.getSimpleName(); + private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000; + private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable"; + private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi"; + private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio"; + private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout"; + private static final boolean AUTO_DISABLE; + private static final boolean ALLOW_WIFI; + private static final byte BASE_SERVER_PRIO; + private static final int NO_INTERFACE_TIMEOUT; + private static final InterfaceScoreRule[] IFACE_SCORE_RULES; + + static { + int tmp; + AUTO_DISABLE = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1)); + ALLOW_WIFI = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0)); + tmp = SystemProperties.getInt(SERVER_PRIO_PROP, 1); + NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000); + + if (tmp < 1) + BASE_SERVER_PRIO = 1; + else + if (tmp > 30) + BASE_SERVER_PRIO = 30; + else + BASE_SERVER_PRIO = (byte)tmp; + + if (ALLOW_WIFI) { + IFACE_SCORE_RULES = new InterfaceScoreRule[] { + new InterfaceScoreRule("wlan", (byte)1), + new InterfaceScoreRule("eth", (byte)2), + }; + } else { + IFACE_SCORE_RULES = new InterfaceScoreRule[] { + new InterfaceScoreRule("eth", (byte)2), + }; + } + }; + + /* + * Internal state + */ + private final Context mContext; + private INetworkManagementService mNetMgr; + private CommonTimeConfig mCTConfig; + private String mCurIface; + private Handler mReconnectHandler = new Handler(); + private Handler mNoInterfaceHandler = new Handler(); + private Object mLock = new Object(); + private boolean mDetectedAtStartup = false; + private byte mEffectivePrio = BASE_SERVER_PRIO; + + /* + * Callback handler implementations. + */ + private INetworkManagementEventObserver mIfaceObserver = + new INetworkManagementEventObserver.Stub() { + + public void interfaceStatusChanged(String iface, boolean up) { + reevaluateServiceState(); + } + public void interfaceLinkStateChanged(String iface, boolean up) { + reevaluateServiceState(); + } + public void interfaceAdded(String iface) { + reevaluateServiceState(); + } + public void interfaceRemoved(String iface) { + reevaluateServiceState(); + } + public void limitReached(String limitName, String iface) { } + }; + + private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reevaluateServiceState(); + } + }; + + private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener = + new CommonTimeConfig.OnServerDiedListener() { + public void onServerDied() { + scheduleTimeConfigReconnect(); + } + }; + + private Runnable mReconnectRunnable = new Runnable() { + public void run() { connectToTimeConfig(); } + }; + + private Runnable mNoInterfaceRunnable = new Runnable() { + public void run() { handleNoInterfaceTimeout(); } + }; + + /* + * Public interface (constructor, systemReady and dump) + */ + public CommonTimeManagementService(Context context) { + mContext = context; + } + + void systemReady() { + if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) { + Log.i(TAG, "No common time service detected on this platform. " + + "Common time services will be unavailable."); + return; + } + + mDetectedAtStartup = true; + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + mNetMgr = INetworkManagementService.Stub.asInterface(b); + + // Network manager is running along-side us, so we should never receiver a remote exception + // while trying to register this observer. + try { + mNetMgr.registerObserver(mIfaceObserver); + } + catch (RemoteException e) { } + + // Register with the connectivity manager for connectivity changed intents. + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + mContext.registerReceiver(mConnectivityMangerObserver, filter); + + // Connect to the common time config service and apply the initial configuration. + connectToTimeConfig(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println(String.format( + "Permission Denial: can't dump CommonTimeManagement service from from " + + "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid())); + return; + } + + if (!mDetectedAtStartup) { + pw.println("Native Common Time service was not detected at startup. " + + "Service is unavailable"); + return; + } + + synchronized (mLock) { + pw.println("Current Common Time Management Service Config:"); + pw.println(String.format(" Native service : %s", + (null == mCTConfig) ? "reconnecting" + : "alive")); + pw.println(String.format(" Bound interface : %s", + (null == mCurIface ? "unbound" : mCurIface))); + pw.println(String.format(" Allow WiFi : %s", ALLOW_WIFI ? "yes" : "no")); + pw.println(String.format(" Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no")); + pw.println(String.format(" Server Priority : %d", mEffectivePrio)); + pw.println(String.format(" No iface timeout : %d", NO_INTERFACE_TIMEOUT)); + } + } + + /* + * Inner helper classes + */ + private static class InterfaceScoreRule { + public final String mPrefix; + public final byte mScore; + public InterfaceScoreRule(String prefix, byte score) { + mPrefix = prefix; + mScore = score; + } + }; + + /* + * Internal implementation + */ + private void cleanupTimeConfig() { + mReconnectHandler.removeCallbacks(mReconnectRunnable); + mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable); + if (null != mCTConfig) { + mCTConfig.release(); + mCTConfig = null; + } + } + + private void connectToTimeConfig() { + // Get access to the common time service configuration interface. If we catch a remote + // exception in the process (service crashed or no running for w/e reason), schedule an + // attempt to reconnect in the future. + cleanupTimeConfig(); + try { + synchronized (mLock) { + mCTConfig = new CommonTimeConfig(); + mCTConfig.setServerDiedListener(mCTServerDiedListener); + mCurIface = mCTConfig.getInterfaceBinding(); + mCTConfig.setAutoDisable(AUTO_DISABLE); + mCTConfig.setMasterElectionPriority(mEffectivePrio); + } + + if (NO_INTERFACE_TIMEOUT >= 0) + mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT); + + reevaluateServiceState(); + } + catch (RemoteException e) { + scheduleTimeConfigReconnect(); + } + } + + private void scheduleTimeConfigReconnect() { + cleanupTimeConfig(); + Log.w(TAG, String.format("Native service died, will reconnect in %d mSec", + NATIVE_SERVICE_RECONNECT_TIMEOUT)); + mReconnectHandler.postDelayed(mReconnectRunnable, + NATIVE_SERVICE_RECONNECT_TIMEOUT); + } + + private void handleNoInterfaceTimeout() { + if (null != mCTConfig) { + Log.i(TAG, "Timeout waiting for interface to come up. " + + "Forcing networkless master mode."); + if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode()) + scheduleTimeConfigReconnect(); + } + } + + private void reevaluateServiceState() { + String bindIface = null; + byte bestScore = -1; + try { + // Check to see if this interface is suitable to use for time synchronization. + // + // TODO : This selection algorithm needs to be enhanced for use with mobile devices. In + // particular, the choice of whether to a wireless interface or not should not be an all + // or nothing thing controlled by properties. It would probably be better if the + // platform had some concept of public wireless networks vs. home or friendly wireless + // networks (something a user would configure in settings or when a new interface is + // added). Then this algorithm could pick only wireless interfaces which were flagged + // as friendly, and be dormant when on public wireless networks. + // + // Another issue which needs to be dealt with is the use of driver supplied interface + // name to determine the network type. The fact that the wireless interface on a device + // is named "wlan0" is just a matter of convention; its not a 100% rule. For example, + // there are devices out there where the wireless is name "tiwlan0", not "wlan0". The + // internal network management interfaces in Android have all of the information needed + // to make a proper classification, there is just no way (currently) to fetch an + // interface's type (available from the ConnectionManager) as well as its address + // (available from either the java.net interfaces or from the NetworkManagment service). + // Both can enumerate interfaces, but that is no way to correlate their results (no + // common shared key; although using the interface name in the connection manager would + // be a good start). Until this gets resolved, we resort to substring searching for + // tags like wlan and eth. + // + String ifaceList[] = mNetMgr.listInterfaces(); + if (null != ifaceList) { + for (String iface : ifaceList) { + + byte thisScore = -1; + for (InterfaceScoreRule r : IFACE_SCORE_RULES) { + if (iface.contains(r.mPrefix)) { + thisScore = r.mScore; + break; + } + } + + if (thisScore <= bestScore) + continue; + + InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface); + if (null == config) + continue; + + if (config.isActive()) { + bindIface = iface; + bestScore = thisScore; + } + } + } + } + catch (RemoteException e) { + // Bad news; we should not be getting remote exceptions from the connectivity manager + // since it is running in SystemServer along side of us. It probably does not matter + // what we do here, but go ahead and unbind the common time service in this case, just + // so we have some defined behavior. + bindIface = null; + } + + boolean doRebind = true; + synchronized (mLock) { + if ((null != bindIface) && (null == mCurIface)) { + Log.e(TAG, String.format("Binding common time service to %s.", bindIface)); + mCurIface = bindIface; + } else + if ((null == bindIface) && (null != mCurIface)) { + Log.e(TAG, "Unbinding common time service."); + mCurIface = null; + } else + if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) { + Log.e(TAG, String.format("Switching common time service binding from %s to %s.", + mCurIface, bindIface)); + mCurIface = bindIface; + } else { + doRebind = false; + } + } + + if (doRebind && (null != mCTConfig)) { + byte newPrio = (bestScore > 0) + ? (byte)(bestScore * BASE_SERVER_PRIO) + : BASE_SERVER_PRIO; + if (newPrio != mEffectivePrio) { + mEffectivePrio = newPrio; + mCTConfig.setMasterElectionPriority(mEffectivePrio); + } + + int res = mCTConfig.setNetworkBinding(mCurIface); + if (res != CommonTimeConfig.SUCCESS) + scheduleTimeConfigReconnect(); + + else if (NO_INTERFACE_TIMEOUT >= 0) { + mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable); + if (null == mCurIface) + mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT); + } + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0dbc7c367b46..c9b59975c32f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -132,6 +132,7 @@ class ServerThread extends Thread { RecognitionManagerService recognition = null; ThrottleService throttle = null; NetworkTimeUpdateService networkTimeUpdater = null; + CommonTimeManagementService commonTimeMgmtService = null; // Critical services... try { @@ -575,6 +576,14 @@ class ServerThread extends Thread { } catch (Throwable e) { reportWtf("starting NetworkTimeUpdate service", e); } + + try { + Slog.i(TAG, "CommonTimeManagementService"); + commonTimeMgmtService = new CommonTimeManagementService(context); + ServiceManager.addService("commontime_management", commonTimeMgmtService); + } catch (Throwable e) { + reportWtf("starting CommonTimeManagementService service", e); + } } // Before things start rolling, be sure we have decided whether @@ -653,6 +662,7 @@ class ServerThread extends Thread { final LocationManagerService locationF = location; final CountryDetectorService countryDetectorF = countryDetector; final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; + final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService; final TextServicesManagerService textServiceManagerServiceF = tsms; final StatusBarManagerService statusBarF = statusBar; @@ -752,6 +762,11 @@ class ServerThread extends Thread { reportWtf("making Network Time Service ready", e); } try { + if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady(); + } catch (Throwable e) { + reportWtf("making Common time management service ready", e); + } + try { if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady(); } catch (Throwable e) { reportWtf("making Text Services Manager Service ready", e); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 9d5caaef6d1f..785212378ad4 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -5754,7 +5754,7 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName comp = new ComponentName(cpi.packageName, cpi.name); ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId); if (cpr == null) { - cpr = new ContentProviderRecord(cpi, app.info, comp); + cpr = new ContentProviderRecord(this, cpi, app.info, comp); mProviderMap.putProviderByClass(comp, cpr); } if (DEBUG_MU) @@ -5826,7 +5826,8 @@ public final class ActivityManagerService extends ActivityManagerNative return msg; } - boolean incProviderCount(ProcessRecord r, ContentProviderRecord cpr) { + boolean incProviderCount(ProcessRecord r, final ContentProviderRecord cpr, + IBinder externalProcessToken) { if (r != null) { Integer cnt = r.conProviders.get(cpr); if (DEBUG_PROVIDER) Slog.v(TAG, @@ -5842,12 +5843,13 @@ public final class ActivityManagerService extends ActivityManagerNative r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); } } else { - cpr.externals++; + cpr.addExternalProcessHandleLocked(externalProcessToken); } return false; } - boolean decProviderCount(ProcessRecord r, ContentProviderRecord cpr) { + boolean decProviderCount(ProcessRecord r, final ContentProviderRecord cpr, + IBinder externalProcessToken) { if (r != null) { Integer cnt = r.conProviders.get(cpr); if (DEBUG_PROVIDER) Slog.v(TAG, @@ -5863,13 +5865,13 @@ public final class ActivityManagerService extends ActivityManagerNative r.conProviders.put(cpr, new Integer(cnt.intValue()-1)); } } else { - cpr.externals++; + cpr.removeExternalProcessHandleLocked(externalProcessToken); } return false; } private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, - String name) { + String name, IBinder token) { ContentProviderRecord cpr; ProviderInfo cpi = null; @@ -5913,7 +5915,7 @@ public final class ActivityManagerService extends ActivityManagerNative // In this case the provider instance already exists, so we can // return it right away. - final boolean countChanged = incProviderCount(r, cpr); + final boolean countChanged = incProviderCount(r, cpr, token); if (countChanged) { if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { // If this is a perceptible app accessing the provider, @@ -5947,7 +5949,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString() + " is crashing; detaching " + r); - boolean lastRef = decProviderCount(r, cpr); + boolean lastRef = decProviderCount(r, cpr, token); appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread); if (!lastRef) { // This wasn't the last ref our process had on @@ -6005,7 +6007,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } ai = getAppInfoForUser(ai, Binder.getOrigCallingUser()); - cpr = new ContentProviderRecord(cpi, ai, comp); + cpr = new ContentProviderRecord(this, cpi, ai, comp); } catch (RemoteException ex) { // pm is in same process, this will never happen. } @@ -6075,8 +6077,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (firstClass) { mProviderMap.putProviderByClass(comp, cpr); } + mProviderMap.putProviderByName(name, cpr); - incProviderCount(r, cpr); + incProviderCount(r, cpr, token); } } @@ -6116,12 +6119,17 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - ContentProviderHolder contentProvider = getContentProviderImpl(caller, name); - return contentProvider; + return getContentProviderImpl(caller, name, null); + } + + public ContentProviderHolder getContentProviderExternal(String name, IBinder token) { + enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY, + "Do not have permission in call getContentProviderExternal()"); + return getContentProviderExternalUnchecked(name, token); } - private ContentProviderHolder getContentProviderExternal(String name) { - return getContentProviderImpl(null, name); + private ContentProviderHolder getContentProviderExternalUnchecked(String name,IBinder token) { + return getContentProviderImpl(null, name, token); } /** @@ -6157,14 +6165,20 @@ public final class ActivityManagerService extends ActivityManagerNative + cpr.info.name + " in process " + r.processName); return; } else { - if (decProviderCount(r, localCpr)) { + if (decProviderCount(r, localCpr, null)) { updateOomAdjLocked(); } } } } - private void removeContentProviderExternal(String name) { + public void removeContentProviderExternal(String name, IBinder token) { + enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY, + "Do not have permission in call removeContentProviderExternal()"); + removeContentProviderExternalUnchecked(name, token); + } + + private void removeContentProviderExternalUnchecked(String name, IBinder token) { synchronized (this) { ContentProviderRecord cpr = mProviderMap.getProviderByName(name, Binder.getOrigCallingUser()); @@ -6178,11 +6192,18 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, Binder.getOrigCallingUser()); - localCpr.externals--; - if (localCpr.externals < 0) { - Slog.e(TAG, "Externals < 0 for content provider " + localCpr); + if (localCpr.hasExternalProcessHandles()) { + if (localCpr.removeExternalProcessHandleLocked(token)) { + updateOomAdjLocked(); + } else { + Slog.e(TAG, "Attmpt to remove content provider " + localCpr + + " with no external reference for token: " + + token + "."); + } + } else { + Slog.e(TAG, "Attmpt to remove content provider: " + localCpr + + " with no external references."); } - updateOomAdjLocked(); } } @@ -6286,7 +6307,7 @@ public final class ActivityManagerService extends ActivityManagerNative ContentProviderHolder holder = null; try { - holder = getContentProviderExternal(name); + holder = getContentProviderExternalUnchecked(name, null); if (holder != null) { return holder.provider.getType(uri); } @@ -6295,7 +6316,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } finally { if (holder != null) { - removeContentProviderExternal(name); + removeContentProviderExternalUnchecked(name, null); } Binder.restoreCallingIdentity(ident); } @@ -6400,7 +6421,7 @@ public final class ActivityManagerService extends ActivityManagerNative public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException { enforceNotIsolatedCaller("openContentUri"); String name = uri.getAuthority(); - ContentProviderHolder cph = getContentProviderExternal(name); + ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null); ParcelFileDescriptor pfd = null; if (cph != null) { // We record the binder invoker's uid in thread-local storage before @@ -6422,7 +6443,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // We've got the fd now, so we're done with the provider. - removeContentProviderExternal(name); + removeContentProviderExternalUnchecked(name, null); } else { Slog.d(TAG, "Failed to get provider for authority '" + name + "'"); } @@ -10253,7 +10274,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<NL; i++) { ContentProviderRecord cpr = (ContentProviderRecord) mLaunchingProviders.get(i); - if (cpr.clients.size() <= 0 && cpr.externals <= 0) { + if (cpr.clients.size() <= 0 && !cpr.hasExternalProcessHandles()) { synchronized (cpr) { cpr.launchingApp = null; cpr.notifyAll(); @@ -13455,7 +13476,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If the provider has external (non-framework) process // dependencies, ensure that its adjustment is at least // FOREGROUND_APP_ADJ. - if (cpr.externals != 0) { + if (cpr.hasExternalProcessHandles()) { if (adj > ProcessList.FOREGROUND_APP_ADJ) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index 38355537e1c6..f338cfcb7737 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -20,24 +20,35 @@ import android.app.IActivityManager.ContentProviderHolder; import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.ProviderInfo; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; import android.os.Process; +import android.os.RemoteException; +import android.util.Slog; import java.io.PrintWriter; +import java.util.HashMap; import java.util.HashSet; class ContentProviderRecord extends ContentProviderHolder { // All attached clients final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>(); + // Handles for non-framework processes supported by this provider + HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle; + // Count for external process for which we have no handles. + int externalProcessNoHandleCount; + final ActivityManagerService service; final int uid; final ApplicationInfo appInfo; final ComponentName name; - int externals; // number of non-framework processes supported by this provider ProcessRecord proc; // if non-null, hosting process. ProcessRecord launchingApp; // if non-null, waiting for this app to be launched. String stringName; - - public ContentProviderRecord(ProviderInfo _info, ApplicationInfo ai, ComponentName _name) { + + public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info, + ApplicationInfo ai, ComponentName _name) { super(_info); + service = _service; uid = ai.uid; appInfo = ai; name = _name; @@ -50,6 +61,7 @@ class ContentProviderRecord extends ContentProviderHolder { appInfo = cpr.appInfo; name = cpr.name; noReleaseNeeded = cpr.noReleaseNeeded; + service = cpr.service; } public boolean canRunHere(ProcessRecord app) { @@ -57,6 +69,57 @@ class ContentProviderRecord extends ContentProviderHolder { && (uid == Process.SYSTEM_UID || uid == app.info.uid); } + public void addExternalProcessHandleLocked(IBinder token) { + if (token == null) { + externalProcessNoHandleCount++; + } else { + if (externalProcessTokenToHandle == null) { + externalProcessTokenToHandle = new HashMap<IBinder, ExternalProcessHandle>(); + } + ExternalProcessHandle handle = externalProcessTokenToHandle.get(token); + if (handle == null) { + handle = new ExternalProcessHandle(token); + externalProcessTokenToHandle.put(token, handle); + } + handle.mAcquisitionCount++; + } + } + + public boolean removeExternalProcessHandleLocked(IBinder token) { + if (hasExternalProcessHandles()) { + boolean hasHandle = false; + if (externalProcessTokenToHandle != null) { + ExternalProcessHandle handle = externalProcessTokenToHandle.get(token); + if (handle != null) { + hasHandle = true; + handle.mAcquisitionCount--; + if (handle.mAcquisitionCount == 0) { + removeExternalProcessHandleInternalLocked(token); + return true; + } + } + } + if (!hasHandle) { + externalProcessNoHandleCount--; + return true; + } + } + return false; + } + + private void removeExternalProcessHandleInternalLocked(IBinder token) { + ExternalProcessHandle handle = externalProcessTokenToHandle.get(token); + handle.unlinkFromOwnDeathLocked(); + externalProcessTokenToHandle.remove(token); + if (externalProcessTokenToHandle.size() == 0) { + externalProcessTokenToHandle = null; + } + } + + public boolean hasExternalProcessHandles() { + return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0); + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("package="); pw.print(info.applicationInfo.packageName); @@ -73,8 +136,9 @@ class ContentProviderRecord extends ContentProviderHolder { pw.print("multiprocess="); pw.print(info.multiprocess); pw.print(" initOrder="); pw.println(info.initOrder); } - if (externals != 0) { - pw.print(prefix); pw.print("externals="); pw.println(externals); + if (hasExternalProcessHandles()) { + pw.print(prefix); pw.print("externals="); + pw.println(externalProcessTokenToHandle.size()); } if (clients.size() > 0) { pw.print(prefix); pw.println("Clients:"); @@ -84,6 +148,7 @@ class ContentProviderRecord extends ContentProviderHolder { } } + @Override public String toString() { if (stringName != null) { return stringName; @@ -96,4 +161,35 @@ class ContentProviderRecord extends ContentProviderHolder { sb.append('}'); return stringName = sb.toString(); } + + // This class represents a handle from an external process to a provider. + private class ExternalProcessHandle implements DeathRecipient { + private static final String LOG_TAG = "ExternalProcessHanldle"; + + private final IBinder mToken; + private int mAcquisitionCount; + + public ExternalProcessHandle(IBinder token) { + mToken = token; + try { + token.linkToDeath(this, 0); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Couldn't register for death for token: " + mToken, re); + } + } + + public void unlinkFromOwnDeathLocked() { + mToken.unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + synchronized (service) { + if (hasExternalProcessHandles() && + externalProcessTokenToHandle.get(mToken) != null) { + removeExternalProcessHandleInternalLocked(mToken); + } + } + } + } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index bdbaab498b97..1c81bb1debcf 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -142,8 +142,7 @@ import java.util.List; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub - implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs, - Choreographer.OnAnimateListener { + implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_ADD_REMOVE = false; @@ -603,6 +602,18 @@ public class WindowManagerService extends IWindowManager.Stub } private LayoutAndSurfaceFields mInnerFields = new LayoutAndSurfaceFields(); + private final class AnimationRunnable implements Runnable { + @Override + public void run() { + synchronized(mWindowMap) { + mAnimationScheduled = false; + performLayoutAndPlaceSurfacesLocked(); + } + } + } + final AnimationRunnable mAnimationRunnable = new AnimationRunnable(); + boolean mAnimationScheduled; + final class DragInputEventReceiver extends InputEventReceiver { public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); @@ -724,7 +735,6 @@ public class WindowManagerService extends IWindowManager.Stub Looper.prepare(); WindowManagerService s = new WindowManagerService(mContext, mPM, mHaveInputMethods, mAllowBootMessages); - s.mChoreographer.addOnAnimateListener(s); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_DISPLAY); android.os.Process.setCanSelfBackground(false); @@ -5441,7 +5451,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mScreenRotationAnimation.setRotation(rotation, mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - mChoreographer.scheduleAnimation(); + scheduleAnimationLocked(); } } Surface.setOrientation(0, rotation); @@ -6882,7 +6892,7 @@ public class WindowManagerService extends IWindowManager.Stub case FORCE_GC: { synchronized(mWindowMap) { - if (mChoreographer.isAnimationScheduled()) { + if (mAnimationScheduled) { // If we are animating, don't do the gc now but // delay a bit so we don't interrupt the animation. mH.sendMessageDelayed(mH.obtainMessage(H.FORCE_GC), @@ -8980,7 +8990,7 @@ public class WindowManagerService extends IWindowManager.Stub if (needRelayout) { requestTraversalLocked(); } else if (mInnerFields.mAnimating) { - mChoreographer.scheduleAnimation(); + scheduleAnimationLocked(); } // Finally update all input windows now that the window changes have stabilized. @@ -9100,10 +9110,10 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public void onAnimate() { - synchronized(mWindowMap) { - performLayoutAndPlaceSurfacesLocked(); + void scheduleAnimationLocked() { + if (!mAnimationScheduled) { + mChoreographer.postAnimationCallback(mAnimationRunnable); + mAnimationScheduled = true; } } @@ -9423,7 +9433,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation"); if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - mChoreographer.scheduleAnimation(); + scheduleAnimationLocked(); } else { mScreenRotationAnimation = null; updateRotation = true; diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 6868cf6b82ea..b013d2799520 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -1593,7 +1593,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); } if (requestAnim) { - mService.mChoreographer.scheduleAnimation(); + mService.scheduleAnimationLocked(); } return true; } @@ -1634,7 +1634,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } if (requestAnim) { - mService.mChoreographer.scheduleAnimation(); + mService.scheduleAnimationLocked(); } return true; } diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 24a4876ee4ef..8cfdb794c03d 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -292,6 +292,20 @@ public class PhoneNumberUtils } /** + * Translates keypad letters to actual digits (e.g. 1-800-GOOG-411 will + * become 1-800-4664-411), and then strips all separators (e.g. 1-800-4664-411 will become + * 18004664411). + * + * @see #convertKeypadLettersToDigits(String) + * @see #stripSeparators(String) + * + * @hide + */ + public static String convertAndStrip(String phoneNumber) { + return stripSeparators(convertKeypadLettersToDigits(phoneNumber)); + } + + /** * Converts pause and tonewait pause characters * to Android representation. * RFC 3601 says pause is 'p' and tonewait is 'w'. diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java index 5d764844406d..2b9fb913241c 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java @@ -1040,7 +1040,8 @@ public abstract class DataConnectionTracker extends Handler { } } if (didDisable) { - if (enabledCount == 0) { + if ((enabledCount == 0) || (apnId == APN_DUN_ID)) { + mRequestedApnType = Phone.APN_TYPE_DEFAULT; onCleanUpConnection(true, apnId, Phone.REASON_DATA_DISABLED); } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java index 3fe57aeb20b5..6e4dd58d6ff3 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java @@ -95,6 +95,9 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { Phone.APN_TYPE_MMS, Phone.APN_TYPE_HIPRI }; + private String[] mDunApnTypes = { + Phone.APN_TYPE_DUN }; + private static final int mDefaultApnId = DataConnectionTracker.APN_DEFAULT_ID; /* Constructor */ @@ -121,11 +124,26 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { createAllDataConnectionList(); broadcastMessenger(); + + Context c = mCdmaPhone.getContext(); + String[] t = c.getResources().getStringArray( + com.android.internal.R.array.config_cdma_dun_supported_types); + if (t != null && t.length > 0) { + ArrayList<String> temp = new ArrayList<String>(); + for(int i=0; i< t.length; i++) { + if (!Phone.APN_TYPE_DUN.equalsIgnoreCase(t[i])) { + temp.add(t[i]); + } + } + temp.add(0, Phone.APN_TYPE_DUN); + mDunApnTypes = temp.toArray(t); + } + } @Override public void dispose() { - cleanUpConnection(false, null); + cleanUpConnection(false, null, false); super.dispose(); @@ -282,7 +300,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { * @param tearDown true if the underlying DataConnection should be disconnected. * @param reason for the clean up. */ - private void cleanUpConnection(boolean tearDown, String reason) { + private void cleanUpConnection(boolean tearDown, String reason, boolean doAll) { if (DBG) log("cleanUpConnection: reason: " + reason); // Clear the reconnect alarm, if set. @@ -302,9 +320,15 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { DataConnectionAc dcac = mDataConnectionAsyncChannels.get(conn.getDataConnectionId()); if (tearDown) { - if (DBG) log("cleanUpConnection: teardown, call conn.disconnect"); - conn.tearDown(reason, obtainMessage(EVENT_DISCONNECT_DONE, - conn.getDataConnectionId(), 0, reason)); + if (doAll) { + if (DBG) log("cleanUpConnection: teardown, conn.tearDownAll"); + conn.tearDownAll(reason, obtainMessage(EVENT_DISCONNECT_DONE, + conn.getDataConnectionId(), 0, reason)); + } else { + if (DBG) log("cleanUpConnection: teardown, conn.tearDown"); + conn.tearDown(reason, obtainMessage(EVENT_DISCONNECT_DONE, + conn.getDataConnectionId(), 0, reason)); + } notificationDeferred = true; } else { if (DBG) log("cleanUpConnection: !tearDown, call conn.resetSynchronously"); @@ -348,8 +372,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { String[] types; int apnId; if (mRequestedApnType.equals(Phone.APN_TYPE_DUN)) { - types = new String[1]; - types[0] = Phone.APN_TYPE_DUN; + types = mDunApnTypes; apnId = DataConnectionTracker.APN_DUN_ID; } else { types = mDefaultApnTypes; @@ -587,7 +610,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { @Override protected void onEnableNewApn() { // No mRequestedApnType check; only one connection is supported - cleanUpConnection(true, Phone.REASON_APN_SWITCHED); + cleanUpConnection(true, Phone.REASON_APN_SWITCHED, false); } /** @@ -769,13 +792,13 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { @Override protected void onCleanUpConnection(boolean tearDown, int apnId, String reason) { // No apnId check; only one connection is supported - cleanUpConnection(tearDown, reason); + cleanUpConnection(tearDown, reason, (apnId == APN_DUN_ID)); } @Override protected void onCleanUpAllConnections(String cause) { // Only one CDMA connection is supported - cleanUpConnection(true, cause); + cleanUpConnection(true, cause, false); } private void createAllDataConnectionList() { @@ -821,7 +844,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED); } else { if (mState == State.FAILED) { - cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED); + cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED, false); mDataConnections.get(0).resetRetryCount(); CdmaCellLocation loc = (CdmaCellLocation)(mPhone.getCellLocation()); @@ -900,7 +923,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { log("onDataStateChanged: No active connection" + "state is CONNECTED, disconnecting/cleanup"); writeEventLogCdmaDataDrop(); - cleanUpConnection(true, null); + cleanUpConnection(true, null, false); return; } diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java index 9d9680d4a43f..db670f8919de 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java @@ -623,4 +623,35 @@ public class PhoneNumberUtilsTest extends AndroidTestCase { // Brazil. assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "BR")); } + + @SmallTest + public void testStripSeparators() { + // Smoke tests which should never fail. + assertEquals("1234567890", PhoneNumberUtils.stripSeparators("1234567890")); + assertEquals("911", PhoneNumberUtils.stripSeparators("911")); + assertEquals("112", PhoneNumberUtils.stripSeparators("112")); + + // Separators should be removed, while '+' or any other digits should not. + assertEquals("+16502910000", PhoneNumberUtils.stripSeparators("+1 (650) 291-0000")); + + // WAIT, PAUSE should *not* be stripped + assertEquals("+16502910000,300;", + PhoneNumberUtils.stripSeparators("+1 (650) 291-0000, 300;")); + } + + @SmallTest + public void testConvertAndStrip() { + // Smoke tests which should never fail. + assertEquals("1234567890", PhoneNumberUtils.convertAndStrip("1234567890")); + assertEquals("911", PhoneNumberUtils.convertAndStrip("911")); + assertEquals("112", PhoneNumberUtils.convertAndStrip("112")); + + // It should convert keypad characters into digits, and strip separators + assertEquals("22233344455566677778889999", + PhoneNumberUtils.convertAndStrip("ABC DEF GHI JKL MNO PQR STUV WXYZ")); + + // Test real cases. + assertEquals("18004664411", PhoneNumberUtils.convertAndStrip("1-800-GOOG-411")); + assertEquals("8002223334", PhoneNumberUtils.convertAndStrip("(800) ABC-DEFG")); + } } diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 674c0b700b5a..5fab2bb2d65d 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -267,6 +267,12 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public void sendBroadcast(Intent intent, int userId) { + throw new UnsupportedOperationException(); + } + @Override public void sendBroadcast(Intent intent, String receiverPermission) { throw new UnsupportedOperationException(); diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 013445664843..1b64f3e3a2f5 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -1754,7 +1754,9 @@ public class WifiStateMachine extends StateMachine { * If we've exceeded the maximum number of retries for DHCP * to a given network, disable the network */ - if (++mReconnectCount > getMaxDhcpRetries()) { + int maxRetries = getMaxDhcpRetries(); + // maxRetries == 0 means keep trying forever + if (maxRetries > 0 && ++mReconnectCount > maxRetries) { loge("Failed " + mReconnectCount + " times, Disabling " + mLastNetworkId); mWifiConfigStore.disableNetwork(mLastNetworkId, |