am dbab18f6: Merge "docs: Android SDK r17 (RC6) Release Notes" into ics-mr1
* commit 'dbab18f64a1b17311a0c865ed43200e9dc239113':
docs: Android SDK r17 (RC6) Release Notes
diff --git a/Android.mk b/Android.mk
index 83c4b5b..5f4224b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -109,6 +109,7 @@
core/java/android/content/pm/IPackageMoveObserver.aidl \
core/java/android/content/pm/IPackageStatsObserver.aidl \
core/java/android/database/IContentObserver.aidl \
+ core/java/android/hardware/ISerialManager.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
core/java/android/net/IConnectivityManager.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
@@ -126,6 +127,7 @@
core/java/android/os/IPermissionController.aidl \
core/java/android/os/IPowerManager.aidl \
core/java/android/os/IRemoteCallback.aidl \
+ core/java/android/os/IUpdateLock.aidl \
core/java/android/os/IVibratorService.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
diff --git a/api/current.txt b/api/current.txt
index 68fb4bc..3e88e1b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6557,6 +6557,7 @@
field public static final int UI_MODE_NIGHT_NO = 16; // 0x10
field public static final int UI_MODE_NIGHT_UNDEFINED = 0; // 0x0
field public static final int UI_MODE_NIGHT_YES = 32; // 0x20
+ field public static final int UI_MODE_TYPE_APPLIANCE = 5; // 0x5
field public static final int UI_MODE_TYPE_CAR = 3; // 0x3
field public static final int UI_MODE_TYPE_DESK = 2; // 0x2
field public static final int UI_MODE_TYPE_MASK = 15; // 0xf
@@ -15245,6 +15246,7 @@
method public abstract void acquired();
method public void cleanup(android.os.IBinder, boolean);
method public void dump();
+ method public void dump(java.io.PrintWriter);
method public boolean isAcquired();
method public void release(android.os.IBinder);
method public abstract void released();
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index fddb429..f4c5359 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
+import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
@@ -109,6 +110,8 @@
runStartService();
} else if (op.equals("force-stop")) {
runForceStop();
+ } else if (op.equals("clear-data")) {
+ runClearData();
} else if (op.equals("kill")) {
runKill();
} else if (op.equals("kill-all")) {
@@ -212,6 +215,19 @@
list[i] = Long.valueOf(strings[i]);
}
intent.putExtra(key, list);
+ } else if (opt.equals("--ef")) {
+ String key = nextArgRequired();
+ String value = nextArgRequired();
+ intent.putExtra(key, Float.valueOf(value));
+ } else if (opt.equals("--efa")) {
+ String key = nextArgRequired();
+ String value = nextArgRequired();
+ String[] strings = value.split(",");
+ float[] list = new float[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Float.valueOf(strings[i]);
+ }
+ intent.putExtra(key, list);
} else if (opt.equals("--ez")) {
String key = nextArgRequired();
String value = nextArgRequired();
@@ -527,6 +543,31 @@
mAm.killAllBackgroundProcesses();
}
+ class ClearUserDataObserver extends IPackageDataObserver.Stub {
+ public int status = -1;
+
+ public void onRemoveCompleted(final String packageName, final boolean succeeded) {
+ synchronized (this) {
+ status = succeeded ? 0 : 1;
+ notify();
+ }
+ }
+ }
+
+ private void runClearData() throws Exception {
+ ClearUserDataObserver observer = new ClearUserDataObserver();
+ mAm.clearApplicationUserData(nextArgRequired(), observer);
+ synchronized (observer) {
+ while (observer.status < 0) {
+ try {
+ observer.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ System.exit(observer.status);
+ }
+
private void sendBroadcast() throws Exception {
Intent intent = makeIntent();
IntentReceiver receiver = new IntentReceiver();
@@ -1252,6 +1293,7 @@
" [--R COUNT] [-S] <INTENT>\n" +
" am startservice <INTENT>\n" +
" am force-stop <PACKAGE>\n" +
+ " am clear-data <PACKAGE>\n" +
" am kill <PACKAGE>\n" +
" am kill-all\n" +
" am broadcast <INTENT>\n" +
@@ -1287,6 +1329,8 @@
"\n" +
"am kill-all: Kill all background processes.\n" +
"\n" +
+ "am clear-data: clear the user data associated with <PACKAGE>.\n" +
+ "\n" +
"am broadcast: send a broadcast Intent.\n" +
"\n" +
"am instrument: start an Instrumentation. Typically this target <COMPONENT>\n" +
@@ -1330,9 +1374,11 @@
" [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
" [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
" [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
+ " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]\n" +
" [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]\n" +
" [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
" [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
+ " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
" [-n <COMPONENT>] [-f <FLAGS>]\n" +
" [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
" [--debug-log-resolution] [--exclude-stopped-packages]\n" +
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index 2df450f3..e3ce1dc 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -46,6 +46,8 @@
{ AID_RADIO, "isms" },
{ AID_RADIO, "iphonesubinfo" },
{ AID_RADIO, "simphonebook" },
+ { AID_MEDIA, "common_time.clock" },
+ { AID_MEDIA, "common_time.config" },
};
void *svcmgr_handle;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2bf1fb7..bb483d1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -42,7 +42,9 @@
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import android.hardware.ISerialManager;
import android.hardware.SensorManager;
+import android.hardware.SerialManager;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbManager;
import android.location.CountryDetector;
@@ -427,6 +429,12 @@
return new UsbManager(ctx, IUsbManager.Stub.asInterface(b));
}});
+ registerService(SERIAL_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(SERIAL_SERVICE);
+ return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
+ }});
+
registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new Vibrator();
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 71f6445..0c22740 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -168,7 +168,7 @@
* {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL},
* {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, or
* {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, or
- * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_TV}.
+ * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_APPLIANCE}.
*/
public int getCurrentModeType() {
if (mService != null) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index b1c1f30..a2b495f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -437,7 +437,12 @@
*/
public WallpaperInfo getWallpaperInfo() {
try {
- return sGlobals.mService.getWallpaperInfo();
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return null;
+ } else {
+ return sGlobals.mService.getWallpaperInfo();
+ }
} catch (RemoteException e) {
return null;
}
@@ -455,6 +460,10 @@
* wallpaper.
*/
public void setResource(int resid) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
try {
Resources resources = mContext.getResources();
/* Set the wallpaper to the default values */
@@ -487,6 +496,10 @@
* wallpaper.
*/
public void setBitmap(Bitmap bitmap) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
if (fd == null) {
@@ -519,6 +532,10 @@
* wallpaper.
*/
public void setStream(InputStream data) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
if (fd == null) {
@@ -562,6 +579,10 @@
* mandatory.
*/
public int getDesiredMinimumWidth() {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return 0;
+ }
try {
return sGlobals.mService.getWidthHint();
} catch (RemoteException e) {
@@ -585,6 +606,10 @@
* mandatory.
*/
public int getDesiredMinimumHeight() {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return 0;
+ }
try {
return sGlobals.mService.getHeightHint();
} catch (RemoteException e) {
@@ -603,7 +628,11 @@
*/
public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
try {
- sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ } else {
+ sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
+ }
} catch (RemoteException e) {
// Ignore
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index bfbd0ac..6f1add0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1627,6 +1627,17 @@
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.os.IUpdateLock} for managing runtime sequences that
+ * must not be interrupted by headless OTA application or similar.
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.os.UpdateLock
+ */
+ public static final String UPDATE_LOCK_SERVICE = "updatelock";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.NetworkManagementService} for handling management of
* system network services
*
@@ -1789,6 +1800,17 @@
public static final String USB_SERVICE = "usb";
/**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.hardware.SerialManager} for access to serial ports.
+ *
+ * @see #getSystemService
+ * @see android.harware.SerialManager
+ *
+ * @hide
+ */
+ public static final String SERIAL_SERVICE = "serial";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 5c3a17a..6015668 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -228,6 +228,7 @@
public static final int UI_MODE_TYPE_DESK = 0x02;
public static final int UI_MODE_TYPE_CAR = 0x03;
public static final int UI_MODE_TYPE_TELEVISION = 0x04;
+ public static final int UI_MODE_TYPE_APPLIANCE = 0x05;
public static final int UI_MODE_NIGHT_MASK = 0x30;
public static final int UI_MODE_NIGHT_UNDEFINED = 0x00;
@@ -239,7 +240,8 @@
* <p>The {@link #UI_MODE_TYPE_MASK} bits define the overall ui mode of the
* device. They may be one of {@link #UI_MODE_TYPE_UNDEFINED},
* {@link #UI_MODE_TYPE_NORMAL}, {@link #UI_MODE_TYPE_DESK},
- * or {@link #UI_MODE_TYPE_CAR}.
+ * {@link #UI_MODE_TYPE_CAR}, {@link #UI_MODE_TYPE_TELEVISION}, or
+ * {@link #UI_MODE_TYPE_APPLIANCE}.
*
* <p>The {@link #UI_MODE_NIGHT_MASK} defines whether the screen
* is in a special mode. They may be one of {@link #UI_MODE_NIGHT_UNDEFINED},
@@ -391,6 +393,7 @@
case UI_MODE_TYPE_DESK: sb.append(" desk"); break;
case UI_MODE_TYPE_CAR: sb.append(" car"); break;
case UI_MODE_TYPE_TELEVISION: sb.append(" television"); break;
+ case UI_MODE_TYPE_APPLIANCE: sb.append(" appliance"); break;
default: sb.append(" uimode="); sb.append(uiMode&UI_MODE_TYPE_MASK); break;
}
switch ((uiMode&UI_MODE_NIGHT_MASK)) {
diff --git a/core/java/android/hardware/ISerialManager.aidl b/core/java/android/hardware/ISerialManager.aidl
new file mode 100644
index 0000000..74d30f7
--- /dev/null
+++ b/core/java/android/hardware/ISerialManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package android.hardware;
+
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface ISerialManager
+{
+ /* Returns a list of all available serial ports */
+ String[] getSerialPorts();
+
+ /* Returns a file descriptor for the serial port. */
+ ParcelFileDescriptor openSerialPort(String name);
+}
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
new file mode 100644
index 0000000..c5e1c2b
--- /dev/null
+++ b/core/java/android/hardware/SerialManager.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+
+package android.hardware;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * @hide
+ */
+public class SerialManager {
+ private static final String TAG = "SerialManager";
+
+ private final Context mContext;
+ private final ISerialManager mService;
+
+ /**
+ * {@hide}
+ */
+ public SerialManager(Context context, ISerialManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns a string array containing the names of available serial ports
+ *
+ * @return names of available serial ports
+ */
+ public String[] getSerialPorts() {
+ try {
+ return mService.getSerialPorts();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in getSerialPorts", e);
+ return null;
+ }
+ }
+
+ /**
+ * Opens and returns the {@link android.hardware.SerialPort} with the given name.
+ * The speed of the serial port must be one of:
+ * 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
+ * 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000,
+ * 1500000, 2000000, 2500000, 3000000, 3500000 or 4000000
+ *
+ * @param name of the serial port
+ * @param speed at which to open the serial port
+ * @return the serial port
+ */
+ public SerialPort openSerialPort(String name, int speed) throws IOException {
+ try {
+ ParcelFileDescriptor pfd = mService.openSerialPort(name);
+ if (pfd != null) {
+ SerialPort port = new SerialPort(name);
+ port.open(pfd, speed);
+ return port;
+ } else {
+ throw new IOException("Could not open serial port " + name);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "exception in UsbManager.openDevice", e);
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java
new file mode 100644
index 0000000..5ef122b
--- /dev/null
+++ b/core/java/android/hardware/SerialPort.java
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+package android.hardware;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class SerialPort {
+
+ private static final String TAG = "SerialPort";
+
+ // used by the JNI code
+ private int mNativeContext;
+ private final String mName;
+ private ParcelFileDescriptor mFileDescriptor;
+
+ /**
+ * SerialPort should only be instantiated by SerialManager
+ * @hide
+ */
+ public SerialPort(String name) {
+ mName = name;
+ }
+
+ /**
+ * SerialPort should only be instantiated by SerialManager
+ * Speed must be one of 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
+ * 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000,
+ * 1500000, 2000000, 2500000, 3000000, 3500000, 4000000
+ *
+ * @hide
+ */
+ public void open(ParcelFileDescriptor pfd, int speed) throws IOException {
+ native_open(pfd.getFileDescriptor(), speed);
+ mFileDescriptor = pfd;
+ }
+
+ /**
+ * Closes the serial port
+ */
+ public void close() throws IOException {
+ if (mFileDescriptor != null) {
+ mFileDescriptor.close();
+ mFileDescriptor = null;
+ }
+ native_close();
+ }
+
+ /**
+ * Returns the name of the serial port
+ *
+ * @return the serial port's name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Reads data into the provided buffer
+ *
+ * @param buffer to read into
+ * @return number of bytes read
+ */
+ public int read(ByteBuffer buffer) throws IOException {
+ if (buffer.isDirect()) {
+ return native_read_direct(buffer, buffer.remaining());
+ } else if (buffer.hasArray()) {
+ return native_read_array(buffer.array(), buffer.remaining());
+ } else {
+ throw new IllegalArgumentException("buffer is not direct and has no array");
+ }
+ }
+
+ /**
+ * Writes data from provided buffer
+ *
+ * @param buffer to write
+ * @param length number of bytes to write
+ */
+ public void write(ByteBuffer buffer, int length) throws IOException {
+ if (buffer.isDirect()) {
+ native_write_direct(buffer, length);
+ } else if (buffer.hasArray()) {
+ native_write_array(buffer.array(), length);
+ } else {
+ throw new IllegalArgumentException("buffer is not direct and has no array");
+ }
+ }
+
+ /**
+ * Sends a stream of zero valued bits for 0.25 to 0.5 seconds
+ */
+ public void sendBreak() {
+ native_send_break();
+ }
+
+ private native void native_open(FileDescriptor pfd, int speed) throws IOException;
+ private native void native_close();
+ private native int native_read_array(byte[] buffer, int length) throws IOException;
+ private native int native_read_direct(ByteBuffer buffer, int length) throws IOException;
+ private native void native_write_array(byte[] buffer, int length) throws IOException;
+ private native void native_write_direct(ByteBuffer buffer, int length) throws IOException;
+ private native void native_send_break();
+}
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 21ecc22..b1dd62c 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -49,6 +49,7 @@
private LinkCapabilities mLinkCapabilities;
private NetworkInfo mNetworkInfo;
private InterfaceObserver mInterfaceObserver;
+ private String mHwAddr;
/* For sending events to connectivity service handler */
private Handler mCsHandler;
@@ -74,15 +75,13 @@
if (mIface.equals(iface) && mLinkUp != up) {
Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down"));
mLinkUp = up;
+ mTracker.mNetworkInfo.setIsAvailable(up);
// use DHCP
if (up) {
mTracker.reconnect();
} else {
- NetworkUtils.stopDhcp(mIface);
- mTracker.mNetworkInfo.setIsAvailable(false);
- mTracker.mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED,
- null, null);
+ mTracker.disconnect();
}
}
}
@@ -104,10 +103,6 @@
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORKTYPE, "");
mLinkProperties = new LinkProperties();
mLinkCapabilities = new LinkCapabilities();
- mLinkUp = false;
-
- mNetworkInfo.setIsAvailable(false);
- setTeardownRequested(false);
}
private void interfaceAdded(String iface) {
@@ -116,7 +111,7 @@
Log.d(TAG, "Adding " + iface);
- synchronized(mIface) {
+ synchronized(this) {
if(!mIface.isEmpty())
return;
mIface = iface;
@@ -125,21 +120,15 @@
mNetworkInfo.setIsAvailable(true);
Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
msg.sendToTarget();
-
- runDhcp();
}
- private void interfaceRemoved(String iface) {
- if (!iface.equals(mIface))
- return;
-
- Log.d(TAG, "Removing " + iface);
+ public void disconnect() {
NetworkUtils.stopDhcp(mIface);
mLinkProperties.clear();
mNetworkInfo.setIsAvailable(false);
- mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+ mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
msg.sendToTarget();
@@ -147,6 +136,21 @@
msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
msg.sendToTarget();
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+ try {
+ service.clearInterfaceAddresses(mIface);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to clear addresses or disable ipv6" + e);
+ }
+ }
+
+ private void interfaceRemoved(String iface) {
+ if (!iface.equals(mIface))
+ return;
+
+ Log.d(TAG, "Removing " + iface);
+ disconnect();
mIface = "";
}
@@ -161,7 +165,7 @@
mLinkProperties = dhcpInfoInternal.makeLinkProperties();
mLinkProperties.setInterfaceName(mIface);
- mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+ mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
msg.sendToTarget();
}
@@ -208,8 +212,15 @@
for (String iface : ifaces) {
if (iface.matches(sIfaceMatch)) {
mIface = iface;
+ service.setInterfaceUp(iface);
InterfaceConfiguration config = service.getInterfaceConfig(iface);
mLinkUp = config.isActive();
+ if (config != null && mHwAddr == null) {
+ mHwAddr = config.hwAddr;
+ if (mHwAddr != null) {
+ mNetworkInfo.setExtraInfo(mHwAddr);
+ }
+ }
reconnect();
break;
}
@@ -239,9 +250,11 @@
* Re-enable connectivity to a network after a {@link #teardown()}.
*/
public boolean reconnect() {
- mTeardownRequested.set(false);
- runDhcp();
- return true;
+ if (mLinkUp) {
+ mTeardownRequested.set(false);
+ runDhcp();
+ }
+ return mLinkUp;
}
/**
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 537750a..1e645a1 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -346,6 +346,18 @@
}
/**
+ * Set the extraInfo field.
+ * @param extraInfo an optional {@code String} providing addditional network state
+ * information passed up from the lower networking layers.
+ * @hide
+ */
+ public void setExtraInfo(String extraInfo) {
+ synchronized (this) {
+ this.mExtraInfo = extraInfo;
+ }
+ }
+
+ /**
* Report the reason an attempt to establish connectivity failed,
* if one is available.
* @return the reason for failure, or null if not available
diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java
new file mode 100644
index 0000000..e728144
--- /dev/null
+++ b/core/java/android/os/CommonClock.java
@@ -0,0 +1,682 @@
+/*
+ * 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.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+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;
+};
+
+/**
+ * Used for accessing the android common time service's common clock and receiving notifications
+ * about common time synchronization status changes.
+ * @hide
+ */
+public class CommonClock {
+ /**
+ * Sentinel value returned by {@link #getTime()} and {@link #getEstimatedError()} when the
+ * common time service is not able to determine the current common time due to a lack of
+ * synchronization.
+ */
+ public static final long TIME_NOT_SYNCED = -1;
+
+ /**
+ * Sentinel value returned by {@link #getTimelineId()} when the common time service is not
+ * currently synced to any timeline.
+ */
+ public static final long INVALID_TIMELINE_ID = 0;
+
+ /**
+ * Sentinel value returned by {@link #getEstimatedError()} when the common time service is not
+ * currently synced to any timeline.
+ */
+ public static final int ERROR_ESTIMATE_UNKNOWN = 0x7FFFFFFF;
+
+ /**
+ * Value used by {@link #getState()} to indicate that there was an internal error while
+ * attempting to determine the state of the common time service.
+ */
+ public static final int STATE_INVALID = -1;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its initial
+ * state and attempting to find the current timeline master, if any. The service will
+ * transition to either {@link #STATE_CLIENT} if it finds an active master, or to
+ * {@link #STATE_MASTER} if no active master is found and this client becomes the master of a
+ * new timeline.
+ */
+ public static final int STATE_INITIAL = 0;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its client
+ * state and is synchronizing its time to a different timeline master on the network.
+ */
+ public static final int STATE_CLIENT = 1;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its master
+ * state and is serving as the timeline master for other common time service clients on the
+ * network.
+ */
+ public static final int STATE_MASTER = 2;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is in its Ronin
+ * state. Common time service instances in the client state enter the Ronin state after their
+ * timeline master becomes unreachable on the network. Common time services who enter the Ronin
+ * state will begin a new master election for the timeline they were recently clients of. As
+ * clients detect they are not the winner and drop out of the election, they will transition to
+ * the {@link #STATE_WAIT_FOR_ELECTION} state. When there is only one client remaining in the
+ * election, it will assume ownership of the timeline and transition to the
+ * {@link #STATE_MASTER} state. During the election, all clients will allow their timeline to
+ * drift without applying correction.
+ */
+ public static final int STATE_RONIN = 3;
+
+ /**
+ * Value used by {@link #getState()} to indicate that the common time service is waiting for a
+ * master election to conclude and for the new master to announce itself before transitioning to
+ * the {@link #STATE_CLIENT} state. If no new master announces itself within the timeout
+ * threshold, the time service will transition back to the {@link #STATE_RONIN} state in order
+ * to restart the election.
+ */
+ public static final int STATE_WAIT_FOR_ELECTION = 4;
+
+ /**
+ * Name of the underlying native binder service
+ */
+ public static final String SERVICE_NAME = "common_time.clock";
+
+ /**
+ * Class constructor.
+ * @throws android.os.RemoteException
+ */
+ public CommonClock()
+ throws RemoteException {
+ mRemote = ServiceManager.getService(SERVICE_NAME);
+ if (null == mRemote)
+ throw new RemoteException();
+
+ mInterfaceDesc = mRemote.getInterfaceDescriptor();
+ mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc);
+ mRemote.linkToDeath(mDeathHandler, 0);
+ registerTimelineChangeListener();
+ }
+
+ /**
+ * Handy class factory method.
+ */
+ static public CommonClock create() {
+ CommonClock retVal;
+
+ try {
+ retVal = new CommonClock();
+ }
+ catch (RemoteException e) {
+ retVal = null;
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Release all native resources held by this {@link android.os.CommonClock} instance. Once
+ * resources have been released, the {@link android.os.CommonClock} instance is disconnected from
+ * the native service and will throw a {@link android.os.RemoteException} if any of its
+ * methods are called. Clients should always call release on their client instances before
+ * releasing their last Java reference to the instance. Failure to do this will cause
+ * non-deterministic native resource reclamation and may cause the common time service to remain
+ * active on the network for longer than it should.
+ */
+ public void release() {
+ unregisterTimelineChangeListener();
+ if (null != mRemote) {
+ try {
+ mRemote.unlinkToDeath(mDeathHandler, 0);
+ }
+ catch (NoSuchElementException e) { }
+ mRemote = null;
+ }
+ mUtils = null;
+ }
+
+ /**
+ * Gets the common clock's current time.
+ *
+ * @return a signed 64-bit value representing the current common time in microseconds, or the
+ * special value {@link #TIME_NOT_SYNCED} if the common time service is currently not
+ * synchronized.
+ * @throws android.os.RemoteException
+ */
+ public long getTime()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetLong(METHOD_GET_COMMON_TIME, TIME_NOT_SYNCED);
+ }
+
+ /**
+ * Gets the current estimation of common clock's synchronization accuracy from the common time
+ * service.
+ *
+ * @return a signed 32-bit value representing the common time service's estimation of
+ * synchronization accuracy in microseconds, or the special value
+ * {@link #ERROR_ESTIMATE_UNKNOWN} if the common time service is currently not synchronized.
+ * Negative values indicate that the local server estimates that the nominal common time is
+ * behind the local server's time (in other words, the local clock is running fast) Positive
+ * values indicate that the local server estimates that the nominal common time is ahead of the
+ * local server's time (in other words, the local clock is running slow)
+ * @throws android.os.RemoteException
+ */
+ public int getEstimatedError()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetInt(METHOD_GET_ESTIMATED_ERROR, ERROR_ESTIMATE_UNKNOWN);
+ }
+
+ /**
+ * Gets the ID of the timeline the common time service is currently synchronizing its clock to.
+ *
+ * @return a long representing the unique ID of the timeline the common time service is
+ * currently synchronizing with, or {@link #INVALID_TIMELINE_ID} if the common time service is
+ * currently not synchronized.
+ * @throws android.os.RemoteException
+ */
+ public long getTimelineId()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetLong(METHOD_GET_TIMELINE_ID, INVALID_TIMELINE_ID);
+ }
+
+ /**
+ * Gets the current state of this clock's common time service in the the master election
+ * algorithm.
+ *
+ * @return a integer indicating the current state of the this clock's common time service in the
+ * master election algorithm or {@link #STATE_INVALID} if there is an internal error.
+ * @throws android.os.RemoteException
+ */
+ public int getState()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetInt(METHOD_GET_STATE, STATE_INVALID);
+ }
+
+ /**
+ * Gets the IP address and UDP port of the current timeline master.
+ *
+ * @return an InetSocketAddress containing the IP address and UDP port of the current timeline
+ * master, or null if there is no current master.
+ * @throws android.os.RemoteException
+ */
+ public InetSocketAddress getMasterAddr()
+ throws RemoteException {
+ throwOnDeadServer();
+ return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ADDRESS);
+ }
+
+ /**
+ * The OnTimelineChangedListener interface defines a method called by the
+ * {@link android.os.CommonClock} instance to indicate that the time synchronization service has
+ * either synchronized with a new timeline, or is no longer a member of any timeline. The
+ * client application can implement this interface and register the listener with the
+ * {@link #setTimelineChangedListener(OnTimelineChangedListener)} method.
+ */
+ public interface OnTimelineChangedListener {
+ /**
+ * Method called when the time service's timeline has changed.
+ *
+ * @param newTimelineId a long which uniquely identifies the timeline the time
+ * synchronization service is now a member of, or {@link #INVALID_TIMELINE_ID} if the the
+ * service is not synchronized to any timeline.
+ */
+ void onTimelineChanged(long newTimelineId);
+ }
+
+ /**
+ * Registers an OnTimelineChangedListener interface.
+ * <p>Call this method with a null listener to stop receiving server death notifications.
+ */
+ public void setTimelineChangedListener(OnTimelineChangedListener listener) {
+ synchronized (mListenerLock) {
+ mTimelineChangedListener = listener;
+ }
+ }
+
+ /**
+ * The OnServerDiedListener interface defines a method called by the
+ * {@link android.os.CommonClock} instance to indicate that the connection to the native media
+ * server has been broken and that the {@link android.os.CommonClock} instance will need to be
+ * released and re-created. The client application can implement this interface and register
+ * the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method.
+ */
+ public interface OnServerDiedListener {
+ /**
+ * Method called when the native media server has died. <p>If the native common time
+ * service encounters a fatal error and needs to restart, the binder connection from the
+ * {@link android.os.CommonClock} instance to the common time service will be broken. To
+ * restore functionality, clients should {@link #release()} their old visualizer and create
+ * a new instance.
+ */
+ void onServerDied();
+ }
+
+ /**
+ * Registers an OnServerDiedListener interface.
+ * <p>Call this method with a null listener to stop receiving server death notifications.
+ */
+ public void setServerDiedListener(OnServerDiedListener listener) {
+ synchronized (mListenerLock) {
+ mServerDiedListener = listener;
+ }
+ }
+
+ protected void finalize() throws Throwable { release(); }
+
+ private void throwOnDeadServer() throws RemoteException {
+ if ((null == mRemote) || (null == mUtils))
+ throw new RemoteException();
+ }
+
+ private final Object mListenerLock = new Object();
+ private OnTimelineChangedListener mTimelineChangedListener = null;
+ private OnServerDiedListener mServerDiedListener = null;
+
+ private IBinder mRemote = null;
+ private String mInterfaceDesc = "";
+ private CommonTimeUtils mUtils;
+
+ private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() {
+ public void binderDied() {
+ synchronized (mListenerLock) {
+ if (null != mServerDiedListener)
+ mServerDiedListener.onServerDied();
+ }
+ }
+ };
+
+ private class TimelineChangedListener extends Binder {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ switch (code) {
+ case METHOD_CBK_ON_TIMELINE_CHANGED:
+ data.enforceInterface(DESCRIPTOR);
+ long timelineId = data.readLong();
+ synchronized (mListenerLock) {
+ if (null != mTimelineChangedListener)
+ mTimelineChangedListener.onTimelineChanged(timelineId);
+ }
+ return true;
+ }
+
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ private static final String DESCRIPTOR = "android.os.ICommonClockListener";
+ };
+
+ private TimelineChangedListener mCallbackTgt = null;
+
+ private void registerTimelineChangeListener() throws RemoteException {
+ if (null != mCallbackTgt)
+ return;
+
+ boolean success = false;
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+ mCallbackTgt = new TimelineChangedListener();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ data.writeStrongBinder(mCallbackTgt);
+ mRemote.transact(METHOD_REGISTER_LISTENER, data, reply, 0);
+ success = (0 == reply.readInt());
+ }
+ catch (RemoteException e) {
+ success = false;
+ }
+ finally {
+ reply.recycle();
+ data.recycle();
+ }
+
+ // Did we catch a remote exception or fail to register our callback target? If so, our
+ // object must already be dead (or be as good as dead). Clear out all of our state so that
+ // our other methods will properly indicate a dead object.
+ if (!success) {
+ mCallbackTgt = null;
+ mRemote = null;
+ mUtils = null;
+ }
+ }
+
+ private void unregisterTimelineChangeListener() {
+ if (null == mCallbackTgt)
+ return;
+
+ android.os.Parcel data = android.os.Parcel.obtain();
+ android.os.Parcel reply = android.os.Parcel.obtain();
+
+ try {
+ data.writeInterfaceToken(mInterfaceDesc);
+ data.writeStrongBinder(mCallbackTgt);
+ mRemote.transact(METHOD_UNREGISTER_LISTENER, data, reply, 0);
+ }
+ catch (RemoteException e) { }
+ finally {
+ reply.recycle();
+ data.recycle();
+ mCallbackTgt = null;
+ }
+ }
+
+ private static final int METHOD_IS_COMMON_TIME_VALID = IBinder.FIRST_CALL_TRANSACTION;
+ private static final int METHOD_COMMON_TIME_TO_LOCAL_TIME = METHOD_IS_COMMON_TIME_VALID + 1;
+ private static final int METHOD_LOCAL_TIME_TO_COMMON_TIME = METHOD_COMMON_TIME_TO_LOCAL_TIME + 1;
+ private static final int METHOD_GET_COMMON_TIME = METHOD_LOCAL_TIME_TO_COMMON_TIME + 1;
+ private static final int METHOD_GET_COMMON_FREQ = METHOD_GET_COMMON_TIME + 1;
+ private static final int METHOD_GET_LOCAL_TIME = METHOD_GET_COMMON_FREQ + 1;
+ private static final int METHOD_GET_LOCAL_FREQ = METHOD_GET_LOCAL_TIME + 1;
+ private static final int METHOD_GET_ESTIMATED_ERROR = METHOD_GET_LOCAL_FREQ + 1;
+ private static final int METHOD_GET_TIMELINE_ID = METHOD_GET_ESTIMATED_ERROR + 1;
+ private static final int METHOD_GET_STATE = METHOD_GET_TIMELINE_ID + 1;
+ private static final int METHOD_GET_MASTER_ADDRESS = METHOD_GET_STATE + 1;
+ private static final int METHOD_REGISTER_LISTENER = METHOD_GET_MASTER_ADDRESS + 1;
+ private static final int METHOD_UNREGISTER_LISTENER = METHOD_REGISTER_LISTENER + 1;
+
+ private static final int METHOD_CBK_ON_TIMELINE_CHANGED = IBinder.FIRST_CALL_TRANSACTION;
+}
diff --git a/core/java/android/os/CommonTimeConfig.java b/core/java/android/os/CommonTimeConfig.java
new file mode 100644
index 0000000..6b0f4fc
--- /dev/null
+++ b/core/java/android/os/CommonTimeConfig.java
@@ -0,0 +1,447 @@
+/*
+ * 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.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/IUpdateLock.aidl b/core/java/android/os/IUpdateLock.aidl
new file mode 100644
index 0000000..4492fb8
--- /dev/null
+++ b/core/java/android/os/IUpdateLock.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Direct interface to the UpdateLockService's functionality
+ *
+ * {@hide}
+ */
+interface IUpdateLock {
+ void acquireUpdateLock(IBinder token, String tag);
+ void releaseUpdateLock(IBinder token);
+}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 73e8d98..43cf74e 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -26,6 +26,7 @@
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
import java.io.RandomAccessFile;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
@@ -103,7 +104,12 @@
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
- trusted.add(cf.generateCertificate(zip.getInputStream(entry)));
+ InputStream is = zip.getInputStream(entry);
+ try {
+ trusted.add(cf.generateCertificate(is));
+ } finally {
+ is.close();
+ }
}
} finally {
zip.close();
@@ -162,8 +168,6 @@
int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
- Log.v(TAG, String.format("comment size %d; signature start %d",
- commentSize, signatureStart));
byte[] eocd = new byte[commentSize + 22];
raf.seek(fileLen - (commentSize + 22));
diff --git a/core/java/android/os/TokenWatcher.java b/core/java/android/os/TokenWatcher.java
index ac3cc92..9b3a2d6 100755
--- a/core/java/android/os/TokenWatcher.java
+++ b/core/java/android/os/TokenWatcher.java
@@ -16,6 +16,8 @@
package android.os;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.WeakHashMap;
import java.util.Set;
import android.util.Log;
@@ -115,15 +117,31 @@
public void dump()
{
+ ArrayList<String> a = dumpInternal();
+ for (String s : a) {
+ Log.i(mTag, s);
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ ArrayList<String> a = dumpInternal();
+ for (String s : a) {
+ pw.println(s);
+ }
+ }
+
+ private ArrayList<String> dumpInternal() {
+ ArrayList<String> a = new ArrayList<String>();
synchronized (mTokens) {
Set<IBinder> keys = mTokens.keySet();
- Log.i(mTag, "Token count: " + mTokens.size());
+ a.add("Token count: " + mTokens.size());
int i = 0;
for (IBinder b: keys) {
- Log.i(mTag, "[" + i + "] " + mTokens.get(b).tag + " - " + b);
+ a.add("[" + i + "] " + mTokens.get(b).tag + " - " + b);
i++;
}
}
+ return a;
}
private Runnable mNotificationTask = new Runnable() {
diff --git a/core/java/android/os/UpdateLock.java b/core/java/android/os/UpdateLock.java
new file mode 100644
index 0000000..4060326
--- /dev/null
+++ b/core/java/android/os/UpdateLock.java
@@ -0,0 +1,166 @@
+/*
+ * 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 android.content.Context;
+import android.util.Log;
+
+/**
+ * Advisory wakelock-like mechanism by which processes that should not be interrupted for
+ * OTA/update purposes can so advise the OS. This is particularly relevant for headless
+ * or kiosk-like operation.
+ *
+ * @hide
+ */
+public class UpdateLock {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "UpdateLock";
+
+ private static IUpdateLock sService;
+ private static void checkService() {
+ if (sService == null) {
+ sService = IUpdateLock.Stub.asInterface(
+ ServiceManager.getService(Context.UPDATE_LOCK_SERVICE));
+ }
+ }
+
+ IBinder mToken;
+ int mCount = 0;
+ boolean mRefCounted = true;
+ boolean mHeld = false;
+ final String mTag;
+
+ /**
+ * Broadcast Intent action sent when the global update lock state changes,
+ * i.e. when the first locker acquires an update lock, or when the last
+ * locker releases theirs. The broadcast is sticky but is sent only to
+ * registered receivers.
+ */
+ public static final String UPDATE_LOCK_CHANGED = "android.os.UpdateLock.UPDATE_LOCK_CHANGED";
+
+ /**
+ * Boolean Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, indicating
+ * whether now is an appropriate time to interrupt device activity with an
+ * update operation. True means that updates are okay right now; false indicates
+ * that perhaps later would be a better time.
+ */
+ public static final String NOW_IS_CONVENIENT = "nowisconvenient";
+
+ /**
+ * Long Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, marking the
+ * wall-clock time [in UTC] at which the broadcast was sent. Note that this is
+ * in the System.currentTimeMillis() time base, which may be non-monotonic especially
+ * around reboots.
+ */
+ public static final String TIMESTAMP = "timestamp";
+
+ /**
+ * Construct an UpdateLock instance.
+ * @param tag An arbitrary string used to identify this lock instance in dump output.
+ */
+ public UpdateLock(String tag) {
+ mTag = tag;
+ mToken = new Binder();
+ }
+
+ /**
+ * Change the refcount behavior of this update lock.
+ */
+ public void setReferenceCounted(boolean isRefCounted) {
+ if (DEBUG) {
+ Log.v(TAG, "setting refcounted=" + isRefCounted + " : " + this);
+ }
+ mRefCounted = isRefCounted;
+ }
+
+ /**
+ * Is this lock currently held?
+ */
+ public boolean isHeld() {
+ synchronized (mToken) {
+ return mHeld;
+ }
+ }
+
+ /**
+ * Acquire an update lock.
+ */
+ public void acquire() {
+ if (DEBUG) {
+ Log.v(TAG, "acquire() : " + this, new RuntimeException("here"));
+ }
+ checkService();
+ synchronized (mToken) {
+ acquireLocked();
+ }
+ }
+
+ private void acquireLocked() {
+ if (!mRefCounted || mCount++ == 0) {
+ if (sService != null) {
+ try {
+ sService.acquireUpdateLock(mToken, mTag);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to contact service to acquire");
+ }
+ }
+ mHeld = true;
+ }
+ }
+
+ /**
+ * Release this update lock.
+ */
+ public void release() {
+ if (DEBUG) Log.v(TAG, "release() : " + this, new RuntimeException("here"));
+ checkService();
+ synchronized (mToken) {
+ releaseLocked();
+ }
+ }
+
+ private void releaseLocked() {
+ if (!mRefCounted || --mCount == 0) {
+ if (sService != null) {
+ try {
+ sService.releaseUpdateLock(mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to contact service to release");
+ }
+ }
+ mHeld = false;
+ }
+ if (mCount < 0) {
+ throw new RuntimeException("UpdateLock under-locked");
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ synchronized (mToken) {
+ // if mHeld is true, sService must be non-null
+ if (mHeld) {
+ Log.wtf(TAG, "UpdateLock finalized while still held");
+ try {
+ sService.releaseUpdateLock(mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to contact service to release");
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index cdb622c..fbf512c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,7 @@
package android.os.storage;
+import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -534,6 +535,7 @@
* @hide
*/
public String getVolumeState(String mountPoint) {
+ if (mMountService == null) return Environment.MEDIA_REMOVED;
try {
return mMountService.getVolumeState(mountPoint);
} catch (RemoteException e) {
@@ -547,6 +549,7 @@
* @hide
*/
public StorageVolume[] getVolumeList() {
+ if (mMountService == null) return new StorageVolume[0];
try {
Parcelable[] list = mMountService.getVolumeList();
if (list == null) return new StorageVolume[0];
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d7fab37..af2d59a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1473,6 +1473,12 @@
public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco";
/**
+ * Master volume (float in the range 0.0f to 1.0f).
+ * @hide
+ */
+ public static final String VOLUME_MASTER = "volume_master";
+
+ /**
* Whether the notifications should use the ring volume (value of 1) or a separate
* notification volume (value of 0). In most cases, users will have this enabled so the
* notification and ringer volumes will be the same. However, power users can disable this
@@ -1797,6 +1803,12 @@
public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
/**
+ * Whether the lockscreen should be completely disabled.
+ * @hide
+ */
+ public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
+
+ /**
* URI for the low battery sound file.
* @hide
*/
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index fecc8f9..dce31db 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -46,6 +46,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -554,12 +555,15 @@
private synchronized void updateSdpRecords() {
ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
- // Add the default records
- uuids.add(BluetoothUuid.HSP_AG);
- uuids.add(BluetoothUuid.ObexObjectPush);
+ Resources R = mContext.getResources();
- if (mContext.getResources().
- getBoolean(com.android.internal.R.bool.config_voice_capable)) {
+ // Add the default records
+ if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) {
+ uuids.add(BluetoothUuid.HSP_AG);
+ uuids.add(BluetoothUuid.ObexObjectPush);
+ }
+
+ if (R.getBoolean(com.android.internal.R.bool.config_voice_capable)) {
uuids.add(BluetoothUuid.Handsfree_AG);
uuids.add(BluetoothUuid.PBAP_PSE);
}
@@ -567,14 +571,16 @@
// Add SDP records for profiles maintained by Android userspace
addReservedSdpRecords(uuids);
- // Enable profiles maintained by Bluez userspace.
- setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE,
- BluetoothPanProfileHandler.NAP_BRIDGE);
+ if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) {
+ // Enable profiles maintained by Bluez userspace.
+ setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE,
+ BluetoothPanProfileHandler.NAP_BRIDGE);
- // Add SDP records for profiles maintained by Bluez userspace
- uuids.add(BluetoothUuid.AudioSource);
- uuids.add(BluetoothUuid.AvrcpTarget);
- uuids.add(BluetoothUuid.NAP);
+ // Add SDP records for profiles maintained by Bluez userspace
+ uuids.add(BluetoothUuid.AudioSource);
+ uuids.add(BluetoothUuid.AvrcpTarget);
+ uuids.add(BluetoothUuid.NAP);
+ }
// Cannot cast uuids.toArray directly since ParcelUuid is parcelable
mAdapterUuids = new ParcelUuid[uuids.size()];
@@ -1743,6 +1749,7 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) {
+ pw.println("state: " + getBluetoothStateInternal());
return;
}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 48fe0df..b6e37a6 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -91,6 +91,10 @@
private static final int MSG_VIBRATE = 4;
private static final int MSG_TIMEOUT = 5;
private static final int MSG_RINGER_MODE_CHANGED = 6;
+ private static final int MSG_MUTE_CHANGED = 7;
+
+ // Pseudo stream type for master volume
+ private static final int STREAM_MASTER = -100;
protected Context mContext;
private AudioManager mAudioManager;
@@ -148,7 +152,13 @@
R.string.volume_icon_description_notification,
R.drawable.ic_audio_notification,
R.drawable.ic_audio_notification_mute,
- true);
+ true),
+ // for now, use media resources for master volume
+ MasterStream(STREAM_MASTER,
+ R.string.volume_icon_description_media,
+ R.drawable.ic_audio_vol,
+ R.drawable.ic_audio_vol_mute,
+ false);
int streamType;
int descRes;
@@ -173,7 +183,8 @@
StreamResources.VoiceStream,
StreamResources.MediaStream,
StreamResources.NotificationStream,
- StreamResources.AlarmStream
+ StreamResources.AlarmStream,
+ StreamResources.MasterStream
};
/** Object that contains data for each slider */
@@ -195,6 +206,16 @@
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAudioService = volumeService;
+ // For now, only show master volume if master volume is supported
+ boolean useMasterVolume = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_useMasterVolume);
+ if (useMasterVolume) {
+ for (int i = 0; i < STREAMS.length; i++) {
+ StreamResources streamRes = STREAMS[i];
+ streamRes.show = (streamRes.streamType == STREAM_MASTER);
+ }
+ }
+
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = mView = inflater.inflate(R.layout.volume_adjust, null);
@@ -245,7 +266,7 @@
mVibrator = new Vibrator();
mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
- mShowCombinedVolumes = !mVoiceCapable;
+ mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
// If we don't want to show multiple volumes, hide the settings button and divider
if (!mShowCombinedVolumes) {
mMoreButton.setVisibility(View.GONE);
@@ -274,7 +295,43 @@
}
private boolean isMuted(int streamType) {
- return mAudioManager.isStreamMute(streamType);
+ if (streamType == STREAM_MASTER) {
+ return mAudioService.isMasterMute();
+ } else {
+ return mAudioService.isStreamMute(streamType);
+ }
+ }
+
+ private int getStreamMaxVolume(int streamType) {
+ if (streamType == STREAM_MASTER) {
+ return mAudioService.getMasterMaxVolume();
+ } else {
+ return mAudioService.getStreamMaxVolume(streamType);
+ }
+ }
+
+ private int getStreamVolume(int streamType) {
+ if (streamType == STREAM_MASTER) {
+ return mAudioService.getMasterVolume();
+ } else {
+ return mAudioService.getStreamVolume(streamType);
+ }
+ }
+
+ private void setStreamVolume(int streamType, int index, int flags) {
+ if (streamType == STREAM_MASTER) {
+ mAudioService.setMasterVolume(index, flags);
+ } else {
+ mAudioService.setStreamVolume(streamType, index, flags);
+ }
+ }
+
+ private int getLastAudibleStreamVolume(int streamType) {
+ if (streamType == STREAM_MASTER) {
+ return mAudioService.getLastAudibleMasterVolume();
+ } else {
+ return mAudioService.getLastAudibleStreamVolume(streamType);
+ }
}
private void createSliders() {
@@ -301,7 +358,7 @@
sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
- sc.seekbarView.setMax(mAudioManager.getStreamMaxVolume(streamType) + plusOne);
+ sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
sc.seekbarView.setOnSeekBarChangeListener(this);
sc.seekbarView.setTag(sc);
mStreamControls.put(streamType, sc);
@@ -342,7 +399,7 @@
/** Update the mute and progress state of a slider */
private void updateSlider(StreamControl sc) {
- sc.seekbarView.setProgress(mAudioManager.getLastAudibleStreamVolume(sc.streamType));
+ sc.seekbarView.setProgress(getLastAudibleStreamVolume(sc.streamType));
final boolean muted = isMuted(sc.streamType);
sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
if (sc.streamType == AudioManager.STREAM_RING && muted
@@ -390,6 +447,23 @@
obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
}
+ public void postMasterVolumeChanged(int flags) {
+ postVolumeChanged(STREAM_MASTER, flags);
+ }
+
+ public void postMuteChanged(int streamType, int flags) {
+ if (hasMessages(MSG_VOLUME_CHANGED)) return;
+ if (mStreamControls == null) {
+ createSliders();
+ }
+ removeMessages(MSG_FREE_RESOURCES);
+ obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
+ }
+
+ public void postMasterMuteChanged(int flags) {
+ postMuteChanged(STREAM_MASTER, flags);
+ }
+
/**
* Override this if you have other work to do when the volume changes (for
* example, vibrating, playing a sound, etc.). Make sure to call through to
@@ -423,10 +497,22 @@
resetTimeout();
}
+ protected void onMuteChanged(int streamType, int flags) {
+
+ if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
+
+ StreamControl sc = mStreamControls.get(streamType);
+ if (sc != null) {
+ sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
+ }
+
+ onVolumeChanged(streamType, flags);
+ }
+
protected void onShowVolumeChanged(int streamType, int flags) {
- int index = mAudioService.isStreamMute(streamType) ?
- mAudioService.getLastAudibleStreamVolume(streamType)
- : mAudioService.getStreamVolume(streamType);
+ int index = isMuted(streamType) ?
+ getLastAudibleStreamVolume(streamType)
+ : getStreamVolume(streamType);
mRingIsSilent = false;
@@ -437,7 +523,7 @@
// get max volume for progress bar
- int max = mAudioService.getStreamMaxVolume(streamType);
+ int max = getStreamMaxVolume(streamType);
switch (streamType) {
@@ -571,6 +657,7 @@
* Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
*/
private ToneGenerator getOrCreateToneGenerator(int streamType) {
+ if (streamType == STREAM_MASTER) return null;
synchronized (this) {
if (mToneGenerators[streamType] == null) {
try {
@@ -620,6 +707,11 @@
break;
}
+ case MSG_MUTE_CHANGED: {
+ onMuteChanged(msg.arg1, msg.arg2);
+ break;
+ }
+
case MSG_FREE_RESOURCES: {
onFreeResources();
break;
@@ -671,8 +763,8 @@
final Object tag = seekBar.getTag();
if (fromUser && tag instanceof StreamControl) {
StreamControl sc = (StreamControl) tag;
- if (mAudioManager.getStreamVolume(sc.streamType) != progress) {
- mAudioManager.setStreamVolume(sc.streamType, progress, 0);
+ if (getStreamVolume(sc.streamType) != progress) {
+ setStreamVolume(sc.streamType, progress, 0);
}
}
resetTimeout();
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 71c5d26..10d5094 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -129,6 +129,7 @@
android_media_ToneGenerator.cpp \
android_hardware_Camera.cpp \
android_hardware_SensorManager.cpp \
+ android_hardware_SerialPort.cpp \
android_hardware_UsbDevice.cpp \
android_hardware_UsbDeviceConnection.cpp \
android_hardware_UsbRequest.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c00e6c9..493aaec 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -76,6 +76,7 @@
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
+extern int register_android_hardware_SerialPort(JNIEnv *env);
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -1165,6 +1166,7 @@
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_SensorManager),
+ REG_JNI(register_android_hardware_SerialPort),
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
diff --git a/core/jni/android_hardware_SerialPort.cpp b/core/jni/android_hardware_SerialPort.cpp
new file mode 100644
index 0000000..7514f48
--- /dev/null
+++ b/core/jni/android_hardware_SerialPort.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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 "SerialPortJNI"
+
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+
+using namespace android;
+
+static jfieldID field_context;
+
+static void
+android_hardware_SerialPort_open(JNIEnv *env, jobject thiz, jobject fileDescriptor, jint speed)
+{
+ switch (speed) {
+ case 50:
+ speed = B50;
+ break;
+ case 75:
+ speed = B75;
+ break;
+ case 110:
+ speed = B110;
+ break;
+ case 134:
+ speed = B134;
+ break;
+ case 150:
+ speed = B150;
+ break;
+ case 200:
+ speed = B200;
+ break;
+ case 300:
+ speed = B300;
+ break;
+ case 600:
+ speed = B600;
+ break;
+ case 1200:
+ speed = B1200;
+ break;
+ case 1800:
+ speed = B1800;
+ break;
+ case 2400:
+ speed = B2400;
+ break;
+ case 4800:
+ speed = B4800;
+ break;
+ case 9600:
+ speed = B9600;
+ break;
+ case 19200:
+ speed = B19200;
+ break;
+ case 38400:
+ speed = B38400;
+ break;
+ case 57600:
+ speed = B57600;
+ break;
+ case 115200:
+ speed = B115200;
+ break;
+ case 230400:
+ speed = B230400;
+ break;
+ case 460800:
+ speed = B460800;
+ break;
+ case 500000:
+ speed = B500000;
+ break;
+ case 576000:
+ speed = B576000;
+ break;
+ case 921600:
+ speed = B921600;
+ break;
+ case 1000000:
+ speed = B1000000;
+ break;
+ case 1152000:
+ speed = B1152000;
+ break;
+ case 1500000:
+ speed = B1500000;
+ break;
+ case 2000000:
+ speed = B2000000;
+ break;
+ case 2500000:
+ speed = B2500000;
+ break;
+ case 3000000:
+ speed = B3000000;
+ break;
+ case 3500000:
+ speed = B3500000;
+ break;
+ case 4000000:
+ speed = B4000000;
+ break;
+ default:
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Unsupported serial port speed");
+ return;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ // duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy
+ fd = dup(fd);
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "Could not open serial port");
+ return;
+ }
+ env->SetIntField(thiz, field_context, fd);
+
+ struct termios tio;
+ if (tcgetattr(fd, &tio))
+ memset(&tio, 0, sizeof(tio));
+
+ tio.c_cflag = speed | CS8 | CLOCAL | CREAD;
+ // Disable output processing, including messing with end-of-line characters.
+ tio.c_oflag &= ~OPOST;
+ tio.c_iflag = IGNPAR;
+ tio.c_lflag = 0; /* turn of CANON, ECHO*, etc */
+ /* no timeout but request at least one character per read */
+ tio.c_cc[VTIME] = 0;
+ tio.c_cc[VMIN] = 1;
+ tcsetattr(fd, TCSANOW, &tio);
+ tcflush(fd, TCIFLUSH);
+}
+
+static void
+android_hardware_SerialPort_close(JNIEnv *env, jobject thiz)
+{
+ int fd = env->GetIntField(thiz, field_context);
+ close(fd);
+ env->SetIntField(thiz, field_context, -1);
+}
+
+static jint
+android_hardware_SerialPort_read_array(JNIEnv *env, jobject thiz, jbyteArray buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+ jbyte* buf = (jbyte *)malloc(length);
+ if (!buf) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return -1;
+ }
+
+ int ret = read(fd, buf, length);
+ if (ret > 0) {
+ // copy data from native buffer to Java buffer
+ env->SetByteArrayRegion(buffer, 0, ret, buf);
+ }
+
+ free(buf);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+ return ret;
+}
+
+static jint
+android_hardware_SerialPort_read_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+
+ jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);
+ if (!buf) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");
+ return -1;
+ }
+
+ int ret = read(fd, buf, length);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+ return ret;
+}
+
+static void
+android_hardware_SerialPort_write_array(JNIEnv *env, jobject thiz, jbyteArray buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+ jbyte* buf = (jbyte *)malloc(length);
+ if (!buf) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return;
+ }
+ env->GetByteArrayRegion(buffer, 0, length, buf);
+
+ jint ret = write(fd, buf, length);
+ free(buf);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+}
+
+static void
+android_hardware_SerialPort_write_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+
+ jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);
+ if (!buf) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");
+ return;
+ }
+ int ret = write(fd, buf, length);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+}
+
+static void
+android_hardware_SerialPort_send_break(JNIEnv *env, jobject thiz)
+{
+ int fd = env->GetIntField(thiz, field_context);
+ tcsendbreak(fd, 0);
+}
+
+static JNINativeMethod method_table[] = {
+ {"native_open", "(Ljava/io/FileDescriptor;I)V",
+ (void *)android_hardware_SerialPort_open},
+ {"native_close", "()V", (void *)android_hardware_SerialPort_close},
+ {"native_read_array", "([BI)I",
+ (void *)android_hardware_SerialPort_read_array},
+ {"native_read_direct", "(Ljava/nio/ByteBuffer;I)I",
+ (void *)android_hardware_SerialPort_read_direct},
+ {"native_write_array", "([BI)V",
+ (void *)android_hardware_SerialPort_write_array},
+ {"native_write_direct", "(Ljava/nio/ByteBuffer;I)V",
+ (void *)android_hardware_SerialPort_write_direct},
+ {"native_send_break", "()V", (void *)android_hardware_SerialPort_send_break},
+};
+
+int register_android_hardware_SerialPort(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("android/hardware/SerialPort");
+ if (clazz == NULL) {
+ LOGE("Can't find android/hardware/SerialPort");
+ return -1;
+ }
+ field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+ if (field_context == NULL) {
+ LOGE("Can't find SerialPort.mNativeContext");
+ return -1;
+ }
+
+ return AndroidRuntime::registerNativeMethods(env, "android/hardware/SerialPort",
+ method_table, NELEM(method_table));
+}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 0c5101f..18cb1f0 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -198,6 +198,38 @@
return index;
}
+static int
+android_media_AudioSystem_setMasterVolume(JNIEnv *env, jobject thiz, jfloat value)
+{
+ return check_AudioSystem_Command(AudioSystem::setMasterVolume(value));
+}
+
+static jfloat
+android_media_AudioSystem_getMasterVolume(JNIEnv *env, jobject thiz)
+{
+ float value;
+ if (AudioSystem::getMasterVolume(&value) != NO_ERROR) {
+ value = -1.0;
+ }
+ return value;
+}
+
+static int
+android_media_AudioSystem_setMasterMute(JNIEnv *env, jobject thiz, jboolean mute)
+{
+ return check_AudioSystem_Command(AudioSystem::setMasterMute(mute));
+}
+
+static jfloat
+android_media_AudioSystem_getMasterMute(JNIEnv *env, jobject thiz)
+{
+ bool mute;
+ if (AudioSystem::getMasterMute(&mute) != NO_ERROR) {
+ mute = false;
+ }
+ return mute;
+}
+
static jint
android_media_AudioSystem_getDevicesForStream(JNIEnv *env, jobject thiz, jint stream)
{
@@ -221,6 +253,10 @@
{"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume},
{"setStreamVolumeIndex","(II)I", (void *)android_media_AudioSystem_setStreamVolumeIndex},
{"getStreamVolumeIndex","(I)I", (void *)android_media_AudioSystem_getStreamVolumeIndex},
+ {"setMasterVolume", "(F)I", (void *)android_media_AudioSystem_setMasterVolume},
+ {"getMasterVolume", "()F", (void *)android_media_AudioSystem_getMasterVolume},
+ {"setMasterMute", "(Z)I", (void *)android_media_AudioSystem_setMasterMute},
+ {"getMasterMute", "()Z", (void *)android_media_AudioSystem_getMasterMute},
{"getDevicesForStream", "(I)I", (void *)android_media_AudioSystem_getDevicesForStream},
};
diff --git a/core/jni/android_view_Display.cpp b/core/jni/android_view_Display.cpp
index 366a52e..f076cc8 100644
--- a/core/jni/android_view_Display.cpp
+++ b/core/jni/android_view_Display.cpp
@@ -28,6 +28,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <utils/misc.h>
#include <utils/Log.h>
+#include <cutils/properties.h>
// ----------------------------------------------------------------------------
@@ -44,6 +45,7 @@
jfieldID ydpi;
};
static offsets_t offsets;
+static bool headless = false;
// ----------------------------------------------------------------------------
@@ -51,10 +53,19 @@
JNIEnv* env, jobject clazz, jint dpy)
{
DisplayInfo info;
- status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
- if (err < 0) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- return;
+ if (headless) {
+ // initialize dummy display with reasonable values
+ info.pixelFormatInfo.format = 1; // RGB_8888
+ info.fps = 60;
+ info.density = 160;
+ info.xdpi = 160;
+ info.ydpi = 160;
+ } else {
+ status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
+ if (err < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
}
env->SetIntField(clazz, offsets.pixelFormat,info.pixelFormatInfo.format);
env->SetFloatField(clazz, offsets.fps, info.fps);
@@ -66,6 +77,7 @@
static jint android_view_Display_getRawWidthNative(
JNIEnv* env, jobject clazz)
{
+ if (headless) return 640;
DisplayID dpy = env->GetIntField(clazz, offsets.display);
return SurfaceComposerClient::getDisplayWidth(dpy);
}
@@ -73,6 +85,7 @@
static jint android_view_Display_getRawHeightNative(
JNIEnv* env, jobject clazz)
{
+ if (headless) return 480;
DisplayID dpy = env->GetIntField(clazz, offsets.display);
return SurfaceComposerClient::getDisplayHeight(dpy);
}
@@ -80,6 +93,7 @@
static jint android_view_Display_getOrientation(
JNIEnv* env, jobject clazz)
{
+ if (headless) return 0; // Surface.ROTATION_0
DisplayID dpy = env->GetIntField(clazz, offsets.display);
return SurfaceComposerClient::getDisplayOrientation(dpy);
}
@@ -87,6 +101,7 @@
static jint android_view_Display_getDisplayCount(
JNIEnv* env, jclass clazz)
{
+ if (headless) return 1;
return SurfaceComposerClient::getNumberOfDisplays();
}
@@ -113,6 +128,12 @@
void nativeClassInit(JNIEnv* env, jclass clazz)
{
+ char value[PROPERTY_VALUE_MAX];
+
+ property_get("ro.config.headless", value, "0");
+ if (strcmp(value, "1") == 0)
+ headless = true;
+
offsets.display = env->GetFieldID(clazz, "mDisplay", "I");
offsets.pixelFormat = env->GetFieldID(clazz, "mPixelFormat", "I");
offsets.fps = env->GetFieldID(clazz, "mRefreshRate", "F");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 97658a1..51f5804 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -121,6 +121,7 @@
<protected-broadcast android:name="android.intent.action.CLEAR_DNS_CACHE" />
<protected-broadcast android:name="android.intent.action.PROXY_CHANGE" />
+ <protected-broadcast android:name="android.os.UpdateLock.UPDATE_LOCK_CHANGED" />
<!-- ====================================== -->
<!-- Permissions for things that cost money -->
@@ -1507,6 +1508,21 @@
android:description="@string/permdesc_bindPackageVerifier"
android:protectionLevel="signature" />
+ <!-- Allows applications to access serial ports via the SerialManager.
+ @hide -->
+ <permission android:name="android.permission.SERIAL_PORT"
+ android:label="@string/permlab_serialPort"
+ android:description="@string/permdesc_serialPort"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to hold an UpdateLock, recommending that a headless
+ OTA reboot *not* occur while the lock is held.
+ @hide -->
+ <permission android:name="android.permission.UPDATE_LOCK"
+ android:label="@string/permlab_updateLock"
+ android:description="@string/permdesc_updateLock"
+ android:protectionLevel="signatureOrSystem" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 86365917..336db61 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="UNIT">%2$s</xliff:g><xliff:g id="NUMBER">%1$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<ongetiteld>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -539,30 +539,30 @@
<item msgid="1735177144948329370">"Tuisfaks"</item>
<item msgid="603878674477207394">"Roeper"</item>
<item msgid="1650824275177931637">"Ander"</item>
- <item msgid="9192514806975898961">"Gepasmaakte"</item>
+ <item msgid="9192514806975898961">"Gepasmaak"</item>
</string-array>
<string-array name="emailAddressTypes">
<item msgid="8073994352956129127">"Tuis"</item>
<item msgid="7084237356602625604">"Werk"</item>
<item msgid="1112044410659011023">"Ander"</item>
- <item msgid="2374913952870110618">"Gepasmaakte"</item>
+ <item msgid="2374913952870110618">"Gepasmaak"</item>
</string-array>
<string-array name="postalAddressTypes">
<item msgid="6880257626740047286">"Tuis"</item>
<item msgid="5629153956045109251">"Werk"</item>
<item msgid="4966604264500343469">"Ander"</item>
- <item msgid="4932682847595299369">"Gepasmaakte"</item>
+ <item msgid="4932682847595299369">"Gepasmaak"</item>
</string-array>
<string-array name="imAddressTypes">
<item msgid="1738585194601476694">"Tuis"</item>
<item msgid="1359644565647383708">"Werk"</item>
<item msgid="7868549401053615677">"Ander"</item>
- <item msgid="3145118944639869809">"Gepasmaakte"</item>
+ <item msgid="3145118944639869809">"Gepasmaak"</item>
</string-array>
<string-array name="organizationTypes">
<item msgid="7546335612189115615">"Werk"</item>
<item msgid="4378074129049520373">"Ander"</item>
- <item msgid="3455047468583965104">"Gepasmaakte"</item>
+ <item msgid="3455047468583965104">"Gepasmaak"</item>
</string-array>
<string-array name="imProtocols">
<item msgid="8595261363518459565">"AIM"</item>
@@ -574,7 +574,7 @@
<item msgid="2506857312718630823">"ICQ"</item>
<item msgid="1648797903785279353">"Jabber"</item>
</string-array>
- <string name="phoneTypeCustom" msgid="1644738059053355820">"Gepasmaakte"</string>
+ <string name="phoneTypeCustom" msgid="1644738059053355820">"Gepasmaak"</string>
<string name="phoneTypeHome" msgid="2570923463033985887">"Tuis"</string>
<string name="phoneTypeMobile" msgid="6501463557754751037">"Mobiel"</string>
<string name="phoneTypeWork" msgid="8863939667059911633">"Werk"</string>
@@ -595,24 +595,24 @@
<string name="phoneTypeWorkPager" msgid="649938731231157056">"Werkroeper"</string>
<string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistent"</string>
<string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
- <string name="eventTypeCustom" msgid="7837586198458073404">"Gepasmaakte"</string>
+ <string name="eventTypeCustom" msgid="7837586198458073404">"Gepasmaak"</string>
<string name="eventTypeBirthday" msgid="2813379844211390740">"Verjaardag"</string>
<string name="eventTypeAnniversary" msgid="3876779744518284000">"Herdenking"</string>
<string name="eventTypeOther" msgid="7388178939010143077">"Ander"</string>
- <string name="emailTypeCustom" msgid="8525960257804213846">"Gepasmaakte"</string>
+ <string name="emailTypeCustom" msgid="8525960257804213846">"Gepasmaak"</string>
<string name="emailTypeHome" msgid="449227236140433919">"Tuis"</string>
<string name="emailTypeWork" msgid="3548058059601149973">"Werk"</string>
<string name="emailTypeOther" msgid="2923008695272639549">"Ander"</string>
<string name="emailTypeMobile" msgid="119919005321166205">"Mobiel"</string>
- <string name="postalTypeCustom" msgid="8903206903060479902">"Gepasmaakte"</string>
+ <string name="postalTypeCustom" msgid="8903206903060479902">"Gepasmaak"</string>
<string name="postalTypeHome" msgid="8165756977184483097">"Tuis"</string>
<string name="postalTypeWork" msgid="5268172772387694495">"Werk"</string>
<string name="postalTypeOther" msgid="2726111966623584341">"Ander"</string>
- <string name="imTypeCustom" msgid="2074028755527826046">"Gepasmaakte"</string>
+ <string name="imTypeCustom" msgid="2074028755527826046">"Gepasmaak"</string>
<string name="imTypeHome" msgid="6241181032954263892">"Tuis"</string>
<string name="imTypeWork" msgid="1371489290242433090">"Werk"</string>
<string name="imTypeOther" msgid="5377007495735915478">"Ander"</string>
- <string name="imProtocolCustom" msgid="6919453836618749992">"Gepasmaakte"</string>
+ <string name="imProtocolCustom" msgid="6919453836618749992">"Gepasmaak"</string>
<string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string>
<string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string>
<string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string>
@@ -624,8 +624,8 @@
<string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string>
<string name="orgTypeWork" msgid="29268870505363872">"Werk"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Ander"</string>
- <string name="orgTypeCustom" msgid="225523415372088322">"Gepasmaakte"</string>
- <string name="relationTypeCustom" msgid="3542403679827297300">"Gepasmaakte"</string>
+ <string name="orgTypeCustom" msgid="225523415372088322">"Gepasmaak"</string>
+ <string name="relationTypeCustom" msgid="3542403679827297300">"Gepasmaak"</string>
<string name="relationTypeAssistant" msgid="6274334825195379076">"Assistent"</string>
<string name="relationTypeBrother" msgid="8757913506784067713">"Broer"</string>
<string name="relationTypeChild" msgid="1890746277276881626">"Kind"</string>
@@ -640,7 +640,7 @@
<string name="relationTypeRelative" msgid="1799819930085610271">"Familielid"</string>
<string name="relationTypeSister" msgid="1735983554479076481">"Suster"</string>
<string name="relationTypeSpouse" msgid="394136939428698117">"Eggenoot"</string>
- <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Gepasmaakte"</string>
+ <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Gepasmaak"</string>
<string name="sipAddressTypeHome" msgid="6093598181069359295">"Tuis"</string>
<string name="sipAddressTypeWork" msgid="6920725730797099047">"Werk"</string>
<string name="sipAddressTypeOther" msgid="4408436162950119849">"Ander"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Jammer, probeer weer"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Laai, (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Gehef."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Koppel jou herlaaier."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Geen SIM-kaart."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Geen SIM-kaart in tablet nie."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabriektoets het gefaal"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Die FACTORY_TEST-handeling word net ondersteun vir pakkette wat in /system/app geïnstalleer is."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Geen pakket is gevind wat die FACTORY_TEST-handeling bied nie."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Wenk: dubbeltik om in en uit te zoem."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Outo-invul"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Stel outo-invul op"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Laat die program toe om te verifieer of \'n pakkie installeerbaar is."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"bind aan \'n pakkieverifieerder"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Laat die houer toe om versoeke te rig aan pakkieverifieerders. Behoort nooit nodig te wees vir normale programme nie."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"kry toegang tot reekspoorte"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Laat die houer toe om toegang te verkry tot reekspoorte wat die SerialManager API gebruik."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"ontmoedig outomatiese toestelopdaterings"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Laat die houer toe om inligting aan die stelsel te bied oor wanneer \'n goeie tyd vir \'n nie-interaktiewe herselflaai sal wees om die toestel op te gradeer."</string>
<string name="save_password_message" msgid="767344687139195790">"Wil jy hê die blaaier moet hierdie wagwoord onthou?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nie nou nie"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Onthou"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 1a511be..59a65d2 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<ርዕስ አልባ>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -222,7 +222,7 @@
<string name="permlab_enableCarMode" msgid="5684504058192921098">"የመኪና ሁነታ አንቃ"</string>
<string name="permdesc_enableCarMode" msgid="5673461159384850628">"ትግበራ የመኪና ሁነታ ለማንቃት ይፈቅዳል።"</string>
<string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"የዳራ ሂደትን አቁም"</string>
- <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"ትግበራ ምንም እንኳ ማህደረ ትውስታ አነስተኛ ባይሆንም ሌላ ትግበራዎች የዳራ ሂደታቸውን ለማቆም ይፈቅዳል።"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"ትግበራ ምንም እንኳን ማህደረ ትውስታ አነስተኛ ባይሆንም ሌላ ትግበራዎች የዳራ ሂደታቸውን ለማቆም ይፈቅዳል።"</string>
<string name="permlab_forceStopPackages" msgid="1447830113260156236">"ሌላ ትግበራዎችን በኃይል አቁም"</string>
<string name="permdesc_forceStopPackages" msgid="7263036616161367402">"ትግበራ ሌሎች ትግበራዎችን በኃይል ለማስቆም ይፈቅዳል።"</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"ትግበራ እንዲዘጋ አስገድድ"</string>
@@ -265,7 +265,7 @@
<string name="permdesc_injectEvents" product="tablet" msgid="7200014808195664505">"ትግበራ የራሱን ግቤት ክስተት(ቁልፎች ተጫን፣ወዘተ)ለሌላ ትግበራዎች ለማድረስ ይፈቅዳል። ተንኮል አዘል ትግበራዎች ስልኩን ለመቆጣጠር ይህን መጠቀም ይችላሉ።"</string>
<string name="permdesc_injectEvents" product="default" msgid="3946098050410874715">"ትግበራ የራሱን ግቤት ክስተት(ቁልፎች ተጫን፣ወዘተ)ለሌላ ትግበራዎች ለማድረስ ይፈቅዳል። ተንኮል አዘል ትግበራዎች ስልኩን ለመቆጣጠር ይህን መጠቀም ይችላሉ።"</string>
<string name="permlab_readInputState" msgid="469428900041249234">"የሚተይቡትን እና የሚወስዱትን እርምጃ ይመዝግቡ"</string>
- <string name="permdesc_readInputState" msgid="5132879321450325445">"ትግበራዎች ከሌላ ትግበራዎች ጋር እንኳ ሲገናኙ የሚጫኑትን ቁልፎች ለመመልከት ይፈቅዳሉ።ለመደበኛ ትግበራዎች በፍፁም አያስፈልግም።"</string>
+ <string name="permdesc_readInputState" msgid="5132879321450325445">"ትግበራዎች ከሌላ ትግበራዎች ጋር እንኳን ሲገናኙ የሚጫኑትን ቁልፎች ለመመልከት ይፈቅዳሉ።ለመደበኛ ትግበራዎች በፍፁም አያስፈልግም።"</string>
<string name="permlab_bindInputMethod" msgid="3360064620230515776">"በግቤት ሜተድ ጠርዝ"</string>
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"ያዡ ግቤት ሜተዱን ወደ ከፍተኛ-ደረጃ በይነገጽ ለመጠረዝ ይፈቅዳል። ለመደበኛ ትግበራዎች በፍፁም አያስፈልግም።"</string>
<string name="permlab_bindTextService" msgid="7358378401915287938">"ለፅሁፍ አገልግሎት አሰረ"</string>
@@ -335,9 +335,9 @@
<string name="permdesc_readProfile" product="default" msgid="6335739730324727203">"ትግበራ በመሣሪያዎ ላይ የተከማቸውን የግል የመገለጫ መረጃ፣ እንደ ስምዎ እና የዕውቂያ መረጃ ለማንበብ ይፈቅዳል። ይህ ማለት ትግበራው እርስዎን ለይቶ የመገለጫ መረጃዎን ለሌሎች መላክ ይችላል።"</string>
<string name="permlab_writeProfile" msgid="4679878325177177400">"የአርስዎ መገለጫ ውሂብ ላይ ይፃፉ"</string>
<string name="permdesc_writeProfile" product="default" msgid="6431297330378229453">"ትግበራ በመሣሪያዎ ላይ የተከማቸውን የግል የመገለጫ መረጃ፣ እንደ ስምዎ እና የዕውቂያ መረጃ ለመለወጥ እና ለማከል ይፈቅዳል። ይህ ማለት ትግበራው እርስዎን ለይቶ የመገለጫ መረጃዎን ለሌሎች መላክ ይችላል።"</string>
- <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"ያንተን ማህበራዊ የውይይት ክፍሎች አንብብ"</string>
- <string name="permdesc_readSocialStream" product="default" msgid="6619997662735851111">"መተግበሪያው ካንተ ጓደኞች ማህበራዊ ዝማኔዎችን እንዲደርስባቸው እና እንዲያመሳስል ይፈቅዳል፡፡ ተንኮል አዘል መተግበሪያዎች ይህን መዳረሻ ባንተና በጓደኞችህ መካከል በማህበራዊ አውታረመረቦች ያሉ የግል ተግባቦቶችን ለመዳረስ ሊጠቀሙበት ይችላሉ፡፡"</string>
- <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"ወደ ያንተ ማህበራዊ የውይይት ክፍሎች ጻፍ"</string>
+ <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"የአንተን ማህበራዊ የውይይት ክፍሎች አንብብ"</string>
+ <string name="permdesc_readSocialStream" product="default" msgid="6619997662735851111">"መተግበሪያው ከአንተ ጓደኞች ማህበራዊ ዝማኔዎችን እንዲደርስባቸው እና እንዲያመሳስል ይፈቅዳል፡፡ ተንኮል አዘል መተግበሪያዎች ይህን መዳረሻ ባንተና በጓደኞችህ መካከል በማህበራዊ አውታረመረቦች ያሉ የግል ተግባቦቶችን ለመዳረስ ሊጠቀሙበት ይችላሉ፡፡"</string>
+ <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"ወደ የአንተ ማህበራዊ የውይይት ክፍሎች ጻፍ"</string>
<string name="permdesc_writeSocialStream" product="default" msgid="2689083745826002521">"መተግበሪያው የጓደኞችህን ማህበራዊ ዝማኔዎችን ለማሳየት ይፈቅዳል፡፡ ተንኮል አዘል መተግበሪያዎች ይህን መዳረሻ ጓደኛ መስለው ለመቅረብ እና የይለፍ ቃልና ሌላ ምስጢራዊ መረጃ እንድትሰጡ ለማድረግ ሊጠቀሙበት ይችላሉ፡፡"</string>
<string name="permlab_readCalendar" msgid="5972727560257612398">"የቀን መቁጠሪያ ክስተቶች ተጨማሪ ሚስጥራዊ መረጃ አንብብ"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="5665520896961671949">"ትግበራ በጡባዊዎ ላይ የተከማቸውን የቀን መቁጠሪያ ክስተቶች በሙሉ አብረው የሚሰሩትንም ሆነ የጓደኞችዎን ጨምሮ ለማንበብ ይፈቅዳል። ይህን ፈቃድ የያዘ ተንኮል አዘል ትግበራ ከባለቤቱ ዕውቅና ውጪ ከቀን አቆጣጠር ላይ የግል መረጃ ማውጣት ይችላል።\'"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"ይቅርታ፣ እንደገና ሞክር"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"ኃይል በመሙላት ላይ፣ <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"ኃይል ሞልቷል።"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"ኃይል መሙያዎን ያያይዙ"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"ምንም SIM ካርድ የለም"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"በጡባዊ ውስጥ ምንም SIM ካርድ የለም።"</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"የፋብሪካሙከራ ተስኗል"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"የፋብሪካ_ ሙከራ ርምጃበ/system/app አካታች ውስጥ የተጫነ ብቻ ተደግፏል።"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"የፋብሪካ_ሙከራ ርምጃ የሚያቀርብምንም አካታች አልተገኘም።"</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"ጠቃሚ ምክር፡ለማጉላት እና ለማሳነስ ነካ ነካ አድርግ።"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"ራስ ሰር ሙላ"</string>
<string name="setup_autofill" msgid="8154593408885654044">"በራስሙላአዋቅር"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">"፣ "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"ትግበራ ፓኬጅ መጫን የሚችል መሆኑን ለማረጋገጥ ይፈቅዳል።"</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"በፓኬጅ አረጋጋጭ የተወሰነ"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"የፓኬጅ አረጋጋጮችን ጥየቃ ለማድረግ ያዡ ይፈቅዳል። ለመደበኛ ትግበራዎች በፍፁም አያስፈልግም።"</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"ተከታታይ ወደቦችን ድረስ"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Allows the holder to access serial ports using the SerialManager API. የተከታታይ አደራጅ APIን በመጠቀም ያዡ የተከታታይ ወደቦችን እንዲደርስ ይፈቅዳል።"</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"ራስ-ሰር የመሳሪያ መሻሻሎች አታበረታታ"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"ያዢው መሳሪያው ለማሻሻል መቼ መስተጋብራዊ ያልሆነ ዳግም ማስጀመር ቢደረግ ጥሩ ጊዜ እንደሆነ መረጃ ለስርዓቱ መረጃ እንዲያቀርብለት ያስችለዋል።"</string>
<string name="save_password_message" msgid="767344687139195790">"አሳሹ ይህን ይለፍ ቃል እንዲያስታወስ ይፈልጋሉ?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"አሁን አይደለም"</string>
<string name="save_password_remember" msgid="6491879678996749466">"አስታውስ"</string>
@@ -1068,8 +1072,8 @@
<string name="ime_action_done" msgid="8971516117910934605">"ተከናውኗል"</string>
<string name="ime_action_previous" msgid="1443550039250105948">"ያለፈው"</string>
<string name="ime_action_default" msgid="2840921885558045721">"አከናውን"</string>
- <string name="dial_number_using" msgid="5789176425167573586">"የደወሉት ቁጥር"\n"<xliff:g id="NUMBER">%s</xliff:g>በመጠቀም ላይ"</string>
- <string name="create_contact_using" msgid="4947405226788104538">\n"በመጠቀም<xliff:g id="NUMBER">%s</xliff:g>ዕውቂያ ፍጠር"</string>
+ <string name="dial_number_using" msgid="5789176425167573586">"<xliff:g id="NUMBER">%s</xliff:g>ን በመጠቀም "\n" ደውል"</string>
+ <string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g>ን በመጠቀም "\n" ዕውቂያ ፍጠር"</string>
<string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"የሚከተለው ወይም ተጨማሪ ትግበራዎች ወደ መለያዎ ለመድረስ አሁን እና ወደፊት ፈቃድ ይጠይቃል።"</string>
<string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"ይህን ጥየቃ መፍቀድ ይፈልጋሉ?"</string>
<string name="grant_permissions_header_text" msgid="2722567482180797717">"የድረስ መጠይቅ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 4532d2f..b5b3e9e 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"غيغابايت"</string>
<string name="terabyteShort" msgid="231613018159186962">"تيرابايت"</string>
<string name="petabyteShort" msgid="5637816680144990219">"بيتابايت"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<بلا عنوان>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"عذرًا، أعد المحاولة"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"جارٍ الشحن، <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"تم الشحن."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"توصيل جهاز الشحن."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"ليس هناك بطاقة SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"ليس هناك بطاقة SIM في الجهاز اللوحي."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ب ت ث"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"أخفق اختبار المصنع"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"إجراء FACTORY_TEST غير متاح سوى للحزم المثبتة في /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"لم يتم العثور على أية حزمة توفر إجراء FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"نصيحة: اضغط مرتين للتكبير والتصغير."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"ملء تلقائي"</string>
<string name="setup_autofill" msgid="8154593408885654044">"إعداد ملء تلقائي"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">"، "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"السماح للتطبيق بالتحقق من إمكانية تثبيت حزمة."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"الالتزام بمحقق حزمة"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"السماح للمالك بإجراء طلبات محققي الحزمة. لن تكون هناك حاجة إليه مع التطبيقات العادية."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"الدخول إلى المنافذ التسلسلية"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"يسمح لحامله بالدخول إلى المنافذ التسلسلية باستخدام واجهة برمجة التطبيقات."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"عدم التوصية بالتحديثات التلقائية للجهاز"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"للسماح للمالك بتقديم معلومات إلى النظام بشأن الوقت المناسب لإجراء إعادة تشغيل غير تفاعلية لترقية الجهاز."</string>
<string name="save_password_message" msgid="767344687139195790">"هل تريد من المتصفح تذكر كلمة المرور هذه؟"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"ليس الآن"</string>
<string name="save_password_remember" msgid="6491879678996749466">"تذكّر"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index be02b34..68b3523 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"Гб"</string>
<string name="terabyteShort" msgid="231613018159186962">"Тб"</string>
<string name="petabyteShort" msgid="5637816680144990219">"Пб"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"запаўняльнік<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"Безназоўны"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Паспрабуйце яшчэ раз"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Зарадка, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Зараджаны."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Падлучыце зарадную прыладу."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Няма SIM-карты."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Няма SIM-карты ў планшэце."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"запаўняльнік<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"запаўняльнік<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Не атрымалася выканаць заводскую праверку"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Дзеянне FACTORY_TEST падтрымліваецца толькі для пакетаў, усталяваных на шляху /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Пакет, які выконвае дзеянне FACTORY_TEST, не знойдзены."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Падказка: націсніце двойчы, каб павялічыць або паменшыць."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Аўтазапаўненне"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Усталёўка аўтазапаўнення"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Дазваляе прыкладанням правяраць магчымасць усталёўкі пакету."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"прывязаць да верыфікатару пакету"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Дазваляе ўладальніку рабіць запыты верыфікатараў пакету. Не патрабуецца для звычайных прыкладанняў."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"атрымаць доступ да паслядоўных партоў"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Дазваляе ўладальніку атрымліваць доступ да паслядоўных партоў з дапамогай API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"адмянiць аўтаматычнае абнаўленне прылад"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Дазваляе ўладальніку адпраўляць у сiстэму звесткi пра тое, калі будзе зручны час для неінтерактыўнага перазапуску ў мэтах абнаўлення прылады."</string>
<string name="save_password_message" msgid="767344687139195790">"Вы хочаце, каб браўзэр запомніў гэты пароль?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Не цяпер"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Запомніць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 20c17af..268c62d 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
<string name="terabyteShort" msgid="231613018159186962">"ТБ"</string>
<string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<неозаглавено>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Съжаляваме, опитайте отново"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Зарежда се, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Зареден."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Свържете зарядното си устройство."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Няма SIM карта."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"В таблета няма SIM карта."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"АБВ"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Фабричният тест не бе успешен"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Действието FACTORY_TEST се поддържа само за пакети, инсталирани в /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Не бе намерен пакет, предоставящ действието FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Съвет: докоснете двукратно, за да увеличите или намалите мащаба."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Автоматично попълване"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Настройка"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Разрешава на приложението да провери дали пакетът може да се инсталира."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"обвързване с верификатор на пакета"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Разрешава на притежателя да прави заявки за верификатори на пакета. Нормалните приложения би трябвало никога да не се нуждаят от това."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"достъп до серийни портове"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Разрешава на притежателя достъп до серийни портове посредством приложния програмен интерфейс (API) SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"без авт. актуализации на устройството"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Разрешава на притежателя да предложи на системата информация за това, кога ще е възможно неинтерактивно рестартиране за надстройване на устройството."</string>
<string name="save_password_message" msgid="767344687139195790">"Искате ли браузърът да запомни тази парола?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Не сега"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Запомняне"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 0ebd8db..d3e4d0d 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<sense títol>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Torna-ho a provar"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"S\'està carregant, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Carregada."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Connecteu el carregador."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"No hi ha cap targeta SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No hi ha cap targeta SIM a la tauleta."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Error a la prova de fàbrica"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"L\'acció FACTORY_TEST només és compatible amb els paquets instal·lats a /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No s\'ha trobat cap paquet que proporcioni l\'acció FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Consell: Piqueu dos cops per ampliar i reduir."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Emplenament automàtic"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Conf. Empl. aut."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Permet que l\'aplicació verifiqui si un paquet es pot instal·lar."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"vincula a un verificador de paquets"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permet que el titular sol·liciti verificadors de paquets. Les aplicacions normals no haurien de necessitar aquest permís."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"accedeix a ports sèrie"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permet que el titular accedeixi a ports sèrie amb l\'API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"no permetis actualitzacions automàtiques del dispositiu"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Permet que el titular ofereixi informació al sistema sobre el moment més adequat per reiniciar el sistema de manera no interactiva per actualitzar-lo."</string>
<string name="save_password_message" msgid="767344687139195790">"Voleu que el navegador recordi aquesta contrasenya?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ara no"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recorda-ho"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 62316f0..1f3eae9 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<bez názvu>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Zkuste to prosím znovu"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Nabíjení, <xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Nabito."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Připojte dobíjecí zařízení."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Není vložena SIM karta."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"V tabletu není karta SIM."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Test továrního nastavení se nezdařil"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Test FACTORY_TEST lze provést pouze u balíčků nainstalovaných ve složce /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nebyl nalezen žádný balíček umožňující test FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dvojitým klepnutím můžete zobrazení přiblížit nebo oddálit."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Aut.vyp."</string>
<string name="setup_autofill" msgid="8154593408885654044">"Nast. aut. vypl."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Umožňuje aplikaci ověřit, zda se dá balíček nainstalovat."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"navázat na ověřovatele balíčků"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Umožňuje držiteli podávat žádosti o ověření balíčků. Běžné aplikace by toto nastavení neměly nikdy potřebovat."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"přístup k sériovým portům"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Umožňuje držiteli přístup k sériovým portům pomocí rozhraní SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"varovat před automatickou aktualizací"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Umožňuje držiteli navrhnout systému informace o vhodné době pro upgrade zařízení neinteraktivním restartováním."</string>
<string name="save_password_message" msgid="767344687139195790">"Chcete, aby si prohlížeč zapamatoval toto heslo?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nyní ne"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamatovat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 7b5215f..d7e9d95 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"Gb"</string>
<string name="terabyteShort" msgid="231613018159186962">"Tb"</string>
<string name="petabyteShort" msgid="5637816680144990219">"Pb"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<uden navn>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -362,7 +362,7 @@
<string name="permdesc_readFrameBuffer" msgid="7530020370469942528">"Tillader, at en applikation læser indholdet fra rammebufferen."</string>
<string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"skift dine lydindstillinger"</string>
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillader, at en applikation ændrer globale lydindstillinger som f.eks. lydstyrke og kanalisering."</string>
- <string name="permlab_recordAudio" msgid="3876049771427466323">"optag lyd"</string>
+ <string name="permlab_recordAudio" msgid="3876049771427466323">"optage lyd"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillader, at en applikation får adgang til lydregistreringsstien."</string>
<string name="permlab_camera" msgid="3616391919559751192">"tag billeder og optag video"</string>
<string name="permdesc_camera" msgid="6004878235852154239">"Tillader, at applikationen tager billeder og optager video med kameraet. Dette giver applikationen mulighed for altid at indsamle de billeder, kameraet ser."</string>
@@ -388,7 +388,7 @@
<string name="permdesc_asec_mount_unmount" msgid="5934375590189368200">"Tillader, at applikationen monterer/demonterer internt lager."</string>
<string name="permlab_asec_rename" msgid="7496633954080472417">"omdøbe internt lager"</string>
<string name="permdesc_asec_rename" msgid="2152829985238876790">"Tillader, at applikationen omdøber internt lager."</string>
- <string name="permlab_vibrate" msgid="7768356019980849603">"kontroller vibrator"</string>
+ <string name="permlab_vibrate" msgid="7768356019980849603">"kontrollere vibrator"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Lader applikationen kontrollere vibratoren."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"kontroller lommelygte"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Tillader, at applikationen kontrollerer lommelygten."</string>
@@ -416,7 +416,7 @@
<string name="permlab_readPhoneState" msgid="2326172951448691631">"læs telefontilstand og identitet"</string>
<string name="permdesc_readPhoneState" msgid="188877305147626781">"Tillader, at applikationen får adgang til enhedens telefonfunktioner. En applikation kan med denne tilladelse registrere denne telefons telefon- og serienummer, om et opkald er aktivt, nummeret som det opkald er forbundet til osv."</string>
<string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"afhold tabletcomputeren fra at gå i dvale"</string>
- <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"afhold telefonen fra at gå i dvale"</string>
+ <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"afholde telefonen fra at gå i dvale"</string>
<string name="permdesc_wakeLock" product="tablet" msgid="4032181488045338551">"Tillader, at en applikation forhindrer tabletcomputeren i at gå i dvale."</string>
<string name="permdesc_wakeLock" product="default" msgid="7584036471227467099">"Tillader, at en applikation forhindrer telefonen i at gå i dvale."</string>
<string name="permlab_devicePower" product="tablet" msgid="2787034722616350417">"tænd eller sluk for tabletcomputeren"</string>
@@ -445,11 +445,11 @@
<string name="permdesc_getAccounts" product="default" msgid="6839262446413155394">"Tillader, at en applikation henter listen over konti, der er kendt af telefonen."</string>
<string name="permlab_authenticateAccounts" msgid="3940505577982882450">"fungerer som en kontogodkender"</string>
<string name="permdesc_authenticateAccounts" msgid="4006839406474208874">"Tillader, at en applikation bruger kontoadministratorens kontogodkendelsesegenskaber, bl.a. oprettelse af konti samt hentning og indstilling af deres adgangskoder."</string>
- <string name="permlab_manageAccounts" msgid="4440380488312204365">"administrer kontolisten"</string>
+ <string name="permlab_manageAccounts" msgid="4440380488312204365">"administrere kontolisten"</string>
<string name="permdesc_manageAccounts" msgid="8804114016661104517">"Tillader, at en applikation foretager handlinger, f.eks. at tilføje og fjerne konti samt slette sin adgangskode."</string>
- <string name="permlab_useCredentials" msgid="6401886092818819856">"brug en kontos godkendelsesoplysninger"</string>
+ <string name="permlab_useCredentials" msgid="6401886092818819856">"bruge en kontos godkendelsesoplysninger"</string>
<string name="permdesc_useCredentials" msgid="7416570544619546974">"Tillader, at en applikation anmoder om godkendelsestokens"</string>
- <string name="permlab_accessNetworkState" msgid="6865575199464405769">"vis netværkstilstand"</string>
+ <string name="permlab_accessNetworkState" msgid="6865575199464405769">"vise netværkstilstand"</string>
<string name="permdesc_accessNetworkState" msgid="558721128707712766">"Tillader, at en applikation viser tilstanden for alle netværk."</string>
<string name="permlab_createNetworkSockets" msgid="9121633680349549585">"fuld internetadgang"</string>
<string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"Tillader, at en applikation opretter netværks-sockets."</string>
@@ -481,11 +481,11 @@
<string name="permdesc_nfc" msgid="9171401851954407226">"Tillader, at en applikation kommunikerer med tags, kort og læsere i Near Field Communication (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="4977406164311535092">"deaktiver tastaturlås"</string>
<string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Tillader, at en applikation deaktiverer tastaturlåsen og al associeret adgangskodesikkerhed. Et legitimt eksempel på dette er, at telefonen deaktiverer tastaturlåsen, når der modtages et indgående telefonopkald, og genaktiverer tastaturlåsen, når opkaldet er afsluttet."</string>
- <string name="permlab_readSyncSettings" msgid="6201810008230503052">"læs indstillinger for synkronisering"</string>
+ <string name="permlab_readSyncSettings" msgid="6201810008230503052">"læse indstillinger for synkronisering"</string>
<string name="permdesc_readSyncSettings" msgid="5315925706353341823">"Tillader, at en applikation læser synkroniseringsindstillingerne, f.eks. om kontakter skal synkroniseres."</string>
- <string name="permlab_writeSyncSettings" msgid="6297138566442486462">"skriv indstillinger for synkronisering"</string>
+ <string name="permlab_writeSyncSettings" msgid="6297138566442486462">"skrive indstillinger for synkronisering"</string>
<string name="permdesc_writeSyncSettings" msgid="2498201614431360044">"Tillader, at en applikation ændrer indstillingerne for synkronisering, f.eks. kontakter skal synkroniseres."</string>
- <string name="permlab_readSyncStats" msgid="7396577451360202448">"læs synkroniseringsstatistikker"</string>
+ <string name="permlab_readSyncStats" msgid="7396577451360202448">"læse synkroniseringsstatistikker"</string>
<string name="permdesc_readSyncStats" msgid="7511448343374465000">"Tillader, at en applikation læser synkroniseringsstatistikker, f.eks. oversigten over forrige synkroniseringer."</string>
<string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"læs abonnerede feeds"</string>
<string name="permdesc_subscribedFeedsRead" msgid="3622200625634207660">"Tillader, at en applikation får detaljer om de aktuelt synkroniserede feeds."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Beklager! Prøv igen"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Oplader, <xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Opladt."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Tilslut din oplader."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Der er ikke noget SIM-kort."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Der er ikke noget SIM-kort i tabletcomputeren."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabrikstest mislykkedes"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Handlingen FACTORY_TEST understøttes kun af pakker installeret i /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Der blev ikke fundet nogen pakke, som leverer handlingen FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dobbeltklik for at zoome ind eller ud."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"AutoFyld"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Opsætning af AutoFyld"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -744,9 +744,9 @@
<string name="autofill_parish" msgid="8202206105468820057">"Sogn"</string>
<string name="autofill_area" msgid="3547409050889952423">"Område"</string>
<string name="autofill_emirate" msgid="2893880978835698818">"Emirat"</string>
- <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"læs browserens oversigt og bogmærker"</string>
+ <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"læse browserens oversigt og bogmærker"</string>
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillader, at applikationen læser alle de webadresser, browseren har besøgt, og alle browserens bogmærker."</string>
- <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriv browserens oversigt og bogmærker"</string>
+ <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skrive i browserens oversigt og bogmærker"</string>
<string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="7193514090469945307">"Tillader, at en applikation ændrer browseroversigten eller bogmærker, der er gemt på din tabletcomputer. Ondsindede applikationer kan bruge dette til at slette eller ændre din browsers data."</string>
<string name="permdesc_writeHistoryBookmarks" product="default" msgid="945571990357114950">"Tillader, at en applikation ændrer browseroversigten eller bogmærker, der er gemt på din telefon. Ondsindede applikationer kan bruge dette til at slette eller ændre din browsers data."</string>
<string name="permlab_setAlarm" msgid="5924401328803615165">"angiv alarm i alarmprogram"</string>
@@ -754,11 +754,15 @@
<string name="permlab_addVoicemail" msgid="5525660026090959044">"tilføj telefonsvarer"</string>
<string name="permdesc_addVoicemail" msgid="4828507394878206682">"Tillader, at applikationen kan føje meddelelser til din telefonsvarers indbakke."</string>
<string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Skift browsertilladelser for geografisk placering"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Giver en applikation tilladelse til at ændre browserens tilladelser for geografisk placering. Skadelige applikationer kan bruge dette til at tillade, at placeringsoplysninger sendes til vilkårlige websteder."</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Giver en applikation tilladelse til at ændre browserens tilladelser for geografisk placering. Skadelige applikationer kan bruge dette til at tillade, at placeringsoplysninger sendes til vilkårlige websites."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"bekræft pakker"</string>
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Tillader, at applikationen bekræfter, at en pakke kan installeres."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"bind til en bekræftelse af pakker"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Giver indehaveren ret til at sende anmodninger om bekræftelse af pakker. Dette bør aldrig være nødvendigt for almindelige applikationer."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"adgang til serielle porte"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Tillader, at indehaveren kan få adgang til serielle porte ved hjælp af SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"undgå automatiske enhedsopdateringer"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Giver indehaveren ret til at give systemet oplysninger om, hvornår det vil være et godt tidspunkt for en ikke-interaktiv genstart for at opgradere enheden."</string>
<string name="save_password_message" msgid="767344687139195790">"Ønsker du, at browseren skal huske denne adgangskode?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ikke nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Husk"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 6550f5b3..25e7481 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<Unbenannt>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -240,9 +240,9 @@
<string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"Broadcast ohne Paket senden"</string>
<string name="permdesc_broadcastPackageRemoved" msgid="3453286591439891260">"Ermöglicht einer App, eine Benachrichtigung zur Entfernung eines Anwendungspakets zu senden. Schädliche Anwendungen können so laufende Anwendungen beenden."</string>
<string name="permlab_broadcastSmsReceived" msgid="5689095009030336593">"per SMS empfangenen Broadcast senden"</string>
- <string name="permdesc_broadcastSmsReceived" msgid="9122419277306740155">"Ermöglicht einer App, eine Benachrichtigung zu senden, dass eine Kurzmitteilung empfangen wurde. Schädliche Anwendungen könnten diese Option verwenden, um den Eingang von Kurzmitteilungen zu erzwingen."</string>
+ <string name="permdesc_broadcastSmsReceived" msgid="9122419277306740155">"Ermöglicht einer App, eine Benachrichtigung zu senden, dass eine Kurzmitteilung empfangen wurde. Schädliche Apps könnten diese Option verwenden, um den Eingang von Kurzmitteilungen zu erzwingen."</string>
<string name="permlab_broadcastWapPush" msgid="3145347413028582371">"von WAP-PUSH empfangenen Broadcast senden"</string>
- <string name="permdesc_broadcastWapPush" msgid="3955303669461378091">"Ermöglicht einer App, eine Benachrichtigung zu senden, dass eine WAP PUSH-Nachricht empfangen wurde. Schädliche Anwendungen könnten diese Option verwenden, um den Erhalt von MMS-Mitteilungen zu erzwingen, oder um unbemerkt den Inhalt einer beliebigen Webseite durch schädliche Inhalte zu ersetzen."</string>
+ <string name="permdesc_broadcastWapPush" msgid="3955303669461378091">"Ermöglicht einer App, eine Benachrichtigung zu senden, dass eine WAP PUSH-Nachricht empfangen wurde. Schädliche Apps könnten diese Option verwenden, um den Erhalt von MMS-Mitteilungen zu erzwingen, oder um unbemerkt den Inhalt einer beliebigen Webseite durch schädliche Inhalte zu ersetzen."</string>
<string name="permlab_setProcessLimit" msgid="2451873664363662666">"Anzahl der laufenden Prozesse beschränken"</string>
<string name="permdesc_setProcessLimit" msgid="7824786028557379539">"Ermöglicht einer App, die maximale Anzahl an laufenden Prozessen zu steuern. Wird nicht für normale Anwendungen benötigt."</string>
<string name="permlab_setAlwaysFinish" msgid="5342837862439543783">"alle Apps im Hintergrund schließen"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Bitte versuchen Sie es erneut."</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Wird geladen... (<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Aufgeladen"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Bitte Ladegerät anschließen"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Keine SIM-Karte"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Keine SIM-Karte im Tablet"</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Werkstest fehlgeschlagen"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Die Aktion FACTORY_TEST wird nur für unter \"/system/app\" gespeicherte Pakete unterstützt."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Es wurden kein Paket mit der Aktion FACTORY_TEST gefunden."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tipp: Zum Heranzoomen und Vergrößern zweimal tippen"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"AutoFill"</string>
<string name="setup_autofill" msgid="8154593408885654044">"AutoFill-Einrichtung"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Ermöglicht der App, zu überprüfen, ob ein Paket installiert werden kann."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"An Paketprüfung binden"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Ermöglicht es dem Inhaber, Anfragen für die Paketprüfung zu senden. Sollte für normale Apps nicht benötigt werden."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"Zugriff auf serielle Schnittstellen"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Ermöglicht dem Inhaber den Zugriff auf serielle Schnittstellen über das SerialManager-API"</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"Automatische Geräte-Updates abweisen"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Ermöglicht dem Inhaber, dem System Informationen zu einem günstigen Zeitpunkt mitzuteilen, an dem ein nicht interaktiver Neustart zur Aktualisierung des Geräts möglich ist"</string>
<string name="save_password_message" msgid="767344687139195790">"Möchten Sie, dass der Browser dieses Passwort speichert?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nicht jetzt"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Speichern"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index e15e2fc..b675cbc 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<χωρίς τίτλο>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Προσπαθήστε αργότερα"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Φόρτιση, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Φορτίστηκε."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Συνδέστε τον φορτιστή."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Δεν υπάρχει κάρτα SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Δεν υπάρχει κάρτα SIM στο tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ΑΒΓ"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Η εργοστασιακή δοκιμή απέτυχε"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Η ενέργεια FACTORY_TEST υποστηρίζεται μόνο για πακέτα που είναι εγκατεστημένα στον κατάλογο /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Δεν βρέθηκε πακέτο που να παρέχει την ενέργεια FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Συμβουλή: διπλό άγγιγμα για μεγέθυνση και σμίκρυνση."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Αυτ.συμπ"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Ρύθμ. Αυτ. συμπ."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Επιτρέπει στην εφαρμογή να επαληθεύσει τη δυνατότητα εγκατάστασης ενός πακέτου."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"δέσμευση με επαλήθευση πακέτου"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Επιτρέπει στον κάτοχο να υποβάλλει ερωτήματα σε προγράμματα επαλήθευσης πακέτου. Δεν απαιτείται για κανονικές εφαρμογές."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"πρόσβαση στις σειριακές θύρες"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Επιτρέπει στον κάτοχο την πρόσβαση στις σειριακές θύρες με τη χρήση του SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"απόρριψη αυτόματων ενημερώσεων συσκευής"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Επιτρέπει στον κάτοχο να παρέχει στο σύστημα πληροφορίες σχετικά με την κατάλληλη ώρα πραγματοποίησης μιας μη διαδραστικής επανεκκίνησης για την αναβάθμιση της συσκευής."</string>
<string name="save_password_message" msgid="767344687139195790">"Θέλετε το πρόγραμμα περιήγησης να διατηρήσει αυτόν τον κωδικό πρόσβασης;"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Να μην γίνει τώρα"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Διατήρηση"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 11c6690..c689c6d 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<untitled>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Sorry, try again"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Charging, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Charged."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Connect your charger."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"No SIM card."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Factory test failed"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"The FACTORY_TEST action is only supported for packages installed in /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No package was found that provides the FACTORY_TEST action."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tip: double-tap to zoom in and out."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Auto-Fill"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Set up Auto-Fill"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Allows the application to verify if a package is installable."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"bind to a package verifier"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Allows the holder to make requests of package verifiers. Should never be needed for normal applications."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"access serial ports"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Allows the holder to access serial ports using the SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"discourage automatic device updates"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Allows the holder to offer information to the system about when would be a good time for a non-interactive reboot to upgrade the device."</string>
<string name="save_password_message" msgid="767344687139195790">"Do you want the browser to remember this password?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Not now"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Remember"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 670cdfa..5ec636f 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<sin título>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Lo sentimos, vuelve a intentarlo"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Cargando <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Cargada."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Conecta tu cargador."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"No hay tarjeta SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No hay tarjeta SIM en el tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Error en la prueba de fábrica"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST se admite solamente en paquetes instalados en /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST ."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Sugerencia: presiona dos veces para acercar o alejar (zoom)"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Autocompl."</string>
<string name="setup_autofill" msgid="8154593408885654044">"Conf func Autocompl"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Permite a la aplicación verificar si se puede instalar un paquete."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"Vincular a un verificador de paquetes"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permite que el titular solicite verificadores de paquetes. Este permiso no debería ser necesario en el caso de las aplicaciones normales."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"Acceder a los puertos serie"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permite acceder a puertos serie a través de la API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"no realizar actualizaciones automáticas"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Permite a su propietario ofrecer información al sistema acerca de cuándo sería adecuado reiniciar el sistema de forma no interactiva y actualizar el dispositivo."</string>
<string name="save_password_message" msgid="767344687139195790">"¿Quieres recordar esta contraseña en el navegador?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ahora no."</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recuerda"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 4691bbe..d1b9aca 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<sin título>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"..."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Inténtalo de nuevo"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Cargado"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Conecta el cargador"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Falta la tarjeta SIM"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No se ha insertado ninguna tarjeta SIM en el tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fallo en la prueba de fábrica"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST solo es compatible con los paquetes instalados en /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Sugerencia: toca dos veces para ampliar o reducir."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Autocompletar"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Config autocomp"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Permite que la aplicación verifique si se puede instalar un paquete."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"enlazar con un detector de paquetes"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permite hacer solicitudes de detectores de paquetes. Las aplicaciones normales no deberían necesitar este permiso."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"acceder a puertos serie"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permite acceder a puertos serie a través de SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"omitir actualizaciones automáticas"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Permite ofrecer información al sistema sobre el momento óptimo para reiniciarse de forma no interactiva y actualizar el dispositivo."</string>
<string name="save_password_message" msgid="767344687139195790">"¿Quieres que el navegador recuerde esta contraseña?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ahora no"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recordar"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 9487ff6..f76d96e 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<pealkirjata>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Vabandust. Proovige uuesti"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Laadimine, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Laetud."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Ühendage laadija."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIM-kaarti pole."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Tahvelarvutis pole SIM-kaarti."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Tehasetest ebaõnnestus"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Toimingut FACTORY_TEST toetatakse ainult kausta \\system\\app installitud pakettide puhul."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Ei leitud ühtegi paketti, mis võimaldaks toimingut FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Vihje: suurendamiseks ja vähendamiseks puudutage kaks korda."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Autom."</string>
<string name="setup_autofill" msgid="8154593408885654044">"Aut. täit. sead."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Võimaldab rakendusel kinnitada, et paketti saab installida."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"sidumine paketi kinnitajaga"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Lubab omanikul teha taotlusi paketi kinnitajate kohta. Tavaliste rakenduste puhul ei tohiks seda kunagi vaja olla."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"juurdepääs jadaportidele"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Võimaldab omanikul SerialManageri API-liidese abil jadaportidele juurde pääseda."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"seadme autom. värskendamiste takistamine"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Võimaldab valdajal pakkuda süsteemile teavet selle kohta, kas on sobiv aeg mitteinteraktiivseks taaskäivitamiseks, et viia seade üle uuele versioonile."</string>
<string name="save_password_message" msgid="767344687139195790">"Kas soovite, et brauser jätaks selle parooli meelde?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Mitte praegu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Pidage meeles"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 8d02fe9..e516b23 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"گیگابایت"</string>
<string name="terabyteShort" msgid="231613018159186962">"ترابایت"</string>
<string name="petabyteShort" msgid="5637816680144990219">"پتابایت"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<بدون عنوان>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"متأسفیم، دوباره امتحان کنید"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"در حال شارژ،<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"شارژ شد."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"شارژر خود را متصل کنید."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"سیم کارت موجود نیست."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"سیم کارت درون رایانه لوحی نیست."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"تست کارخانه انجام نشد"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"عملکرد FACTORY_TEST تنها برای بسته های نصب شده در /system/app پشتیبانی می شود."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"بسته ای یافت نشد که عملکرد FACTORY_TEST را ارائه کند."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"نکته: برای انجام بزرگنمایی مثبت و منفی، دو بار ضربه بزنید."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"تکمیل خودکار"</string>
<string name="setup_autofill" msgid="8154593408885654044">"تنظیم تکمیل خودکار"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">"، "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"به برنامه اجازه میدهد قابل نصب بودن بسته را تأیید کند."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"اتصال به یک تأیید کننده بسته"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"به دارنده اجازه ایجاد درخواست تأییدکنندگان بسته را میدهد. برای برنامههای عادی مورد نیاز نیست."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"دسترسی به درگاههای سریال"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"به دارنده اجازه میدهد با استفاده از SerialManager API به درگاههای سریال دسترسی داشته باشد."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"ترغیب به انجام ندادن بهروزرسانیهای خودکار دستگاه"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"به دارنده اجازه میدهد اطلاعاتی در مورد زمان مناسب برای یک راهاندازی مجدد غیرتعاملی جهت ارتقای دستگاه را به سیستم ارائه دهد."</string>
<string name="save_password_message" msgid="767344687139195790">"می خواهید مرورگر این رمز ورود را به خاطر داشته باشد؟"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"اکنون خیر"</string>
<string name="save_password_remember" msgid="6491879678996749466">"به خاطر سپردن"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 97f4ab1..e2c2640 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"Gt"</string>
<string name="terabyteShort" msgid="231613018159186962">"Tt"</string>
<string name="petabyteShort" msgid="5637816680144990219">"Pt"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<nimetön>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Yritä uudelleen"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Ladataan (<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Muutettu."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Kytke laturi."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Ei SIM-korttia."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Tablet-laitteessa ei ole SIM-korttia."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Tehdastesti epäonnistui"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST-toimintoa tuetaan vain paketeille, jotka on tallennettu kansioon /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST-toiminnon tarjoavaa pakettia ei löytynyt."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Vinkki: lähennä ja loitonna kaksoisnapauttamalla"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Automaattinen täyttö"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Autom. täyttö"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Antaa sovelluksen vahvistaa pakkauksen olevan asennettavissa."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"sitoudu paketin vahvistajaan"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Sovellus voi tehdä pakettien vahvistuspyyntöjä. Ei tavallisten sovelluksien käyttöön."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"käytä sarjaportteja"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Luvan haltija voi käyttää sarjaportteja SerialManager-sovellusliittymän avulla."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"estä automaattiset laitteen päivitykset"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Antaa luvan haltijan ilmoittaa järjestelmälle, milloin on hyvä aika suorittaa laitteen päivitys."</string>
<string name="save_password_message" msgid="767344687139195790">"Haluatko selaimen muistavan tämän salasanan?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ei nyt"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Muista"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3fe492e..b3f0511 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"Go"</string>
<string name="terabyteShort" msgid="231613018159186962">"To"</string>
<string name="petabyteShort" msgid="5637816680144990219">"Po"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<sans titre>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Incorrect. Merci de réessayer."</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"En charge (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Chargé"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Branchez votre chargeur."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Aucune carte SIM n\'a été trouvée."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Aucune carte SIM n\'est insérée dans la tablette."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Échec du test usine"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"L\'action FACTORY_TEST est uniquement prise en charge pour les paquets de données installés dans in/system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Impossible de trouver un paquet proposant l\'action FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Conseil : Appuyez deux fois pour effectuer un zoom avant ou arrière."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Saisie auto"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Config. saisie auto"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Permet à l\'application de vérifier qu\'un package peut être installé."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"associer à un vérificateur de package"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permet à l\'application d\'effectuer des requêtes de vérificateur de package. Cette autorisation ne doit jamais être utilisée pour les applications standards."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"accéder aux ports série"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permet à l\'application autorisée d\'accéder aux ports série avec l\'API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"déconseiller mises à jour auto appareil"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Permet à l\'application autorisée d\'indiquer au système le moment opportun pour un redémarrage non interactif en vue de la mise à jour de l\'appareil."</string>
<string name="save_password_message" msgid="767344687139195790">"Voulez-vous que le navigateur se souvienne de ce mot de passe ?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Pas maintenant"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Mémoriser"</string>
@@ -854,8 +858,8 @@
<string name="days" msgid="4774547661021344602">"jours"</string>
<string name="hour" msgid="2126771916426189481">"heure"</string>
<string name="hours" msgid="894424005266852993">"heures"</string>
- <string name="minute" msgid="9148878657703769868">"mn"</string>
- <string name="minutes" msgid="5646001005827034509">"mn"</string>
+ <string name="minute" msgid="9148878657703769868">"min"</string>
+ <string name="minutes" msgid="5646001005827034509">"min"</string>
<string name="second" msgid="3184235808021478">"s"</string>
<string name="seconds" msgid="3161515347216589235">"s"</string>
<string name="week" msgid="5617961537173061583">"semaine"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index daf82fb..5b07d8b 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<शीर्षक-रहित>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"क्षमा करें, पुन: प्रयास करें"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"चार्ज हो रही है, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"चार्ज हो चुकी है."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"अपना चार्जर कनेक्ट करें."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"कोई सिम कार्ड नहीं है."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"टेबलेट में कोई सिम कार्ड नहीं है."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"फ़ैक्ट्री परीक्षण विफल"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST क्रिया केवल /system/app में इंस्टॉल किए गए पैकेज के लिए समर्थित है."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"ऐसा कोई पैकेज नहीं मिला था जो FACTORY_TEST कार्रवाई प्रदान करता हो."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"युक्ति: ज़म इन और आउट के लिए दो बार टैप करें."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"स्वतः भरण"</string>
<string name="setup_autofill" msgid="8154593408885654044">"स्वत: भरण सेटअप करें"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"एप्लिकेशन को इंस्टॉल करने योग्य पैकेज सत्यापित करने देता है."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"पैकेज प्रमाणक से आबद्ध करें"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"धारक को पैकेज प्रमाणक के अनुरोध करने की अनुमति देता है. सामान्य एप्लिकेशन के लिए कभी आवश्यक नहीं होना चाहिए."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"सीरियल पोर्ट पर पहुंचें"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"SerialManager API का उपयोग करके धारक को सीरियल पोर्ट पर पहुंच प्रदान करता है."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"स्वचालित उपकरण अपडेट का समर्थन न करें"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"धारक को उपकरण अपग्रेड करने के लिए, गैर-सहभागी रीबूट के ठीक समय के बारे में सिस्टम पर जानकारी प्रस्तुत करने देता है."</string>
<string name="save_password_message" msgid="767344687139195790">"क्या आप चाहते हैं कि ब्राउज़र पासवर्ड को याद रखे?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"अभी नहीं"</string>
<string name="save_password_remember" msgid="6491879678996749466">"याद रखें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index a945aa0..6627069 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<bez naslova>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Pokušajte ponovno"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Punjenje, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Napunjeno."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Priključite punjač."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nema SIM kartice."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"U tabletnom uređaju nema SIM kartice."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Tvorničko testiranje nije uspjelo"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Radnja FACTORY_TEST podržana je samo za pakete instalirane na /sustavu/aplikaciji."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nije pronađen paket koji sadrži radnju FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Savjet: Dvaput dotaknite za povećanje i smanjivanje."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Automatsko popunjavanje"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Postavi autopop."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Aplikaciji omogućuje da provjeri je li paket moguće instalirati."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"vezano uz paketnu provjeru"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Nositelju omogućuje da traži paketnu provjeru. Nikad ne bi trebalo biti potrebno za uobičajene aplikacije."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"pristup serijskim priključcima"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Rukovatelju omogućuje pristup serijskim priključcima pomoću značajke SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"odvratiti automatska ažuriranja uređaja"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Omogućuje vlasniku davanje informacija sustavu o pogodnom trenutku za nadogradnju uređaja ponovnim pokretanjem bez interakcije."</string>
<string name="save_password_message" msgid="767344687139195790">"Želite li da preglednik zapamti ovu zaporku?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne sada"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamti"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 88a814d..8976168 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<névtelen>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Sajnáljuk, próbálja újra"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Töltés (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Feltöltve."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Csatlakoztassa a töltőt."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nincs SIM-kártya."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Nincs SIM-kártya a táblagépben."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"A gyári teszt sikertelen"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"A FACTORY_TEST művelet csak a /system/app könyvtárba telepített csomagok esetében használható."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nem található olyan csomag, amely tartalmazná a FACTORY_TEST műveletet."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tipp: érintse meg kétszer a nagyításhoz és kicsinyítéshez."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Automatikus kitöltés"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Autom. kitöltés"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Lehetővé teszi az alkalmazás számára, hogy ellenőrizze, egy csomag telepíthető-e."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"egy csomaghitelesítőhöz kötődnek"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Lehetővé teszi, hogy a tulajdonos kérelmeket nyújtson be a csomag hitelesítőivel kapcsolatban. Soha nem lehet rá szükség szokásos alkalmazások esetében."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"soros portok elérése"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Lehetővé teszi a tulajdonos számára a soros portok elérését a SerialManager API segítségével."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"automatikus eszközfrissítés ellenzése"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Lehetővé teszi a tulajdonos számára, hogy információkat adjon meg a rendszer számára azzal kapcsolatban, hogy mikor lehet elvégezni egy beavatkozás nélküli újraindítást az eszköz frissítése céljából."</string>
<string name="save_password_message" msgid="767344687139195790">"Szeretné, hogy a böngésző megjegyezze a jelszót?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Most nem"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Megjegyzés"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 35d0008..495da75 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<tanpa judul>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -63,10 +63,10 @@
<string name="RuacMmi" msgid="7827887459138308886">"Penolakan panggilan yang tidak diinginkan"</string>
<string name="CndMmi" msgid="3116446237081575808">"Pengiriman nomor panggilan"</string>
<string name="DndMmi" msgid="1265478932418334331">"Jangan ganggu"</string>
- <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"Nomor penelepon bawaan \"dibatasi\". Panggilan selanjutnya: Dibatasi"</string>
- <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"Nomor pengguna bawaan \"dibatasi\". Panggilan selanjutnya: Tidak dibatasi."</string>
- <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"Nomor penelepon bawaan tidak dibatasi. Panggilan selanjutnya: Dibatasi"</string>
- <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"Nomor penelepon bawaan tidak dibatasi. Panggilan selanjutnya: Tidak dibatasi"</string>
+ <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"Nomor penelepon default \"dibatasi\". Panggilan selanjutnya: Dibatasi"</string>
+ <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"Nomor pengguna default \"dibatasi\". Panggilan selanjutnya: Tidak dibatasi."</string>
+ <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"Nomor penelepon default tidak dibatasi. Panggilan selanjutnya: Dibatasi"</string>
+ <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"Nomor penelepon default tidak dibatasi. Panggilan selanjutnya: Tidak dibatasi"</string>
<string name="serviceNotProvisioned" msgid="8614830180508686666">"Layanan tidak diperlengkapi."</string>
<string name="CLIRPermanent" msgid="5460892159398802465">"Setelan nomor penelepon tidak dapat diubah."</string>
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Akses terbatas berubah"</string>
@@ -120,15 +120,15 @@
<string name="httpErrorUnsupportedScheme" msgid="5257172771607996054">"Protokol tidak didukung."</string>
<string name="httpErrorFailedSslHandshake" msgid="3088290300440289771">"Sambungan aman ini tidak dapat dilakukan."</string>
<string name="httpErrorBadUrl" msgid="6088183159988619736">"Laman tidak dapat dibuka karena URL tidak valid."</string>
- <string name="httpErrorFile" msgid="8250549644091165175">"Berkas tidak dapat diakses."</string>
- <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Berkas yang diminta tidak ditemukan."</string>
+ <string name="httpErrorFile" msgid="8250549644091165175">"File tidak dapat diakses."</string>
+ <string name="httpErrorFileNotFound" msgid="5588380756326017105">"File yang diminta tidak ditemukan."</string>
<string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Terlalu banyak permintaan yang diproses. Coba lagi nanti."</string>
<string name="notification_title" msgid="1259940370369187045">"Galat saat masuk untuk <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
<string name="contentServiceSync" msgid="8353523060269335667">"Sinkron"</string>
<string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sinkron"</string>
<string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Terlalu banyak <xliff:g id="CONTENT_TYPE">%s</xliff:g> penghapusan."</string>
- <string name="low_memory" product="tablet" msgid="2292820184396262278">"Penyimpanan tablet penuh! Hapus beberapa berkas untuk menambah ruang kosong."</string>
- <string name="low_memory" product="default" msgid="6632412458436461203">"Penyimpanan di ponsel penuh! Hapus sebagian berkas untuk mendapatkan ruang."</string>
+ <string name="low_memory" product="tablet" msgid="2292820184396262278">"Penyimpanan tablet penuh! Hapus beberapa file untuk menambah ruang kosong."</string>
+ <string name="low_memory" product="default" msgid="6632412458436461203">"Penyimpanan di ponsel penuh! Hapus sebagian file untuk mendapatkan ruang."</string>
<string name="me" msgid="6545696007631404292">"Saya"</string>
<string name="power_dialog" product="tablet" msgid="8545351420865202853">"Opsi tablet"</string>
<string name="power_dialog" product="default" msgid="1319919075463988638">"Opsi telepon"</string>
@@ -291,23 +291,23 @@
<string name="permlab_clearAppUserData" msgid="2192134353540277878">"hapus data aplikasi lain"</string>
<string name="permdesc_clearAppUserData" msgid="7546345080434325456">"Mengizinkan aplikasi menghapus data pengguna."</string>
<string name="permlab_deleteCacheFiles" msgid="1518556602634276725">"hapus tembolok aplikasi lain"</string>
- <string name="permdesc_deleteCacheFiles" msgid="2283074077168165971">"Mengizinkan aplikasi menghapus berkas tembolok."</string>
+ <string name="permdesc_deleteCacheFiles" msgid="2283074077168165971">"Mengizinkan aplikasi menghapus file tembolok."</string>
<string name="permlab_getPackageSize" msgid="4799785352306641460">"ukur ruang penyimpanan aplikasi"</string>
<string name="permdesc_getPackageSize" msgid="5557253039670753437">"Mengizinkan aplikasi menarik kode, data, dan ukuran tembolok"</string>
<string name="permlab_installPackages" msgid="335800214119051089">"pasang aplikasi secara langsung"</string>
<string name="permdesc_installPackages" msgid="526669220850066132">"Mengizinkan aplikasi memasang paket Android baru atau yang diperbarui. Aplikasi hasad dapat menggunakan ini untuk menambahkan aplikasi dengan izin yang kuat secara sembarangan."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"hapus semua data tembolok aplikasi"</string>
- <string name="permdesc_clearAppCache" product="tablet" msgid="3097119797652477973">"Mengizinkan aplikasi mengosongkan penyimpanan tablet dengan menghapus berkas dalam direktori tembolok aplikasi. Akses sangat dibatasi biasannya pada proses sistem."</string>
- <string name="permdesc_clearAppCache" product="default" msgid="7740465694193671402">"Mengizinkan aplikasi mengosongkan penyimpanan ponsel dengan menghapus berkas dalam direktori tembolok aplikasi. Akses sangat terbatas, biasanya pada proses sistem."</string>
+ <string name="permdesc_clearAppCache" product="tablet" msgid="3097119797652477973">"Mengizinkan aplikasi mengosongkan penyimpanan tablet dengan menghapus file dalam direktori tembolok aplikasi. Akses sangat dibatasi biasannya pada proses sistem."</string>
+ <string name="permdesc_clearAppCache" product="default" msgid="7740465694193671402">"Mengizinkan aplikasi mengosongkan penyimpanan ponsel dengan menghapus file dalam direktori tembolok aplikasi. Akses sangat terbatas, biasanya pada proses sistem."</string>
<string name="permlab_movePackage" msgid="728454979946503926">"Pindahkan sumber daya aplikasi"</string>
<string name="permdesc_movePackage" msgid="6323049291923925277">"Mengizinkan aplikasi memindah sumber daya aplikasi dari media internal ke eksternal dan sebaliknya."</string>
<string name="permlab_readLogs" msgid="6615778543198967614">"baca data log sensitif"</string>
- <string name="permdesc_readLogs" product="tablet" msgid="4077356893924755294">"Mengizinkan aplikasi membaca dari berbagai berkas log sistem. Ini memungkinkan aplikasi menemukan informasi umum tentang apa yang Anda lakukan dengan tablet, kemungkinan termasuk informasi pribadi."</string>
- <string name="permdesc_readLogs" product="default" msgid="8896449437464867766">"Mengizinkan aplikasi membaca dari berbagai berkas log sistem. Ini memungkinkan aplikasi menemukan informasi umum tentang apa yang Anda lakukan dengan ponsel, kemungkinan termasuk informasi pribadi."</string>
+ <string name="permdesc_readLogs" product="tablet" msgid="4077356893924755294">"Mengizinkan aplikasi membaca dari berbagai file log sistem. Ini memungkinkan aplikasi menemukan informasi umum tentang apa yang Anda lakukan dengan tablet, kemungkinan termasuk informasi pribadi."</string>
+ <string name="permdesc_readLogs" product="default" msgid="8896449437464867766">"Mengizinkan aplikasi membaca dari berbagai file log sistem. Ini memungkinkan aplikasi menemukan informasi umum tentang apa yang Anda lakukan dengan ponsel, kemungkinan termasuk informasi pribadi."</string>
<string name="permlab_anyCodecForPlayback" msgid="715805555823881818">"menggunakan media pengawasandi apa pun untuk pemutaran"</string>
<string name="permdesc_anyCodecForPlayback" msgid="2101444559995480174">"Mengizinkan aplikasi menggunakan pengawasandi media apa pun yang terpasang guna mengawasandikan media untuk diputar."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"baca/tulis ke sumber daya yang dimiliki oleh diag"</string>
- <string name="permdesc_diagnostic" msgid="3121238373951637049">"Mengizinkan aplikasi membaca dan menulis ke sumber daya yang dimiliki oleh grup diag; misalnya, berkas dalam /dev. Ini berisiko mempengaruhi kestabilan dan keamanan sistem. Ini sebaiknya HANYA digunakan untuk diagnostik khusus perangkat keras oleh pabrik atau operator."</string>
+ <string name="permdesc_diagnostic" msgid="3121238373951637049">"Mengizinkan aplikasi membaca dan menulis ke sumber daya yang dimiliki oleh grup diag; misalnya, file dalam /dev. Ini berisiko mempengaruhi kestabilan dan keamanan sistem. Ini sebaiknya HANYA digunakan untuk diagnostik khusus perangkat keras oleh pabrik atau operator."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"aktifkan atau nonaktifkan komponen aplikasi"</string>
<string name="permdesc_changeComponentState" product="tablet" msgid="4647419365510068321">"Izinkan aplikasi mengubah apakah komponen aplikasi lain diaktifkan atau tidak. Aplikasi berbahaya dapat menggunakan ini untuk menonaktifkan kemampuan tablet penting. Hati-hatilah saat menggunakan izin ini, karena komponen aplikasi tidak akan dapat digunakan, tidak konsisten, atau tidak stabil."</string>
<string name="permdesc_changeComponentState" product="default" msgid="3443473726140080761">"Izinkan aplikasi untuk mengubah apakah komponen aplikasi lain diaktifkan atau tidak. Aplikasi jahat dapat menggunakan ini untuk menonaktifkan kemampuan ponsel yang penting. Hati-hatilah saat menggunakan izin ini karena berpeluang menyebabkan komponen aplikasi menjadi tidak dapat digunakan, tidak konsisten, atau tidak stabil."</string>
@@ -375,7 +375,7 @@
<string name="permdesc_reboot" product="tablet" msgid="4555793623560701557">"Mengizinkan aplikasi memaksa tablet dinyalakan ulang."</string>
<string name="permdesc_reboot" product="default" msgid="7914933292815491782">"Mengizinkan aplikasi memaksa ponsel untuk melakukan reboot."</string>
<string name="permlab_mount_unmount_filesystems" msgid="1761023272170956541">"pasang dan lepas filesystem"</string>
- <string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Mengizinkan aplikasi memuat dan melepas sistem berkas untuk penyimpanan aman."</string>
+ <string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Mengizinkan aplikasi memuat dan melepas sistem file untuk penyimpanan aman."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"format penyimpanan eksternal"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Mengizinkan aplikasi memformat penyimpanan aman."</string>
<string name="permlab_asec_access" msgid="3411338632002193846">"dapatkan informasi pada penyimpanan internal"</string>
@@ -430,7 +430,7 @@
<string name="permdesc_setWallpaper" msgid="6417041752170585837">"Mengizinkan aplikasi mengatur wallpaper sistem."</string>
<string name="permlab_setWallpaperHints" msgid="3600721069353106851">"atur petunjuk ukuran wallpaper"</string>
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Mengizinkan aplikasi mengatur petunjuk ukuran wallpaper sistem."</string>
- <string name="permlab_masterClear" msgid="2315750423139697397">"setel ulang sistem ke setelan bawaan pabrik"</string>
+ <string name="permlab_masterClear" msgid="2315750423139697397">"setel ulang sistem ke setelan default pabrik"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Mengizinkan aplikasi menyetel ulang sistem sepenuhnya ke setelan pabriknya, menghapus semua data, konfigurasi, dan aplikasi yang terpasang."</string>
<string name="permlab_setTime" msgid="2021614829591775646">"atur waktu"</string>
<string name="permdesc_setTime" product="tablet" msgid="209693136361006073">"Izinkan aplikasi mengubah waktu jam pada tablet."</string>
@@ -501,7 +501,7 @@
<string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Mengizinkan aplikasi menulis ke kartu SD."</string>
<string name="permlab_mediaStorageWrite" product="default" msgid="6859839199706879015">"ubah/hapus konten penyimpanan media internal"</string>
<string name="permdesc_mediaStorageWrite" product="default" msgid="8232008512478316233">"Mengizinkan aplikasi mengubah konten penyimpanan media internal."</string>
- <string name="permlab_cache_filesystem" msgid="5656487264819669824">"akses sistem berkas tembolok."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"akses sistem file tembolok."</string>
<string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Mengizinkan aplikasi membaca dan menulis filesystem tembolok."</string>
<string name="permlab_use_sip" msgid="5986952362795870502">"lakukan//terima panggilan internet"</string>
<string name="permdesc_use_sip" msgid="6320376185606661843">"Mengizinkan aplikasi menggunakan layanan SIP melakukan/menerima telepon internet"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Maaf, harap coba lagi"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Mengisi daya, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Terisi."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Hubungkan pengisi daya."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Tidak ada kartu SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Tidak ada kartu SIM dalam tablet."</string>
@@ -690,10 +690,10 @@
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Anda telah salah memasukkan PIN <xliff:g id="NUMBER_0">%d</xliff:g> kali. "\n\n"Coba lagi dalam <xliff:g id="NUMBER_1">%d</xliff:g> detik."</string>
<string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="8687762517114904651">"Anda telah salah menggambar pola pembuka kunci <xliff:g id="NUMBER_0">%d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%d</xliff:g> upaya yang gagal, Anda akan diminta membuka kunci tablet menggunakan info masuk Google."\n\n" Harap masuk lagi dalam <xliff:g id="NUMBER_2">%d</xliff:g> detik."</string>
<string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="3351013842320127827">"Anda telah salah menggambar pola pembuka kunci sebanyak <xliff:g id="NUMBER_0">%d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%d</xliff:g> upaya gagal lagi, Anda akan diminta membuka kunci ponsel menggunakan info masuk Google."\n\n" Harap coba lagi dalam <xliff:g id="NUMBER_2">%d</xliff:g> detik."</string>
- <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="6128106399745755604">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER_0">%d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%d</xliff:g> upaya gagal lagi, tablet akan disetel ulang ke setelan bawaan pabrik dan semua data pengguna hilang."</string>
- <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="8603565142156826565">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER_0">%d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%d</xliff:g> upaya gagal lagi, ponsel akan disetel ulang ke setelan bawaan pabrik dan semua data pengguna hilang."</string>
- <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="280873516493934365">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini tablet akan disetel ulang ke setelan bawaan pabrik."</string>
- <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="3025504721764922246">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini ponsel akan disetel ulang ke setelan bawaan pabrik."</string>
+ <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="6128106399745755604">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER_0">%d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%d</xliff:g> upaya gagal lagi, tablet akan disetel ulang ke setelan default pabrik dan semua data pengguna hilang."</string>
+ <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="8603565142156826565">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER_0">%d</xliff:g> kali. Setelah <xliff:g id="NUMBER_1">%d</xliff:g> upaya gagal lagi, ponsel akan disetel ulang ke setelan default pabrik dan semua data pengguna hilang."</string>
+ <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="280873516493934365">"Anda telah gagal mencoba membuka gembok tablet sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini tablet akan disetel ulang ke setelan default pabrik."</string>
+ <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="3025504721764922246">"Anda telah gagal mencoba membuka gembok ponsel sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Kini ponsel akan disetel ulang ke setelan default pabrik."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Coba lagi dalam <xliff:g id="NUMBER">%d</xliff:g> detik."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Lupa pola?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Pembuka kunci akun"</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Uji pabrik gagal"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Tindakan FACTORY_TEST hanya didukung untuk paket yang terpasang pada /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Tidak ada paket yang memberikan tindakan FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Kiat: ketuk dua kali untuk memperbesar dan memperkecil."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"IsiOtomatis"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Penyiapan IsiOtomatis"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -744,22 +744,26 @@
<string name="autofill_parish" msgid="8202206105468820057">"Kampung"</string>
<string name="autofill_area" msgid="3547409050889952423">"Area"</string>
<string name="autofill_emirate" msgid="2893880978835698818">"Emirat"</string>
- <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"baca riwayat dan bookmark Peramban"</string>
- <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Izinkan aplikasi membaca semua URL yang telah dikunjungi Peramban, dam semua bookmark Peramban."</string>
- <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"tuliskan riwayat dan bookmark Peramban"</string>
- <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="7193514090469945307">"Mengizinkan aplikasi memodifikasi riwayat atau bookmark Peramban yang disimpan pada tablet Anda. Aplikasi berbahaya dapat menggunakan ini untuk menghapus atau mengubah data Peramban."</string>
- <string name="permdesc_writeHistoryBookmarks" product="default" msgid="945571990357114950">"Mengizinkan aplikasi mengubah riwayat atau bookmark Peramban yang tersimpan pada ponsel. Aplikasi hasad dapat menggunakan ini untuk menghapus atau mengubah data Peramban."</string>
+ <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"baca riwayat dan bookmark Browser"</string>
+ <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Izinkan aplikasi membaca semua URL yang telah dikunjungi Browser, dam semua bookmark Browser."</string>
+ <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"tuliskan riwayat dan bookmark Browser"</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="7193514090469945307">"Mengizinkan aplikasi memodifikasi riwayat atau bookmark Browser yang disimpan pada tablet Anda. Aplikasi berbahaya dapat menggunakan ini untuk menghapus atau mengubah data Browser."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="default" msgid="945571990357114950">"Mengizinkan aplikasi mengubah riwayat atau bookmark Browser yang tersimpan pada ponsel. Aplikasi hasad dapat menggunakan ini untuk menghapus atau mengubah data Browser."</string>
<string name="permlab_setAlarm" msgid="5924401328803615165">"setel alarm di jam alarm"</string>
<string name="permdesc_setAlarm" msgid="5966966598149875082">"Perbolehkan aplikasi untuk menyetel alarm di aplikasi jam alarm yang terpasang. Beberapa aplikasi jam alarm tidak dapat menerapkan fitur ini."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"tambahkan kotak pesan"</string>
<string name="permdesc_addVoicemail" msgid="4828507394878206682">"Memungkinkan aplikasi menambahkan pesan ke kotak masuk untuk kotak pesan Anda."</string>
- <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Ubah izin geolokasi Peramban"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Mengizinkan aplikasi mengubah izin geolokasi Peramban. Aplikasi hasad dapat menggunakan ini untuk mengizinkan pengiriman informasi lokasi ke situs web sembarangan."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Ubah izin geolokasi Browser"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Mengizinkan aplikasi mengubah izin geolokasi Browser. Aplikasi hasad dapat menggunakan ini untuk mengizinkan pengiriman informasi lokasi ke situs web sembarangan."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verifikasi paket"</string>
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Mengizinkan aplikasi memverifikasi bahwa suatu paket dapat dipasang."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"mengikat ke pemverifikasi paket"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Mengizinkan pemegangnya mengajukan permintaan pemverifikasian paket. Tidak diperlukan untuk aplikasi normal."</string>
- <string name="save_password_message" msgid="767344687139195790">"Apakah Anda ingin peramban menyimpan sandi ini?"</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"akses port serial"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Memungkinkan pemegangnya mengakses port serial menggunakan API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"menghindari pembaruan perangkat otomatis"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Memungkinkan pemegang untuk menawarkan informasi ke sistem mengenai kapan waktu yang baik bagi pemulaian ulang non-interaktif untuk meningkatkan versi perangkat."</string>
+ <string name="save_password_message" msgid="767344687139195790">"Apakah Anda ingin browser menyimpan sandi ini?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Tidak sekarang"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Ingat"</string>
<string name="save_password_never" msgid="8274330296785855105">"Jangan"</string>
@@ -898,8 +902,8 @@
<string name="capital_on" msgid="1544682755514494298">"HIDUP"</string>
<string name="capital_off" msgid="6815870386972805832">"MATI"</string>
<string name="whichApplication" msgid="4533185947064773386">"Tindakan lengkap menggunakan"</string>
- <string name="alwaysUse" msgid="4583018368000610438">"Gunakan secara bawaan untuk tindakan ini."</string>
- <string name="clearDefaultHintMsg" msgid="4815455344600932173">"Bersihkan bawaan pada Setelan Beranda > Aplikasi > Kelola aplikasi."</string>
+ <string name="alwaysUse" msgid="4583018368000610438">"Gunakan secara default untuk tindakan ini."</string>
+ <string name="clearDefaultHintMsg" msgid="4815455344600932173">"Bersihkan default pada Setelan Beranda > Aplikasi > Kelola aplikasi."</string>
<string name="chooseActivity" msgid="1009246475582238425">"Pilih tindakan"</string>
<string name="chooseUsbActivity" msgid="7892597146032121735">"Pilih sebuah aplikasi untuk perangkat USB"</string>
<string name="noApplications" msgid="1691104391758345586">"Tidak ada aplikasi dapat melakukan tindakan ini."</string>
@@ -949,8 +953,8 @@
<string name="volume_icon_description_incall" msgid="8890073218154543397">"Volume panggilan"</string>
<string name="volume_icon_description_media" msgid="4217311719665194215">"Volume media"</string>
<string name="volume_icon_description_notification" msgid="7044986546477282274">"Volume pemberitahuan"</string>
- <string name="ringtone_default" msgid="3789758980357696936">"Nada dering bawaan"</string>
- <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Nada dering bawaan (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_default" msgid="3789758980357696936">"Nada dering default"</string>
+ <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Nada dering default (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="4440324407807468713">"Senyap"</string>
<string name="ringtone_picker_title" msgid="3515143939175119094">"Nada dering"</string>
<string name="ringtone_unknown" msgid="5477919988701784788">"Nada dering tidak dikenal"</string>
@@ -990,26 +994,26 @@
<string name="time_picker_dialog_title" msgid="8349362623068819295">"Setel waktu"</string>
<string name="date_picker_dialog_title" msgid="5879450659453782278">"Setel tanggal"</string>
<string name="date_time_set" msgid="5777075614321087758">"Setel"</string>
- <string name="default_permission_group" msgid="2690160991405646128">"Bawaan"</string>
+ <string name="default_permission_group" msgid="2690160991405646128">"Default"</string>
<string name="no_permissions" msgid="7283357728219338112">"Tidak perlu izin"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Sembunyikan"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Tampilkan semua"</b></string>
<string name="usb_storage_activity_title" msgid="2399289999608900443">"Penyimpanan Massal USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB terhubung"</string>
- <string name="usb_storage_message" product="nosdcard" msgid="6631094834151575841">"Anda telah terhubung ke komputer melalui USB. Sentuh tombol di bawah jika Anda ingin menyalin berkas antara komputer dan penyimpanan USB Android Anda."</string>
- <string name="usb_storage_message" product="default" msgid="4510858346516069238">"Anda telah terhubung ke komputer melalui USB. Sentuh tombol di bawah jika Anda ingin menyalin berkas antara komputer dan kartu SD Android Anda."</string>
+ <string name="usb_storage_message" product="nosdcard" msgid="6631094834151575841">"Anda telah terhubung ke komputer melalui USB. Sentuh tombol di bawah jika Anda ingin menyalin file antara komputer dan penyimpanan USB Android Anda."</string>
+ <string name="usb_storage_message" product="default" msgid="4510858346516069238">"Anda telah terhubung ke komputer melalui USB. Sentuh tombol di bawah jika Anda ingin menyalin file antara komputer dan kartu SD Android Anda."</string>
<string name="usb_storage_button_mount" msgid="1052259930369508235">"Hidupkan penyimpanan USB"</string>
<string name="usb_storage_error_message" product="nosdcard" msgid="3276413764430468454">"Tidak ada masalah menggunakan penyimpanan USB untuk penyimpanan massal USB."</string>
<string name="usb_storage_error_message" product="default" msgid="120810397713773275">"Tidak ada masalah menggunakan kartu SD untuk penyimpanan massal USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB terhubung"</string>
- <string name="usb_storage_notification_message" msgid="7380082404288219341">"Pilih untuk menyalin berkas ke/dari komputer Anda."</string>
+ <string name="usb_storage_notification_message" msgid="7380082404288219341">"Pilih untuk menyalin file ke/dari komputer Anda."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Matikan penyimpanan USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Pilih untuk mematikan penyimpanan USB."</string>
<string name="usb_storage_stop_title" msgid="660129851708775853">"Penyimpanan USB sedang digunakan"</string>
<string name="usb_storage_stop_message" product="nosdcard" msgid="1368842269463745067">"Sebelum mematikan penyimpanan USB, pastikan Anda telah melepas (“dikeluarkan”) penyimpanan USB Android Anda dari komputer."</string>
<string name="usb_storage_stop_message" product="default" msgid="3613713396426604104">"Sebelum mematikan penyimpanan USB, pastikan bahwa Anda telah melepas (“dikeluarkan”) kartu SD Android dari komputer."</string>
<string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Matikan penyimpanan USB"</string>
- <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Ada masalah ketika mematikan penyimpanan USB. Periksa untuk memastikan bahwa Anda telah melepaskan inang USB, lalu coba sekali lagi."</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Ada masalah ketika mematikan penyimpanan USB. Periksa untuk memastikan bahwa Anda telah melepaskan host USB, lalu coba sekali lagi."</string>
<string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Hidupkan penyimpanan USB"</string>
<string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Jika Anda menghidupkan penyimpanan USB, sebagian aplikasi yang Anda gunakan akan berhenti dan mungkin tidak tersedia sampai Anda mematikan penyimpanan USB."</string>
<string name="dlg_error_title" msgid="7323658469626514207">"Operasi USB gagal"</string>
@@ -1021,7 +1025,7 @@
<string name="usb_notification_message" msgid="4447869605109736382">"Sentuh untuk opsi USB lainnya"</string>
<string name="extmedia_format_title" product="nosdcard" msgid="7980995592595097841">"Format penyimpanan USB"</string>
<string name="extmedia_format_title" product="default" msgid="8663247929551095854">"Format kartu SD"</string>
- <string name="extmedia_format_message" product="nosdcard" msgid="8296908079722897772">"Format penyimpanan USB, menghapus semua berkas yang disimpan di sana? Tindakan tidak dapat diurungkan!"</string>
+ <string name="extmedia_format_message" product="nosdcard" msgid="8296908079722897772">"Format penyimpanan USB, menghapus semua file yang disimpan di sana? Tindakan tidak dapat diurungkan!"</string>
<string name="extmedia_format_message" product="default" msgid="3621369962433523619">"Apakah Anda yakin ingin memformat kartu SD? Semua data pada kartu akan hilang."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Debugging USB terhubung"</string>
@@ -1036,8 +1040,8 @@
<string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Memeriksa galat."</string>
<string name="ext_media_nofs_notification_title" product="nosdcard" msgid="7788040745686229307">"Penyimpanan USB kosong"</string>
<string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Kartu SD kosong"</string>
- <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="8623130522556087311">"Penyimpanan USB kosong atau tidak memiliki sistem berkas yang tidak didukung."</string>
- <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Kartu SD kosong atau memiliki sistem berkas yang tidak didukung."</string>
+ <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="8623130522556087311">"Penyimpanan USB kosong atau tidak memiliki sistem file yang tidak didukung."</string>
+ <string name="ext_media_nofs_notification_message" product="default" msgid="3817704088027829380">"Kartu SD kosong atau memiliki sistem file yang tidak didukung."</string>
<string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="2090046769532713563">"Penyimpanan USB rusak"</string>
<string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"Kartu SD rusak"</string>
<string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="529021299294450667">"Penyimpanan USB rusak. Anda mungkin perlu memformat ulang."</string>
@@ -1057,8 +1061,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Tidak ditemukan aktivitas yang sesuai"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"perbarui statistik penggunaan komponen"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Mengizinkan modifikasi statistik penggunaan komponen yang dikumpulkan. Tidak untuk digunakan pada aplikasi normal."</string>
- <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Mengizinkan penjalanan layanan kontainer bawaan untuk menyalin isi. Tidak untuk penggunaan aplikasi normal."</string>
- <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Mengizinkan penjalanan layanan kontainer bawaan untuk menyalin isi. Tidak untuk penggunaan aplikasi normal."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Mengizinkan penjalanan layanan kontainer default untuk menyalin isi. Tidak untuk penggunaan aplikasi normal."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Mengizinkan penjalanan layanan kontainer default untuk menyalin isi. Tidak untuk penggunaan aplikasi normal."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Ketuk dua kali untuk kontrol perbesar/perkecil"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Galat mengembangkan widget"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Buka"</string>
@@ -1086,8 +1090,8 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN diaktifkan oleh <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="1610714069627824309">"Ketuk untuk mengelola jaringan."</string>
<string name="vpn_text_long" msgid="4907843483284977618">"Tersambung ke <xliff:g id="SESSION">%s</xliff:g>. Ketuk untuk mengelola jaringan."</string>
- <string name="upload_file" msgid="2897957172366730416">"Pilih berkas"</string>
- <string name="no_file_chosen" msgid="6363648562170759465">"Tidak ada berkas yang dipilih"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Pilih file"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Tidak ada file yang dipilih"</string>
<string name="reset" msgid="2448168080964209908">"Setel ulang"</string>
<string name="submit" msgid="1602335572089911941">"Kirim"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Mode mobil diaktifkan"</string>
@@ -1225,6 +1229,6 @@
<string name="status_bar_device_locked" msgid="3092703448690669768">"Perangkat tergembok."</string>
<string name="list_delimeter" msgid="3975117572185494152">", "</string>
<string name="sending" msgid="8715108995741758718">"Mengirim..."</string>
- <string name="launchBrowserDefault" msgid="2057951947297614725">"Luncurkan Peramban?"</string>
+ <string name="launchBrowserDefault" msgid="2057951947297614725">"Luncurkan Browser?"</string>
<string name="SetupCallDefault" msgid="6870275517518479651">"Terima Panggilan?"</string>
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 50b5418..5808bc6 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<senza nome>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Riprova"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"In carica (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Carico."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Collegare il caricabatterie."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nessuna SIM presente."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Nessuna scheda SIM presente nel tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Test di fabbrica non riuscito"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"L\'azione FACTORY_TEST è supportata soltanto per i pacchetti installati in /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nessun pacchetto trovato che fornisca l\'azione FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Suggerimento. Tocca due volte per aumentare/ridurre lo zoom."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Compl. auto"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Compil. automatica"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Consente all\'applicazione di verificare se un pacchetto è installabile."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"associazione a verifica pacchetto"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permette al proprietario di effettuare richieste relative alle verifiche dei pacchetti. Non dovrebbe mai essere necessario per le normali applicazioni."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"accesso alle porte seriali"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permette al proprietario di accedere alle porte seriali utilizzando l\'API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"scoraggiamento aggiorn. automatici disp."</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Consente al titolare di dare al sistema informazioni relative agli orari opportuni per il riavvio non interattivo al fine di eseguire l\'upgrade del dispositivo."</string>
<string name="save_password_message" msgid="767344687139195790">"Memorizzare la password nel browser?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Non ora"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Memorizza"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index afdcb48..6a698f3 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<ללא כותרת>"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"מצטערים, נסה שוב"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"טוען (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"נטען."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"חבר את המטען."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"אין כרטיס SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"אין כרטיס SIM בטבלט."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"אבג"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"בדיקת היצרן נכשלה"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"הפעולה FACTORY_TEST נתמכת רק עבור חבילות שהותקנו ב-/system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"לא נמצאה חבילה המספקת את הפעולה FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"טיפ: הקש פעמיים כדי להתקרב ולהתרחק."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"מילוי אוטומטי"</string>
<string name="setup_autofill" msgid="8154593408885654044">"הגדר מילוי אוטומטי"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"מאפשר ליישום לאמת שחבילה ניתנת להתקנה."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"הכפפה למאמת חבילה"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"מאפשר לבעלים להגיש בקשות של מאמתי חבילות. לא אמור להידרש לעולם ביישומים רגילים."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"גישה ליציאות טוריות"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"מאפשר לבעלים לגשת ליציאות טוריות באמצעות ממשק ה- API של SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"דחה עדכוני מכשיר אוטומטיים"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"מאפשר לבעלים להציע מידע למערכת לגבי זמן מתאים לאתחול מחדש לא אינטראקטיבי לשם שדרוג המכשיר."</string>
<string name="save_password_message" msgid="767344687139195790">"האם ברצונך שהדפדפן יזכור סיסמה זו?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"לא כעת"</string>
<string name="save_password_remember" msgid="6491879678996749466">"זכור"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index a5b98d5..91e459a 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<新規>"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"やり直してください"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"充電しています: <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"充電完了"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"充電してください"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIMカードが挿入されていません"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"タブレット内にSIMカードがありません。"</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"出荷時試験が失敗"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST操作は、/system/appにインストールされたパッケージのみが対象です。"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST操作を行うパッケージは見つかりませんでした。"</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"ヒント: ダブルタップで拡大/縮小できます。"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"自動入力"</string>
<string name="setup_autofill" msgid="8154593408885654044">"自動入力設定"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$3$2$1"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">"、 "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"パッケージがインストール可能かを確認することをアプリケーションに許可します。"</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"パッケージベリファイアにバインド"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"パッケージベリファイアのリクエストを所有者に許可します。通常のアプリケーションにはまったく必要ありません。"</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"シリアルポートへのアクセス"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"SerialManager APIを使用してシリアルポートにアクセスすることを所有者に許可します。"</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"端末の自動更新の抑制"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"非対話型の再起動により端末をアップグレードするのに適したタイミングについて、システムに情報を提供することを権利所有者に許可します。"</string>
<string name="save_password_message" msgid="767344687139195790">"このパスワードをブラウザで保存しますか?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"今は保存しない"</string>
<string name="save_password_remember" msgid="6491879678996749466">"保存"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 38fa392..33b1450 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<제목없음>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"죄송합니다. 다시 시도해 주세요."</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"충전 중(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"충전되었습니다."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"충전기를 연결하세요."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIM 카드가 없습니다."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"태블릿에 SIM 카드가 없습니다."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="AMPM">%P</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>시"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="AMPM">%p</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>시"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"출고 테스트 불합격"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST 작업은 /system/app 디렉토리에 설치된 패키지에 대해서만 지원됩니다."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST 작업을 제공하는 패키지가 없습니다."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"도움말: 축소/확대하려면 두 번 누릅니다."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"자동완성"</string>
<string name="setup_autofill" msgid="8154593408885654044">"자동완성 설정"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$3$2$1"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"애플리케이션이 패키지가 설치 가능한지 확인할 수 있도록 합니다."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"패키지 인증 연결"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"소유자가 패키지 인증을 요청할 수 있도록 합니다. 일반 애플리케이션에는 필요하지 않습니다."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"직렬 포트에 액세스"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"SerialManager API를 사용하여 권한을 가진 프로그램이 직렬 포트에 액세스할 수 있도록 합니다."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"지금은 자동 기기 업데이트를 권장하지 않음"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"기기를 자동으로 다시 부팅하여 업그레이드해도 괜찮은 시간에 대한 정보를 사용자가 시스템에 제공할 수 있도록 허용합니다."</string>
<string name="save_password_message" msgid="767344687139195790">"브라우저에 이 비밀번호를 저장하시겠습니까?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"나중에"</string>
<string name="save_password_remember" msgid="6491879678996749466">"저장"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index c8f42dc..d7a834c 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<be pavadinimo>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Apgailestaujame, bandykite dar kartą"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Įkraunama, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Įkrauta."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Prijunkite kroviklį."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nėra SIM kortelės."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Planšetiniame kompiuteryje nėra SIM kortelės."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Gamyklos bandymas nepavyko"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Veiksmas FACTORY_TEST palaikomas tik paketuose, įdiegtuose /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nerasta paketo, kuris teiktų FACTORY_TEST veiksmą."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Patarimas: bakstelėkite du kartus, kad padidintumėte ar sumažintumėte mastelį."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Automatinis pildymas"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Aut. pild. sąr."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Leidžiama programa patikrinti, ar paketą galima įdiegti."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"susaistyti su paketo tikrinimo programa"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Savininkui leidžiama teikti užklausas patikrinti paketą. Įprastinėms programoms to neturėtų prireikti."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"pasiekti nuosekliuosius prievadus"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Leidžiama savininkui pasiekti nuosekliuosius prievadus naudojant „SerialManager“ API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"Atsisakyti autom. įrenginio atnaujinimų"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Leidžia savininkui pateikti pasiūlymą sistemai dėl tinkamo laiko iš naujo neinteraktyviai įkelti programą, kad būtų naujovinamas įrenginys."</string>
<string name="save_password_message" msgid="767344687139195790">"Ar norite, kad naršyklė atsimintų šį slaptažodį?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne dabar"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Atsiminti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 6c2af5e..927f1e9 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<bez nosaukuma>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Mēģiniet vēlreiz"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Notiek uzlāde (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Uzlādēts."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Pievienojiet uzlādes ierīci."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nav SIM kartes."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Planšetdatorā nav SIM kartes."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> (<xliff:g id="AMPM">%P</xliff:g>)"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> (<xliff:g id="AMPM">%p</xliff:g>)"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Rūpnīcas pārbaude neizdevās"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Darbība FACTORY_TEST tiek atbalstīta tikai pakotnēm, kas ir instalētas šeit: /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Netika atrasts neviena pakotne, kas nodrošina darbību FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Padoms: divreiz pieskarieties, lai tuvinātu un tālinātu."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Aizpildīt automātiski"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Iest. aut. aizp."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Ļauj lietojumprogrammai verificēt, vai pakotne ir instalējama."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"saistīšana ar pakotnes verificētāju"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Atļauj īpašniekam sūtīt pakotņu verificētāju pieprasījumus. Nekad netiek izmantots parastām lietojumprogrammām."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"piekļuve seriālajiem portiem"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Ļauj īpašniekam piekļūt seriālajiem portiem, izmantojot SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"neatļaut automāt. ierīces atjauninājumus"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Ļauj īpašniekam piedāvāt sistēmai vēlamo laiku, kad veikt neinteraktīvu atsāknēšanu, lai jauninātu ierīci."</string>
<string name="save_password_message" msgid="767344687139195790">"Vai vēlaties, lai pārlūkprogrammā tiktu saglabāta šī parole?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne tagad"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Atcerēties"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 005fd6e..de1c82f 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<tidak bertajuk>"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Maaf, cuba lagi"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Mengecas, (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Sudah dicas."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Sambungkan pengecas anda."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Tiada kad SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Tiada kad SIM dalam tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Ujian kilang gagal"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Tindakan FACTORY_TEST hanya disokong untuk pakej yang dipasangkan dalam /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Tiada pakej yang menyediakan tindakan FACTORY_TEST ditemui."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Petua: ketik dua kali untuk mengezum masuk dan keluar."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"AutoIsi"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Sediakan AutoIsi"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Membenarkan aplikasi untuk mengesahkan bahawa pakej boleh dipasang."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"ikat kepada pengesah pakej"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Membenarkan pemegang membuat permintaan pengesah pakej. Tidak akan diperlukan untuk aplikasi normal."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"akses port bersiri"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Membenarkan pemegang mengakses port bersiri menggunakan API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"tidak menggalakkan kemas kini peranti automatik"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Membenarkan aplikasi untuk menawarkan maklumat kepada sistem tentang bila akan menjadi masa yang baik untuk but semula bukan interaktif untuk menaik taraf peranti."</string>
<string name="save_password_message" msgid="767344687139195790">"Adakah anda mahu penyemak imbas mengingati kata laluan ini?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Bukan sekarang"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Ingat"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 4773ee5..da5dfec 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<uten navn>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Prøv igjen"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Lader, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Fullt ladet"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Koble til en batterilader."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Mangler SIM-kort."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Nettbrettet mangler SIM-kort."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabrikktesten feilet"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"The FACTORY_TEST action is only supported for packages installed in /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No package was found that provides the FACTORY_TEST action."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Dobbelttrykk for å zoome inn og ut."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Autofyll"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Konfig. autofyll"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Lar appen bekrefte om en pakke kan installeres."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"bind til en pakkeverifikator"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Gir innehaveren rett til å rette forespørsler mot pakkeverifikatorer. Bør aldri være nødvendig for normale applikasjoner."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"tilgang til serielle porter"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Gir innehaveren tilgang til serielle porter ved hjelp av SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"unngå automatiske enhetsoppdateringer"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Lar innehaveren informere systemet om gunstige tidspunkter for ikke-interaktiv omstart for oppgradering av enheten."</string>
<string name="save_password_message" msgid="767344687139195790">"Ønsker du at nettleseren skal huske dette passordet?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ikke nå"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Husk"</string>
@@ -1061,7 +1065,7 @@
<string name="permdesc_copyProtectedData" msgid="537780957633976401">"Tillater bruk av standard meldingsbeholdertjeneste for kopiering av innhold. Brukes ikke for normale apper."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Trykk to ganger for zoomkontroll"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Feil under oppakking av gadget"</string>
- <string name="ime_action_go" msgid="8320845651737369027">"Gå"</string>
+ <string name="ime_action_go" msgid="8320845651737369027">"Utfør"</string>
<string name="ime_action_search" msgid="658110271822807811">"Søk"</string>
<string name="ime_action_send" msgid="2316166556349314424">"Send"</string>
<string name="ime_action_next" msgid="3138843904009813834">"Neste"</string>
@@ -1070,7 +1074,7 @@
<string name="ime_action_default" msgid="2840921885558045721">"Utfør"</string>
<string name="dial_number_using" msgid="5789176425167573586">"Ring nummeret"\n"<xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="create_contact_using" msgid="4947405226788104538">"Lag kontakt"\n"med nummeret <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Ett eller flere av de følgende programmene ber om tillatelse til å få tilgang til kontoen din fra nå av."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Følgende app(er) ber om tilgang til kontoen din fra nå av."</string>
<string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Vil du tillate dette?"</string>
<string name="grant_permissions_header_text" msgid="2722567482180797717">"Forespørsel om tilgang"</string>
<string name="allow" msgid="7225948811296386551">"Tillat"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 9a32869..9c82364 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<zonder titel>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Probeer het opnieuw"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Opladen, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Opgeladen."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Sluit de oplader aan."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Geen SIM-kaart."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Geen SIM-kaart in tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabriekstest mislukt"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"De actie FACTORY_TEST wordt alleen ondersteund voor pakketten die zijn geïnstalleerd in /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Er is geen pakket gevonden dat de actie FACTORY_TEST levert."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tip: tik tweemaal om in of uit te zoomen."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"AutoFill"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Aut. aanv. inst."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Hiermee kan de app controleren of een pakket kan worden geïnstalleerd."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"koppelen aan pakketcontroleprogramma"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Hiermee kan de houder verzoeken indienen voor pakketcontroles. Nooit vereist voor normale apps."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"toegang krijgen tot seriële poorten"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"De houder toestaan toegang tot seriële poorten te krijgen met de SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"automatische apparaatupdates afwijzen"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Toestaan dat de houder informatie aan het systeem levert over welk moment een goed tijdstip is voor een niet-interactieve reboot om het apparaat bij te werken."</string>
<string name="save_password_message" msgid="767344687139195790">"Wilt u dat de browser dit wachtwoord onthoudt?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Niet nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Onthouden"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 19b9752..2d9be15 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<bez nazwy>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -495,7 +495,7 @@
<string name="permdesc_readDictionary" msgid="1082972603576360690">"Zezwala aplikacji na odczytywanie wszelkich prywatnych słów, nazw i wyrażeń zapisanych przez użytkownika w swoim słowniku."</string>
<string name="permlab_writeDictionary" msgid="6703109511836343341">"zapisywanie w słowniku zdefiniowanym przez użytkownika"</string>
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Zezwala aplikacjom na zapisywanie nowych słów w słowniku użytkownika."</string>
- <string name="permlab_sdcardWrite" product="nosdcard" msgid="85430876310764752">"modyfik./usuwan. z nośnika USB"</string>
+ <string name="permlab_sdcardWrite" product="nosdcard" msgid="85430876310764752">"Modyfikowanie/usuwanie z nośnika USB"</string>
<string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modyfikowanie/usuwanie zawartości karty SD"</string>
<string name="permdesc_sdcardWrite" product="nosdcard" msgid="6594393334785738252">"Umożliwia zapis na nośnik USB."</string>
<string name="permdesc_sdcardWrite" product="default" msgid="6643963204976471878">"Umożliwia aplikacji zapis na karcie SD."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Spróbuj ponownie"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Ładowanie (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Naładowany."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Podłącz ładowarkę."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Brak karty SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Brak karty SIM w tablecie."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Nieudany test fabryczny"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Czynność FACTORY_TEST jest obsługiwana tylko dla pakietów zainstalowanych w katalogu /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nie znaleziono żadnego pakietu, który zapewnia działanie FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Wskazówka: dotknij dwukrotnie, aby powiększyć lub pomniejszyć."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Autouzupełnianie"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Opcje autouzupełniania"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Umożliwia aplikacji zweryfikowanie, czy pakiet można zainstalować."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"powiązanie z weryfikatorem pakietów"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Zezwala na wysyłanie żądań weryfikacji pakietu. To uprawnienie nie powinno być potrzebne zwykłym aplikacjom."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"dostęp do portów szeregowych"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Umożliwia posiadaczowi dostęp do portów szeregowych przy użyciu interfejsu API narzędzia SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"odradzanie automatycznych aktualizacji urządzenia"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Umożliwia posiadaczowi poinformowanie systemu, kiedy będzie dobry moment na ponowne uruchomienie wymagane do uaktualnienia urządzenia."</string>
<string name="save_password_message" msgid="767344687139195790">"Czy chcesz, aby zapamiętać to hasło w przeglądarce?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nie teraz"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamiętaj"</string>
@@ -1221,7 +1225,7 @@
<string name="sha1_fingerprint" msgid="7930330235269404581">"Odcisk cyfrowy SHA-1:"</string>
<string name="activity_chooser_view_see_all" msgid="180268188117163072">"Pokaż wszystkie"</string>
<string name="activity_chooser_view_dialog_title_default" msgid="3325054276356556835">"Wybierz czynność"</string>
- <string name="share_action_provider_share_with" msgid="1791316789651185229">"Udostępnij..."</string>
+ <string name="share_action_provider_share_with" msgid="1791316789651185229">"Udostępnij przez..."</string>
<string name="status_bar_device_locked" msgid="3092703448690669768">"Urządzenie zablokowane."</string>
<string name="list_delimeter" msgid="3975117572185494152">", "</string>
<string name="sending" msgid="8715108995741758718">"Wysyłanie..."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 094797d..d5d6659 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<sem título>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Lamentamos, tente novamente"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"A carregar, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Ligue o carregador."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nenhum cartão SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Nenhum cartão SIM no tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"O teste de fábrica falhou"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"A acção FACTORY_TEST apenas é suportada para pacotes instalados em /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Não foi localizado qualquer pacote que forneça a acção FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Sugestão: toque duas vezes para aumentar ou diminuir o zoom."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Pr. aut."</string>
<string name="setup_autofill" msgid="8154593408885654044">"Conf preench aut"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Permite que a aplicação verifique se um pacote é instalável."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"vincular a um verificador de pacotes"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permite ao titular solicitar verificadores de pacotes. Nunca deverá ser necessário para aplicações normais."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"aceder a portas série"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permite ao titular aceder a portas de série através da API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"desaconselhar atualizações automáticas do aparelho"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Permite ao titular disponibilizar informações ao sistema acerca do melhor momento para efetuar um reinício não interativo para atualização do aparelho."</string>
<string name="save_password_message" msgid="767344687139195790">"Quer que o browser memorize esta palavra-passe?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 2287064..c17652a 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<sem título>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Tente novamente"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Carregando, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Conecte o seu carregador."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Sem cartão SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Não há um cartão SIM no tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Falha no teste de fábrica"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"A ação FACTORY_TEST é suportada apenas para pacotes instalados em /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nenhum pacote que forneça a ação FACTORY_TEST foi encontrado."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Dica: toque duas vezes para aumentar e diminuir o zoom."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Preenchimento automático"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Conf preen autom"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Permite que o aplicativo verifique se um pacote pode ser instalado."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"vincular a um verificador de pacote"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permite que o titular faça solicitações de verificadores de pacote. Nunca deve ser necessário para aplicativos normais."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"acessar portas seriais"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permite que o detentor tenha acesso a portas seriais usando a API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"impedir atualiz. autom. do dispositivo"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Permite que o proprietário forneça informações ao sistema sobre quando seria um bom momento para uma reinicialização não interativa para atualizar o dispositivo."</string>
<string name="save_password_message" msgid="767344687139195790">"Deseja que o navegador lembre desta senha?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index 834b025..918e760 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -26,7 +26,8 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <!-- no translation found for fileSizeSuffix (9164292791500531949) -->
+ <skip />
<string name="untitled" msgid="6071602020171759109">"<senza titel>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<!-- no translation found for ellipsis_two_dots (1228078994866030736) -->
@@ -825,7 +826,8 @@
<!-- no translation found for lockscreen_plugged_in (8057762828355572315) -->
<skip />
<string name="lockscreen_charged" msgid="4938930459620989972">"Chargià"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <!-- no translation found for lockscreen_battery_short (4477264849386850266) -->
+ <skip />
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Connectai Voss chargiader."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nagina carta SIM."</string>
<!-- no translation found for lockscreen_missing_sim_message (151659196095791474) -->
@@ -893,8 +895,10 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <!-- no translation found for hour_ampm (4584338083529355982) -->
+ <skip />
+ <!-- no translation found for hour_cap_ampm (2083465992940444366) -->
+ <skip />
<string name="factorytest_failed" msgid="5410270329114212041">"Il test da fabrica n\'è betg reussì"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"L\'acziun FACTORY_TEST vegn mo sustegnida per pachets installads en /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Betg chattà in pachet che porscha l\'acziun FACTORY_TEST."</string>
@@ -908,7 +912,7 @@
<skip />
<!-- no translation found for setup_autofill (8154593408885654044) -->
<skip />
- <!-- no translation found for autofill_address_name_separator (2504700673286691795) -->
+ <!-- no translation found for autofill_address_name_separator (6350145154779706772) -->
<skip />
<!-- no translation found for autofill_address_summary_name_format (3268041054899214945) -->
<skip />
@@ -964,6 +968,14 @@
<skip />
<!-- no translation found for permdesc_bindPackageVerifier (2409521927385789318) -->
<skip />
+ <!-- no translation found for permlab_serialPort (546083327654631076) -->
+ <skip />
+ <!-- no translation found for permdesc_serialPort (2991639985224598193) -->
+ <skip />
+ <!-- no translation found for permlab_updateLock (3527558366616680889) -->
+ <skip />
+ <!-- no translation found for permdesc_updateLock (1655625832166778492) -->
+ <skip />
<string name="save_password_message" msgid="767344687139195790">"Vulais Vus ch\'il navigatur memorisescha quest pled-clav?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Betg ussa"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Memorisar"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 7d2b677..a27e1d5 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GO"</string>
<string name="terabyteShort" msgid="231613018159186962">"TO"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PO"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<fără titlu>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Ne pare rău, încercaţi din nou"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Se încarcă, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Încărcată."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Conectaţi încărcătorul."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Niciun card SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Nu există card SIM în computerul tablet PC."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Testarea de fabrică nu a reuşit"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Acţiunea FACTORY_TEST este acceptată doar pentru pachetele instalate în /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nu s-a găsit niciun pachet care să ofere acţiunea FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Sfat: apăsaţi de două ori pentru a mări şi a micşora."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Completare automată"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Complet. autom."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Permite aplicaţiei să verifice dacă un pachet poate fi instalat."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"conectare la un verificator de pachete"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Permite deţinătorului să efectueze solicitări pentru verificatori de pachete. Nu ar trebui să fie necesare pentru aplicaţiile obişnuite."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"acces la porturi seriale"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Permite posesorului accesul la porturile serial utilizând API-ul SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"descuraj. actual. autom. ale dispozitiv."</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Permite proprietarului să ofere sistemului informaţii cu privire la momentul oportun pentru o repornire noninteractivă în scopul trecerii dispozitivului la o versiune superioară."</string>
<string name="save_password_message" msgid="767344687139195790">"Doriţi ca browserul să reţină această parolă?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nu acum"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Reţineţi"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 79ef41e..9bc1593 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"ГБ"</string>
<string name="terabyteShort" msgid="231613018159186962">"TБ"</string>
<string name="petabyteShort" msgid="5637816680144990219">"ПБ"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<без названия>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"..."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Повторите попытку"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Идет зарядка (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Батарея заряжена"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Подключите зарядное устройство."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Нет SIM-карты"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"SIM-карта не установлена."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"АБВ"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Не удалось провести стандартный тест"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Действие FACTORY_TEST поддерживается только для пакетов, установленных в /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Пакет, обеспечивающий действие FACTORY_TEST, не найден."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Совет: нажмите дважды, чтобы увеличить и уменьшить масштаб."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Автозаполнение"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Нужна настройка"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Позволяет приложению проверить, может ли пакет быть установлен."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"связываться с верификатором пакетов"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Позволяет приложению отправлять запросы на проверку пакетов. Не требуется для обычных приложений."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"доступ к последовательным портам"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Открыть владельцу доступ к последовательным портам с помощью SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"предотвращать авт. обновления устройства"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Позволяет владельцу сообщить системе о подходящем моменте для неинтерактивной перезагрузки в ходе обновления устройства."</string>
<string name="save_password_message" msgid="767344687139195790">"Вы хотите, чтобы браузер запомнил этот пароль?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Не сейчас"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Запомнить"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index be51dd3..879cc94 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<bez názvu>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Je nám ľúto, skúste to znova"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Prebieha nabíjanie, <xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Nabité."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Pripojte nabíjačku."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nie je vložená karta SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"V tablete nie je žiadna karta SIM."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Továrenský test zlyhal"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Test FACTORY_TEST je možné uskutočniť iba pri balíčkoch nainštalovaných v priečinku /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nebol nájdený žiadny balíček umožňujúci test FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dvojitým klepnutím môžete zobrazenie priblížiť alebo oddialiť."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Automatické dopĺňanie"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Nast. Auto. dopĺň."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Umožňuje aplikácii overiť, či sa dá balík nainštalovať."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"naviazať na overovateľa balíka"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Umožňuje držiteľovi podávať žiadosti o overenie balíkov. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"prístup k sériovým portom"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Držiteľa oprávňuje na prístup k sériovým portom pomocou rozhrania API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"zakázať automatické aktualizácie zariad."</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Umožňuje držiteľovi poskytnúť systému informácie o vhodnom čase na automatický reštart zariadenia kvôli inovovaniu."</string>
<string name="save_password_message" msgid="767344687139195790">"Chcete, aby si prehliadač zapamätal toto heslo?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Teraz nie"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamätať"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index a07b0c8..2b7a874 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<brez naslova>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Poskusite znova"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Polnjenje (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Napolnjeno."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Priključite napajalnik."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Ni kartice SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"V tabličnem računalniku ni kartice SIM."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Tovarniški preskus ni uspel"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Dejanje FACTORY_TEST je podprto le za pakete, nameščene v razdelku /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Ni bilo najdenega paketa, ki omogoča dejanje FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Namig: tapnite dvakrat, če želite povečati ali pomanjšati."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Samozapolni"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Nam. sam. izpoln."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Programu omogoča, da preveri, ali je paket mogoče namestiti."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"poveži s preverjanjem paketov"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Imetniku omogoča zahtevanje preverjanja paketov. Tega nikoli ni treba uporabiti za navadne programe."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"dostop do serijskih vrat"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Imetniku omogoča, da z API-jem za SerialManager dostopa do serijskih vrat."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"odvrnitev samodejnih posodobitev naprave"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Dovoli, da lastnik sistemu ponudi informacije o tem, kdaj je primeren čas za neinteraktiven vnovični zagon, s katerim nadgradi napravo."</string>
<string name="save_password_message" msgid="767344687139195790">"Ali želite, da si brskalnik zapomni to geslo?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne zdaj"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapomni si"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 2547ab8..95ea316 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<без наслова>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Покушајте поново"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Пуњење, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Батерија је напуњена."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Повежите пуњач."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Нема SIM картице."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"У таблету нема SIM картице."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Фабричко тестирање није успело"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Радња FACTORY_TEST је подржана само за пакете инсталиране у директоријуму /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Није пронађен ниједан пакет који обезбеђује радњу FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Савет: Додирните двапут да бисте увећали и умањили приказ."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Аутоматски попуни"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Подеси аут. поп."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Омогућава апликацији да верификује да ли је пакет могуће инсталирати."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"обавезивање на верификатор пакета"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Омогућава власнику да упућује захтеве верификаторима пакета. Не би требало да икада буде потребно за обичне апликације."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"приступ серијским портовима"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Омогућава власнику да приступи серијским портовима помоћу SerialManager API-ја."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"одговарање од аутом. ажурирања уређаја"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Дозвољава носиоцу да систему понуди информације о томе када је погодно време да неинтерактивно поновно покретање надогради уређај."</string>
<string name="save_password_message" msgid="767344687139195790">"Желите ли да прегледач запамти ову лозинку?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Не сада"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Запамти"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 4a4c22d..cdba6ab 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<utan titel>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Försök igen"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Laddar (<xliff:g id="PERCENT">%%</xliff:g> <xliff:g id="NUMBER">%d</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Laddad."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Anslut din laddare."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Inget SIM-kort."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Inget SIM-kort i pekdatorn."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Det gick fel vid fabrikstestet"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Åtgärden FACTORY_TEST stöds endast för paket som har installerats i /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Vi hittade inget paket som erbjuder åtgärden FACTORY_TEST."</string>
@@ -728,7 +728,8 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tips! Dubbelklicka om du vill zooma in eller ut."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Autofyll"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Konfig. Autofyll"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <!-- no translation found for autofill_address_name_separator (6350145154779706772) -->
+ <skip />
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +760,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Tillåter att appen kontrollerar om ett paket går att installera."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"binda till en paketverifierare"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Tillåter att innehavaren skickar förfrågningar till paketverifierare. Det ska inte behövas för vanliga appar."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"åtkomst till serieportar"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Innebär att innehavaren får åtkomst till serieportar med programmeringsgränssnittet för SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"hindra automatiska enhetsuppdateringar"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Innehavaren kan informera systemet när det är lämpligt att göra en omstart utan interaktivitet för att uppgradera enheten."</string>
<string name="save_password_message" msgid="767344687139195790">"Vill du att webbläsaren ska komma ihåg lösenordet?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Inte nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Kom ihåg"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index d5e77dd..406cfcc 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"Kishika nafasi<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<untitled>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -335,9 +335,9 @@
<string name="permdesc_readProfile" product="default" msgid="6335739730324727203">"Inaruhusu programu kusoma maelezo mafupi ya binafsi yaliyohifadhiwa kwenye kifaa chako, kama vile jina lako na maelezo yako ya anwani. Hii ina maanisha programu inaweza kukutambua na kutuma habari maelezo yako mafupi kwa wengine."</string>
<string name="permlab_writeProfile" msgid="4679878325177177400">"andika kwenye data ya maelezo yako mafupi"</string>
<string name="permdesc_writeProfile" product="default" msgid="6431297330378229453">"Inaruhusu programu kubadilisha au kuongeza maelezo binafsi ya maelezo yako mafupi yaliyohifadhiwa kwenye kifaa chako, kama vile jina lako na maelezo ya anwani. Hii ina maanisha programu nyingine ziweze kukutambua na kutuma maelezo ya maelezo yako mafupi kwa wengine."</string>
- <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"soma mkondo wako wa kijamii"</string>
+ <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"soma mipasho yako wa kijamii"</string>
<string name="permdesc_readSocialStream" product="default" msgid="6619997662735851111">"Inaruhusu programu kufikia na kulandanisha usasisho kutoka kwako na marafiki wako. Prog hasidi zinaweza kutumia hizi kusoma mawasiliano ya kibinafsi kati yako na marafiki wako kwenye mitandao ya kijamii."</string>
- <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"andika kwa mkondo wako wa kijamii"</string>
+ <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"andika kwa mipasho yako wa kijamii"</string>
<string name="permdesc_writeSocialStream" product="default" msgid="2689083745826002521">"Inaruhusu programu kuonyesha usasisho ya kijamii kutoka kwa marafiki wako. Prog hasidi zinaweza kutumia hizi zikijifanya kuwa rafiki na kukuhadaa kuonyesha nenosiri au taarifa zingine za siri."</string>
<string name="permlab_readCalendar" msgid="5972727560257612398">"soma matukio ya kalenda pamoja na maelezo ya siri"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="5665520896961671949">"Huruhusu programu kusoma matukio yote ya kalenda yaliyohifadhiwa kwenye kompyuta yako ndogo, pamoja na za marafiki au wafanyakazi wenza. Programu hasidi yenye kibali hiki kinaweza kuchukua maelezo ya kibinagsi kutoka kwa kalenda hizi bila ufahamu wa mmiliki."</string>
@@ -637,7 +637,7 @@
<string name="relationTypeParent" msgid="4755635567562925226">"Mzazi"</string>
<string name="relationTypePartner" msgid="7266490285120262781">"Mshirika"</string>
<string name="relationTypeReferredBy" msgid="101573059844135524">"Alipendekezwa na"</string>
- <string name="relationTypeRelative" msgid="1799819930085610271">"Ndugu"</string>
+ <string name="relationTypeRelative" msgid="1799819930085610271">"Jamaa"</string>
<string name="relationTypeSister" msgid="1735983554479076481">"Dada"</string>
<string name="relationTypeSpouse" msgid="394136939428698117">"Mwenzi wa Ndoa"</string>
<string name="sipAddressTypeCustom" msgid="2473580593111590945">"Maalum"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Samahani, jaribu tena"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Inachaji <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Imechajiwa."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"Kishika nafasi<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Unganisha chaja yako"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Hakuna SIM kadi."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Hakuna SIM kadi katika kompyuta ndogo."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"Kishika nafasi<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"Kishika nafasi<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Jaribio la kiwanda limeshindikana"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Tendo la JARIBIO_LA KIWANDA linahimiliwa tu kwa furushi zilizosakinishwa katika /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Hakuna furushi lililopatikana ambalo linatoa tendo la JARIBIO_LA KIWANDA."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Dokezo: gonga mara mbili ili kukuza ndani na nje."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Mjazo-Atomatiki"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Sanidi Mjazo-Atomatiki"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" Kishika nafasi "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Inaruhusu programu kuthibitisha kuwa furushi linaweza kusakinishwa."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"Funga kwa kithibitishaji cha furushi"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Inaruhusu mmiliki kufanya maombi ya furushi la vibainishi. Kamwe hazitaitajika kwa programu za kawaida."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"kituo tambulishi cha ufikivu"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Inaruhusu mmiliki kufikia vituo tambulishi kwa kutumia KisimamiziTambulishi cha API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"pinga usasishaji kifaa kiotomatiki"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Inaruhusu mmiliki kutoa maelezo kwa mfumo kuhusu ni lini itakuwa wakati mzuri wa uwashaji upya usiotagusana ili kupandisha gredi kifaa."</string>
<string name="save_password_message" msgid="767344687139195790">"Unataka kuvinjari ili ukumbuke nenosiri hili?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Si Sasa"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Kumbuka"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index b3855a0..c3eedbb 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<ไม่มีชื่อ>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"ขออภัย โปรดลองอีกครั้ง"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"กำลังชาร์จ, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"ชาร์จแล้ว"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"เสียบที่ชาร์จของคุณ"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"ไม่มีซิมการ์ด"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"ไม่มีซิมการ์ดในแท็บเล็ต"</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"การทดสอบจากโรงงานล้มเหลว"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"การทำงาน FACTORY_TEST ได้รับการสนับสนุนเฉพาะสำหรับแพ็คเก็จที่ติดตั้งใน /system/app เท่านั้น"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"ไม่พบแพคเกจที่มีการทำงาน FACTORY_TEST"</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"เคล็ดลับ: แตะสองครั้งเพื่อขยายและย่อ"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"ป้อนอัตโนมัติ"</string>
<string name="setup_autofill" msgid="8154593408885654044">"ตั้งค่าการป้อนอัตโนมัติ"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"อนุญาตให้แอปพลิเคชันยืนยันว่าแพคเกจสามารถติดตั้งได้หรือไม่"</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"เชื่อมโยงกับการยืนยันแพคเกจ"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"อนุญาตให้ผู้ใช้ส่งคำขอให้มีการยืนยันแพคเกจ ไม่จำเป็นสำหรับแอปพลิเคชันโดยทั่วไป"</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"เข้าถึงพอร์ตอนุกรม"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"อนุญาตให้ผู้ถือสามารถเข้าถึงพอร์ตอนุกรมโดยใช้ SerialManager API"</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"ป้องกันการอัปเดตอุปกรณ์อัตโนมัติ"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"ช่วยให้เจ้าของแจ้งข้อมูลเกี่ยวกับเวลาที่เหมาะสมในการรีบูตแบบไม่โต้ตอบเพื่ออัปเกรดอุปกรณ์ไปยังระบบได้"</string>
<string name="save_password_message" msgid="767344687139195790">"คุณต้องการให้เบราว์เซอร์จำรหัสผ่านนี้หรือไม่"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"ยังไม่ใช้งานขณะนี้"</string>
<string name="save_password_remember" msgid="6491879678996749466">"จำไว้"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 16e40a5..23353ea 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<walang pamagat>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Paumanhin, subukang muli"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Nagcha-charge, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Naka-charge."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Ikonekta ang iyong charger."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Walang SIM card."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Walang SIM card sa tablet."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Nabigo ang factory na pagsubok"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Suportado lang ang pagkilos na FACTORY_TEST para sa mga package na naka-install sa /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Walang nakitang package na nagbibigay ng pagkilos na FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Tip: mag-double-tap upang mag-zoom in at out."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"AutoFill"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Setup AutoFill"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Binibigyang-daan ang application na i-verify kung ang isang package ay maaaring i-install."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"sumailalim sa taga-verify ng package"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Nagbibigay-daan sa may-ari na gumawa ng mga kahilingan ng mga taga-verify ng package. Hindi kailanman dapat na kailanganin para sa karaniwang mga application."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"mag-access sa mga serial port"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Binibigyang-daan ang may-ari na mag-access ng mga serial port gamit ang SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"wag payagan awtomatiko update ng device"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Pinapayagan ang may-hawak na mag-alok ng impormasyon sa system tungkol sa kung kailan ang magandang oras para sa hindi interactive na pag-reboot upang i-upgrade ang device."</string>
<string name="save_password_message" msgid="767344687139195790">"Gusto mo bang tandaan ng browser ang password na ito?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Hindi ngayon"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Tandaan"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 80e3467..0501f2c 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<başlıksız>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Maalesef, tekrar deneyin"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Şarj oluyor (<xliff:g id="PERCENT">%%</xliff:g><xliff:g id="NUMBER">%d</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Şarj oldu."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="PERCENT">%%</xliff:g><xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Şarj cihazınızı bağlayın."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIM kart yok."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Tablette SIM kart yok."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabrika testi yapılamadı"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST işlemi yalnızca /system/app dizinine yüklenmiş paketler için desteklenir."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST işlemini sağlayan hiçbir paket bulunamadı."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"İpucu: Yakınlaştırmak ve uzaklaştırmak için iki kez hafifçe vurun."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"OtoDoldr"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Oto Doldr Ayarla"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Uygulamaya, bir paketin yüklenebilir olduğunu doğrulama izni verir."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"paket doğrulayıcıya bağlan"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"İzin sahibinin, paket doğrulayıcı isteklerinde bulunmasına izin verir. Normal uygulamalar için asla gerekli olmaz."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"seri bağlantı noktalarına eriş"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"İzin sahibinin, SerialManager API\'sını kullanarak seri bağlantı noktalarına erişmesine olanak sağlar."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"otomatik cihaz güncellemelerinin yapılmasını engelle"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Cihazın yeni sürüme geçirilmesinde kesintisiz yeniden başlatmanın ne zaman uygun olacağı bilgisinin sisteme teklif edilmesine izin verir."</string>
<string name="save_password_message" msgid="767344687139195790">"Tarayıcının bu şifreyi anımsamasını istiyor musunuz?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Şimdi değil"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Anımsa"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index b1a604a..8c808c1 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"Гб"</string>
<string name="terabyteShort" msgid="231613018159186962">"Тб"</string>
<string name="petabyteShort" msgid="5637816680144990219">"Пб"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<без назви>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">".."</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Спробуйте ще"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Заряджається, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Заряджено."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Підкл. заряд. пристрій."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Нема SIM-карти."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"У пристр. нема SIM-карти."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Помилка завод. тесту"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Дія FACTORY_TEST підтримується лише для пакетів, установлених у /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Не було знайдено жодного пакета, який надає дію FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Порада: двічі нат. для збіл. або змен."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Автозаповн."</string>
<string name="setup_autofill" msgid="8154593408885654044">"Налашт. автозап."</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Дозволяє програмі перевіряти, чи можливо встановити пакет."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"прив’язуватися до програми перевірки пакетів"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Дозволяє власникові робити запити на програми перевірки пакетів. Ніколи не застосовується для звичайних програм."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"отримувати доступ до послідовних портів"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Дозволяє власнику отримувати доступ до послідовних портів за допомогою API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"перешкоджати автом. оновленням пристрою"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Дозволяє власникові надавати системі інформацію про найкращий час для оновлення пристрою шляхом неінтерактивного перезавантаження."</string>
<string name="save_password_message" msgid="767344687139195790">"Хочете, щоб переглядач запам\'ятав цей пароль?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Не зараз"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Запам\'ятати"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index ef2f4f0..d2d5bba 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<không có tiêu đề>"</string>
<string name="ellipsis" msgid="7899829516048813237">"…"</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Rất tiếc, hãy thử lại"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Đang sạc, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Đã sạc."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Kết nối bộ sạc của bạn."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Không có thẻ SIM nào."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Không có thẻ SIM nào trong máy tính bảng."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Thử nghiệm ban đầu không thành công"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Tác vụ FACTORY_TEST chỉ được hỗ trợ cho các gói được cài đặt trong /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Không tìm thấy gói cung cấp tác vụ FACTORY_TEST."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Mẹo: nhấn đúp để phóng to và thu nhỏ."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Tự động điền"</string>
<string name="setup_autofill" msgid="8154593408885654044">"C.đặt TĐ điền"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Cho phép ứng dụng xác minh gói có thể cài đặt."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"liên kết với trình xác minh gói"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Cho phép chủ nhân yêu cầu trình xác minh gói. Không cần cho ứng dụng thông thường."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"truy cập cổng nối tiếp"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Cho phép chủ sở hữu truy cập cổng nối tiếp sử dụng API SerialManager."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"ko khuyến khích cập nhật th.bị tự động"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Cho phép chủ sở hữu cung cấp thông tin tới hệ thống về thời điểm thích hợp để khởi động lại không tương tác nhằm nâng cấp thiết bị."</string>
<string name="save_password_message" msgid="767344687139195790">"Bạn có muốn trình duyệt nhớ mật khẩu này không?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Không phải bây giờ"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Nhớ"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index d345864..82f675c 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<无标题>"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"很抱歉,请重试"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"正在充电,<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"已充满。"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"连接您的充电器。"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"没有 SIM 卡"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"平板电脑中没有 SIM 卡。"</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="AMPM">%P</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="AMPM">%p</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"出厂测试失败"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"只有在 /system/app 中安装的包支持 FACTORY_TEST 操作。"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"未发现支持 FACTORY_TEST 操作的包。"</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"提示:点按两次可放大和缩小。"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"自动填充"</string>
<string name="setup_autofill" msgid="8154593408885654044">"设置自动填充"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"允许应用程序验证软件包是否可以安装。"</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"绑定到软件包验证程序"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"允许手机用户请求使用软件包验证程序。普通应用程序无需获取此权限。"</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"访问串行端口"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"允许持有人使用 SerialManager API 访问串行端口。"</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"阻止自动设备更新"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"允许应用向系统提供相关信息,以确定何时适合执行非交互式重启以升级设备。"</string>
<string name="save_password_message" msgid="767344687139195790">"是否希望浏览器记住此密码?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"暂不保存"</string>
<string name="save_password_remember" msgid="6491879678996749466">"记住"</string>
@@ -909,7 +913,7 @@
<string name="anr_title" msgid="4351948481459135709"></string>
<string name="anr_activity_application" msgid="8339738283149696827">"<xliff:g id="APPLICATION">%2$s</xliff:g> 无响应。"\n\n"要将它关闭吗?"</string>
<string name="anr_activity_process" msgid="7018289416670457797">"活动 <xliff:g id="ACTIVITY">%1$s</xliff:g> 无响应。"\n\n"要将它关闭吗?"</string>
- <string name="anr_application_process" msgid="7208175830253210526">"<xliff:g id="APPLICATION">%1$s</xliff:g> 无响应。要将它关闭吗?"</string>
+ <string name="anr_application_process" msgid="7208175830253210526">"<xliff:g id="APPLICATION">%1$s</xliff:g>无响应。要将它关闭吗?"</string>
<string name="anr_process" msgid="306819947562555821">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 无响应。"\n\n"要将它关闭吗?"</string>
<string name="force_close" msgid="8346072094521265605">"确定"</string>
<string name="report" msgid="4060218260984795706">"报告"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index d5a94b9..5180fbb 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"(未命名)"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"很抱歉,請再試一次"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"充電中 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"充電完成。"</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"請連接充電器。"</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"沒有 SIM 卡。"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"平板電腦中沒有 SIM 卡。"</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"出廠測試失敗"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"只有安裝在 /system/app 裡的程式才能支援 FACTORY_TEST 操作。"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"找不到提供 FACTORY_TEST 的程式。"</string>
@@ -728,7 +728,8 @@
<string name="double_tap_toast" msgid="1068216937244567247">"提示:輕按兩下可放大縮小。"</string>
<string name="autofill_this_form" msgid="1272247532604569872">"自動填入功能"</string>
<string name="setup_autofill" msgid="8154593408885654044">"設定自動填入功能"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <!-- no translation found for autofill_address_name_separator (6350145154779706772) -->
+ <skip />
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +760,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"允許應用程式驗證是否可安裝特定套件。"</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"繫結至套件驗證程序"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"允許應用程式要求驗證套件,一般應用程式不需使用這個選項。"</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"存取序列埠"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"允許應用程式使用 SerialManager API 存取序列埠。"</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"不建議自動更新裝置"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"允許應用程式提供資訊,建議系統何時適合透過非互動方式重新啟動並升級裝置。"</string>
<string name="save_password_message" msgid="767344687139195790">"是否記住此密碼?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"現在不要"</string>
<string name="save_password_remember" msgid="6491879678996749466">"記住"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index e004b66..f1db22b 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -26,7 +26,7 @@
<string name="gigabyteShort" msgid="3259882455212193214">"GB"</string>
<string name="terabyteShort" msgid="231613018159186962">"TB"</string>
<string name="petabyteShort" msgid="5637816680144990219">"PB"</string>
- <string name="fileSizeSuffix" msgid="7670819340156489359">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
+ <string name="fileSizeSuffix" msgid="9164292791500531949">"<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="6071602020171759109">"<untitled>"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="ellipsis_two_dots" msgid="1228078994866030736">"‥"</string>
@@ -70,11 +70,11 @@
<string name="serviceNotProvisioned" msgid="8614830180508686666">"Isevisi ayilungiselelwe."</string>
<string name="CLIRPermanent" msgid="5460892159398802465">"Ilungiselelo le-ID yomshayeli alikwazi ukushintshwa."</string>
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Ukufinyelela okuvinjelwe kushintshiwe"</string>
- <string name="RestrictedOnData" msgid="8653794784690065540">"Insizakalo yedatha ivaliwe."</string>
- <string name="RestrictedOnEmergency" msgid="6581163779072833665">"Insizakalo ephuthumayo ivimbelwe."</string>
- <string name="RestrictedOnNormal" msgid="4953867011389750673">"Insizakalo yezwi ivimbelwe."</string>
+ <string name="RestrictedOnData" msgid="8653794784690065540">"Isevisi yedatha ivaliwe."</string>
+ <string name="RestrictedOnEmergency" msgid="6581163779072833665">"Isevisi ephuthumayo ivimbelwe."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Isevisi yezwi ivimbelwe."</string>
<string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Wonke amasevisi Wezwi avimbelwe."</string>
- <string name="RestrictedOnSms" msgid="8314352327461638897">"Insizakalo ye-SMS ivaliwe."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Isevisi ye-SMS ivaliwe."</string>
<string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Amasevisi Wezwi/Idatha avimbelwe."</string>
<string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Amasevisi Wezwi/SMS avimbelwe."</string>
<string name="RestrictedOnAll" msgid="2714924667937117304">"Wonke amasevisi Wezwi/Idatha/SMS avimbelwe."</string>
@@ -274,7 +274,7 @@
<string name="permdesc_bindVpnService" msgid="6011554199384584151">"Ivumela umbambi ukuhlanganisa uxhumano nomsebenzisi kwezinga eliphezulu lwephephadonga. Akusoze kwadingeka kwezinhlelo zokusebenza ezivamile."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"hlanganisa kwiphephadonga"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Ivumela umbambi ukuhlanganisa uxhumano nomsebenzisi kwezinga eliphezulu lwephephadonga. Akusoze kwadingeka kwezinhlelo zokusebenza ezivamile."</string>
- <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bophezela kube insizakalo yesinqunjana"</string>
+ <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"bophezela kube isevisi yesinqunjana"</string>
<string name="permdesc_bindRemoteViews" msgid="2930855984822926963">"Ivumela umbambi ukuhlanganisa uxhumano nomsebenzisi kwezinga eliphezulu lensizakalo yesinqunjwana. Akusoze kwadingeka kwezinhlelo zokusebenza ezivamile."</string>
<string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"xhumana nomphathi wedivaysi"</string>
<string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Ivumela umphathi ukuthumela okuqukethwe kumphathi wedivaysi. Akusoze kwadingeka izinhlelo zokusebenza ezivamile."</string>
@@ -408,7 +408,7 @@
<string name="permlab_locationUpdates" msgid="7785408253364335740">"lawula izaziso zokubuyekeza indawo"</string>
<string name="permdesc_locationUpdates" msgid="2300018303720930256">"Ivumela ukuvula/ukuvimbela izaziso zesibuyekezo sendawo kusuka emsakazweni. Akumele isebenziswe izinhlelo zokusebenza ezivamile."</string>
<string name="permlab_checkinProperties" msgid="7855259461268734914">"finyelela kwizakhiwo zokuhlola"</string>
- <string name="permdesc_checkinProperties" msgid="7150307006141883832">"Ivumela ukufinyelela kokufunda/ukubhalela ezicini ezilayishwe insizakalo zokuhlola. Akuyona eyokusebenziswa izinhlelo zokusebenza ezivamile."</string>
+ <string name="permdesc_checkinProperties" msgid="7150307006141883832">"Ivumela ukufinyelela kokufunda/ukubhalela ezicini ezilayishwe isevisi zokuhlola. Akuyona eyokusebenziswa izinhlelo zokusebenza ezivamile."</string>
<string name="permlab_bindGadget" msgid="776905339015863471">"khetha izinqunjwana"</string>
<string name="permdesc_bindGadget" msgid="2098697834497452046">"Ivumela uhlelo lokusebenza ukutshela isistimu ukuthi yimaphi amawijethi angasebenziswa yiziphi izinhlelo zokusebenza. Ngalemvume, izinhlelo zokusebenza zinganikeza ukufinyelela idatha yomuntu siqu kwezinye izinhlelo zokusebenza. Ayisebenziswa izinhlelo zokusebenza ezivamile."</string>
<string name="permlab_modifyPhoneState" msgid="8423923777659292228">"guqula isimo sefoni"</string>
@@ -504,7 +504,7 @@
<string name="permlab_cache_filesystem" msgid="5656487264819669824">"finyelela kunqolobane yesistimu yefayela"</string>
<string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Ivumela uhlelo lokusebenza ukufunda nokubhala uhlelo lwesistimu lwenqolobane."</string>
<string name="permlab_use_sip" msgid="5986952362795870502">"yena/thola amakholi e-Inthanethi"</string>
- <string name="permdesc_use_sip" msgid="6320376185606661843">"Ivumela uhlelo lokusebenza ukusebenzisa insizakalo ye-SIP ukwenza/ukuthola amakholi e-Inthanethi."</string>
+ <string name="permdesc_use_sip" msgid="6320376185606661843">"Ivumela uhlelo lokusebenza ukusebenzisa isevisi ye-SIP ukwenza/ukuthola amakholi e-Inthanethi."</string>
<string name="permlab_readNetworkUsageHistory" msgid="7862593283611493232">"funda ukusetshenziswa komlando wohleloxhumano"</string>
<string name="permdesc_readNetworkUsageHistory" msgid="6040738474779135653">"Ivumela uhlelo lokusebenza ukufunda umlando wokusebenza kohleloxhumano kwezinhleloxhumano eziqondile nezinhlelo zokusebenza."</string>
<string name="permlab_manageNetworkPolicy" msgid="2562053592339859990">"phatha inqubomgomo yenethiwekhi"</string>
@@ -666,7 +666,7 @@
<string name="lockscreen_password_wrong" msgid="6237443657358168819">"Uxolo, zama futhi"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Iyashaja (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Kushajiwe."</string>
- <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+ <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Xhuma ishaja yakho."</string>
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Alikho ikhadi le-SIM."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Alikho ikhadi le-SIM efonini."</string>
@@ -715,8 +715,8 @@
<string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
- <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
- <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+ <string name="hour_ampm" msgid="4584338083529355982">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
+ <string name="hour_cap_ampm" msgid="2083465992940444366">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Ukuhlola kwemboni kwehlulekile"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Isenzo SOKUHLOLA_KWASEMBONINI sisekelwa kuphela amaphakheji afakwe kwisistimu/uhlelokusebenza."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Ayikho iphakheji etholakele enikeze isenzo SOKUHLOLA KWASEMBONINI."</string>
@@ -728,7 +728,7 @@
<string name="double_tap_toast" msgid="1068216937244567247">"Ithiphu; thepha kabili ukusondeza ngaphandle nangaphakathi."</string>
<string name="autofill_this_form" msgid="1272247532604569872">"Ukugcwalisa Okuzenzakalelayo"</string>
<string name="setup_autofill" msgid="8154593408885654044">"Misa Ukugcwalisa Okuzenzakalelayo"</string>
- <string name="autofill_address_name_separator" msgid="2504700673286691795">" "</string>
+ <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
<string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
<string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
@@ -759,6 +759,10 @@
<string name="permdesc_packageVerificationAgent" msgid="6033195477325381106">"Vumela ukuthi isisetshenziswa siqinisekise ukuthi ngabe iphakheji iyafakeka."</string>
<string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"bopha okokuqinisekisa iphakheji"</string>
<string name="permdesc_bindPackageVerifier" msgid="2409521927385789318">"Ivumela umnikazi ukuthi enze izicelo zezinsiza eziqinisekisa iphakheji. Akumele kudingeke ekusetshenzisweni okujwayelekile."</string>
+ <string name="permlab_serialPort" msgid="546083327654631076">"finyelela kuma- serial port"</string>
+ <string name="permdesc_serialPort" msgid="2991639985224598193">"Ivumela umnikai ukuthi athole inombolo ye-serial ukue angene kwiindawo ze-serial esebenzisa i-SerialManager API."</string>
+ <string name="permlab_updateLock" msgid="3527558366616680889">"gxeka izibuyekezo zedivayisi zokuzenzakalela"</string>
+ <string name="permdesc_updateLock" msgid="1655625832166778492">"Ivumela umphathi ukunikela ngolwazi ohlelweni mayela nokuthi kuzoba nini isikhathi esilungile sokuqalisa kabusha okungenakuxoxisana ukuze kuthuthukiswe idivayisi."</string>
<string name="save_password_message" msgid="767344687139195790">"Ingabe ufuna ukuba isiphequluli sikhumbule lephasiwedi?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Hha yi manje"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Khumbula"</string>
@@ -1057,8 +1061,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Ayikho imisebenzi efanayo etholakele"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"buyekeza izibalo zokusebenzisa ingxenye"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Ivumela ukuguqula kwezibalo zokusebenzisa zengxenye eqoqiwe. Akumele isebenziswe izinhlelo zokusebenza ezivamile."</string>
- <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Ivumela uhlelo lokusebenza ukucelela insizakalo yesiqukathi esimisiwe ukukopisha kokuqukethwe. Ayisebenziswa izinhlelo zokusebenza ezivamile."</string>
- <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Ivumela ukucelela insizakalo yesiqukathi esimisiwe ukukopisha kokuqukethwe. Ayisebenziselwa izinhlelo zokusebenza ezivamile."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Ivumela uhlelo lokusebenza ukucelela isevisi yesiqukathi esimisiwe ukukopisha kokuqukethwe. Ayisebenziswa izinhlelo zokusebenza ezivamile."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Ivumela ukucelela isevisi yesiqukathi esimisiwe ukukopisha kokuqukethwe. Ayisebenziselwa izinhlelo zokusebenza ezivamile."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Thepha kabili ukuthola ukulawula ukusondeza"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Iphutha lewijethi"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Iya"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 24afe15..f280e9f 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -61,6 +61,25 @@
As of Honeycomb, blurring is not supported anymore. -->
<bool name="config_sf_slowBlur">true</bool>
+ <!-- Flag indicating that the media framework should allow changing
+ master volume stream and nothing else . -->
+ <bool name="config_useMasterVolume">false</bool>
+
+ <!-- Array of integer pairs controlling the rate at which the master volume changes
+ in response to volume up and down key events.
+ The first integer of each pair is compared against the current master volume
+ (in range 0 to 100).
+ The last pair with first integer <= the current volume is chosen,
+ and the second integer of the pair indicates the amount to increase the master volume
+ when volume up is pressed. -->
+ <integer-array name="config_masterVolumeRamp">
+ <item>0</item> <item>5</item> <!-- default: always increase volume by 5% -->
+ </integer-array>
+
+ <!-- Flag indicating whether the AUDIO_BECOMING_NOISY notification should
+ be sent during an change to the audio output device. -->
+ <bool name="config_sendAudioBecomingNoisy">true</bool>
+
<!-- The duration (in milliseconds) of a short animation. -->
<integer name="config_shortAnimTime">200</integer>
@@ -265,6 +284,10 @@
point on the move. A value of 0 means no periodic scans will be used in the framework. -->
<integer translatable="false" name="config_wifi_framework_scan_interval">300000</integer>
+ <!-- Wifi driver stop delay, in milliseconds.
+ Default value is 2 minutes. -->
+ <integer translatable="false" name="config_wifi_driver_stop_delay">120000</integer>
+
<!-- Flag indicating whether the keyguard should be bypassed when
the slider is open. This can be set or unset depending how easily
the slider can be opened (for example, in a pocket or purse). -->
@@ -277,13 +300,16 @@
<!-- Don't name config resources like this. It should look like config_annoyDianne -->
<bool name="config_annoy_dianne">true</bool>
+ <!-- XXXXXX END OF RESOURCES USING WRONG NAMING CONVENTION -->
+
<!-- If this is true, the screen will come on when you unplug usb/power/whatever. -->
<bool name="config_unplugTurnsOnScreen">false</bool>
<!-- If this is true, the screen will fade off. -->
<bool name="config_animateScreenLights">true</bool>
- <!-- XXXXXX END OF RESOURCES USING WRONG NAMING CONVENTION -->
+ <!-- If this is true, key chords can be used to take a screenshot on the device. -->
+ <bool name="config_enableScreenshotChord">true</bool>
<!-- If true, the screen can be rotated via the accelerometer in all 4
rotations as the default behavior. -->
@@ -308,6 +334,14 @@
A value of -1 means no change in orientation by default. -->
<integer name="config_carDockRotation">-1</integer>
+ <!-- Control the default UI mode type to use when there is no other type override
+ happening. One of the following values (See Configuration.java):
+ 1 UI_MODE_TYPE_NORMAL
+ 4 UI_MODE_TYPE_TELEVISION
+ 5 UI_MODE_TYPE_APPLIANCE
+ Any other values will have surprising consequences. -->
+ <integer name="config_defaultUiModeType">1</integer>
+
<!-- Control whether being in the desk dock (and powered) always
keeps the screen on. By default it stays on when plugged in to
AC. 0 will not keep it on; or together 1 to stay on when plugged
@@ -360,6 +394,12 @@
<string-array name="config_usbHostBlacklist">
</string-array>
+ <!-- List of paths to serial ports that are available to the serial manager.
+ for example, /dev/ttyUSB0
+ -->
+ <string-array translatable="false" name="config_serialPorts">
+ </string-array>
+
<!-- Vibrator pattern for feedback about a long screen/key press -->
<integer-array name="config_longPressVibePattern">
<item>0</item>
@@ -531,6 +571,9 @@
specified -->
<string name="default_wallpaper_component" translatable="false">@null</string>
+ <!-- True if WallpaperService is enabled -->
+ <bool name="config_enableWallpaperService">true</bool>
+
<!-- Component name of the service providing network location support. -->
<string name="config_networkLocationProvider" translatable="false">@null</string>
@@ -588,6 +631,10 @@
cell broadcasting sms, and MMS. -->
<bool name="config_sms_capable">true</bool>
+ <!-- Enable/disable default bluetooth profiles:
+ HSP_AG, ObexObjectPush, Audio, NAP -->
+ <bool name="config_bluetooth_default_profiles">true</bool>
+
<!-- IP address of the dns server to use if nobody else suggests one -->
<string name="config_default_dns_server" translatable="false">8.8.8.8</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7b785ec..7022e0c 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2283,6 +2283,16 @@
<string name="permdesc_bindPackageVerifier">Allows the holder to make requests of
package verifiers. Should never be needed for normal applications.</string>
+ <!-- Title of an application permission which allows the application to access serial ports via the SerialManager. [CHAR LIMIT=40] -->
+ <string name="permlab_serialPort">access serial ports</string>
+ <!-- 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 application to suggest that now is a bad time to reboot the device in order to apply an OTA. [CHAR LIMIT=40] -->
+ <string name="permlab_updateLock">discourage automatic device updates</string>
+ <!-- Description of an application permission which allows the application to suggest that now is a bad time to reboot the device in order to apply an OTA. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_updateLock">Allows the holder to offer information to the system about when would be a good time for a noninteractive reboot to upgrade the device.</string>
+
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
<string name="save_password_message">Do you want the browser to remember this password?</string>
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index 3ffa085..7233e7f 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -46,7 +46,7 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- if (!UtilHelper.isWifiOnly()) {
+ if (!UtilHelper.isWifiOnly(getContext())) {
suite.addTestSuite(WifiApStress.class);
suite.addTestSuite(WifiStressTest.class);
} else {
@@ -64,7 +64,7 @@
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- if (!UtilHelper.isWifiOnly()) {
+ if (!UtilHelper.isWifiOnly(getContext())) {
String valueStr = (String) icicle.get("softap_iterations");
if (valueStr != null) {
int iteration = Integer.parseInt(valueStr);
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
index 20aae47..9c1922f 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
@@ -40,14 +40,13 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- if (!UtilHelper.isWifiOnly()) {
+ if (!UtilHelper.isWifiOnly(getContext())) {
suite.addTestSuite(ConnectivityManagerMobileTest.class);
} else {
// 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/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
index 1b966bf..b9fe6ed 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
@@ -16,12 +16,31 @@
package com.android.connectivitymanagertest;
-import android.os.SystemProperties;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
public class UtilHelper {
- public static boolean isWifiOnly() {
- return "wifi-only".equals(SystemProperties.get("ro.carrier"));
+
+ private static Boolean mIsWifiOnly = null;
+ private static final Object sLock = new Object();
+
+ /**
+ * Return true if device is a wifi only device.
+ */
+ public static boolean isWifiOnly(Context context) {
+ synchronized (sLock) {
+ // cache the result from pkgMgr statically. It will never change, since its a
+ // device configuration setting
+ if (mIsWifiOnly == null) {
+ PackageManager pkgMgr = context.getPackageManager();
+ mIsWifiOnly = Boolean.valueOf(!pkgMgr
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ && pkgMgr.hasSystemFeature(PackageManager.FEATURE_WIFI));
+ String deviceType = mIsWifiOnly ? "wifi-only" : "telephony";
+ Log.d("ConnectivityManagerTest", String.format("detected a %s device", deviceType));
+ }
+ }
+ return mIsWifiOnly;
}
-
-
}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
index b1f4bf1..52326d5 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
@@ -16,37 +16,31 @@
package com.android.connectivitymanagertest.functional;
-import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
-import com.android.connectivitymanagertest.UtilHelper;
-
-import android.content.Intent;
import android.content.Context;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.app.Instrumentation;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.State;
-import android.net.NetworkInfo.DetailedState;
import android.net.wifi.WifiManager;
-
-import android.test.suitebuilder.annotation.LargeTest;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.provider.Settings;
import android.test.ActivityInstrumentationTestCase2;
-import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
-import com.android.connectivitymanagertest.NetworkState;
+import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
-public class ConnectivityManagerMobileTest
- extends ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> {
+import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
+import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
+import com.android.connectivitymanagertest.NetworkState;
+import com.android.connectivitymanagertest.UtilHelper;
+
+public class ConnectivityManagerMobileTest extends
+ ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> {
private static final String LOG_TAG = "ConnectivityManagerMobileTest";
- private static final String PKG_NAME = "com.android.connectivitymanagertest";
private String TEST_ACCESS_POINT;
private ConnectivityManagerTestActivity cmActivity;
private WakeLock wl;
+ private boolean mIsWifiOnlyDevice;
public ConnectivityManagerMobileTest() {
super(ConnectivityManagerTestActivity.class);
@@ -69,7 +63,8 @@
log("airplane is not disabled, disable it.");
cmActivity.setAirplaneMode(getInstrumentation().getContext(), false);
}
- if (!UtilHelper.isWifiOnly()) {
+ mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext());
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT)) {
// Note: When the test fails in setUp(), tearDown is not called. In that case,
@@ -166,7 +161,7 @@
public void testConnectToWifi() {
assertNotNull("SSID is null", TEST_ACCESS_POINT);
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
//Prepare for connectivity verification
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
@@ -185,7 +180,7 @@
log("wifi state is enabled");
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
@@ -197,7 +192,7 @@
cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
assertTrue(false);
}
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
log("Mobile state transition validation failed.");
log("reason: " +
@@ -232,13 +227,13 @@
ConnectivityManagerTestActivity.LONG_TIMEOUT));
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
//Prepare for connectivity state verification
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
@@ -258,7 +253,7 @@
// Wait for Wifi to be connected and mobile to be disconnected
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
@@ -288,7 +283,7 @@
sleep(ConnectivityManagerTestActivity.SHORT_TIMEOUT);
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
networkInfo.getState(),
@@ -304,7 +299,7 @@
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
@@ -316,7 +311,7 @@
cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
assertTrue(false);
}
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
log("Mobile state transition validation failed.");
log("reason: " +
@@ -393,7 +388,7 @@
cmActivity.setAirplaneMode(getInstrumentation().getContext(), true);
NetworkInfo networkInfo;
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
@@ -419,7 +414,7 @@
cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
assertTrue("State validation failed", false);
}
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
log("state validation for Mobile failed");
log("reason: " +
@@ -471,7 +466,7 @@
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
ConnectivityManagerTestActivity.LONG_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
index 2069789..feb63cd 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
@@ -16,10 +16,6 @@
package com.android.connectivitymanagertest.stress;
-import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
-import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
-import com.android.connectivitymanagertest.UtilHelper;
-
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.State;
@@ -31,15 +27,15 @@
import android.net.wifi.WifiManager;
import android.os.Environment;
import android.os.PowerManager;
-import android.os.IPowerManager;
-import android.os.SystemClock;
-import android.os.ServiceManager;
import android.provider.Settings;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
-
import android.util.Log;
+import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
+import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
+import com.android.connectivitymanagertest.UtilHelper;
+
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -79,6 +75,7 @@
private String mPassword;
private ConnectivityManagerStressTestRunner mRunner;
private BufferedWriter mOutputWriter = null;
+ private boolean mIsWifiOnlyDevice;
public WifiStressTest() {
super(ConnectivityManagerTestActivity.class);
@@ -100,6 +97,7 @@
mOutputWriter = new BufferedWriter(new FileWriter(new File(
Environment.getExternalStorageDirectory(), OUTPUT_FILE), true));
mAct.turnScreenOn();
+ mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext());
if (!mAct.mWifiManager.isWifiEnabled()) {
log("Enable wi-fi before stress tests.");
if (!mAct.enableWifi()) {
@@ -271,7 +269,7 @@
assertTrue("Wait for Wi-Fi to idle timeout",
mAct.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
6 * ConnectivityManagerTestActivity.SHORT_TIMEOUT));
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
// use long timeout as the pppd startup may take several retries.
assertTrue("Wait for cellular connection timeout",
mAct.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
@@ -282,7 +280,7 @@
assertEquals("Wi-Fi is reconnected", State.DISCONNECTED,
mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState());
- if (!UtilHelper.isWifiOnly()) {
+ if (!mIsWifiOnlyDevice) {
assertEquals("Cellular connection is down", State.CONNECTED,
mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState());
assertTrue("Mobile is connected, but no data connection.", mAct.pingTest(null));
diff --git a/data/etc/android.hardware.bluetooth.xml b/data/etc/android.hardware.bluetooth.xml
new file mode 100644
index 0000000..4aa1744
--- /dev/null
+++ b/data/etc/android.hardware.bluetooth.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- Adds the feature indicating support for the Bluetooth API -->
+<permissions>
+ <feature name="android.hardware.bluetooth" />
+</permissions>
diff --git a/include/common_time/ICommonClock.h b/include/common_time/ICommonClock.h
new file mode 100644
index 0000000..d7073f1
--- /dev/null
+++ b/include/common_time/ICommonClock.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_ICOMMONCLOCK_H
+#define ANDROID_ICOMMONCLOCK_H
+
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class ICommonClockListener : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonClockListener);
+
+ virtual void onTimelineChanged(uint64_t timelineID) = 0;
+};
+
+class BnCommonClockListener : public BnInterface<ICommonClockListener> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+class ICommonClock : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonClock);
+
+ // Name of the ICommonClock service registered with the service manager.
+ static const String16 kServiceName;
+
+ // a reserved invalid timeline ID
+ static const uint64_t kInvalidTimelineID;
+
+ // a reserved invalid error estimate
+ static const int32_t kErrorEstimateUnknown;
+
+ enum State {
+ // the device just came up and is trying to discover the master
+ STATE_INITIAL,
+
+ // the device is a client of a master
+ STATE_CLIENT,
+
+ // the device is acting as master
+ STATE_MASTER,
+
+ // the device has lost contact with its master and needs to participate
+ // in the election of a new master
+ STATE_RONIN,
+
+ // the device is waiting for announcement of the newly elected master
+ STATE_WAIT_FOR_ELECTION,
+ };
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) = 0;
+ virtual status_t commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) = 0;
+ virtual status_t localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) = 0;
+ virtual status_t getCommonTime(int64_t* commonTime) = 0;
+ virtual status_t getCommonFreq(uint64_t* freq) = 0;
+ virtual status_t getLocalTime(int64_t* localTime) = 0;
+ virtual status_t getLocalFreq(uint64_t* freq) = 0;
+ virtual status_t getEstimatedError(int32_t* estimate) = 0;
+ virtual status_t getTimelineID(uint64_t* id) = 0;
+ virtual status_t getState(State* state) = 0;
+ virtual status_t getMasterAddr(struct sockaddr_storage* addr) = 0;
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener) = 0;
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener) = 0;
+
+ // Simple helper to make it easier to connect to the CommonClock service.
+ static inline sp<ICommonClock> getInstance() {
+ sp<IBinder> binder = defaultServiceManager()->checkService(
+ ICommonClock::kServiceName);
+ sp<ICommonClock> clk = interface_cast<ICommonClock>(binder);
+ return clk;
+ }
+};
+
+class BnCommonClock : public BnInterface<ICommonClock> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_ICOMMONCLOCK_H
diff --git a/include/common_time/ICommonTimeConfig.h b/include/common_time/ICommonTimeConfig.h
new file mode 100644
index 0000000..497b666
--- /dev/null
+++ b/include/common_time/ICommonTimeConfig.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_ICOMMONTIMECONFIG_H
+#define ANDROID_ICOMMONTIMECONFIG_H
+
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class String16;
+
+class ICommonTimeConfig : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonTimeConfig);
+
+ // Name of the ICommonTimeConfig service registered with the service
+ // manager.
+ static const String16 kServiceName;
+
+ virtual status_t getMasterElectionPriority(uint8_t *priority) = 0;
+ virtual status_t setMasterElectionPriority(uint8_t priority) = 0;
+ virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) = 0;
+ virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr) = 0;
+ virtual status_t getMasterElectionGroupId(uint64_t *id) = 0;
+ virtual status_t setMasterElectionGroupId(uint64_t id) = 0;
+ virtual status_t getInterfaceBinding(String16& ifaceName) = 0;
+ virtual status_t setInterfaceBinding(const String16& ifaceName) = 0;
+ virtual status_t getMasterAnnounceInterval(int *interval) = 0;
+ virtual status_t setMasterAnnounceInterval(int interval) = 0;
+ virtual status_t getClientSyncInterval(int *interval) = 0;
+ virtual status_t setClientSyncInterval(int interval) = 0;
+ virtual status_t getPanicThreshold(int *threshold) = 0;
+ virtual status_t setPanicThreshold(int threshold) = 0;
+ virtual status_t getAutoDisable(bool *autoDisable) = 0;
+ virtual status_t setAutoDisable(bool autoDisable) = 0;
+ virtual status_t forceNetworklessMasterMode() = 0;
+
+ // Simple helper to make it easier to connect to the CommonTimeConfig service.
+ static inline sp<ICommonTimeConfig> getInstance() {
+ sp<IBinder> binder = defaultServiceManager()->checkService(
+ ICommonTimeConfig::kServiceName);
+ sp<ICommonTimeConfig> clk = interface_cast<ICommonTimeConfig>(binder);
+ return clk;
+ }
+};
+
+class BnCommonTimeConfig : public BnInterface<ICommonTimeConfig> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_ICOMMONTIMECONFIG_H
diff --git a/include/common_time/cc_helper.h b/include/common_time/cc_helper.h
new file mode 100644
index 0000000..cbdacd0
--- /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-diable mode,
+// the service will go into networkless mode whenever it has no active clients.
+// It tracks active clients using registered CommonClockListeners (the callback
+// interface for onTimelineChanged) since this provides a convienent death
+// handler notification for when the service's clients die unexpectedly. This
+// means that users of the common time service should really always have a
+// CommonClockListener, unless they know that the time service is not running in
+// auto disabled mode, or that there is at least one other registered listener
+// active in the system. The CCHelper makes this a little easier by sharing a
+// ref counted ICommonClock interface across all clients and automatically
+// registering and unregistering a listener whenever there are CCHelper
+// instances active in the process.
+class CCHelper {
+ public:
+ CCHelper();
+ ~CCHelper();
+
+ status_t isCommonTimeValid(bool* valid, uint32_t* timelineID);
+ status_t commonTimeToLocalTime(int64_t commonTime, int64_t* localTime);
+ status_t localTimeToCommonTime(int64_t localTime, int64_t* commonTime);
+ status_t getCommonTime(int64_t* commonTime);
+ status_t getCommonFreq(uint64_t* freq);
+ status_t getLocalTime(int64_t* localTime);
+ status_t getLocalFreq(uint64_t* freq);
+
+ private:
+ class CommonClockListener : public BnCommonClockListener {
+ public:
+ void onTimelineChanged(uint64_t timelineID);
+ };
+
+ static bool verifyClock_l();
+
+ static Mutex lock_;
+ static sp<ICommonClock> common_clock_;
+ static sp<ICommonClockListener> common_clock_listener_;
+ static uint32_t ref_count_;
+};
+
+
+} // namespace android
+#endif // __CC_HELPER_H__
diff --git a/include/common_time/local_clock.h b/include/common_time/local_clock.h
new file mode 100644
index 0000000..845d1c21
--- /dev/null
+++ b/include/common_time/local_clock.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef __LOCAL_CLOCK_H__
+#define __LOCAL_CLOCK_H__
+
+#include <stdint.h>
+
+#include <hardware/local_time_hal.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class LocalClock {
+ public:
+ LocalClock();
+
+ bool initCheck();
+
+ int64_t getLocalTime();
+ uint64_t getLocalFreq();
+ status_t setLocalSlew(int16_t rate);
+ int32_t getDebugLog(struct local_time_debug_event* records,
+ int max_records);
+
+ private:
+ static Mutex dev_lock_;
+ static local_time_hw_device_t* dev_;
+};
+
+} // namespace android
+#endif // __LOCAL_CLOCK_H__
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index 1c401e2..a17ba2b 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -418,7 +418,7 @@
*/
status_t dump(int fd, const Vector<String16>& args) const;
-private:
+protected:
/* copying audio tracks is not allowed */
AudioTrack(const AudioTrack& other);
AudioTrack& operator = (const AudioTrack& other);
@@ -489,8 +489,27 @@
int mAuxEffectId;
Mutex mLock;
status_t mRestoreStatus;
+ bool mIsTimed;
};
+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 */
+ 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 9e3cb7f..f72452c 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -54,6 +54,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status) = 0;
diff --git a/include/media/IAudioTrack.h b/include/media/IAudioTrack.h
index 47d530b..7343a48 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 {
@@ -69,6 +69,23 @@
/* get this tracks control block */
virtual sp<IMemory> getCblk() const = 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/IMediaPlayer.h b/include/media/IMediaPlayer.h
index e905903..ed98dce 100644
--- a/include/media/IMediaPlayer.h
+++ b/include/media/IMediaPlayer.h
@@ -22,6 +22,10 @@
#include <binder/Parcel.h>
#include <utils/KeyedVector.h>
+// Fwd decl to make sure everyone agrees that the scope of struct sockaddr_in is
+// global, and not in android::
+struct sockaddr_in;
+
namespace android {
class Parcel;
@@ -58,6 +62,7 @@
virtual status_t attachAuxEffect(int effectId) = 0;
virtual status_t setParameter(int key, const Parcel& request) = 0;
virtual status_t getParameter(int key, Parcel* reply) = 0;
+ virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint) = 0;
// Invoke a generic method on the player by using opaque parcels
// for the request and reply.
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 80f43a3..74a265e 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -29,6 +29,10 @@
#include <media/AudioSystem.h>
#include <media/Metadata.h>
+// Fwd decl to make sure everyone agrees that the scope of struct sockaddr_in is
+// global, and not in android::
+struct sockaddr_in;
+
namespace android {
class Parcel;
@@ -46,6 +50,9 @@
// The shared library with the test player is passed passed as an
// argument to the 'test:' url in the setDataSource call.
TEST_PLAYER = 5,
+
+ AAH_RX_PLAYER = 100,
+ AAH_TX_PLAYER = 101,
};
@@ -136,6 +143,14 @@
virtual status_t setParameter(int key, const Parcel &request) = 0;
virtual status_t getParameter(int key, Parcel *reply) = 0;
+ // Right now, only the AAX TX player supports this functionality. For now,
+ // provide a default implementation which indicates a lack of support for
+ // this functionality to make life easier for all of the other media player
+ // maintainers out there.
+ virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint) {
+ return INVALID_OPERATION;
+ }
+
// Invoke a generic method on the player by using opaque parcels
// for the request and reply.
//
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index e6a0cc5..7cc1693 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_MEDIAPLAYER_H
#define ANDROID_MEDIAPLAYER_H
+#include <arpa/inet.h>
+
#include <binder/IMemory.h>
#include <media/IMediaPlayerClient.h>
#include <media/IMediaPlayer.h>
@@ -201,6 +203,7 @@
status_t attachAuxEffect(int effectId);
status_t setParameter(int key, const Parcel& request);
status_t getParameter(int key, Parcel* reply);
+ status_t setRetransmitEndpoint(const char* addrString, uint16_t port);
private:
void clear_l();
@@ -209,6 +212,7 @@
status_t getDuration_l(int *msec);
status_t attachNewPlayer(const sp<IMediaPlayer>& player);
status_t reset_l();
+ status_t doSetRetransmitEndpoint(const sp<IMediaPlayer>& player);
sp<IMediaPlayer> mPlayer;
thread_id_t mLockThreadId;
@@ -231,6 +235,8 @@
int mVideoHeight;
int mAudioSessionId;
float mSendLevel;
+ struct sockaddr_in mRetransmitEndpoint;
+ bool mRetransmitEndpointValid;
};
}; // namespace android
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 612ff93..e045b2c 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -955,6 +955,7 @@
UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK,
UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR,
UI_MODE_TYPE_TELEVISION = ACONFIGURATION_UI_MODE_TYPE_TELEVISION,
+ UI_MODE_TYPE_APPLIANCE = ACONFIGURATION_UI_MODE_TYPE_APPLIANCE,
// uiMode bits for the night switch.
MASK_UI_MODE_NIGHT = 0x30,
diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk
new file mode 100644
index 0000000..526f17b
--- /dev/null
+++ b/libs/common_time/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libcommon_time_client
+# (binder marshalers for ICommonClock as well as common clock and local clock
+# helper code)
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libcommon_time_client
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := cc_helper.cpp \
+ local_clock.cpp \
+ ICommonClock.cpp \
+ ICommonTimeConfig.cpp \
+ utils.cpp
+LOCAL_SHARED_LIBRARIES := libbinder \
+ libhardware \
+ libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/common_time/ICommonClock.cpp b/libs/common_time/ICommonClock.cpp
new file mode 100644
index 0000000..28b43ac
--- /dev/null
+++ b/libs/common_time/ICommonClock.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <linux/socket.h>
+
+#include <common_time/ICommonClock.h>
+#include <binder/Parcel.h>
+
+#include "utils.h"
+
+namespace android {
+
+/***** ICommonClock *****/
+
+enum {
+ IS_COMMON_TIME_VALID = IBinder::FIRST_CALL_TRANSACTION,
+ COMMON_TIME_TO_LOCAL_TIME,
+ LOCAL_TIME_TO_COMMON_TIME,
+ GET_COMMON_TIME,
+ GET_COMMON_FREQ,
+ GET_LOCAL_TIME,
+ GET_LOCAL_FREQ,
+ GET_ESTIMATED_ERROR,
+ GET_TIMELINE_ID,
+ GET_STATE,
+ GET_MASTER_ADDRESS,
+ REGISTER_LISTENER,
+ UNREGISTER_LISTENER,
+};
+
+const String16 ICommonClock::kServiceName("common_time.clock");
+const uint64_t ICommonClock::kInvalidTimelineID = 0;
+const int32_t ICommonClock::kErrorEstimateUnknown = 0x7FFFFFFF;
+
+class BpCommonClock : public BpInterface<ICommonClock>
+{
+ public:
+ BpCommonClock(const sp<IBinder>& impl)
+ : BpInterface<ICommonClock>(impl) {}
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(IS_COMMON_TIME_VALID,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *valid = reply.readInt32();
+ *timelineID = reply.readInt32();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeInt64(commonTime);
+ status_t status = remote()->transact(COMMON_TIME_TO_LOCAL_TIME,
+ data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *localTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeInt64(localTime);
+ status_t status = remote()->transact(LOCAL_TIME_TO_COMMON_TIME,
+ data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *commonTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getCommonTime(int64_t* commonTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_COMMON_TIME, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *commonTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getCommonFreq(uint64_t* freq) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_COMMON_FREQ, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *freq = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getLocalTime(int64_t* localTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_LOCAL_TIME, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *localTime = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getLocalFreq(uint64_t* freq) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_LOCAL_FREQ, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *freq = reply.readInt64();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getEstimatedError(int32_t* estimate) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_ESTIMATED_ERROR, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *estimate = reply.readInt32();
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getTimelineID(uint64_t* id) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_TIMELINE_ID, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *id = static_cast<uint64_t>(reply.readInt64());
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getState(State* state) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_STATE, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *state = static_cast<State>(reply.readInt32());
+ }
+ }
+ return status;
+ }
+
+ virtual status_t getMasterAddr(struct sockaddr_storage* addr) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ADDRESS, data, &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK)
+ deserializeSockaddr(&reply, addr);
+ }
+ return status;
+ }
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+
+ status_t status = remote()->transact(REGISTER_LISTENER, data, &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+ status_t status = remote()->transact(UNREGISTER_LISTENER, data, &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClock, "android.os.ICommonClock");
+
+status_t BnCommonClock::onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags) {
+ switch(code) {
+ case IS_COMMON_TIME_VALID: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ bool valid;
+ uint32_t timelineID;
+ status_t status = isCommonTimeValid(&valid, &timelineID);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(valid);
+ reply->writeInt32(timelineID);
+ }
+ return OK;
+ } break;
+
+ case COMMON_TIME_TO_LOCAL_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t commonTime = data.readInt64();
+ int64_t localTime;
+ status_t status = commonTimeToLocalTime(commonTime, &localTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(localTime);
+ }
+ return OK;
+ } break;
+
+ case LOCAL_TIME_TO_COMMON_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t localTime = data.readInt64();
+ int64_t commonTime;
+ status_t status = localTimeToCommonTime(localTime, &commonTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(commonTime);
+ }
+ return OK;
+ } break;
+
+ case GET_COMMON_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t commonTime;
+ status_t status = getCommonTime(&commonTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(commonTime);
+ }
+ return OK;
+ } break;
+
+ case GET_COMMON_FREQ: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t freq;
+ status_t status = getCommonFreq(&freq);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(freq);
+ }
+ return OK;
+ } break;
+
+ case GET_LOCAL_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t localTime;
+ status_t status = getLocalTime(&localTime);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(localTime);
+ }
+ return OK;
+ } break;
+
+ case GET_LOCAL_FREQ: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t freq;
+ status_t status = getLocalFreq(&freq);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(freq);
+ }
+ return OK;
+ } break;
+
+ case GET_ESTIMATED_ERROR: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int32_t error;
+ status_t status = getEstimatedError(&error);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(error);
+ }
+ return OK;
+ } break;
+
+ case GET_TIMELINE_ID: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t id;
+ status_t status = getTimelineID(&id);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(static_cast<int64_t>(id));
+ }
+ return OK;
+ } break;
+
+ case GET_STATE: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ State state;
+ status_t status = getState(&state);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(static_cast<int32_t>(state));
+ }
+ return OK;
+ } break;
+
+ case GET_MASTER_ADDRESS: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ struct sockaddr_storage addr;
+ status_t status = getMasterAddr(&addr);
+
+ if ((status == OK) && !canSerializeSockaddr(&addr)) {
+ status = UNKNOWN_ERROR;
+ }
+
+ reply->writeInt32(status);
+
+ if (status == OK) {
+ serializeSockaddr(reply, &addr);
+ }
+
+ return OK;
+ } break;
+
+ case REGISTER_LISTENER: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ sp<ICommonClockListener> listener =
+ interface_cast<ICommonClockListener>(data.readStrongBinder());
+ status_t status = registerListener(listener);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case UNREGISTER_LISTENER: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ sp<ICommonClockListener> listener =
+ interface_cast<ICommonClockListener>(data.readStrongBinder());
+ status_t status = unregisterListener(listener);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+/***** ICommonClockListener *****/
+
+enum {
+ ON_TIMELINE_CHANGED = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpCommonClockListener : public BpInterface<ICommonClockListener>
+{
+ public:
+ BpCommonClockListener(const sp<IBinder>& impl)
+ : BpInterface<ICommonClockListener>(impl) {}
+
+ virtual void onTimelineChanged(uint64_t timelineID) {
+ Parcel data, reply;
+ data.writeInterfaceToken(
+ ICommonClockListener::getInterfaceDescriptor());
+ data.writeInt64(timelineID);
+ remote()->transact(ON_TIMELINE_CHANGED, data, &reply);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClockListener,
+ "android.os.ICommonClockListener");
+
+status_t BnCommonClockListener::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch(code) {
+ case ON_TIMELINE_CHANGED: {
+ CHECK_INTERFACE(ICommonClockListener, data, reply);
+ uint32_t timelineID = data.readInt64();
+ onTimelineChanged(timelineID);
+ return NO_ERROR;
+ } break;
+ }
+
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/libs/common_time/ICommonTimeConfig.cpp b/libs/common_time/ICommonTimeConfig.cpp
new file mode 100644
index 0000000..8eb37cb
--- /dev/null
+++ b/libs/common_time/ICommonTimeConfig.cpp
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <linux/socket.h>
+
+#include <common_time/ICommonTimeConfig.h>
+#include <binder/Parcel.h>
+
+#include "utils.h"
+
+namespace android {
+
+/***** ICommonTimeConfig *****/
+
+enum {
+ GET_MASTER_ELECTION_PRIORITY = IBinder::FIRST_CALL_TRANSACTION,
+ SET_MASTER_ELECTION_PRIORITY,
+ GET_MASTER_ELECTION_ENDPOINT,
+ SET_MASTER_ELECTION_ENDPOINT,
+ GET_MASTER_ELECTION_GROUP_ID,
+ SET_MASTER_ELECTION_GROUP_ID,
+ GET_INTERFACE_BINDING,
+ SET_INTERFACE_BINDING,
+ GET_MASTER_ANNOUNCE_INTERVAL,
+ SET_MASTER_ANNOUNCE_INTERVAL,
+ GET_CLIENT_SYNC_INTERVAL,
+ SET_CLIENT_SYNC_INTERVAL,
+ GET_PANIC_THRESHOLD,
+ SET_PANIC_THRESHOLD,
+ GET_AUTO_DISABLE,
+ SET_AUTO_DISABLE,
+ FORCE_NETWORKLESS_MASTER_MODE,
+};
+
+const String16 ICommonTimeConfig::kServiceName("common_time.config");
+
+class BpCommonTimeConfig : public BpInterface<ICommonTimeConfig>
+{
+ public:
+ BpCommonTimeConfig(const sp<IBinder>& impl)
+ : BpInterface<ICommonTimeConfig>(impl) {}
+
+ virtual status_t getMasterElectionPriority(uint8_t *priority) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ELECTION_PRIORITY,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *priority = static_cast<uint8_t>(reply.readInt32());
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterElectionPriority(uint8_t priority) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(static_cast<int32_t>(priority));
+ status_t status = remote()->transact(SET_MASTER_ELECTION_PRIORITY,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ELECTION_ENDPOINT,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ deserializeSockaddr(&reply, addr);
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterElectionEndpoint(
+ const struct sockaddr_storage *addr) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ if (!canSerializeSockaddr(addr))
+ return BAD_VALUE;
+ if (NULL == addr) {
+ data.writeInt32(0);
+ } else {
+ data.writeInt32(1);
+ serializeSockaddr(&data, addr);
+ }
+ status_t status = remote()->transact(SET_MASTER_ELECTION_ENDPOINT,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getMasterElectionGroupId(uint64_t *id) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ELECTION_GROUP_ID,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *id = static_cast<uint64_t>(reply.readInt64());
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterElectionGroupId(uint64_t id) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt64(id);
+ status_t status = remote()->transact(SET_MASTER_ELECTION_GROUP_ID,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getInterfaceBinding(String16& ifaceName) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_INTERFACE_BINDING,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ ifaceName = reply.readString16();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setInterfaceBinding(const String16& ifaceName) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeString16(ifaceName);
+ status_t status = remote()->transact(SET_INTERFACE_BINDING,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getMasterAnnounceInterval(int *interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_MASTER_ANNOUNCE_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *interval = reply.readInt32();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setMasterAnnounceInterval(int interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(interval);
+ status_t status = remote()->transact(SET_MASTER_ANNOUNCE_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getClientSyncInterval(int *interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_CLIENT_SYNC_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *interval = reply.readInt32();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setClientSyncInterval(int interval) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(interval);
+ status_t status = remote()->transact(SET_CLIENT_SYNC_INTERVAL,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getPanicThreshold(int *threshold) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_PANIC_THRESHOLD,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *threshold = reply.readInt32();
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setPanicThreshold(int threshold) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(threshold);
+ status_t status = remote()->transact(SET_PANIC_THRESHOLD,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t getAutoDisable(bool *autoDisable) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_AUTO_DISABLE,
+ data,
+ &reply);
+ if (status == OK) {
+ status = reply.readInt32();
+ if (status == OK) {
+ *autoDisable = (0 != reply.readInt32());
+ }
+ }
+
+ return status;
+ }
+
+ virtual status_t setAutoDisable(bool autoDisable) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ data.writeInt32(autoDisable ? 1 : 0);
+ status_t status = remote()->transact(SET_AUTO_DISABLE,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+
+ virtual status_t forceNetworklessMasterMode() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+ status_t status = remote()->transact(FORCE_NETWORKLESS_MASTER_MODE,
+ data,
+ &reply);
+
+ if (status == OK) {
+ status = reply.readInt32();
+ }
+
+ return status;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonTimeConfig, "android.os.ICommonTimeConfig");
+
+status_t BnCommonTimeConfig::onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags) {
+ switch(code) {
+ case GET_MASTER_ELECTION_PRIORITY: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint8_t priority;
+ status_t status = getMasterElectionPriority(&priority);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(static_cast<int32_t>(priority));
+ }
+ return OK;
+ } break;
+
+ case SET_MASTER_ELECTION_PRIORITY: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint8_t priority = static_cast<uint8_t>(data.readInt32());
+ status_t status = setMasterElectionPriority(priority);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_MASTER_ELECTION_ENDPOINT: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ struct sockaddr_storage addr;
+ status_t status = getMasterElectionEndpoint(&addr);
+
+ if ((status == OK) && !canSerializeSockaddr(&addr)) {
+ status = UNKNOWN_ERROR;
+ }
+
+ reply->writeInt32(status);
+
+ if (status == OK) {
+ serializeSockaddr(reply, &addr);
+ }
+
+ return OK;
+ } break;
+
+ case SET_MASTER_ELECTION_ENDPOINT: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ struct sockaddr_storage addr;
+ int hasAddr = data.readInt32();
+
+ status_t status;
+ if (hasAddr) {
+ deserializeSockaddr(&data, &addr);
+ status = setMasterElectionEndpoint(&addr);
+ } else {
+ status = setMasterElectionEndpoint(&addr);
+ }
+
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_MASTER_ELECTION_GROUP_ID: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint64_t id;
+ status_t status = getMasterElectionGroupId(&id);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt64(id);
+ }
+ return OK;
+ } break;
+
+ case SET_MASTER_ELECTION_GROUP_ID: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ uint64_t id = static_cast<uint64_t>(data.readInt64());
+ status_t status = setMasterElectionGroupId(id);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_INTERFACE_BINDING: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ String16 ret;
+ status_t status = getInterfaceBinding(ret);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeString16(ret);
+ }
+ return OK;
+ } break;
+
+ case SET_INTERFACE_BINDING: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ String16 ifaceName;
+ ifaceName = data.readString16();
+ status_t status = setInterfaceBinding(ifaceName);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_MASTER_ANNOUNCE_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval;
+ status_t status = getMasterAnnounceInterval(&interval);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(interval);
+ }
+ return OK;
+ } break;
+
+ case SET_MASTER_ANNOUNCE_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval = data.readInt32();
+ status_t status = setMasterAnnounceInterval(interval);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_CLIENT_SYNC_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval;
+ status_t status = getClientSyncInterval(&interval);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(interval);
+ }
+ return OK;
+ } break;
+
+ case SET_CLIENT_SYNC_INTERVAL: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int interval = data.readInt32();
+ status_t status = setClientSyncInterval(interval);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_PANIC_THRESHOLD: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int threshold;
+ status_t status = getPanicThreshold(&threshold);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(threshold);
+ }
+ return OK;
+ } break;
+
+ case SET_PANIC_THRESHOLD: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ int threshold = data.readInt32();
+ status_t status = setPanicThreshold(threshold);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case GET_AUTO_DISABLE: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ bool autoDisable;
+ status_t status = getAutoDisable(&autoDisable);
+ reply->writeInt32(status);
+ if (status == OK) {
+ reply->writeInt32(autoDisable ? 1 : 0);
+ }
+ return OK;
+ } break;
+
+ case SET_AUTO_DISABLE: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ bool autoDisable = (0 != data.readInt32());
+ status_t status = setAutoDisable(autoDisable);
+ reply->writeInt32(status);
+ return OK;
+ } break;
+
+ case FORCE_NETWORKLESS_MASTER_MODE: {
+ CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+ status_t status = forceNetworklessMasterMode();
+ reply->writeInt32(status);
+ return OK;
+ } break;
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
+
diff --git a/libs/common_time/cc_helper.cpp b/libs/common_time/cc_helper.cpp
new file mode 100644
index 0000000..8d8556c
--- /dev/null
+++ b/libs/common_time/cc_helper.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include <common_time/cc_helper.h>
+#include <common_time/ICommonClock.h>
+#include <utils/threads.h>
+
+namespace android {
+
+Mutex CCHelper::lock_;
+sp<ICommonClock> CCHelper::common_clock_;
+sp<ICommonClockListener> CCHelper::common_clock_listener_;
+uint32_t CCHelper::ref_count_ = 0;
+
+bool CCHelper::verifyClock_l() {
+ bool ret = false;
+
+ if (common_clock_ == NULL) {
+ common_clock_ = ICommonClock::getInstance();
+ if (common_clock_ == NULL)
+ goto bailout;
+ }
+
+ if (ref_count_ > 0) {
+ if (common_clock_listener_ == NULL) {
+ common_clock_listener_ = new CommonClockListener();
+ if (common_clock_listener_ == NULL)
+ goto bailout;
+
+ if (OK != common_clock_->registerListener(common_clock_listener_))
+ goto bailout;
+ }
+ }
+
+ ret = true;
+
+bailout:
+ if (!ret) {
+ common_clock_listener_ = NULL;
+ common_clock_ = NULL;
+ }
+ return ret;
+}
+
+CCHelper::CCHelper() {
+ Mutex::Autolock lock(&lock_);
+ ref_count_++;
+ verifyClock_l();
+}
+
+CCHelper::~CCHelper() {
+ Mutex::Autolock lock(&lock_);
+
+ assert(ref_count_ > 0);
+ ref_count_--;
+
+ // If we were the last CCHelper instance in the system, and we had
+ // previously register a listener, unregister it now so that the common time
+ // service has the chance to go into auto-disabled mode.
+ if (!ref_count_ &&
+ (common_clock_ != NULL) &&
+ (common_clock_listener_ != NULL)) {
+ common_clock_->unregisterListener(common_clock_listener_);
+ common_clock_listener_ = NULL;
+ }
+}
+
+void CCHelper::CommonClockListener::onTimelineChanged(uint64_t timelineID) {
+ // do nothing; listener is only really used as a token so the server can
+ // find out when clients die.
+}
+
+// Helper methods which attempts to make calls to the common time binder
+// service. If the first attempt fails with DEAD_OBJECT, the helpers will
+// attempt to make a connection to the service again (assuming that the process
+// hosting the service had crashed and the client proxy we are holding is dead)
+// If the second attempt fails, or no connection can be made, the we let the
+// error propagate up the stack and let the caller deal with the situation as
+// best they can.
+#define CCHELPER_METHOD(decl, call) \
+ status_t CCHelper::decl { \
+ Mutex::Autolock lock(&lock_); \
+ \
+ if (!verifyClock_l()) \
+ return DEAD_OBJECT; \
+ \
+ status_t status = common_clock_->call; \
+ if (DEAD_OBJECT == status) { \
+ if (!verifyClock_l()) \
+ return DEAD_OBJECT; \
+ status = common_clock_->call; \
+ } \
+ \
+ return status; \
+ }
+
+#define VERIFY_CLOCK()
+
+CCHELPER_METHOD(isCommonTimeValid(bool* valid, uint32_t* timelineID),
+ isCommonTimeValid(valid, timelineID))
+CCHELPER_METHOD(commonTimeToLocalTime(int64_t commonTime, int64_t* localTime),
+ commonTimeToLocalTime(commonTime, localTime))
+CCHELPER_METHOD(localTimeToCommonTime(int64_t localTime, int64_t* commonTime),
+ localTimeToCommonTime(localTime, commonTime))
+CCHELPER_METHOD(getCommonTime(int64_t* commonTime),
+ getCommonTime(commonTime))
+CCHELPER_METHOD(getCommonFreq(uint64_t* freq),
+ getCommonFreq(freq))
+CCHELPER_METHOD(getLocalTime(int64_t* localTime),
+ getLocalTime(localTime))
+CCHELPER_METHOD(getLocalFreq(uint64_t* freq),
+ getLocalFreq(freq))
+
+} // namespace android
diff --git a/libs/common_time/local_clock.cpp b/libs/common_time/local_clock.cpp
new file mode 100644
index 0000000..9be3c46
--- /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) {
+ LOGE("Failed to open local time HAL module (res = %d)", res);
+ } else {
+ res = local_time_hw_device_open(mod, &dev_);
+ if (res) {
+ LOGE("Failed to open local time HAL device (res = %d)", res);
+ dev_ = NULL;
+ }
+ }
+}
+
+bool LocalClock::initCheck() {
+ return (NULL != dev_);
+}
+
+int64_t LocalClock::getLocalTime() {
+ assert(NULL != dev_);
+ assert(NULL != dev_->get_local_time);
+
+ return dev_->get_local_time(dev_);
+}
+
+uint64_t LocalClock::getLocalFreq() {
+ assert(NULL != dev_);
+ assert(NULL != dev_->get_local_freq);
+
+ return dev_->get_local_freq(dev_);
+}
+
+status_t LocalClock::setLocalSlew(int16_t rate) {
+ assert(NULL != dev_);
+
+ if (!dev_->set_local_slew)
+ return INVALID_OPERATION;
+
+ return static_cast<status_t>(dev_->set_local_slew(dev_, rate));
+}
+
+int32_t LocalClock::getDebugLog(struct local_time_debug_event* records,
+ int max_records) {
+ assert(NULL != dev_);
+
+ if (!dev_->get_debug_log)
+ return INVALID_OPERATION;
+
+ return dev_->get_debug_log(dev_, records, max_records);
+}
+
+} // namespace android
diff --git a/libs/common_time/utils.cpp b/libs/common_time/utils.cpp
new file mode 100644
index 0000000..6539171
--- /dev/null
+++ b/libs/common_time/utils.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <linux/socket.h>
+
+#include <binder/Parcel.h>
+
+namespace android {
+
+bool canSerializeSockaddr(const struct sockaddr_storage* addr) {
+ switch (addr->ss_family) {
+ case AF_INET:
+ case AF_INET6:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr) {
+ switch (addr->ss_family) {
+ case AF_INET: {
+ const struct sockaddr_in* s =
+ reinterpret_cast<const struct sockaddr_in*>(addr);
+ p->writeInt32(AF_INET);
+ p->writeInt32(ntohl(s->sin_addr.s_addr));
+ p->writeInt32(static_cast<int32_t>(ntohs(s->sin_port)));
+ } break;
+
+ case AF_INET6: {
+ const struct sockaddr_in6* s =
+ reinterpret_cast<const struct sockaddr_in6*>(addr);
+ const int32_t* a =
+ reinterpret_cast<const int32_t*>(s->sin6_addr.s6_addr);
+ p->writeInt32(AF_INET6);
+ p->writeInt32(ntohl(a[0]));
+ p->writeInt32(ntohl(a[1]));
+ p->writeInt32(ntohl(a[2]));
+ p->writeInt32(ntohl(a[3]));
+ p->writeInt32(static_cast<int32_t>(ntohs(s->sin6_port)));
+ p->writeInt32(ntohl(s->sin6_flowinfo));
+ p->writeInt32(ntohl(s->sin6_scope_id));
+ } break;
+ }
+}
+
+void deserializeSockaddr(const Parcel* p, struct sockaddr_storage* addr) {
+ memset(addr, 0, sizeof(addr));
+
+ addr->ss_family = p->readInt32();
+ switch(addr->ss_family) {
+ case AF_INET: {
+ struct sockaddr_in* s =
+ reinterpret_cast<struct sockaddr_in*>(addr);
+ s->sin_addr.s_addr = htonl(p->readInt32());
+ s->sin_port = htons(static_cast<uint16_t>(p->readInt32()));
+ } break;
+
+ case AF_INET6: {
+ struct sockaddr_in6* s =
+ reinterpret_cast<struct sockaddr_in6*>(addr);
+ int32_t* a = reinterpret_cast<int32_t*>(s->sin6_addr.s6_addr);
+
+ a[0] = htonl(p->readInt32());
+ a[1] = htonl(p->readInt32());
+ a[2] = htonl(p->readInt32());
+ a[3] = htonl(p->readInt32());
+ s->sin6_port = htons(static_cast<uint16_t>(p->readInt32()));
+ s->sin6_flowinfo = htonl(p->readInt32());
+ s->sin6_scope_id = htonl(p->readInt32());
+ } break;
+ }
+}
+
+} // namespace android
diff --git a/libs/common_time/utils.h b/libs/common_time/utils.h
new file mode 100644
index 0000000..ce79d0d
--- /dev/null
+++ b/libs/common_time/utils.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_LIBCOMMONCLOCK_UTILS_H
+#define ANDROID_LIBCOMMONCLOCK_UTILS_H
+
+#include <linux/socket.h>
+
+#include <binder/Parcel.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+extern bool canSerializeSockaddr(const struct sockaddr_storage* addr);
+extern void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr);
+extern status_t deserializeSockaddr(const Parcel* p,
+ struct sockaddr_storage* addr);
+
+}; // namespace android
+
+#endif // ANDROID_LIBCOMMONCLOCK_UTILS_H
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a0881a7..cb206b7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -52,6 +52,7 @@
private final Handler mHandler;
private long mVolumeKeyUpTime;
private int mVolumeControlStream = -1;
+ private final boolean mUseMasterVolume;
private static String TAG = "AudioManager";
private static boolean localLOGV = false;
@@ -95,7 +96,8 @@
* @see #EXTRA_VIBRATE_SETTING
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String VIBRATE_SETTING_CHANGED_ACTION = "android.media.VIBRATE_SETTING_CHANGED";
+ public static final String VIBRATE_SETTING_CHANGED_ACTION =
+ "android.media.VIBRATE_SETTING_CHANGED";
/**
* @hide Broadcast intent when the volume for a particular stream type changes.
@@ -109,6 +111,27 @@
public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
/**
+ * @hide Broadcast intent when the master volume changes.
+ * Includes the new volume
+ *
+ * @see #EXTRA_MASTER_VOLUME_VALUE
+ * @see #EXTRA_PREV_MASTER_VOLUME_VALUE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String MASTER_VOLUME_CHANGED_ACTION =
+ "android.media.MASTER_VOLUME_CHANGED_ACTION";
+
+ /**
+ * @hide Broadcast intent when the master mute state changes.
+ * Includes the the new volume
+ *
+ * @see #EXTRA_MASTER_VOLUME_MUTED
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String MASTER_MUTE_CHANGED_ACTION =
+ "android.media.MASTER_MUTE_CHANGED_ACTION";
+
+ /**
* The new vibrate setting for a particular type.
*
* @see #VIBRATE_SETTING_CHANGED_ACTION
@@ -145,6 +168,27 @@
public static final String EXTRA_PREV_VOLUME_STREAM_VALUE =
"android.media.EXTRA_PREV_VOLUME_STREAM_VALUE";
+ /**
+ * @hide The new master volume value for the master volume changed intent.
+ * Value is integer between 0 and 100 inclusive.
+ */
+ public static final String EXTRA_MASTER_VOLUME_VALUE =
+ "android.media.EXTRA_MASTER_VOLUME_VALUE";
+
+ /**
+ * @hide The previous master volume value for the master volume changed intent.
+ * Value is integer between 0 and 100 inclusive.
+ */
+ public static final String EXTRA_PREV_MASTER_VOLUME_VALUE =
+ "android.media.EXTRA_PREV_MASTER_VOLUME_VALUE";
+
+ /**
+ * @hide The new master volume mute state for the master mute changed intent.
+ * Value is boolean
+ */
+ public static final String EXTRA_MASTER_VOLUME_MUTED =
+ "android.media.EXTRA_MASTER_VOLUME_MUTED";
+
/** The audio stream for phone calls */
public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;
/** The audio stream for system sounds */
@@ -360,6 +404,8 @@
public AudioManager(Context context) {
mContext = context;
mHandler = new Handler(context.getMainLooper());
+ mUseMasterVolume = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useMasterVolume);
}
private static IAudioService getService()
@@ -375,11 +421,12 @@
/**
* @hide
*/
- public void preDispatchKeyEvent(int keyCode, int stream) {
+ public void preDispatchKeyEvent(KeyEvent event, int stream) {
/*
* If the user hits another key within the play sound delay, then
* cancel the sound
*/
+ int keyCode = event.getKeyCode();
if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP
&& keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
&& mVolumeKeyUpTime + VolumePanel.PLAY_SOUND_DELAY
@@ -388,15 +435,20 @@
* The user has hit another key during the delay (e.g., 300ms)
* since the last volume key up, so cancel any sounds.
*/
- adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME,
+ if (mUseMasterVolume) {
+ adjustMasterVolume(ADJUST_SAME, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ } else {
+ adjustSuggestedStreamVolume(ADJUST_SAME,
stream, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ }
}
}
/**
* @hide
*/
- public void handleKeyDown(int keyCode, int stream) {
+ public void handleKeyDown(KeyEvent event, int stream) {
+ int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -405,19 +457,33 @@
* responsive to the user.
*/
int flags = FLAG_SHOW_UI | FLAG_VIBRATE;
- if (mVolumeControlStream != -1) {
- stream = mVolumeControlStream;
- flags |= FLAG_FORCE_STREAM;
+ if (mUseMasterVolume) {
+ adjustMasterVolume(
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? ADJUST_RAISE
+ : ADJUST_LOWER,
+ flags);
+ } else {
+ if (mVolumeControlStream != -1) {
+ stream = mVolumeControlStream;
+ flags |= FLAG_FORCE_STREAM;
+ }
+ adjustSuggestedStreamVolume(
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? ADJUST_RAISE
+ : ADJUST_LOWER,
+ stream,
+ flags);
}
- adjustSuggestedStreamVolume(
- keyCode == KeyEvent.KEYCODE_VOLUME_UP
- ? ADJUST_RAISE
- : ADJUST_LOWER,
- stream,
- flags);
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
- // TODO: Actually handle MUTE.
+ if (event.getRepeatCount() == 0) {
+ if (mUseMasterVolume) {
+ setMasterMute(!isMasterMute());
+ } else {
+ // TODO: Actually handle MUTE.
+ }
+ }
break;
}
}
@@ -425,7 +491,8 @@
/**
* @hide
*/
- public void handleKeyUp(int keyCode, int stream) {
+ public void handleKeyUp(KeyEvent event, int stream) {
+ int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -433,21 +500,24 @@
* Play a sound. This is done on key up since we don't want the
* sound to play when a user holds down volume down to mute.
*/
- int flags = FLAG_PLAY_SOUND;
- if (mVolumeControlStream != -1) {
- stream = mVolumeControlStream;
- flags |= FLAG_FORCE_STREAM;
+ if (mUseMasterVolume) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ adjustMasterVolume(ADJUST_SAME, FLAG_PLAY_SOUND);
+ }
+ } else {
+ int flags = FLAG_PLAY_SOUND;
+ if (mVolumeControlStream != -1) {
+ stream = mVolumeControlStream;
+ flags |= FLAG_FORCE_STREAM;
+ }
+ adjustSuggestedStreamVolume(
+ ADJUST_SAME,
+ stream,
+ flags);
}
- adjustSuggestedStreamVolume(
- ADJUST_SAME,
- stream,
- flags);
mVolumeKeyUpTime = SystemClock.uptimeMillis();
break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- // TODO: Actually handle MUTE.
- break;
}
}
@@ -470,7 +540,13 @@
public void adjustStreamVolume(int streamType, int direction, int flags) {
IAudioService service = getService();
try {
- service.adjustStreamVolume(streamType, direction, flags);
+ Log.d(TAG, "adjustStreamVolume mUseMasterVolume=" + mUseMasterVolume
+ + " direction=" + direction + " flags=0x" + Integer.toHexString(flags));
+ if (mUseMasterVolume) {
+ service.adjustMasterVolume(direction, flags);
+ } else {
+ service.adjustStreamVolume(streamType, direction, flags);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustStreamVolume", e);
}
@@ -496,7 +572,11 @@
public void adjustVolume(int direction, int flags) {
IAudioService service = getService();
try {
- service.adjustVolume(direction, flags);
+ if (mUseMasterVolume) {
+ service.adjustMasterVolume(direction, flags);
+ } else {
+ service.adjustVolume(direction, flags);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustVolume", e);
}
@@ -522,9 +602,31 @@
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
IAudioService service = getService();
try {
- service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
+ if (mUseMasterVolume) {
+ service.adjustMasterVolume(direction, flags);
+ } else {
+ service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
+ }
} catch (RemoteException e) {
- Log.e(TAG, "Dead object in adjustVolume", e);
+ Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);
+ }
+ }
+
+ /**
+ * Adjusts the master volume for the device's audio amplifier.
+ * <p>
+ *
+ * @param steps The number of volume steps to adjust. A positive
+ * value will raise the volume.
+ * @param flags One or more flags.
+ * @hide
+ */
+ public void adjustMasterVolume(int steps, int flags) {
+ IAudioService service = getService();
+ try {
+ service.adjustMasterVolume(steps, flags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in adjustMasterVolume", e);
}
}
@@ -570,7 +672,11 @@
public int getStreamMaxVolume(int streamType) {
IAudioService service = getService();
try {
- return service.getStreamMaxVolume(streamType);
+ if (mUseMasterVolume) {
+ return service.getMasterMaxVolume();
+ } else {
+ return service.getStreamMaxVolume(streamType);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getStreamMaxVolume", e);
return 0;
@@ -588,7 +694,11 @@
public int getStreamVolume(int streamType) {
IAudioService service = getService();
try {
- return service.getStreamVolume(streamType);
+ if (mUseMasterVolume) {
+ return service.getMasterVolume();
+ } else {
+ return service.getStreamVolume(streamType);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getStreamVolume", e);
return 0;
@@ -603,7 +713,11 @@
public int getLastAudibleStreamVolume(int streamType) {
IAudioService service = getService();
try {
- return service.getLastAudibleStreamVolume(streamType);
+ if (mUseMasterVolume) {
+ return service.getLastAudibleMasterVolume();
+ } else {
+ return service.getLastAudibleStreamVolume(streamType);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getLastAudibleStreamVolume", e);
return 0;
@@ -646,13 +760,82 @@
public void setStreamVolume(int streamType, int index, int flags) {
IAudioService service = getService();
try {
- service.setStreamVolume(streamType, index, flags);
+ if (mUseMasterVolume) {
+ service.setMasterVolume(index, flags);
+ } else {
+ service.setStreamVolume(streamType, index, flags);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setStreamVolume", e);
}
}
/**
+ * Returns the maximum volume index for master volume.
+ *
+ * @hide
+ */
+ public int getMasterMaxVolume() {
+ IAudioService service = getService();
+ try {
+ return service.getMasterMaxVolume();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getMasterMaxVolume", e);
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the current volume index for master volume.
+ *
+ * @return The current volume index for master volume.
+ * @hide
+ */
+ public int getMasterVolume() {
+ IAudioService service = getService();
+ try {
+ return service.getMasterVolume();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getMasterVolume", e);
+ return 0;
+ }
+ }
+
+ /**
+ * Get last audible volume before master volume was muted.
+ *
+ * @hide
+ */
+ public int getLastAudibleMasterVolume() {
+ IAudioService service = getService();
+ try {
+ return service.getLastAudibleMasterVolume();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in getLastAudibleMasterVolume", e);
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the volume index for master volume.
+ *
+ * @param index The volume index to set. See
+ * {@link #getMasterMaxVolume(int)} for the largest valid value.
+ * @param flags One or more flags.
+ * @see #getMasterMaxVolume(int)
+ * @see #getMasterVolume(int)
+ * @hide
+ */
+ public void setMasterVolume(int index, int flags) {
+ IAudioService service = getService();
+ try {
+ service.setMasterVolume(index, flags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in setMasterVolume", e);
+ }
+ }
+
+ /**
* Solo or unsolo a particular stream. All other streams are muted.
* <p>
* The solo command is protected against client process death: if a process
@@ -723,6 +906,35 @@
}
/**
+ * set master mute state.
+ *
+ * @hide
+ */
+ public void setMasterMute(boolean state) {
+ IAudioService service = getService();
+ try {
+ service.setMasterMute(state, mICallBack);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in setMasterMute", e);
+ }
+ }
+
+ /**
+ * get master mute state.
+ *
+ * @hide
+ */
+ public boolean isMasterMute() {
+ IAudioService service = getService();
+ try {
+ return service.isMasterMute();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in isMasterMute", e);
+ return false;
+ }
+ }
+
+ /**
* forces the stream controlled by hard volume keys
* specifying streamType == -1 releases control to the
* logic.
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 37aacab..985cdf4 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -114,6 +114,7 @@
// AudioHandler message.whats
private static final int MSG_SET_SYSTEM_VOLUME = 0;
private static final int MSG_PERSIST_VOLUME = 1;
+ private static final int MSG_PERSIST_MASTER_VOLUME = 2;
private static final int MSG_PERSIST_RINGER_MODE = 3;
private static final int MSG_PERSIST_VIBRATE_SETTING = 4;
private static final int MSG_MEDIA_SERVER_DIED = 5;
@@ -131,7 +132,6 @@
// Timeout for connection to bluetooth headset service
private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
-
/** @see AudioSystemThread */
private AudioSystemThread mAudioSystemThread;
/** @see AudioHandler */
@@ -149,6 +149,13 @@
private static final int NUM_SOUNDPOOL_CHANNELS = 4;
private static final int SOUND_EFFECT_VOLUME = 1000;
+ // Internally master volume is a float in the 0.0 - 1.0 range,
+ // but to support integer based AudioManager API we translate it to 0 - 100
+ private static final int MAX_MASTER_VOLUME = 100;
+
+ // Maximum volume adjust steps allowed in a single batch call.
+ private static final int MAX_BATCH_VOLUME_ADJUST_STEPS = 4;
+
/* Sound effect file names */
private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
private static final String[] SOUND_EFFECT_FILES = new String[] {
@@ -268,6 +275,11 @@
// Forced device usage for communications
private int mForcedUseForComm;
+ // True if we have master volume support
+ private final boolean mUseMasterVolume;
+
+ private final int[] mMasterVolumeRamp;
+
// List of binder death handlers for setMode() client processes.
// The last process to have called setMode() is at the top of the list.
private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
@@ -393,6 +405,13 @@
TelephonyManager tmgr = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE);
tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+ mUseMasterVolume = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_useMasterVolume);
+ restoreMasterVolume();
+
+ mMasterVolumeRamp = context.getResources().getIntArray(
+ com.android.internal.R.array.config_masterVolumeRamp);
}
private void createAudioSystemThread() {
@@ -575,6 +594,22 @@
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
+ /** @see AudioManager#adjustMasterVolume(int) */
+ public void adjustMasterVolume(int steps, int flags) {
+ ensureValidSteps(steps);
+ int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
+ int delta = 0;
+ int numSteps = Math.abs(steps);
+ int direction = steps > 0 ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
+ for (int i = 0; i < numSteps; ++i) {
+ delta = findVolumeDelta(direction, volume);
+ volume += delta;
+ }
+
+ //Log.d(TAG, "adjustMasterVolume volume: " + volume + " steps: " + steps);
+ setMasterVolume(volume, flags);
+ }
+
/** @see AudioManager#setStreamVolume(int, int, int) */
public void setStreamVolume(int streamType, int index, int flags) {
ensureValidStreamType(streamType);
@@ -607,6 +642,41 @@
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
+ private int findVolumeDelta(int direction, int volume) {
+ int delta = 0;
+ if (direction == AudioManager.ADJUST_RAISE) {
+ if (volume == MAX_MASTER_VOLUME) {
+ return 0;
+ }
+ // This is the default value if we make it to the end
+ delta = mMasterVolumeRamp[1];
+ // If we're raising the volume move down the ramp array until we
+ // find the volume we're above and use that groups delta.
+ for (int i = mMasterVolumeRamp.length - 1; i > 1; i -= 2) {
+ if (volume >= mMasterVolumeRamp[i - 1]) {
+ delta = mMasterVolumeRamp[i];
+ break;
+ }
+ }
+ } else if (direction == AudioManager.ADJUST_LOWER){
+ if (volume == 0) {
+ return 0;
+ }
+ int length = mMasterVolumeRamp.length;
+ // This is the default value if we make it to the end
+ delta = -mMasterVolumeRamp[length - 1];
+ // If we're lowering the volume move up the ramp array until we
+ // find the volume we're below and use the group below it's delta
+ for (int i = 2; i < length; i += 2) {
+ if (volume <= mMasterVolumeRamp[i]) {
+ delta = -mMasterVolumeRamp[i - 1];
+ break;
+ }
+ }
+ }
+ return delta;
+ }
+
// UI update and Broadcast Intent
private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
if (!mVoiceCapable && (streamType == AudioSystem.STREAM_RING)) {
@@ -624,6 +694,27 @@
mContext.sendBroadcast(intent);
}
+ // UI update and Broadcast Intent
+ private void sendMasterVolumeUpdate(int flags, int oldVolume, int newVolume) {
+ mVolumePanel.postMasterVolumeChanged(flags);
+
+ Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION);
+ intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume);
+ intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_VALUE, newVolume);
+ mContext.sendBroadcast(intent);
+ }
+
+ // UI update and Broadcast Intent
+ private void sendMasterMuteUpdate(boolean muted, int flags) {
+ mVolumePanel.postMasterMuteChanged(flags);
+
+ Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
+ intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted);
+ long origCallerIdentityToken = Binder.clearCallingIdentity();
+ mContext.sendStickyBroadcast(intent);
+ Binder.restoreCallingIdentity(origCallerIdentityToken);
+ }
+
/**
* Sets the stream state's index, and posts a message to set system volume.
* This will not call out to the UI. Assumes a valid stream type.
@@ -677,18 +768,64 @@
return (mStreamStates[streamType].muteCount() != 0);
}
+ /** @see AudioManager#setMasterMute(boolean, IBinder) */
+ public void setMasterMute(boolean state, IBinder cb) {
+ if (state != AudioSystem.getMasterMute()) {
+ AudioSystem.setMasterMute(state);
+ sendMasterMuteUpdate(state, AudioManager.FLAG_SHOW_UI);
+ }
+ }
+
+ /** get master mute state. */
+ public boolean isMasterMute() {
+ return AudioSystem.getMasterMute();
+ }
+
/** @see AudioManager#getStreamVolume(int) */
public int getStreamVolume(int streamType) {
ensureValidStreamType(streamType);
return (mStreamStates[streamType].mIndex + 5) / 10;
}
+ public int getMasterVolume() {
+ if (isMasterMute()) return 0;
+ return getLastAudibleMasterVolume();
+ }
+
+ public void setMasterVolume(int volume, int flags) {
+ if (volume < 0) {
+ volume = 0;
+ } else if (volume > MAX_MASTER_VOLUME) {
+ volume = MAX_MASTER_VOLUME;
+ }
+ doSetMasterVolume((float)volume / MAX_MASTER_VOLUME, flags);
+ }
+
+ private void doSetMasterVolume(float volume, int flags) {
+ // don't allow changing master volume when muted
+ if (!AudioSystem.getMasterMute()) {
+ int oldVolume = getMasterVolume();
+ AudioSystem.setMasterVolume(volume);
+
+ int newVolume = getMasterVolume();
+ if (newVolume != oldVolume) {
+ // Post a persist master volume msg
+ sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME, 0, SENDMSG_REPLACE,
+ Math.round(volume * (float)1000.0), 0, null, PERSIST_DELAY);
+ sendMasterVolumeUpdate(flags, oldVolume, newVolume);
+ }
+ }
+ }
+
/** @see AudioManager#getStreamMaxVolume(int) */
public int getStreamMaxVolume(int streamType) {
ensureValidStreamType(streamType);
return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
}
+ public int getMasterMaxVolume() {
+ return MAX_MASTER_VOLUME;
+ }
/** Get last audible volume before stream was muted. */
public int getLastAudibleStreamVolume(int streamType) {
@@ -696,6 +833,11 @@
return (mStreamStates[streamType].mLastAudibleIndex + 5) / 10;
}
+ /** Get last audible master volume before it was muted. */
+ public int getLastAudibleMasterVolume() {
+ return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
+ }
+
/** @see AudioManager#getRingerMode() */
public int getRingerMode() {
return mRingerMode;
@@ -750,6 +892,16 @@
}
}
+ private void restoreMasterVolume() {
+ if (mUseMasterVolume) {
+ float volume = Settings.System.getFloat(mContentResolver,
+ Settings.System.VOLUME_MASTER, -1.0f);
+ if (volume >= 0.0f) {
+ AudioSystem.setMasterVolume(volume);
+ }
+ }
+ }
+
/** @see AudioManager#shouldVibrate(int) */
public boolean shouldVibrate(int vibrateType) {
@@ -1733,6 +1885,12 @@
}
}
+ private void ensureValidSteps(int steps) {
+ if (Math.abs(steps) > MAX_BATCH_VOLUME_ADJUST_STEPS) {
+ throw new IllegalArgumentException("Bad volume adjust steps " + steps);
+ }
+ }
+
private void ensureValidStreamType(int streamType) {
if (streamType < 0 || streamType >= mStreamStates.length) {
throw new IllegalArgumentException("Bad stream type " + streamType);
@@ -1838,8 +1996,7 @@
return;
}
- handler
- .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
+ handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
}
boolean checkAudioSettingsPermission(String method) {
@@ -2203,6 +2360,11 @@
persistVolume((VolumeStreamState) msg.obj, (msg.arg1 != 0), (msg.arg2 != 0));
break;
+ case MSG_PERSIST_MASTER_VOLUME:
+ Settings.System.putFloat(mContentResolver, Settings.System.VOLUME_MASTER,
+ (float)msg.arg1 / (float)1000.0);
+ break;
+
case MSG_PERSIST_RINGER_MODE:
persistRingerMode();
break;
@@ -2266,6 +2428,9 @@
// Restore ringer mode
setRingerModeInt(getRingerMode(), false);
+ // Restore master volume
+ restoreMasterVolume();
+
// indicate the end of reconfiguration phase to audio HAL
AudioSystem.setParameters("restarting=false");
break;
@@ -2686,6 +2851,11 @@
adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
BluetoothProfile.A2DP);
}
+
+ if (mUseMasterVolume) {
+ // Send sticky broadcast for initial master mute state
+ sendMasterMuteUpdate(false, 0);
+ }
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
// a package is being removed, not replaced
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 95d93b2..a4520b8 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -253,5 +253,9 @@
public static native int initStreamVolume(int stream, int indexMin, int indexMax);
public static native int setStreamVolumeIndex(int stream, int index);
public static native int getStreamVolumeIndex(int stream);
+ public static native int setMasterVolume(float value);
+ public static native float getMasterVolume();
+ public static native int setMasterMute(boolean mute);
+ public static native boolean getMasterMute();
public static native int getDevicesForStream(int stream);
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5294d36..17d8e4d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -30,10 +30,14 @@
void adjustVolume(int direction, int flags);
void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
-
+
void adjustStreamVolume(int streamType, int direction, int flags);
-
+
+ void adjustMasterVolume(int direction, int flags);
+
void setStreamVolume(int streamType, int index, int flags);
+
+ void setMasterVolume(int index, int flags);
void setStreamSolo(int streamType, boolean state, IBinder cb);
@@ -41,12 +45,22 @@
boolean isStreamMute(int streamType);
+ void setMasterMute(boolean state, IBinder cb);
+
+ boolean isMasterMute();
+
int getStreamVolume(int streamType);
-
+
+ int getMasterVolume();
+
int getStreamMaxVolume(int streamType);
+
+ int getMasterMaxVolume();
int getLastAudibleStreamVolume(int streamType);
+ int getLastAudibleMasterVolume();
+
void setRingerMode(int ringerMode);
int getRingerMode();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 8d71dcf..ba22a13 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -35,6 +35,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Set;
import java.lang.ref.WeakReference;
@@ -1486,6 +1487,34 @@
*/
public native static int native_pullBatteryData(Parcel reply);
+ /**
+ * Sets the target re-transmit endpoint for the low level player. When set, the player will
+ * attempt to re-mux its media data using the A@H RTP profile and re-transmit to the target
+ * endpoint. setRetransmitEndpoint may only be called before setDataSource has been called;
+ * while the player is in the Idle state.
+ *
+ * @param endpoint the address and UDP port of the re-transmission target or null if no
+ * re-transmission is to be performed.
+ * @throws IllegalStateException if it is called in an invalid state
+ * @return The status code.
+ * @hide pending API council
+ */
+ public int setRetransmitEndpoint(InetSocketAddress endpoint)
+ throws IllegalStateException
+ {
+ String addrString = null;
+ int port = 0;
+
+ if (null != endpoint) {
+ addrString = endpoint.getAddress().getHostAddress();
+ port = endpoint.getPort();
+ }
+
+ return native_setRetransmitEndpoint(addrString, port);
+ }
+
+ private native final int native_setRetransmitEndpoint(String addrString, int port);
+
@Override
protected void finalize() { native_finalize(); }
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index bcf7b89..91d0add 100755
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -84,6 +84,7 @@
// to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp
private static final int NATIVE_EVENT_PCM_CAPTURE = 0;
private static final int NATIVE_EVENT_FFT_CAPTURE = 1;
+ private static final int NATIVE_EVENT_SERVER_DIED = 2;
// Error codes:
/**
@@ -147,6 +148,10 @@
* PCM and FFT capture listener registered by client
*/
private OnDataCaptureListener mCaptureListener = null;
+ /**
+ * Server Died listener registered by client
+ */
+ private OnServerDiedListener mServerDiedListener = null;
// accessed by native methods
private int mNativeVisualizer;
@@ -396,6 +401,9 @@
public interface OnDataCaptureListener {
/**
* Method called when a new waveform capture is available.
+ * <p>Data in the waveform buffer is valid only within the scope of the callback.
+ * Applications which needs access to the waveform data after returning from the callback
+ * should make a copy of the data instead of holding a reference.
* @param visualizer Visualizer object on which the listener is registered.
* @param waveform array of bytes containing the waveform representation.
* @param samplingRate sampling rate of the audio visualized.
@@ -404,6 +412,9 @@
/**
* Method called when a new frequency capture is available.
+ * <p>Data in the fft buffer is valid only within the scope of the callback.
+ * Applications which needs access to the fft data after returning from the callback
+ * should make a copy of the data instead of holding a reference.
* @param visualizer Visualizer object on which the listener is registered.
* @param fft array of bytes containing the frequency representation.
* @param samplingRate sampling rate of the audio visualized.
@@ -452,6 +463,43 @@
}
/**
+ * @hide
+ *
+ * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that
+ * the connection to the native media server has been broken and that the Visualizer object will
+ * need to be released and re-created.
+ * The client application can implement this interface and register the listener with the
+ * {@link #setServerDiedListener(OnServerDiedListener)} method.
+ */
+ public interface OnServerDiedListener {
+ /**
+ * @hide
+ *
+ * Method called when the native media server has died.
+ * <p>If the native media server encounters a fatal error and needs to restart, the binder
+ * connection from the {@link #Visualizer} to the media server will be broken. Data capture
+ * callbacks will stop happening, and client initiated calls to the {@link #Visualizer}
+ * instance will fail with the error code {@link #DEAD_OBJECT}. To restore functionality,
+ * clients should {@link #release()} their old visualizer and create a new instance.
+ */
+ void onServerDied();
+ }
+
+ /**
+ * @hide
+ *
+ * Registers an OnServerDiedListener interface.
+ * <p>Call this method with a null listener to stop receiving server death notifications.
+ * @return {@link #SUCCESS} in case of success,
+ */
+ public int setServerDiedListener(OnServerDiedListener listener) {
+ synchronized (mListenerLock) {
+ mServerDiedListener = listener;
+ }
+ return SUCCESS;
+ }
+
+ /**
* Helper class to handle the forwarding of native events to the appropriate listeners
*/
private class NativeEventHandler extends Handler
@@ -463,11 +511,7 @@
mVisualizer = v;
}
- @Override
- public void handleMessage(Message msg) {
- if (mVisualizer == null) {
- return;
- }
+ private void handleCaptureMessage(Message msg) {
OnDataCaptureListener l = null;
synchronized (mListenerLock) {
l = mVisualizer.mCaptureListener;
@@ -476,6 +520,7 @@
if (l != null) {
byte[] data = (byte[])msg.obj;
int samplingRate = msg.arg1;
+
switch(msg.what) {
case NATIVE_EVENT_PCM_CAPTURE:
l.onWaveFormDataCapture(mVisualizer, data, samplingRate);
@@ -484,11 +529,41 @@
l.onFftDataCapture(mVisualizer, data, samplingRate);
break;
default:
- Log.e(TAG,"Unknown native event: "+msg.what);
+ Log.e(TAG,"Unknown native event in handleCaptureMessge: "+msg.what);
break;
}
}
}
+
+ private void handleServerDiedMessage(Message msg) {
+ OnServerDiedListener l = null;
+ synchronized (mListenerLock) {
+ l = mVisualizer.mServerDiedListener;
+ }
+
+ if (l != null)
+ l.onServerDied();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mVisualizer == null) {
+ return;
+ }
+
+ switch(msg.what) {
+ case NATIVE_EVENT_PCM_CAPTURE:
+ case NATIVE_EVENT_FFT_CAPTURE:
+ handleCaptureMessage(msg);
+ break;
+ case NATIVE_EVENT_SERVER_DIED:
+ handleServerDiedMessage(msg);
+ break;
+ default:
+ Log.e(TAG,"Unknown native event: "+msg.what);
+ break;
+ }
+ }
}
//---------------------------------------------------------
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 63cbf5e..f3c27d6 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -722,6 +722,45 @@
return service->pullBatteryData(reply);
}
+static jint
+android_media_MediaPlayer_setRetransmitEndpoint(JNIEnv *env, jobject thiz,
+ jstring addrString, jint port) {
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return INVALID_OPERATION;
+ }
+
+ const char *cAddrString = NULL;
+
+ if (NULL != addrString) {
+ cAddrString = env->GetStringUTFChars(addrString, NULL);
+ if (cAddrString == NULL) { // Out of memory
+ return NO_MEMORY;
+ }
+ }
+ LOGV("setRetransmitEndpoint: %s:%d",
+ cAddrString ? cAddrString : "(null)", port);
+
+ status_t ret;
+ if (cAddrString && (port > 0xFFFF)) {
+ ret = BAD_VALUE;
+ } else {
+ ret = mp->setRetransmitEndpoint(cAddrString,
+ static_cast<uint16_t>(port));
+ }
+
+ if (NULL != addrString) {
+ env->ReleaseStringUTFChars(addrString, cAddrString);
+ }
+
+ if (ret == INVALID_OPERATION ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ }
+
+ return ret;
+}
+
static jboolean
android_media_MediaPlayer_setParameter(JNIEnv *env, jobject thiz, jint key, jobject java_request)
{
@@ -799,6 +838,7 @@
{"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData},
{"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_setParameter},
{"getParameter", "(ILandroid/os/Parcel;)V", (void *)android_media_MediaPlayer_getParameter},
+ {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint},
};
static const char* const kClassPathName = "android/media/MediaPlayer";
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 27b28ee..c05b003 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -23,6 +23,7 @@
#include <nativehelper/jni.h>
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
+#include <utils/threads.h>
#include "media/Visualizer.h"
using namespace android;
@@ -38,6 +39,7 @@
#define NATIVE_EVENT_PCM_CAPTURE 0
#define NATIVE_EVENT_FFT_CAPTURE 1
+#define NATIVE_EVENT_SERVER_DIED 2
// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/audiofx/Visualizer";
@@ -54,6 +56,43 @@
struct visualizer_callback_cookie {
jclass visualizer_class; // Visualizer class
jobject visualizer_ref; // Visualizer object instance
+
+ // Lazily allocated arrays used to hold callback data provided to java
+ // applications. These arrays are allocated during the first callback and
+ // reallocated when the size of the callback data changes. Allocating on
+ // demand and saving the arrays means that applications cannot safely hold a
+ // reference to the provided data (they need to make a copy if they want to
+ // hold onto outside of the callback scope), but it avoids GC thrash caused
+ // by constantly allocating and releasing arrays to hold callback data.
+ Mutex callback_data_lock;
+ jbyteArray waveform_data;
+ jbyteArray fft_data;
+
+ visualizer_callback_cookie() {
+ waveform_data = NULL;
+ fft_data = NULL;
+ }
+
+ ~visualizer_callback_cookie() {
+ cleanupBuffers();
+ }
+
+ void cleanupBuffers() {
+ AutoMutex lock(&callback_data_lock);
+ if (waveform_data || fft_data) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ if (waveform_data) {
+ env->DeleteGlobalRef(waveform_data);
+ waveform_data = NULL;
+ }
+
+ if (fft_data) {
+ env->DeleteGlobalRef(fft_data);
+ fft_data = NULL;
+ }
+ }
+ }
};
// ----------------------------------------------------------------------------
@@ -66,7 +105,6 @@
~visualizerJniStorage() {
}
-
};
@@ -93,6 +131,26 @@
// ----------------------------------------------------------------------------
+static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
+ if (NULL != *array) {
+ uint32_t len = env->GetArrayLength(*array);
+ if (len == size)
+ return;
+
+ env->DeleteGlobalRef(*array);
+ *array = NULL;
+ }
+
+ jbyteArray localRef = env->NewByteArray(size);
+ if (NULL != localRef) {
+ // Promote to global ref.
+ *array = (jbyteArray)env->NewGlobalRef(localRef);
+
+ // Release our (now pointless) local ref.
+ env->DeleteLocalRef(localRef);
+ }
+}
+
static void captureCallback(void* user,
uint32_t waveformSize,
uint8_t *waveform,
@@ -106,6 +164,7 @@
visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user;
JNIEnv *env = AndroidRuntime::getJNIEnv();
+ AutoMutex lock(&callbackInfo->callback_data_lock);
LOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
callbackInfo,
@@ -118,7 +177,11 @@
}
if (waveformSize != 0 && waveform != NULL) {
- jbyteArray jArray = env->NewByteArray(waveformSize);
+ jbyteArray jArray;
+
+ ensureArraySize(env, &callbackInfo->waveform_data, waveformSize);
+ jArray = callbackInfo->waveform_data;
+
if (jArray != NULL) {
jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
memcpy(nArray, waveform, waveformSize);
@@ -131,12 +194,15 @@
samplingrate,
0,
jArray);
- env->DeleteLocalRef(jArray);
}
}
if (fftSize != 0 && fft != NULL) {
- jbyteArray jArray = env->NewByteArray(fftSize);
+ jbyteArray jArray;
+
+ ensureArraySize(env, &callbackInfo->fft_data, fftSize);
+ jArray = callbackInfo->fft_data;
+
if (jArray != NULL) {
jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
memcpy(nArray, fft, fftSize);
@@ -149,7 +215,6 @@
samplingrate,
0,
jArray);
- env->DeleteLocalRef(jArray);
}
}
@@ -220,6 +285,23 @@
}
+static void android_media_visualizer_effect_callback(int32_t event,
+ void *user,
+ void *info) {
+ if ((event == AudioEffect::EVENT_ERROR) &&
+ (*((status_t*)info) == DEAD_OBJECT)) {
+ visualizerJniStorage* lpJniStorage = (visualizerJniStorage*)user;
+ visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData;
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ env->CallStaticVoidMethod(
+ callbackInfo->visualizer_class,
+ fields.midPostNativeEvent,
+ callbackInfo->visualizer_ref,
+ NATIVE_EVENT_SERVER_DIED,
+ 0, 0, 0);
+ }
+}
static jint
android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
@@ -255,8 +337,8 @@
// create the native Visualizer object
lpVisualizer = new Visualizer(0,
- NULL,
- NULL,
+ android_media_visualizer_effect_callback,
+ lpJniStorage,
sessionId);
if (lpVisualizer == NULL) {
LOGE("Error creating Visualizer");
@@ -345,7 +427,17 @@
return VISUALIZER_ERROR_NO_INIT;
}
- return translateError(lpVisualizer->setEnabled(enabled));
+ jint retVal = translateError(lpVisualizer->setEnabled(enabled));
+
+ if (!enabled) {
+ visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField(
+ thiz, fields.fidJniData);
+
+ if (NULL != lpJniStorage)
+ lpJniStorage->mCallbackData.cleanupBuffers();
+ }
+
+ return retVal;
}
static jboolean
diff --git a/media/libaah_rtp/Android.mk b/media/libaah_rtp/Android.mk
new file mode 100644
index 0000000..54fd9ec
--- /dev/null
+++ b/media/libaah_rtp/Android.mk
@@ -0,0 +1,40 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libaah_rtp
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaah_rtp
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ aah_decoder_pump.cpp \
+ aah_rx_player.cpp \
+ aah_rx_player_core.cpp \
+ aah_rx_player_ring_buffer.cpp \
+ aah_rx_player_substream.cpp \
+ aah_tx_packet.cpp \
+ aah_tx_player.cpp \
+ aah_tx_sender.cpp \
+ pipe_event.cpp
+
+LOCAL_C_INCLUDES := \
+ frameworks/base/include \
+ frameworks/base/include/media/stagefright/openmax \
+ frameworks/base/media \
+ frameworks/base/media/libstagefright
+
+LOCAL_SHARED_LIBRARIES := \
+ libcommon_time_client \
+ libbinder \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libutils
+
+LOCAL_LDLIBS := \
+ -lpthread
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/media/libaah_rtp/aah_decoder_pump.cpp b/media/libaah_rtp/aah_decoder_pump.cpp
new file mode 100644
index 0000000..68bac4f
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.cpp
@@ -0,0 +1,518 @@
+/*
+ * 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) {
+ LOGE("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))) {
+ LOGV("Decoded sample missing timestamp, cannot render.");
+ } 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) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGW("%s: setVolume failed", __FUNCTION__);
+ }
+
+ renderer_->start();
+ }
+ }
+ } else {
+ LOGE("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) {
+ LOGE("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) {
+ LOGE("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 {
+ LOGE("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) {
+ LOGE("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) {
+ LOGW("%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_) {
+ LOGE("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);
+
+ LOGW("%s: Failed to decode data (res = %d)",
+ __PRETTY_FUNCTION__, res);
+
+ if ((consecutive_errors >= kMaxErrorsBeforeFatal) ||
+ (consecutive_long_errors >= kMaxLongErrorsBeforeFatal)) {
+ LOGE("%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) {
+ LOGW("%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(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) {
+ LOGE("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) {
+ LOGE("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();
+
+ MBQueue::iterator I;
+ for (I = in_queue_.begin(); I != in_queue_.end(); ++I) {
+ (*I)->release();
+ }
+ in_queue_.clear();
+
+ last_queued_pts_valid_ = false;
+ last_ts_transform_valid_ = false;
+ last_volume_ = 0xFF;
+ thread_status_ = OK;
+
+ decoder_ = NULL;
+ format_ = NULL;
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::read(MediaBuffer **buffer,
+ const ReadOptions *options) {
+ if (!buffer) {
+ return BAD_VALUE;
+ }
+
+ *buffer = NULL;
+
+ // While its not time to shut down, and we have no data to process, wait.
+ AutoMutex lock(&thread_lock_);
+ while (!thread_->exitPending() && in_queue_.empty())
+ thread_cond_.wait(thread_lock_);
+
+ // At this point, if its not time to shutdown then we must have something to
+ // process. Go ahead and pop the front of the queue for processing.
+ if (!thread_->exitPending()) {
+ CHECK(!in_queue_.empty());
+
+ *buffer = *(in_queue_.begin());
+ in_queue_.erase(in_queue_.begin());
+ }
+
+ // If we managed to get a buffer, then everything must be OK. If not, then
+ // we must be shutting down.
+ return (NULL == *buffer) ? INVALID_OPERATION : OK;
+}
+
+AAH_DecoderPump::ThreadWrapper::ThreadWrapper(AAH_DecoderPump* owner)
+ : Thread(false /* canCallJava*/ )
+ , owner_(owner) {
+}
+
+bool AAH_DecoderPump::ThreadWrapper::threadLoop() {
+ CHECK(NULL != owner_);
+ owner_->workThread();
+ return false;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_decoder_pump.h b/media/libaah_rtp/aah_decoder_pump.h
new file mode 100644
index 0000000..3f323d1
--- /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(sp<MetaData> params);
+ status_t shutdown();
+
+ void setRenderTSTransform(const LinearTransform& trans);
+ void setRenderVolume(uint8_t volume);
+ bool isAboutToUnderflow(int64_t threshold);
+ bool getStatus() const { return thread_status_; }
+
+ // MediaSource methods
+ virtual status_t start(MetaData *params) { return OK; }
+ virtual sp<MetaData> getFormat() { return format_; }
+ virtual status_t stop() { return OK; }
+ virtual status_t read(MediaBuffer **buffer,
+ const ReadOptions *options);
+
+ protected:
+ virtual ~AAH_DecoderPump();
+
+ private:
+ class ThreadWrapper : public Thread {
+ public:
+ friend class AAH_DecoderPump;
+ explicit ThreadWrapper(AAH_DecoderPump* owner);
+
+ private:
+ virtual bool threadLoop();
+ AAH_DecoderPump* owner_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+ };
+
+ void* workThread();
+ virtual status_t shutdown_l();
+ void queueToRenderer(MediaBuffer* decoded_sample);
+ void stopAndCleanupRenderer();
+
+ sp<MetaData> format_;
+ int32_t format_channels_;
+ int32_t format_sample_rate_;
+
+ sp<MediaSource> decoder_;
+ OMXClient& omx_;
+ Mutex init_lock_;
+
+ sp<ThreadWrapper> thread_;
+ Condition thread_cond_;
+ Mutex thread_lock_;
+ status_t thread_status_;
+
+ Mutex render_lock_;
+ TimedAudioTrack* renderer_;
+ bool last_queued_pts_valid_;
+ int64_t last_queued_pts_;
+ bool last_ts_transform_valid_;
+ LinearTransform last_ts_transform_;
+ uint8_t last_volume_;
+ CCHelper cc_helper_;
+
+ // protected by the thread_lock_
+ typedef List<MediaBuffer*> MBQueue;
+ MBQueue in_queue_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_DecoderPump);
+};
+
+} // namespace android
+#endif // __DECODER_PUMP_H__
diff --git a/media/libaah_rtp/aah_rx_player.cpp b/media/libaah_rtp/aah_rx_player.cpp
new file mode 100644
index 0000000..bf72383
--- /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) {
+ LOGE("Failed to allocate thread wrapper!");
+ return NO_MEMORY;
+ }
+
+ if (!ring_buffer_.initCheck()) {
+ LOGE("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) {
+ LOGE("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)) {
+ LOGE("Failed to parse URL \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ if ((a > 255) || (b > 255) || (c > 255) || (d > 255) || (port == 0)) {
+ LOGE("Bad multicast address \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ LOGI("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) {
+ LOGW("AAH_RXPlayer failed to fetch handle to audio flinger."
+ " Master volume control will not be possible.");
+ }
+
+ audio_flinger_ = interface_cast<IAudioFlinger>(binder);
+ }
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player.h b/media/libaah_rtp/aah_rx_player.h
new file mode 100644
index 0000000..53361b4
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.h
@@ -0,0 +1,299 @@
+/*
+ * 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)
+ struct PacketBuffer {
+ ssize_t length_;
+ uint8_t data_[0];
+
+ // 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(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();
+
+ private:
+ void cleanupDecoder();
+ bool shouldAbort(const char* log_tag);
+ void processCompletedBuffer();
+ bool setupSubstreamMeta();
+ bool setupMP3SubstreamMeta();
+ bool setupAACSubstreamMeta();
+ 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_;
+ const char* codec_mime_type_;
+ sp<MetaData> substream_meta_;
+
+ MediaBuffer* buffer_in_progress_;
+ uint32_t expected_buffer_size_;
+ uint32_t buffer_filled_;
+
+ Vector<uint8_t> aux_data_in_progress_;
+ uint32_t aux_data_expected_size_;
+
+ sp<AAH_DecoderPump> decoder_;
+
+ static int64_t kAboutToUnderflowThreshold;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Substream);
+ };
+
+ typedef DefaultKeyedVector< uint32_t, sp<Substream> > SubstreamVec;
+
+ status_t startWorkThread();
+ void stopWorkThread();
+ virtual bool threadLoop();
+ bool setupSocket();
+ void cleanupSocket();
+ void resetPipeline();
+ void reset_l();
+ bool processRX(PacketBuffer* pb);
+ void processRingBuffer();
+ void processCommandPacket(PacketBuffer* pb);
+ bool processGaps();
+ int computeNextGapRetransmitTimeout();
+ void fetchAudioFlinger();
+
+ PipeEvent wakeup_work_thread_evt_;
+ sp<ThreadWrapper> thread_wrapper_;
+ Mutex api_lock_;
+ bool is_playing_;
+ bool data_source_set_;
+
+ struct sockaddr_in listen_addr_;
+ int sock_fd_;
+ bool multicast_joined_;
+
+ struct sockaddr_in transmitter_addr_;
+ bool transmitter_known_;
+
+ uint32_t current_epoch_;
+ bool current_epoch_known_;
+
+ SeqNoGap current_gap_;
+ GapStatus current_gap_status_;
+ uint64_t next_retrans_req_time_;
+
+ RXRingBuffer ring_buffer_;
+ SubstreamVec substreams_;
+ OMXClient omx_;
+ CCHelper cc_helper_;
+
+ // Connection to audio flinger used to hack a path to setMasterVolume.
+ sp<IAudioFlinger> audio_flinger_;
+
+ static const uint32_t kRTPRingBufferSize;
+ static const uint32_t kRetransRequestMagic;
+ static const uint32_t kFastStartRequestMagic;
+ static const uint32_t kRetransNAKMagic;
+ static const uint32_t kGapRerequestTimeoutUSec;
+ static const uint32_t kFastStartTimeoutUSec;
+ static const uint32_t kRTPActivityTimeoutUSec;
+
+ static const uint32_t INVOKE_GET_MASTER_VOLUME = 3;
+ static const uint32_t INVOKE_SET_MASTER_VOLUME = 4;
+
+ static uint64_t monotonicUSecNow();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_RXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_RX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_rx_player_core.cpp b/media/libaah_rtp/aah_rx_player_core.cpp
new file mode 100644
index 0000000..7fbf9c8
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_core.cpp
@@ -0,0 +1,808 @@
+/*
+ * 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) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGW("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) {
+ LOGE("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) {
+ LOGE("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);
+ LOGE("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) {
+ LOGW("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) {
+ LOGW("Failed to increase socket buffer size to %d. (errno %d)",
+ buf_size, errno);
+ } else {
+ LOGI("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) {
+ LOGE("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) {
+ LOGW("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) {
+ LOGE("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)) {
+ LOGE("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) {
+ LOGE("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_) {
+ LOGE("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);
+ LOGV("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.
+ LOGE("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) {
+ LOGE("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) {
+ LOGV("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))) {
+ LOGV("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_);
+
+ LOGV("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) {
+ LOGV("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) {
+ LOGV("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_) {
+ LOGV("%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) {
+ LOGV("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) {
+ LOGV("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)) {
+ LOGV("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) {
+ LOGE("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) {
+ LOGV("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;
+ LOGI("*** %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()) {
+ LOGV("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_)) {
+ LOGV("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);
+ LOGV("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) {
+ LOGE("Error when sending retransmit request (%d)", errno);
+ } else {
+ LOGV("%s request for range [%hu, %hu] sent",
+ (kGS_FastStartGap == gap_status) ? "Fast Start" : "Retransmit",
+ gap.start_seq_, gap.end_seq_);
+ }
+
+ // Update the current gap info.
+ current_gap_ = gap;
+ current_gap_status_ = gap_status;
+ next_retrans_req_time_ = monotonicUSecNow() +
+ ((kGS_FastStartGap == current_gap_status_)
+ ? kFastStartTimeoutUSec
+ : kGapRerequestTimeoutUSec);
+ }
+
+ return false;
+}
+
+// Compute when its time to send the next gap retransmission in milliseconds.
+// Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit
+// right now.
+int AAH_RXPlayer::computeNextGapRetransmitTimeout() {
+ if (kGS_NoGap == current_gap_status_) {
+ return -1;
+ }
+
+ int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow();
+
+ timeout_delta /= 1000;
+ if (timeout_delta <= 0) {
+ return 0;
+ }
+
+ return static_cast<uint32_t>(timeout_delta);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_ring_buffer.cpp b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
new file mode 100644
index 0000000..22467f7
--- /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))) {
+ LOGW("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]) {
+ LOGD("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_) {
+ LOGD("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)) {
+ LOGD("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_;
+ LOGD("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(SeqNoGap* nak) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+
+ // If we were waiting for our first fast start fill-in packet, and we
+ // received a NAK, then apparantly we are not getting our fast start. Just
+ // clear the waiting flag and go back to normal behavior.
+ if (waiting_for_fast_start_) {
+ waiting_for_fast_start_ = false;
+ }
+
+ // If we have not received a packet since last reset, or there is no data in
+ // the ring, then there is nothing to skip.
+ if ((!rd_seq_known_) || (rd_ == wr_)) {
+ return;
+ }
+
+ // If rd_ is currently pointing at an unprocessed packet, then there is no
+ // gap to skip.
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ return;
+ }
+
+ // Looks like there must be a gap here. Advance rd until we have passed
+ // over the portion of it indicated by nak (or all of the gap if nak is
+ // NULL). Then reset fetched_first_packet_ so that the next read will show
+ // up as being discontiguous.
+ uint16_t seq_after_gap = (NULL == nak) ? 0 : nak->end_seq_ + 1;
+ while ((rd_ != wr_) &&
+ (NULL == ring_[rd_]) &&
+ ((NULL == nak) || (seq_after_gap != rd_seq_))) {
+ rd_ = (rd_ + 1) % capacity_;
+ ++rd_seq_;
+ }
+ fetched_first_packet_ = false;
+}
+
+int AAH_RXPlayer::RXRingBuffer::computeInactivityTimeout() {
+ AutoMutex lock(&lock_);
+
+ if (!rtp_activity_timeout_valid_) {
+ return -1;
+ }
+
+ uint64_t now = monotonicUSecNow();
+ if (rtp_activity_timeout_ <= now) {
+ return 0;
+ }
+
+ return (rtp_activity_timeout_ - now) / 1000;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::PacketBuffer::allocate(ssize_t length) {
+ if (length <= 0) {
+ return NULL;
+ }
+
+ uint32_t alloc_len = sizeof(PacketBuffer) + length;
+ PacketBuffer* ret = reinterpret_cast<PacketBuffer*>(
+ new uint8_t[alloc_len]);
+
+ if (NULL != ret) {
+ ret->length_ = length;
+ }
+
+ return ret;
+}
+
+void AAH_RXPlayer::PacketBuffer::destroy(PacketBuffer* pb) {
+ uint8_t* kill_me = reinterpret_cast<uint8_t*>(pb);
+ delete[] kill_me;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_substream.cpp b/media/libaah_rtp/aah_rx_player_substream.cpp
new file mode 100644
index 0000000..3e3a95c
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_substream.cpp
@@ -0,0 +1,676 @@
+/*
+ * 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"
+#include "aah_tx_packet.h"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+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;
+ codec_mime_type_ = "";
+
+ decoder_ = new AAH_DecoderPump(omx);
+ if (decoder_ == NULL) {
+ LOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__);
+ }
+ if (OK != decoder_->initCheck()) {
+ LOGE("%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();
+}
+
+AAH_RXPlayer::Substream::~Substream() {
+ shutdown();
+}
+
+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;
+
+ aux_data_in_progress_.clear();
+ aux_data_expected_size_ = 0;
+}
+
+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_) {
+ LOGV("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_) {
+ LOGE("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) {
+ LOGV("Discarding payload too short to contain TRTP header (len = %u)",
+ amt);
+ return;
+ }
+
+ // Check the TRTP version number.
+ if (0x01 != buf[0]) {
+ LOGV("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:
+ LOGV("RXed packet with unhandled TRTP header type (Video).");
+ return;
+ case 0x03:
+ LOGV("RXed packet with unhandled TRTP header type (Subpicture).");
+ return;
+ case 0x04:
+ LOGV("RXed packet with unhandled TRTP header type (Control).");
+ return;
+ default:
+ LOGV("RXed packet with unhandled TRTP header type (%u).",
+ header_type);
+ return;
+ }
+
+ if (substream_details_known_ && (header_type != substream_type_)) {
+ LOGV("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) {
+ LOGV("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 = U32_AT(buf + 2);
+ if (trtp_len < min_length) {
+ LOGV("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) {
+ uint32_t ts_upper = U32_AT(buf + parse_offset);
+ parse_offset += 4;
+ ts = (static_cast<int64_t>(ts_upper) << 32) | ts_lower;
+ }
+
+ // 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) {
+ LOGV("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) {
+ LOGV("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) {
+ LOGV("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)) {
+ LOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP.");
+ return;
+ }
+
+ // Check for the presence of codec aux data.
+ if (flags & 0x10) {
+ min_length += 4;
+ trtp_header_len += 4;
+
+ if (trtp_len < min_length) {
+ LOGV("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) {
+ LOGV("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;
+ }
+
+ aux_data_expected_size_ = U32_AT(buf + parse_offset);
+ aux_data_in_progress_.clear();
+ if (aux_data_in_progress_.capacity() < aux_data_expected_size_) {
+ aux_data_in_progress_.setCapacity(aux_data_expected_size_);
+ }
+ } else {
+ aux_data_expected_size_ = 0;
+ }
+
+ if ((aux_data_expected_size_ + trtp_header_len) > trtp_len) {
+ LOGV("Expected codec aux data length (%u) and TRTP header overhead (%u)"
+ " too large for total TRTP payload length (%u).",
+ aux_data_expected_size_, trtp_header_len, trtp_len);
+ 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
+ - aux_data_expected_size_;
+ if (!expected_buffer_size_) {
+ LOGV("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_ + aux_data_expected_size_) < todo) {
+ LOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;"
+ " dropping payload.", todo,
+ expected_buffer_size_ + aux_data_expected_size_);
+ return;
+ }
+
+ buffer_filled_ = 0;
+ buffer_in_progress_ = new MediaBuffer(expected_buffer_size_);
+ if ((NULL == buffer_in_progress_) ||
+ (NULL == buffer_in_progress_->data())) {
+ LOGV("Failed to allocate MediaBuffer of length %u",
+ expected_buffer_size_);
+ cleanupBufferInProgress();
+ return;
+ }
+
+ sp<MetaData> meta = buffer_in_progress_->meta_data();
+ if (meta == NULL) {
+ LOGV("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, codec_mime_type_);
+ if (ts_valid) {
+ meta->setInt64(kKeyTime, ts);
+ }
+
+ // Skip over the header we have already extracted.
+ amt -= trtp_header_len;
+ buf += trtp_header_len;
+
+ // Extract as much of the expected aux data as we can.
+ todo = MIN(aux_data_expected_size_, amt);
+ if (todo) {
+ aux_data_in_progress_.appendArray(buf, todo);
+ buf += todo;
+ amt -= todo;
+ }
+
+ // Extract as much of the expected payload as we can.
+ todo = MIN(expected_buffer_size_, amt);
+ if (todo > 0) {
+ uint8_t* tgt =
+ reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+ memcpy(tgt, buf, todo);
+ buffer_filled_ = amt;
+ buf += todo;
+ amt -= todo;
+ }
+
+ 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_) {
+ LOGV("TRTP Receiver skipping payload continuation; no buffer currently"
+ " in progress.");
+ return;
+ }
+
+ CHECK(aux_data_in_progress_.size() <= aux_data_expected_size_);
+ uint32_t aux_left = aux_data_expected_size_ - aux_data_in_progress_.size();
+ if (aux_left) {
+ uint32_t todo = MIN(aux_left, amt);
+ aux_data_in_progress_.appendArray(buf, todo);
+ amt -= todo;
+ buf += todo;
+
+ if (!amt)
+ return;
+ }
+
+ CHECK(buffer_filled_ < expected_buffer_size_);
+ uint32_t buffer_left = expected_buffer_size_ - buffer_filled_;
+ if (amt > buffer_left) {
+ LOGV("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() {
+ status_t res;
+
+ CHECK(NULL != buffer_in_progress_);
+
+ if (decoder_ == NULL) {
+ LOGV("Dropping complete buffer, no decoder pump allocated");
+ goto bailout;
+ }
+
+ // Make sure our metadata used to initialize the decoder has been properly
+ // set up.
+ if (!setupSubstreamMeta())
+ goto bailout;
+
+ // If our decoder has not be set up, do so now.
+ res = decoder_->init(substream_meta_);
+ if (OK != res) {
+ LOGE("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) {
+ LOGD("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();
+}
+
+bool AAH_RXPlayer::Substream::setupSubstreamMeta() {
+ switch (codec_type_) {
+ case TRTPAudioPacket::kCodecMPEG1Audio:
+ codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_MPEG;
+ return setupMP3SubstreamMeta();
+
+ case TRTPAudioPacket::kCodecAACAudio:
+ codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_AAC;
+ return setupAACSubstreamMeta();
+
+ default:
+ LOGV("Failed to setup substream metadata for unsupported codec type"
+ " (%u)", codec_type_);
+ break;
+ }
+
+ return false;
+}
+
+bool AAH_RXPlayer::Substream::setupMP3SubstreamMeta() {
+ const uint8_t* buffer_data = NULL;
+ int sample_rate;
+ int channel_count;
+ size_t frame_size;
+ status_t res;
+
+ buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data());
+ if (buffer_in_progress_->size() < 4) {
+ LOGV("MP3 payload too short to contain header, dropping payload.");
+ return false;
+ }
+
+ // 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)) {
+ LOGV("Failed to parse MP3 header in payload, droping payload.");
+ return false;
+ }
+
+
+ // 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) {
+ LOGE("Failed to allocate MetaData structure for MP3 substream");
+ return false;
+ }
+
+ 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)) {
+ LOGW("MP3 format change detected, forcing decoder reset.");
+ cleanupDecoder();
+
+ substream_meta_->setInt32(kKeyChannelCount, channel_count);
+ substream_meta_->setInt32(kKeySampleRate, sample_rate);
+ }
+ }
+
+ return true;
+}
+
+bool AAH_RXPlayer::Substream::setupAACSubstreamMeta() {
+ int32_t sample_rate, channel_cnt;
+ static const size_t overhead = sizeof(sample_rate)
+ + sizeof(channel_cnt);
+
+ if (aux_data_in_progress_.size() < overhead) {
+ LOGE("Not enough aux data (%u) to initialize AAC substream decoder",
+ aux_data_in_progress_.size());
+ return false;
+ }
+
+ const uint8_t* aux_data = aux_data_in_progress_.array();
+ size_t aux_data_size = aux_data_in_progress_.size();
+ sample_rate = U32_AT(aux_data);
+ channel_cnt = U32_AT(aux_data + sizeof(sample_rate));
+
+ const uint8_t* esds_data = NULL;
+ size_t esds_data_size = 0;
+ if (aux_data_size > overhead) {
+ esds_data = aux_data + overhead;
+ esds_data_size = aux_data_size - overhead;
+ }
+
+ // Do we already have metadata? If so, has it changed at all? If not, then
+ // there should be nothing else to do. Otherwise, release our old stream
+ // metadata and make new metadata.
+ if (substream_meta_ != NULL) {
+ uint32_t type;
+ const void* data;
+ size_t size;
+ 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 nothing has changed about the codec aux data (esds, sample rate,
+ // channel count), then we can just do nothing and get out. Otherwise,
+ // we will need to reset the decoder and make a new metadata object to
+ // deal with the format change.
+ bool hasData = (esds_data != NULL);
+ bool hadData = substream_meta_->findData(kKeyESDS, &type, &data, &size);
+ bool esds_change = (hadData != hasData);
+
+ if (!esds_change && hasData)
+ esds_change = ((size != esds_data_size) ||
+ memcmp(data, esds_data, size));
+
+ if (!esds_change &&
+ (prev_sample_rate == sample_rate) &&
+ (prev_channel_count == channel_cnt)) {
+ return true; // no change, just get out.
+ }
+
+ LOGW("AAC format change detected, forcing decoder reset.");
+ cleanupDecoder();
+ substream_meta_ = NULL;
+ }
+
+ CHECK(substream_meta_ == NULL);
+
+ substream_meta_ = new MetaData();
+ if (substream_meta_ == NULL) {
+ LOGE("Failed to allocate MetaData structure for AAC substream");
+ return false;
+ }
+
+ substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
+ substream_meta_->setInt32 (kKeySampleRate, sample_rate);
+ substream_meta_->setInt32 (kKeyChannelCount, channel_cnt);
+
+ if (esds_data) {
+ substream_meta_->setData(kKeyESDS, kTypeESDS,
+ esds_data, esds_data_size);
+ }
+
+ return true;
+}
+
+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 and AAC.
+ // Also check for conflicts with previously delivered codec types.
+ if (substream_details_known_) {
+ if (codec_type != codec_type_) {
+ LOGV("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;
+ }
+
+ return true;
+ }
+
+ switch (codec_type) {
+ // MP3 and AAC are all we support right now.
+ case TRTPAudioPacket::kCodecMPEG1Audio:
+ case TRTPAudioPacket::kCodecAACAudio:
+ break;
+
+ default:
+ LOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported"
+ " codec type (%u)", ssrc_, codec_type);
+ return false;
+ }
+
+ substream_type_ = substream_type;
+ codec_type_ = codec_type;
+ substream_details_known_ = true;
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.cpp b/media/libaah_rtp/aah_tx_packet.cpp
new file mode 100644
index 0000000..c7ad3e0
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.cpp
@@ -0,0 +1,344 @@
+/*
+ * 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/MediaDebug.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(const void* data, size_t len) {
+ CHECK(!mIsPacked);
+ mAccessUnitData = data;
+ mAccessUnitLen = len;
+}
+
+void TRTPAudioPacket::setAuxData(const void* data, size_t len) {
+ CHECK(!mIsPacked);
+ mAuxData = data;
+ mAuxDataLen = 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 +
+ mAuxDataLen +
+ 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;
+ bool hasAux = mAuxData && mAuxDataLen;
+ uint8_t flags = (static_cast<int>(hasAux) << 4) |
+ (static_cast<int>(mRandomAccessPoint) << 3) |
+ (static_cast<int>(mDropable) << 2) |
+ (static_cast<int>(mDiscontinuity) << 1) |
+ (static_cast<int>(mEndOfStream));
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU8(cur, mCodecType);
+ writeU8(cur, flags);
+ writeU8(cur, mVolume);
+
+ if (hasAux) {
+ writeU32(cur, mAuxDataLen);
+ memcpy(cur, mAuxData, mAuxDataLen);
+ cur += mAuxDataLen;
+ }
+
+ 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;
+ }
+
+
+ int auxDataLenField = (NULL != mAuxData) ? sizeof(uint32_t) : 0;
+ return TRTPPacket::TRTPHeaderLen() +
+ 3 +
+ auxDataLenField +
+ pcmParamLength;
+}
+
+bool TRTPControlPacket::pack() {
+ if (mIsPacked) {
+ return false;
+ }
+
+ // command packets contain a 2-byte command ID
+ int packetLen = kRTPHeaderLen +
+ TRTPHeaderLen() +
+ 2;
+
+ mPacket = new uint8_t[packetLen];
+ if (!mPacket) {
+ return false;
+ }
+
+ mPacketLen = packetLen;
+
+ uint8_t* cur = mPacket;
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU16(cur, mCommandID);
+
+ mIsPacked = true;
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.h b/media/libaah_rtp/aah_tx_packet.h
new file mode 100644
index 0000000..62436a0
--- /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 <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;
+};
+
+class TRTPAudioPacket : public TRTPPacket {
+ public:
+ TRTPAudioPacket()
+ : TRTPPacket(kHeaderTypeAudio)
+ , mCodecType(kCodecInvalid)
+ , mRandomAccessPoint(false)
+ , mDropable(false)
+ , mDiscontinuity(false)
+ , mEndOfStream(false)
+ , mVolume(0)
+ , mAccessUnitData(NULL)
+ , mAccessUnitLen(0)
+ , mAuxData(NULL)
+ , mAuxDataLen(0) { }
+
+ enum TRTPAudioCodecType {
+ kCodecInvalid = 0,
+ kCodecPCMBigEndian = 1,
+ kCodecPCMLittleEndian = 2,
+ kCodecMPEG1Audio = 3,
+ kCodecAACAudio = 4,
+ };
+
+ 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(const void* data, size_t len);
+ void setAuxData(const void* data, size_t len);
+
+ virtual bool pack();
+
+ protected:
+ virtual int TRTPHeaderLen() const;
+
+ private:
+ TRTPAudioCodecType mCodecType;
+ bool mRandomAccessPoint;
+ bool mDropable;
+ bool mDiscontinuity;
+ bool mEndOfStream;
+ uint8_t mVolume;
+ const void* mAccessUnitData;
+ size_t mAccessUnitLen;
+ const void* mAuxData;
+ size_t mAuxDataLen;
+};
+
+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;
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp
new file mode 100644
index 0000000..6328115
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.cpp
@@ -0,0 +1,1177 @@
+/*
+ * 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/AMessage.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.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;
+
+ mAudioCodecData = NULL;
+
+ reset();
+}
+
+AAH_TXPlayer::~AAH_TXPlayer() {
+ if (mQueueStarted) {
+ mQueue.stop();
+ }
+
+ reset();
+}
+
+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) {
+ LOGE("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();
+
+ mUri.setTo(url);
+
+ 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();
+
+ LOGI("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) {
+ LOGI("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;
+
+ LOGV("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) {
+ LOGI("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;
+ }
+ }
+
+ mAudioFormat = mAudioSource->getFormat();
+ if (!mAudioFormat->findInt64(kKeyDuration, &mDurationUs))
+ mDurationUs = 1;
+
+ const char* mime_type = NULL;
+ if (!mAudioFormat->findCString(kKeyMIMEType, &mime_type)) {
+ LOGE("Failed to find audio substream MIME type during prepare.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_MPEG)) {
+ mAudioCodec = TRTPAudioPacket::kCodecMPEG1Audio;
+ } else
+ if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_AAC)) {
+ mAudioCodec = TRTPAudioPacket::kCodecAACAudio;
+
+ uint32_t type;
+ int32_t sample_rate;
+ int32_t channel_count;
+ const void* esds_data;
+ size_t esds_len;
+
+ if (!mAudioFormat->findInt32(kKeySampleRate, &sample_rate)) {
+ LOGE("Failed to find sample rate for AAC substream.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ if (!mAudioFormat->findInt32(kKeyChannelCount, &channel_count)) {
+ LOGE("Failed to find channel count for AAC substream.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ if (!mAudioFormat->findData(kKeyESDS, &type, &esds_data, &esds_len)) {
+ LOGE("Failed to find codec init data for AAC substream.");
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ CHECK(NULL == mAudioCodecData);
+ mAudioCodecDataSize = esds_len
+ + sizeof(sample_rate)
+ + sizeof(channel_count);
+ mAudioCodecData = new uint8_t[mAudioCodecDataSize];
+ if (NULL == mAudioCodecData) {
+ LOGE("Failed to allocate %u bytes for AAC substream codec aux"
+ " data.", mAudioCodecDataSize);
+ mAudioCodecDataSize = 0;
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ uint8_t* tmp = mAudioCodecData;
+ tmp[0] = static_cast<uint8_t>((sample_rate >> 24) & 0xFF);
+ tmp[1] = static_cast<uint8_t>((sample_rate >> 16) & 0xFF);
+ tmp[2] = static_cast<uint8_t>((sample_rate >> 8) & 0xFF);
+ tmp[3] = static_cast<uint8_t>((sample_rate ) & 0xFF);
+ tmp[4] = static_cast<uint8_t>((channel_count >> 24) & 0xFF);
+ tmp[5] = static_cast<uint8_t>((channel_count >> 16) & 0xFF);
+ tmp[6] = static_cast<uint8_t>((channel_count >> 8) & 0xFF);
+ tmp[7] = static_cast<uint8_t>((channel_count ) & 0xFF);
+
+ memcpy(tmp + 8, esds_data, esds_len);
+ } else {
+ LOGE("Unsupported MIME type \"%s\" in audio substream", mime_type);
+ abortPrepare(BAD_VALUE);
+ return;
+ }
+
+ status_t err = mAudioSource->start();
+ if (err != OK) {
+ LOGI("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)) {
+ LOGE("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)) {
+ LOGE("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)) {
+ LOGE("getCurrentPosition get common time failed");
+ return INVALID_OPERATION;
+ }
+
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(commonTimeNow,
+ &mediaTimeNow)) {
+ LOGE("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) {
+ LOGI("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();
+ mAudioCodec = TRTPAudioPacket::kCodecInvalid;
+ mAudioFormat = NULL;
+ delete[] mAudioCodecData;
+ mAudioCodecData = NULL;
+ mAudioCodecDataSize = 0;
+
+ 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) {
+ 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) {
+ LOGE("%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(int streamType) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::setRetransmitEndpoint(
+ const struct sockaddr_in* endpoint) {
+ Mutex::Autolock lock(mLock);
+
+ if (NULL == endpoint)
+ return BAD_VALUE;
+
+ // Once the endpoint has been registered, it may not be changed.
+ if (mEndpointRegistered)
+ return INVALID_OPERATION;
+
+ mEndpoint.addr = endpoint->sin_addr.s_addr;
+ mEndpoint.port = endpoint->sin_port;
+ mEndpointValid = true;
+
+ 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) {
+ LOGV("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)) {
+ LOGI("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) {
+ LOGI("cache has filled up (> %d), resuming.",
+ kHighWaterMarkBytes);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ LOGV("cache has filled up (> %d), prepare is done",
+ kHighWaterMarkBytes);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+ }
+ }
+
+ int64_t cachedDurationUs;
+ bool eos;
+ if (getCachedDuration_l(&cachedDurationUs, &eos)) {
+ LOGV("cachedDurationUs = %.2f secs, eos=%d",
+ cachedDurationUs / 1E6, eos);
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDurationUs < kLowWaterMarkUs)) {
+ LOGI("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) {
+ LOGI("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) {
+ LOGV("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.
+ LOGE("*** 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) {
+ LOGI("*** %s reached end of stream", __PRETTY_FUNCTION__);
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+ pause_l(false);
+ sendEOS_l();
+ } else {
+ LOGE("*** %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());
+ LOGV("*** %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));
+ LOGV("*** 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.
+ LOGE("*** 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;
+ }
+ }
+
+ LOGV("*** transmitting packet with pts=%lld", mediaTimeUs);
+
+ sp<TRTPAudioPacket> packet = new TRTPAudioPacket();
+ packet->setPTS(mediaTimeUs);
+ packet->setSubstreamID(1);
+
+ packet->setCodecType(mAudioCodec);
+ 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());
+
+ // TODO : while its pretty much universally true that audio ES payloads
+ // are all RAPs across all codecs, it might be a good idea to throttle
+ // the frequency with which we send codec out of band data to the RXers.
+ // If/when we do, we need to flag only those payloads which have
+ // required out of band data attached to them as RAPs.
+ packet->setRandomAccessPoint(true);
+
+ if (mAudioCodecData && mAudioCodecDataSize) {
+ packet->setAuxData(mAudioCodecData, mAudioCodecDataSize);
+ }
+
+ queuePacketToSender_l(packet);
+ mediaBuffer->release();
+
+ mLastQueuedMediaTimePTSValid = true;
+ mLastQueuedMediaTimePTS = mediaTimeUs;
+ }
+
+ { // Explicit scope for the autolock pattern.
+ Mutex::Autolock autoLock(mLock);
+
+ // If someone externally has cleared this flag, its because we should be
+ // shutting down. Do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Looks like no one canceled us explicitly. Clear our flag and post a
+ // new event to ourselves.
+ mPumpAudioEventPending = false;
+ postPumpAudioEvent_l(10000);
+ }
+}
+
+void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) {
+ if (mAAH_Sender == NULL) {
+ return;
+ }
+
+ sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket,
+ mAAH_Sender->handlerID());
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return;
+ }
+
+ message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr);
+ message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port);
+ }
+
+ packet->setProgramID(mProgramID);
+ packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet);
+
+ message->post();
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_player.h b/media/libaah_rtp/aah_tx_player.h
new file mode 100644
index 0000000..6843f9b
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.h
@@ -0,0 +1,176 @@
+/*
+ * 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(int streamType);
+ virtual status_t setRetransmitEndpoint(const struct sockaddr_in*
+ endpoint);
+
+ 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;
+ TRTPAudioPacket::TRTPAudioCodecType mAudioCodec;
+ sp<MetaData> mAudioFormat;
+ uint8_t* mAudioCodecData;
+ size_t mAudioCodecDataSize;
+
+ int64_t mDurationUs;
+ int64_t mBitrate;
+
+ sp<AAH_TXSender> mAAH_Sender;
+ LinearTransform mCurrentClockTransform;
+ bool mCurrentClockTransformValid;
+ int64_t mLastQueuedMediaTimePTS;
+ bool mLastQueuedMediaTimePTSValid;
+ bool mPlayRateIsPaused;
+ CCHelper mCCHelper;
+
+ Mutex mEndpointLock;
+ AAH_TXSender::Endpoint mEndpoint;
+ bool mEndpointValid;
+ bool mEndpointRegistered;
+ uint16_t mProgramID;
+ uint8_t mTRTPVolume;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_TXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_sender.cpp b/media/libaah_rtp/aah_tx_sender.cpp
new file mode 100644
index 0000000..1dcb6e9
--- /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) {
+ LOGW("%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) {
+ LOGW("%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) {
+ LOGW("%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) {
+ LOGW("%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 = endpoint.port;
+
+ ssize_t result = sendto(mSocket,
+ packet->getPacket(),
+ packet->getPacketLen(),
+ 0,
+ (const struct sockaddr *) &addr,
+ sizeof(addr));
+ if (result == -1) {
+ LOGW("%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);
+ LOGD("*** %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) {
+ LOGE("%s poll failed", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (exitPending()) {
+ LOGI("*** %s exiting", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (pollFds[0].revents) {
+ handleRetryRequest();
+ }
+
+ return true;
+}
+
+void AAH_TXSender::RetryReceiver::handleRetryRequest() {
+ LOGV("*** 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) {
+ LOGE("%s recvfrom failed, errno=%d", __PRETTY_FUNCTION__, errno);
+ return;
+ }
+
+ if (static_cast<size_t>(result) < sizeof(RetryPacket)) {
+ LOGW("%s short packet received", __PRETTY_FUNCTION__);
+ return;
+ }
+
+ uint32_t host_request_id = ntohl(request.id);
+ if ((host_request_id != kRetryRequestID) &&
+ (host_request_id != kFastStartRequestID)) {
+ LOGW("%s received retry request with bogus ID (%08x)",
+ __PRETTY_FUNCTION__, host_request_id);
+ return;
+ }
+
+ Endpoint endpoint(request.endpointIP, 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) {
+ LOGW("%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) {
+ LOGW("%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) {
+ LOGW("%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) {
+ LOGW("%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) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ }
+}
+
+// Endpoint
+
+AAH_TXSender::Endpoint::Endpoint()
+ : addr(0)
+ , port(0) { }
+
+AAH_TXSender::Endpoint::Endpoint(uint32_t a, uint16_t p)
+ : addr(a)
+ , port(p) {}
+
+bool AAH_TXSender::Endpoint::operator<(const Endpoint& other) const {
+ return ((addr < other.addr) ||
+ (addr == other.addr && port < other.port));
+}
+
+// EndpointState
+
+AAH_TXSender::EndpointState::EndpointState(uint32_t _epoch)
+ : retry(kRetryBufferCapacity)
+ , playerRefCount(1)
+ , trtpSeqNumber(0)
+ , nextProgramID(0)
+ , epoch(_epoch) { }
+
+// CircularBuffer
+
+template <typename T>
+CircularBuffer<T>::CircularBuffer(size_t capacity)
+ : mCapacity(capacity)
+ , mHead(0)
+ , mTail(0)
+ , mFillCount(0) {
+ mBuffer = new T[capacity];
+}
+
+template <typename T>
+CircularBuffer<T>::~CircularBuffer() {
+ delete [] mBuffer;
+}
+
+template <typename T>
+void CircularBuffer<T>::push_back(const T& item) {
+ if (this->isFull()) {
+ this->pop_front();
+ }
+ mBuffer[mHead] = item;
+ mHead = (mHead + 1) % mCapacity;
+ mFillCount++;
+}
+
+template <typename T>
+void CircularBuffer<T>::pop_front() {
+ CHECK(!isEmpty());
+ mBuffer[mTail] = T();
+ mTail = (mTail + 1) % mCapacity;
+ mFillCount--;
+}
+
+template <typename T>
+size_t CircularBuffer<T>::size() const {
+ return mFillCount;
+}
+
+template <typename T>
+bool CircularBuffer<T>::isFull() const {
+ return (mFillCount == mCapacity);
+}
+
+template <typename T>
+bool CircularBuffer<T>::isEmpty() const {
+ return (mFillCount == 0);
+}
+
+template <typename T>
+const T& CircularBuffer<T>::itemAt(size_t index) const {
+ CHECK(index < mFillCount);
+ return mBuffer[(mTail + index) % mCapacity];
+}
+
+template <typename T>
+const T& CircularBuffer<T>::operator[](size_t index) const {
+ return itemAt(index);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_sender.h b/media/libaah_rtp/aah_tx_sender.h
new file mode 100644
index 0000000..47814de
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.h
@@ -0,0 +1,158 @@
+/*
+ * 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;
+};
+
+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;
+};
+
+struct RetryPacket {
+ uint32_t id;
+ uint32_t endpointIP;
+ uint16_t endpointPort;
+ uint16_t seqStart;
+ uint16_t seqEnd;
+} __attribute__((packed));
+
+} // namespace android
+
+#endif // __AAH_TX_SENDER_H__
diff --git a/media/libaah_rtp/pipe_event.cpp b/media/libaah_rtp/pipe_event.cpp
new file mode 100644
index 0000000..96c674b
--- /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 {
+ LOGE("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) {
+ LOGE("Wait error in PipeEvent; sleeping to prevent overload!");
+ usleep(1000);
+ }
+
+ return (res > 0);
+}
+
+void PipeEvent::setEvent() {
+ char foo = 'q';
+ write(pipe_[1], &foo, 1);
+}
+
+} // namespace android
+
diff --git a/media/libaah_rtp/pipe_event.h b/media/libaah_rtp/pipe_event.h
new file mode 100644
index 0000000..637bfef
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.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 __PIPE_EVENT_H__
+#define __PIPE_EVENT_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];
+};
+
+} // namespace android
+
+#endif // __PIPE_EVENT_H__
diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp
index 0633744..676007b 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -202,7 +202,7 @@
status_t AudioEffect::setEnabled(bool enabled)
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
status_t status = NO_ERROR;
@@ -231,7 +231,7 @@
{
if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) {
LOGV("command() bad status %d", mStatus);
- return INVALID_OPERATION;
+ return mStatus;
}
if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) {
@@ -263,7 +263,7 @@
status_t AudioEffect::setParameter(effect_param_t *param)
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -281,7 +281,7 @@
status_t AudioEffect::setParameterDeferred(effect_param_t *param)
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -307,7 +307,7 @@
status_t AudioEffect::setParameterCommit()
{
if (mStatus != NO_ERROR) {
- return INVALID_OPERATION;
+ return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
}
Mutex::Autolock _l(mCblk->lock);
@@ -321,7 +321,7 @@
status_t AudioEffect::getParameter(effect_param_t *param)
{
if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) {
- return INVALID_OPERATION;
+ return mStatus;
}
if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -341,7 +341,7 @@
void AudioEffect::binderDied()
{
LOGW("IEffect died");
- mStatus = NO_INIT;
+ mStatus = DEAD_OBJECT;
if (mCbf) {
status_t status = DEAD_OBJECT;
mCbf(EVENT_ERROR, mUserData, &status);
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 498ad45..6097200 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -79,7 +79,8 @@
// ---------------------------------------------------------------------------
AudioTrack::AudioTrack()
- : mStatus(NO_INIT)
+ : mStatus(NO_INIT),
+ mIsTimed(false)
{
}
@@ -94,7 +95,8 @@
void* user,
int notificationFrames,
int sessionId)
- : mStatus(NO_INIT)
+ : mStatus(NO_INIT),
+ mIsTimed(false)
{
mStatus = set(streamType, sampleRate, format, channelMask,
frameCount, flags, cbf, user, notificationFrames,
@@ -112,7 +114,8 @@
void* user,
int notificationFrames,
int sessionId)
- : mStatus(NO_INIT)
+ : mStatus(NO_INIT),
+ mIsTimed(false)
{
mStatus = set(streamType, sampleRate, format, channelMask,
0, flags, cbf, user, notificationFrames,
@@ -520,6 +523,10 @@
{
int afSamplingRate;
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) {
return NO_INIT;
}
@@ -533,6 +540,10 @@
uint32_t AudioTrack::getSampleRate()
{
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
AutoMutex lock(mLock);
return mCblk->sampleRate;
}
@@ -558,6 +569,10 @@
return NO_ERROR;
}
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
if (loopStart >= loopEnd ||
loopEnd - loopStart > cblk->frameCount ||
cblk->server > loopStart) {
@@ -641,6 +656,8 @@
status_t AudioTrack::setPosition(uint32_t position)
{
+ if (mIsTimed) return INVALID_OPERATION;
+
AutoMutex lock(mLock);
Mutex::Autolock _l(mCblk->lock);
@@ -787,6 +804,7 @@
((uint16_t)flags) << 16,
sharedBuffer,
output,
+ mIsTimed,
&mSessionId,
&status);
@@ -955,6 +973,7 @@
{
if (mSharedBuffer != 0) return INVALID_OPERATION;
+ if (mIsTimed) return INVALID_OPERATION;
if (ssize_t(userSize) < 0) {
// sanity-check. user is most-likely passing an error code.
@@ -1017,6 +1036,36 @@
// -------------------------------------------------------------------------
+TimedAudioTrack::TimedAudioTrack() {
+ mIsTimed = true;
+}
+
+status_t TimedAudioTrack::allocateTimedBuffer(size_t size, sp<IMemory>* buffer)
+{
+ return mAudioTrack->allocateTimedBuffer(size, buffer);
+}
+
+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)) {
+ mCblk->flags &= ~CBLK_DISABLED_ON;
+ LOGW("queueTimedBuffer() track %p disabled, restarting", this);
+ mAudioTrack->start();
+ }
+
+ 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;
@@ -1459,4 +1508,3 @@
// -------------------------------------------------------------------------
}; // namespace android
-
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index d58834b..c7b49cd 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -90,6 +90,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -105,6 +106,7 @@
data.writeInt32(flags);
data.writeStrongBinder(sharedBuffer->asBinder());
data.writeInt32(output);
+ data.writeInt32(isTimed);
int lSessionId = 0;
if (sessionId != NULL) {
lSessionId = *sessionId;
@@ -684,11 +686,12 @@
uint32_t flags = data.readInt32();
sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder());
int output = data.readInt32();
+ bool isTimed = data.readInt32();
int sessionId = data.readInt32();
status_t status;
sp<IAudioTrack> track = createTrack(pid,
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 bc8ff34..a496fb8 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -35,7 +35,10 @@
FLUSH,
MUTE,
PAUSE,
- ATTACH_AUX_EFFECT
+ ATTACH_AUX_EFFECT,
+ ALLOCATE_TIMED_BUFFER,
+ QUEUE_TIMED_BUFFER,
+ SET_MEDIA_TIME_TRANSFORM,
};
class BpAudioTrack : public BpInterface<IAudioTrack>
@@ -113,6 +116,52 @@
}
return status;
}
+
+ virtual status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeInt32(size);
+ status_t status = remote()->transact(ALLOCATE_TIMED_BUFFER,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ if (status == NO_ERROR) {
+ *buffer = interface_cast<IMemory>(reply.readStrongBinder());
+ }
+ }
+ return status;
+ }
+
+ virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeStrongBinder(buffer->asBinder());
+ data.writeInt64(pts);
+ status_t status = remote()->transact(QUEUE_TIMED_BUFFER,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ }
+ return status;
+ }
+
+ virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+ int target) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeInt64(xform.a_zero);
+ data.writeInt64(xform.b_zero);
+ data.writeInt32(xform.a_to_b_numer);
+ data.writeInt32(xform.a_to_b_denom);
+ data.writeInt32(target);
+ status_t status = remote()->transact(SET_MEDIA_TIME_TRANSFORM,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ }
+ return status;
+ }
};
IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack");
@@ -158,10 +207,38 @@
reply->writeInt32(attachAuxEffect(data.readInt32()));
return NO_ERROR;
} break;
+ case ALLOCATE_TIMED_BUFFER: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ sp<IMemory> buffer;
+ status_t status = allocateTimedBuffer(data.readInt32(), &buffer);
+ reply->writeInt32(status);
+ if (status == NO_ERROR) {
+ reply->writeStrongBinder(buffer->asBinder());
+ }
+ return NO_ERROR;
+ } break;
+ case QUEUE_TIMED_BUFFER: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ sp<IMemory> buffer = interface_cast<IMemory>(
+ data.readStrongBinder());
+ uint64_t pts = data.readInt64();
+ reply->writeInt32(queueTimedBuffer(buffer, pts));
+ return NO_ERROR;
+ } break;
+ case SET_MEDIA_TIME_TRANSFORM: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ LinearTransform xform;
+ xform.a_zero = data.readInt64();
+ xform.b_zero = data.readInt64();
+ xform.a_to_b_numer = data.readInt32();
+ xform.a_to_b_denom = data.readInt32();
+ int target = data.readInt32();
+ reply->writeInt32(setMediaTimeTransform(xform, target));
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
}; // namespace android
-
diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp
index a945b97..f586aad 100644
--- a/media/libmedia/IEffect.cpp
+++ b/media/libmedia/IEffect.cpp
@@ -83,8 +83,15 @@
size = *pReplySize;
}
data.writeInt32(size);
- remote()->transact(COMMAND, data, &reply);
- status_t status = reply.readInt32();
+
+ status_t status = remote()->transact(COMMAND, data, &reply);
+ if (status != NO_ERROR) {
+ if (pReplySize != NULL)
+ *pReplySize = 0;
+ return status;
+ }
+
+ status = reply.readInt32();
size = reply.readInt32();
if (size != 0 && pReplyData != NULL && pReplySize != NULL) {
reply.read(pReplyData, size);
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index 9c1e6b7..36a9efe 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -15,6 +15,7 @@
** limitations under the License.
*/
+#include <arpa/inet.h>
#include <stdint.h>
#include <sys/types.h>
@@ -55,6 +56,7 @@
SET_VIDEO_SURFACETEXTURE,
SET_PARAMETER,
GET_PARAMETER,
+ SET_RETRANSMIT_ENDPOINT,
};
class BpMediaPlayer: public BpInterface<IMediaPlayer>
@@ -291,6 +293,25 @@
return remote()->transact(GET_PARAMETER, data, reply);
}
+ status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint) {
+ Parcel data, reply;
+ status_t err;
+
+ data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+ if (NULL != endpoint) {
+ data.writeInt32(sizeof(*endpoint));
+ data.write(endpoint, sizeof(*endpoint));
+ } else {
+ data.writeInt32(0);
+ }
+
+ err = remote()->transact(SET_RETRANSMIT_ENDPOINT, data, &reply);
+ if (OK != err) {
+ return err;
+ }
+
+ return reply.readInt32();
+ }
};
IMPLEMENT_META_INTERFACE(MediaPlayer, "android.media.IMediaPlayer");
@@ -459,6 +480,20 @@
CHECK_INTERFACE(IMediaPlayer, data, reply);
return getParameter(data.readInt32(), reply);
} break;
+ case SET_RETRANSMIT_ENDPOINT: {
+ CHECK_INTERFACE(IMediaPlayer, data, reply);
+
+ struct sockaddr_in endpoint;
+ int amt = data.readInt32();
+ if (amt == sizeof(endpoint)) {
+ data.read(&endpoint, sizeof(struct sockaddr_in));
+ reply->writeInt32(setRetransmitEndpoint(&endpoint));
+ } else {
+ reply->writeInt32(setRetransmitEndpoint(NULL));
+ }
+
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp
index bf40481..00113d9 100644
--- a/media/libmedia/Visualizer.cpp
+++ b/media/libmedia/Visualizer.cpp
@@ -173,7 +173,7 @@
uint32_t replySize = mCaptureSize;
status = command(VISUALIZER_CMD_CAPTURE, 0, NULL, &replySize, waveform);
LOGV("getWaveForm() command returned %d", status);
- if (replySize == 0) {
+ if ((status == NO_ERROR) && (replySize == 0)) {
status = NOT_ENOUGH_DATA;
}
} else {
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index c2e1ddf..111962c 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -63,6 +63,7 @@
mAudioSessionId = AudioSystem::newAudioSessionId();
AudioSystem::acquireAudioSessionId(mAudioSessionId);
mSendLevel = 0;
+ mRetransmitEndpointValid = false;
}
MediaPlayer::~MediaPlayer()
@@ -95,6 +96,7 @@
mCurrentPosition = -1;
mSeekPosition = -1;
mVideoWidth = mVideoHeight = 0;
+ mRetransmitEndpointValid = false;
}
status_t MediaPlayer::setListener(const sp<MediaPlayerListener>& listener)
@@ -146,7 +148,8 @@
const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != 0) {
sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
- if (NO_ERROR != player->setDataSource(url, headers)) {
+ if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+ (NO_ERROR != player->setDataSource(url, headers))) {
player.clear();
}
err = attachNewPlayer(player);
@@ -162,7 +165,8 @@
const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != 0) {
sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
- if (NO_ERROR != player->setDataSource(fd, offset, length)) {
+ if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+ (NO_ERROR != player->setDataSource(fd, offset, length))) {
player.clear();
}
err = attachNewPlayer(player);
@@ -177,7 +181,8 @@
const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != 0) {
sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
- if (NO_ERROR != player->setDataSource(source)) {
+ if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+ (NO_ERROR != player->setDataSource(source))) {
player.clear();
}
err = attachNewPlayer(player);
@@ -471,6 +476,18 @@
return NO_ERROR;
}
+status_t MediaPlayer::doSetRetransmitEndpoint(const sp<IMediaPlayer>& player) {
+ Mutex::Autolock _l(mLock);
+
+ if (player == NULL)
+ return UNKNOWN_ERROR;
+
+ if (mRetransmitEndpointValid)
+ return player->setRetransmitEndpoint(&mRetransmitEndpoint);
+
+ return OK;
+}
+
status_t MediaPlayer::reset()
{
LOGV("reset");
@@ -599,6 +616,36 @@
return INVALID_OPERATION;
}
+status_t MediaPlayer::setRetransmitEndpoint(const char* addrString,
+ uint16_t port) {
+ LOGV("MediaPlayer::setRetransmitEndpoint(%s:%hu)",
+ addrString ? addrString : "(null)", port);
+
+ Mutex::Autolock _l(mLock);
+ if ((mPlayer == NULL) && (mCurrentState == MEDIA_PLAYER_IDLE)) {
+ if (NULL == addrString) {
+ mRetransmitEndpointValid = false;
+ return OK;
+ }
+
+ struct in_addr saddr;
+ if(!inet_aton(addrString, &saddr)) {
+ return BAD_VALUE;
+ }
+
+ memset(&mRetransmitEndpoint, 0, sizeof(&mRetransmitEndpoint));
+ mRetransmitEndpoint.sin_family = AF_INET;
+ mRetransmitEndpoint.sin_addr = saddr;
+ mRetransmitEndpoint.sin_port = htons(port);
+ mRetransmitEndpointValid = true;
+
+ return OK;
+ }
+
+ LOGV("setRetransmitEndpoint: after idle");
+ return INVALID_OPERATION;
+}
+
void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
LOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index a3e2517..e521648 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -29,7 +29,8 @@
libstagefright_omx \
libstagefright_foundation \
libgui \
- libdl
+ libdl \
+ libaah_rtp
LOCAL_STATIC_LIBRARIES := \
libstagefright_nuplayer \
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index f27d3d6..865eb6c 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;
@@ -487,6 +492,7 @@
mStatus = NO_INIT;
mAudioSessionId = audioSessionId;
mUID = uid;
+ mRetransmitEndpointValid = false;
#if CALLBACK_ANTAGONIZER
LOGD("create Antagonizer");
@@ -593,6 +599,10 @@
return NU_PLAYER;
}
+ if (!strncasecmp("aahRX://", url, 8)) {
+ return AAH_RX_PLAYER;
+ }
+
// use MidiFile for MIDI extensions
int lenURL = strlen(url);
for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
@@ -629,6 +639,14 @@
LOGV("Create Test Player stub");
p = new TestPlayerStub();
break;
+ case AAH_RX_PLAYER:
+ LOGV(" create A@H RX Player");
+ p = createAAH_RXPlayer();
+ break;
+ case AAH_TX_PLAYER:
+ LOGV(" create A@H TX Player");
+ p = createAAH_TXPlayer();
+ break;
default:
LOGE("Unknown player type: %d", playerType);
return NULL;
@@ -695,7 +713,12 @@
close(fd);
return mStatus;
} else {
- player_type playerType = getPlayerType(url);
+ // Until re-transmit functionality is added to the existing core android
+ // players, we use the special AAH TX player whenever we were configured
+ // for retransmission.
+ player_type playerType = mRetransmitEndpointValid
+ ? AAH_TX_PLAYER
+ : getPlayerType(url);
LOGV("player type = %d", playerType);
// create the right type of player
@@ -711,6 +734,15 @@
LOGV(" setDataSource");
mStatus = p->setDataSource(url, headers);
if (mStatus == NO_ERROR) {
+ // Set the re-transmission endpoint if one was chosen.
+ if (mRetransmitEndpointValid) {
+ mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint);
+ if (mStatus != NO_ERROR) {
+ LOGE("setRetransmitEndpoint error: %d", mStatus);
+ return mStatus;
+ }
+ }
+
mPlayer = p;
} else {
LOGE(" error: %d", mStatus);
@@ -745,7 +777,12 @@
LOGV("calculated length = %lld", length);
}
- player_type playerType = getPlayerType(fd, offset, length);
+ // Until re-transmit functionality is added to the existing core android
+ // players, we use the special AAH TX player whenever we were configured for
+ // retransmission.
+ player_type playerType = mRetransmitEndpointValid
+ ? AAH_TX_PLAYER
+ : getPlayerType(fd, offset, length);
LOGV("player type = %d", playerType);
// create the right type of player
@@ -759,7 +796,18 @@
// now set data source
mStatus = p->setDataSource(fd, offset, length);
- if (mStatus == NO_ERROR) mPlayer = p;
+ if (mStatus == NO_ERROR) {
+ // Set the re-transmission endpoint if one was chosen.
+ if (mRetransmitEndpointValid) {
+ mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint);
+ if (mStatus != NO_ERROR) {
+ LOGE("setRetransmitEndpoint error: %d", mStatus);
+ return mStatus;
+ }
+ }
+
+ mPlayer = p;
+ }
return mStatus;
}
@@ -767,7 +815,14 @@
status_t MediaPlayerService::Client::setDataSource(
const sp<IStreamSource> &source) {
// create the right type of player
- sp<MediaPlayerBase> p = createPlayer(NU_PLAYER);
+ // Until re-transmit functionality is added to the existing core android
+ // players, we use the special AAH TX player whenever we were configured for
+ // retransmission.
+ player_type playerType = mRetransmitEndpointValid
+ ? AAH_TX_PLAYER
+ : NU_PLAYER;
+ LOGV("player type = %d", playerType);
+ sp<MediaPlayerBase> p = createPlayer(playerType);
if (p == NULL) {
return NO_INIT;
@@ -782,6 +837,15 @@
mStatus = p->setDataSource(source);
if (mStatus == OK) {
+ // Set the re-transmission endpoint if one was chosen.
+ if (mRetransmitEndpointValid) {
+ mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint);
+ if (mStatus != NO_ERROR) {
+ LOGE("setRetransmitEndpoint error: %d", mStatus);
+ return mStatus;
+ }
+ }
+
mPlayer = p;
}
@@ -1005,6 +1069,7 @@
status_t MediaPlayerService::Client::reset()
{
LOGV("[%d] reset", mConnId);
+ mRetransmitEndpointValid = false;
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
return p->reset();
@@ -1031,9 +1096,21 @@
status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume)
{
LOGV("[%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;
}
@@ -1067,6 +1144,36 @@
return p->getParameter(key, reply);
}
+status_t MediaPlayerService::Client::setRetransmitEndpoint(
+ const struct sockaddr_in* endpoint) {
+
+ if (NULL != endpoint) {
+ uint32_t a = ntohl(endpoint->sin_addr.s_addr);
+ uint16_t p = ntohs(endpoint->sin_port);
+ LOGV("[%d] setRetransmitEndpoint(%u.%u.%u.%u:%hu)", mConnId,
+ (a >> 24), (a >> 16) & 0xFF, (a >> 8) & 0xFF, (a & 0xFF), p);
+ } else {
+ LOGV("[%d] setRetransmitEndpoint = <none>", mConnId);
+ }
+
+ sp<MediaPlayerBase> p = getPlayer();
+
+ // Right now, the only valid time to set a retransmit endpoint is before
+ // player selection has been made (since the presence or absence of a
+ // retransmit endpoint is going to determine which player is selected during
+ // setDataSource).
+ if (p != 0) return INVALID_OPERATION;
+
+ if (NULL != endpoint) {
+ mRetransmitEndpoint = *endpoint;
+ mRetransmitEndpointValid = true;
+ } else {
+ mRetransmitEndpointValid = false;
+ }
+
+ return NO_ERROR;
+}
+
void MediaPlayerService::Client::notify(
void* cookie, int msg, int ext1, int ext2, const Parcel *obj)
{
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index b04fddb..dc0eb17 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -18,6 +18,8 @@
#ifndef ANDROID_MEDIAPLAYERSERVICE_H
#define ANDROID_MEDIAPLAYERSERVICE_H
+#include <arpa/inet.h>
+
#include <utils/Log.h>
#include <utils/threads.h>
#include <utils/List.h>
@@ -270,6 +272,7 @@
virtual status_t attachAuxEffect(int effectId);
virtual status_t setParameter(int key, const Parcel &request);
virtual status_t getParameter(int key, Parcel *reply);
+ virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint);
sp<MediaPlayerBase> createPlayer(player_type playerType);
@@ -332,6 +335,8 @@
uid_t mUID;
sp<ANativeWindow> mConnectedWindow;
sp<IBinder> mConnectedWindowBinder;
+ struct sockaddr_in mRetransmitEndpoint;
+ bool mRetransmitEndpointValid;
// Metadata filters.
media::Metadata::Filter mMetadataAllow; // protected by mLock
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
index 2444c4b..4d683fb 100644
--- a/native/include/android/configuration.h
+++ b/native/include/android/configuration.h
@@ -79,6 +79,7 @@
ACONFIGURATION_UI_MODE_TYPE_DESK = 0x02,
ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
ACONFIGURATION_UI_MODE_TYPE_TELEVISION = 0x04,
+ ACONFIGURATION_UI_MODE_TYPE_APPLIANCE = 0x05,
ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 1ebed1f..cdb16a0 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -43,6 +43,7 @@
<bool name="assisted_gps_enabled">true</bool>
<!-- 0 == mobile, 1 == wifi. -->
<integer name="def_network_preference">1</integer>
+ <bool name="def_netstats_enabled">true</bool>
<bool name="def_usb_mass_storage_enabled">true</bool>
<bool name="def_wifi_on">false</bool>
<bool name="def_networks_available_notification_on">true</bool>
@@ -70,6 +71,8 @@
<integer name="def_lockscreen_sounds_enabled">1</integer>
<string name="def_lock_sound" translatable="false">/system/media/audio/ui/Lock.ogg</string>
<string name="def_unlock_sound" translatable="false">/system/media/audio/ui/Unlock.ogg</string>
+ <bool name="def_lockscreen_disabled">false</bool>
+ <bool name="def_device_provisioned">false</bool>
<!-- Notifications use ringer volume -->
<bool name="def_notifications_use_ring_volume">true</bool>
@@ -136,4 +139,11 @@
<bool name="def_dtmf_tones_enabled">true</bool>
<!-- Default for UI touch sounds enabled -->
<bool name="def_sound_effects_enabled">true</bool>
+
+ <!-- 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 a5e3483..2081b95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1337,7 +1337,9 @@
loadBooleanSetting(stmt, Settings.System.DIM_SCREEN,
R.bool.def_dim_screen);
loadSetting(stmt, Settings.System.STAY_ON_WHILE_PLUGGED_IN,
- "1".equals(SystemProperties.get("ro.kernel.qemu")) ? 1 : 0);
+ ("1".equals(SystemProperties.get("ro.kernel.qemu")) ||
+ mContext.getResources().getBoolean(R.bool.def_stay_on_while_plugged_in))
+ ? 1 : 0);
loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT,
R.integer.def_screen_off_timeout);
@@ -1554,6 +1556,18 @@
loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
R.bool.def_accessibility_speak_password);
+
+ loadBooleanSetting(stmt, Settings.System.LOCKSCREEN_DISABLED,
+ R.bool.def_lockscreen_disabled);
+
+ loadBooleanSetting(stmt, Settings.Secure.DEVICE_PROVISIONED,
+ R.bool.def_device_provisioned);
+
+ 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/proguard.flags b/packages/SystemUI/proguard.flags
index fe5b983..c886eea 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -13,3 +13,5 @@
public void setGlowAlpha(float);
public void setGlowScale(float);
}
+
+-keep class com.android.systemui.statusbar.tv.TvStatusBar
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 005d12f..c424284 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -24,6 +24,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
+import android.util.DisplayMetrics;
import android.util.AttributeSet;
import android.util.Slog;
import android.view.animation.AccelerateInterpolator;
@@ -55,11 +56,11 @@
protected IStatusBarService mBarService;
final Display mDisplay;
+ private boolean mNaturallyPortrait = true;
View mCurrentView = null;
View[] mRotatedViews = new View[4];
int mBarSize;
- boolean mVertical;
boolean mHidden, mLowProfile, mShowMenu;
int mDisabledFlags = 0;
@@ -119,9 +120,12 @@
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ DisplayMetrics metrics = new DisplayMetrics();
+ mDisplay.getMetrics(metrics);
+ mNaturallyPortrait = metrics.heightPixels >= metrics.widthPixels;
+
final Resources res = mContext.getResources();
mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
- mVertical = false;
mShowMenu = false;
}
@@ -252,13 +256,15 @@
}
public void reorient() {
- final int rot = mDisplay.getRotation();
+ int rot = mDisplay.getRotation();
+ // cycle all views around 90 degrees if this is a landscape-natural device
+ if (!mNaturallyPortrait) rot = (rot+1)%4;
+
for (int i=0; i<4; i++) {
mRotatedViews[i].setVisibility(View.GONE);
}
mCurrentView = mRotatedViews[rot];
mCurrentView.setVisibility(View.VISIBLE);
- mVertical = (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270);
// force the low profile & disabled states into compliance
setLowProfile(mLowProfile, false, true /* force */);
@@ -352,9 +358,9 @@
mCurrentView.getWidth(), mCurrentView.getHeight(),
visibilityToString(mCurrentView.getVisibility())));
- pw.println(String.format(" disabled=0x%08x vertical=%s hidden=%s low=%s menu=%s",
+ pw.println(String.format(" disabled=0x%08x portrait=%s hidden=%s low=%s menu=%s",
mDisabledFlags,
- mVertical ? "true" : "false",
+ mNaturallyPortrait ? "true" : "false",
mHidden ? "true" : "false",
mLowProfile ? "true" : "false",
mShowMenu ? "true" : "false"));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 6913239..2b1c1ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -448,8 +448,7 @@
// Sanity-check that someone hasn't set up the config wrong and asked for a navigation
// bar on a tablet that has only the system bar
if (mWindowManager.hasNavigationBar()) {
- throw new RuntimeException(
- "Tablet device cannot show navigation bar and system bar");
+ Slog.e(TAG, "Tablet device cannot show navigation bar and system bar");
}
} catch (RemoteException ex) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
new file mode 100644
index 0000000..6bc4467
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -0,0 +1,110 @@
+/*
+ * 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.systemui.statusbar.tv;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarNotification;
+import com.android.systemui.statusbar.StatusBar;
+
+import android.os.IBinder;
+import android.view.View;
+
+/*
+ * Status bar implementation for "large screen" products that mostly present no on-screen nav
+ */
+
+public class TvStatusBar extends StatusBar {
+ View mView;
+
+ @Override
+ public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
+ }
+
+ @Override
+ public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old,
+ StatusBarIcon icon) {
+ }
+
+ @Override
+ public void removeIcon(String slot, int index, int viewIndex) {
+ }
+
+ @Override
+ public void addNotification(IBinder key, StatusBarNotification notification) {
+ }
+
+ @Override
+ public void updateNotification(IBinder key, StatusBarNotification notification) {
+ }
+
+ @Override
+ public void removeNotification(IBinder key) {
+ }
+
+ @Override
+ public void disable(int state) {
+ }
+
+ @Override
+ public void animateExpand() {
+ }
+
+ @Override
+ public void setSystemUiVisibility(int vis) {
+ }
+
+ @Override
+ public void topAppWindowChanged(boolean visible) {
+ }
+
+ @Override
+ public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
+ }
+
+ @Override
+ public void setHardKeyboardStatus(boolean available, boolean enabled) {
+ }
+
+ @Override
+ public void toggleRecentApps() {
+ }
+
+ @Override
+ protected View makeStatusBarView() {
+ synchronized (this) {
+ if (mView == null) {
+ mView = new View(mContext);
+ }
+ }
+ return mView;
+ }
+
+ @Override
+ protected int getStatusBarGravity() {
+ return 0;
+ }
+
+ @Override
+ public int getStatusBarHeight() {
+ return 0;
+ }
+
+ @Override
+ public void animateCollapse() {
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
index abed18f..83f7788 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
@@ -52,8 +52,7 @@
}
public void preDispatchKeyEvent(KeyEvent event) {
- getAudioManager().preDispatchKeyEvent(event.getKeyCode(),
- AudioManager.USE_DEFAULT_STREAM_TYPE);
+ getAudioManager().preDispatchKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
}
public boolean dispatchKeyEvent(KeyEvent event) {
@@ -79,7 +78,7 @@
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- getAudioManager().handleKeyDown(keyCode, AudioManager.USE_DEFAULT_STREAM_TYPE);
+ getAudioManager().handleKeyDown(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
return true;
}
@@ -197,8 +196,7 @@
AudioManager audioManager = (AudioManager)mContext.getSystemService(
Context.AUDIO_SERVICE);
if (audioManager != null) {
- getAudioManager().handleKeyUp(keyCode,
- AudioManager.USE_DEFAULT_STREAM_TYPE);
+ getAudioManager().handleKeyUp(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
}
}
return true;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f1fe43b..b87b8c3 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1416,7 +1416,7 @@
// doesn't have one of these. In this case, we execute it here and
// eat the event instead, because we have mVolumeControlStreamType
// and they don't.
- getAudioManager().handleKeyDown(keyCode, mVolumeControlStreamType);
+ getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
return true;
}
@@ -1478,7 +1478,7 @@
// doesn't have one of these. In this case, we execute it here and
// eat the event instead, because we have mVolumeControlStreamType
// and they don't.
- getAudioManager().handleKeyUp(keyCode, mVolumeControlStreamType);
+ getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
return true;
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 0de76a7..8fae4c7 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -283,7 +283,8 @@
/** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */
boolean mEnableShiftMenuBugReports = false;
-
+
+ boolean mHeadless;
boolean mSafeMode;
WindowState mStatusBar = null;
boolean mStatusBarCanHide;
@@ -316,7 +317,7 @@
boolean mSystemReady;
boolean mSystemBooted;
boolean mHdmiPlugged;
- int mUiMode = Configuration.UI_MODE_TYPE_NORMAL;
+ int mUiMode;
int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
int mLidOpenRotation;
int mCarDockRotation;
@@ -444,6 +445,7 @@
// Screenshot trigger states
// Time to volume and power must be pressed within this interval of each other.
private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150;
+ private boolean mScreenshotChordEnabled;
private boolean mVolumeDownKeyTriggered;
private long mVolumeDownKeyTime;
private boolean mVolumeDownKeyConsumedByScreenshotChord;
@@ -611,7 +613,8 @@
}
private void interceptScreenshotChord() {
- if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
+ if (mScreenshotChordEnabled
+ && mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
@@ -664,7 +667,7 @@
if (mGlobalActions == null) {
mGlobalActions = new GlobalActions(mContext);
}
- final boolean keyguardShowing = mKeyguardMediator.isShowingAndNotHidden();
+ final boolean keyguardShowing = keyguardIsShowingTq();
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
if (keyguardShowing) {
// since it took two seconds of long press to bring this up,
@@ -761,7 +764,11 @@
mWindowManager = windowManager;
mWindowManagerFuncs = windowManagerFuncs;
mPowerManager = powerManager;
- mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
+ mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
+ if (!mHeadless) {
+ // don't create KeyguardViewMediator if headless
+ mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
+ }
mHandler = new Handler();
mOrientationListener = new MyOrientationListener(mContext);
try {
@@ -771,6 +778,8 @@
settingsObserver.observe();
mShortcutManager = new ShortcutManager(context, mHandler);
mShortcutManager.observe();
+ mUiMode = context.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultUiModeType);
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
mHomeIntent.addCategory(Intent.CATEGORY_HOME);
mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -828,6 +837,9 @@
mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_safeModeEnabledVibePattern);
+ mScreenshotChordEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableScreenshotChord);
+
// Controls rotation and the like.
initializeHdmiState();
@@ -1458,12 +1470,8 @@
}
static ITelephony getTelephonyService() {
- ITelephony telephonyService = ITelephony.Stub.asInterface(
+ return ITelephony.Stub.asInterface(
ServiceManager.checkService(Context.TELEPHONY_SERVICE));
- if (telephonyService == null) {
- Log.w(TAG, "Unable to find ITelephony interface.");
- }
- return telephonyService;
}
static IAudioService getAudioService() {
@@ -1503,7 +1511,7 @@
// If we think we might have a volume down & power key chord on the way
// but we're not sure, then tell the dispatcher to wait a little while and
// try again later before dispatching.
- if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ if (mScreenshotChordEnabled && (flags & KeyEvent.FLAG_FALLBACK) == 0) {
if (mVolumeDownKeyTriggered && !mPowerKeyTriggered) {
final long now = SystemClock.uptimeMillis();
final long timeoutTime = mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
@@ -1786,7 +1794,7 @@
* given the situation with the keyguard.
*/
void launchHomeFromHotKey() {
- if (mKeyguardMediator.isShowingAndNotHidden()) {
+ if (mKeyguardMediator != null && mKeyguardMediator.isShowingAndNotHidden()) {
// don't launch home if keyguard showing
} else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {
// when in keyguard restricted mode, must first verify unlock
@@ -2520,6 +2528,9 @@
/** {@inheritDoc} */
public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+ // do nothing if headless
+ if (mHeadless) return;
+
// lid changed state
mLidOpen = lidOpen ? LID_OPEN : LID_CLOSED;
updateKeyboardVisibility();
@@ -2716,9 +2727,10 @@
// the same as if it were open and in front.
// This will prevent any keys other than the power button from waking the screen
// when the keyguard is hidden by another activity.
- final boolean keyguardActive = (isScreenOn ?
- mKeyguardMediator.isShowingAndNotHidden() :
- mKeyguardMediator.isShowing());
+ final boolean keyguardActive = (mKeyguardMediator == null ? false :
+ (isScreenOn ?
+ mKeyguardMediator.isShowingAndNotHidden() :
+ mKeyguardMediator.isShowing()));
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
@@ -2744,7 +2756,7 @@
// the device some other way (which is why we have an exemption here for injected
// events).
int result;
- if (isScreenOn || isInjected) {
+ if ((isScreenOn && !mHeadless) || isInjected) {
// When the screen is on or if the key is injected pass the key to the application.
result = ACTION_PASS_TO_USER;
} else {
@@ -2983,7 +2995,7 @@
final boolean isWakeMotion = (policyFlags
& (WindowManagerPolicy.FLAG_WAKE | WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0;
if (isWakeMotion) {
- if (mKeyguardMediator.isShowing()) {
+ if (mKeyguardMediator != null && mKeyguardMediator.isShowing()) {
// If the keyguard is showing, let it decide what to do with the wake motion.
mKeyguardMediator.onWakeMotionWhenKeyguardShowingTq();
} else {
@@ -3042,7 +3054,9 @@
mScreenOnEarly = false;
mScreenOnFully = false;
}
- mKeyguardMediator.onScreenTurnedOff(why);
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.onScreenTurnedOff(why);
+ }
synchronized (mLock) {
updateOrientationListenerLp();
updateLockScreenTimeout();
@@ -3058,31 +3072,33 @@
Slog.i(TAG, "Screen turning on...", here);
}
if (screenOnListener != null) {
- mKeyguardMediator.onScreenTurnedOn(new KeyguardViewManager.ShowListener() {
- @Override public void onShown(IBinder windowToken) {
- if (windowToken != null) {
- try {
- mWindowManager.waitForWindowDrawn(windowToken,
- new IRemoteCallback.Stub() {
- @Override public void sendResult(Bundle data) {
- Slog.i(TAG, "Lock screen displayed!");
- screenOnListener.onScreenOn();
- synchronized (mLock) {
- mScreenOnFully = true;
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.onScreenTurnedOn(new KeyguardViewManager.ShowListener() {
+ @Override public void onShown(IBinder windowToken) {
+ if (windowToken != null) {
+ try {
+ mWindowManager.waitForWindowDrawn(windowToken,
+ new IRemoteCallback.Stub() {
+ @Override public void sendResult(Bundle data) {
+ Slog.i(TAG, "Lock screen displayed!");
+ screenOnListener.onScreenOn();
+ synchronized (mLock) {
+ mScreenOnFully = true;
+ }
}
- }
- });
- } catch (RemoteException e) {
- }
- } else {
- Slog.i(TAG, "No lock screen!");
- screenOnListener.onScreenOn();
- synchronized (mLock) {
- mScreenOnFully = true;
+ });
+ } catch (RemoteException e) {
+ }
+ } else {
+ Slog.i(TAG, "No lock screen!");
+ screenOnListener.onScreenOn();
+ synchronized (mLock) {
+ mScreenOnFully = true;
+ }
}
}
- }
- });
+ });
+ }
} else {
synchronized (mLock) {
mScreenOnFully = true;
@@ -3107,15 +3123,20 @@
/** {@inheritDoc} */
public void enableKeyguard(boolean enabled) {
- mKeyguardMediator.setKeyguardEnabled(enabled);
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.setKeyguardEnabled(enabled);
+ }
}
/** {@inheritDoc} */
public void exitKeyguardSecurely(OnKeyguardExitResult callback) {
- mKeyguardMediator.verifyUnlock(callback);
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.verifyUnlock(callback);
+ }
}
private boolean keyguardIsShowingTq() {
+ if (mKeyguardMediator == null) return false;
return mKeyguardMediator.isShowingAndNotHidden();
}
@@ -3127,11 +3148,13 @@
/** {@inheritDoc} */
public boolean isKeyguardSecure() {
+ if (mKeyguardMediator == null) return false;
return mKeyguardMediator.isSecure();
}
/** {@inheritDoc} */
public boolean inKeyguardRestrictedKeyInputMode() {
+ if (mKeyguardMediator == null) return false;
return mKeyguardMediator.isInputRestricted();
}
@@ -3387,8 +3410,10 @@
/** {@inheritDoc} */
public void systemReady() {
- // tell the keyguard
- mKeyguardMediator.onSystemReady();
+ if (mKeyguardMediator != null) {
+ // tell the keyguard
+ mKeyguardMediator.onSystemReady();
+ }
android.os.SystemProperties.set("dev.bootcomplete", "1");
synchronized (mLock) {
updateOrientationListenerLp();
@@ -3412,6 +3437,7 @@
/** {@inheritDoc} */
public void showBootMessage(final CharSequence msg, final boolean always) {
+ if (mHeadless) return;
mHandler.post(new Runnable() {
@Override public void run() {
if (mBootMsgDialog == null) {
@@ -3496,7 +3522,9 @@
public void run() {
synchronized (this) {
if (localLOGV) Log.v(TAG, "mScreenLockTimeout activating keyguard");
- mKeyguardMediator.doKeyguardTimeout();
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.doKeyguardTimeout();
+ }
mLockScreenTimerActive = false;
}
}
@@ -3510,7 +3538,8 @@
private void updateLockScreenTimeout() {
synchronized (mScreenLockTimeout) {
- boolean enable = (mAllowLockscreenWhenOn && mScreenOnEarly && mKeyguardMediator.isSecure());
+ boolean enable = (mAllowLockscreenWhenOn && mScreenOnEarly &&
+ mKeyguardMediator != null && mKeyguardMediator.isSecure());
if (mLockScreenTimerActive != enable) {
if (enable) {
if (localLOGV) Log.v(TAG, "setting lockscreen timer");
@@ -3705,7 +3734,7 @@
public void screenOnStoppedLw() {
if (mPowerManager.isScreenOn()) {
- if (!mKeyguardMediator.isShowingAndNotHidden()) {
+ if (mKeyguardMediator != null && !mKeyguardMediator.isShowingAndNotHidden()) {
long curTime = SystemClock.uptimeMillis();
mPowerManager.userActivity(curTime, false, LocalPowerManager.OTHER_EVENT);
}
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index fa49592..fd0609a 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -8,12 +8,14 @@
AudioResampler.cpp.arm \
AudioResamplerSinc.cpp.arm \
AudioResamplerCubic.cpp.arm \
- AudioPolicyService.cpp
+ AudioPolicyService.cpp \
+ AudioBufferProvider.cpp
LOCAL_C_INCLUDES := \
system/media/audio_effects/include
LOCAL_SHARED_LIBRARIES := \
+ libcommon_time_client \
libcutils \
libutils \
libbinder \
diff --git a/services/audioflinger/AudioBufferProvider.cpp b/services/audioflinger/AudioBufferProvider.cpp
new file mode 100644
index 0000000..678fd58
--- /dev/null
+++ b/services/audioflinger/AudioBufferProvider.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef __STRICT_ANSI__
+#define __STDINT_LIMITS
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include "AudioBufferProvider.h"
+
+namespace android {
+
+const int64_t AudioBufferProvider::kInvalidPTS = INT64_MAX;
+
+}; // namespace android
diff --git a/services/audioflinger/AudioBufferProvider.h b/services/audioflinger/AudioBufferProvider.h
index 81c5c39..62ad6bd 100644
--- a/services/audioflinger/AudioBufferProvider.h
+++ b/services/audioflinger/AudioBufferProvider.h
@@ -38,8 +38,15 @@
};
virtual ~AudioBufferProvider() {}
-
- virtual status_t getNextBuffer(Buffer* buffer) = 0;
+
+ // value representing an invalid presentation timestamp
+ static const int64_t kInvalidPTS;
+
+ // pts is the local time when the next sample yielded by getNextBuffer
+ // will be rendered.
+ // Pass kInvalidPTS if the PTS is unknown or not applicable.
+ virtual status_t getNextBuffer(Buffer* buffer, int64_t pts) = 0;
+
virtual void releaseBuffer(Buffer* buffer) = 0;
};
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index ce701ca..6efd671 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -58,6 +58,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>
+
// ----------------------------------------------------------------------------
@@ -66,7 +69,6 @@
static const char* kDeadlockedString = "AudioFlinger may be deadlocked\n";
static const char* kHardwareLockedString = "Hardware lock is taken\n";
-//static const nsecs_t kStandbyTimeInNsecs = seconds(3);
static const float MAX_GAIN = 4096.0f;
static const float MAX_GAIN_INT = 0x1000;
@@ -94,6 +96,9 @@
// maximum divider applied to the active sleep time in the mixer thread loop
static const uint32_t kMaxThreadSleepTimeShift = 2;
+nsecs_t AudioFlinger::mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;
+bool AudioFlinger::mStaticInitDone = false;
+Mutex AudioFlinger::mStaticInitLock;
// ----------------------------------------------------------------------------
@@ -158,11 +163,37 @@
AudioFlinger::AudioFlinger()
: BnAudioFlinger(),
- mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1),
- mBtNrecIsOff(false)
+ mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterVolumeSW(1.0f),
+ mMasterVolumeSupportLvl(MVS_NONE), mMasterMute(false), mNextUniqueId(1),
+ mBtNrecIsOff(false)
{
}
+bool AudioFlinger::doStaticInit() {
+ if (!mStaticInitDone) {
+ Mutex::Autolock _l(mStaticInitLock);
+
+ if (mStaticInitDone)
+ return true;
+
+ 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);
+ LOGI("Using %u mSec as standby time.", int_val);
+ } else {
+ mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;
+ LOGI("Using default %u mSec as standby time.",
+ (uint32_t)(mStandbyTimeInNsecs / 1000000));
+ }
+ }
+
+ mStaticInitDone = true;
+ }
+
+ return mStaticInitDone;
+}
void AudioFlinger::onFirstRef()
{
int rc = 0;
@@ -172,6 +203,10 @@
/* TODO: move all this work into an Init() function */
mHardwareStatus = AUDIO_HW_IDLE;
+ // Make sure all static variables are set up. Right now, this is limited to
+ // the standby time parameter.
+ doStaticInit();
+
for (size_t i = 0; i < ARRAY_SIZE(audio_interfaces); i++) {
const hw_module_t *mod;
audio_hw_device_t *dev;
@@ -198,6 +233,33 @@
return;
}
+ // 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)) {
+ AutoMutex lock(mHardwareLock);
+ 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];
@@ -209,11 +271,22 @@
mMode = AUDIO_MODE_NORMAL;
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;
}
status_t AudioFlinger::initCheck() const
@@ -292,7 +365,10 @@
String8 result;
int 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;
@@ -384,6 +460,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -447,7 +524,7 @@
LOGV("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
@@ -540,20 +617,29 @@
return PERMISSION_DENIED;
}
+ float swmv = value;
+
// when hw supports master volume, don't scale in sw mixer
- { // scope for the lock
- AutoMutex lock(mHardwareLock);
- mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
- if (mPrimaryHardwareDev->set_master_volume(mPrimaryHardwareDev, value) == NO_ERROR) {
- value = 1.0f;
+ if (MVS_NONE != mMasterVolumeSupportLvl) {
+ for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
+ AutoMutex lock(mHardwareLock);
+ audio_hw_device_t *dev = mAudioHwDevs[i];
+
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ if (NULL != dev->set_master_volume) {
+ dev->set_master_volume(dev, value);
+ }
+ mHardwareStatus = AUDIO_HW_IDLE;
}
- mHardwareStatus = AUDIO_HW_IDLE;
+
+ swmv = 1.0;
}
Mutex::Autolock _l(mLock);
- mMasterVolume = value;
+ mMasterVolume = value;
+ mMasterVolumeSW = swmv;
for (uint32_t i = 0; i < mPlaybackThreads.size(); i++)
- mPlaybackThreads.valueAt(i)->setMasterVolume(value);
+ mPlaybackThreads.valueAt(i)->setMasterVolume(swmv);
return NO_ERROR;
}
@@ -641,9 +727,27 @@
float AudioFlinger::masterVolume() 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;
}
+float AudioFlinger::masterVolumeSW() const
+{
+ return mMasterVolumeSW;
+}
+
bool AudioFlinger::masterMute() const
{
return mMasterMute;
@@ -819,7 +923,7 @@
for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
audio_hw_device_t *dev = mAudioHwDevs[i];
char *s = dev->get_parameters(dev, keys.string());
- out_s8 += String8(s);
+ out_s8 += String8(s ? s : "");
free(s);
}
return out_s8;
@@ -1374,7 +1478,8 @@
readOutputParameters();
- mMasterVolume = mAudioFlinger->masterVolume();
+ mMasterVolume = mAudioFlinger->masterVolumeSW();
+
mMasterMute = mAudioFlinger->masterMute();
for (int stream = 0; stream < AUDIO_STREAM_CNT; stream++) {
@@ -1485,6 +1590,7 @@
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status)
{
sp<Track> track;
@@ -1534,9 +1640,14 @@
}
}
- track = new Track(this, client, streamType, sampleRate, format,
- channelMask, frameCount, sharedBuffer, sessionId);
- if (track->getCblk() == NULL || track->name() < 0) {
+ if (!isTimed) {
+ track = new Track(this, client, streamType, sampleRate, format,
+ channelMask, frameCount, sharedBuffer, sessionId);
+ } else {
+ track = TimedTrack::create(this, client, streamType, sampleRate, format,
+ channelMask, frameCount, sharedBuffer, sessionId);
+ }
+ if (track == NULL || track->getCblk() == NULL || track->name() < 0) {
lStatus = NO_MEMORY;
goto Exit;
}
@@ -1873,6 +1984,10 @@
acquireWakeLock();
+ LocalClock lc;
+ uint64_t localTimeFreq;
+ localTimeFreq = lc.getLocalFreq();
+
while (!exitPending())
{
#ifdef DEBUG_CPU_USAGE
@@ -1955,7 +2070,7 @@
}
}
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
sleepTime = idleSleepTime;
sleepTimeShift = 0;
continue;
@@ -1971,8 +2086,21 @@
}
if (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
@@ -1981,7 +2109,7 @@
sleepTimeShift--;
}
sleepTime = 0;
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
//TODO: delay standby when effects have a tail
} else {
// If no tracks are ready, sleep once for the duration of an output
@@ -2126,7 +2254,7 @@
LOG_ASSERT(minFrames <= cblk->frameCount);
}
}
- if ((cblk->framesReady() >= minFrames) && track->isReady() &&
+ if ((track->framesReady() >= minFrames) && track->isReady() &&
!track->isPaused() && !track->isTerminated())
{
//LOGV("track %d u=%08x, s=%08x [OK] on thread %p", track->name(), cblk->user, cblk->server, this);
@@ -2788,7 +2916,8 @@
// output audio to hardware
while (frameCount) {
buffer.frameCount = frameCount;
- activeTrack->getNextBuffer(&buffer);
+ activeTrack->getNextBuffer(&buffer,
+ AudioBufferProvider::kInvalidPTS);
if (UNLIKELY(buffer.raw == 0)) {
memset(curBuf, 0, frameCount * mFrameSize);
break;
@@ -3041,7 +3170,7 @@
}
}
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
sleepTime = idleSleepTime;
continue;
}
@@ -3058,7 +3187,7 @@
if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
// mix buffers...
if (outputsReady(outputTracks)) {
- mAudioMixer->process();
+ mAudioMixer->process(AudioBufferProvider::kInvalidPTS);
} else {
memset(mMixBuffer, 0, mixBufferSize);
}
@@ -3095,7 +3224,7 @@
// enable changes in effect chain
unlockEffectChains(effectChains);
- standbyTime = systemTime() + kStandbyTimeInNsecs;
+ standbyTime = systemTime() + mStandbyTimeInNsecs;
for (size_t i = 0; i < outputTracks.size(); i++) {
outputTracks[i]->write(mMixBuffer, writeFrames);
}
@@ -3453,7 +3582,8 @@
(int)mAuxBuffer);
}
-status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(
+ AudioBufferProvider::Buffer* buffer, int64_t pts)
{
audio_track_cblk_t* cblk = this->cblk();
uint32_t framesReady;
@@ -3494,10 +3624,14 @@
return NOT_ENOUGH_DATA;
}
+uint32_t AudioFlinger::PlaybackThread::Track::framesReady() const{
+ return mCblk->framesReady();
+}
+
bool AudioFlinger::PlaybackThread::Track::isReady() const {
if (mFillingUpStatus != FS_FILLING || isStopped() || isPausing()) return true;
- if (mCblk->framesReady() >= mCblk->frameCount ||
+ if (framesReady() >= mCblk->frameCount ||
(mCblk->flags & CBLK_FORCEREADY_MSK)) {
mFillingUpStatus = FS_FILLED;
android_atomic_and(~CBLK_FORCEREADY_MSK, &mCblk->flags);
@@ -3666,6 +3800,406 @@
mAuxBuffer = buffer;
}
+// timed audio tracks
+
+sp<AudioFlinger::PlaybackThread::TimedTrack>
+AudioFlinger::PlaybackThread::TimedTrack::create(
+ const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_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,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_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) {
+
+ LOGV("%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
+ LOGW("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)) {
+ LOGV("*** head buffer is too far from PTS: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ }
+ if (!mLocalTimeToSampleTransform.doForwardTransform(
+ (effectivePTS - pts) << 32, &sampleDelta)) {
+ LOGV("*** too late during sample rate transform: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ }
+
+ LOGV("*** %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);
+
+ LOGV("*** 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);
+ LOGV("*** 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
+ LOGV("*** too late: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ } else {
+ // skip over the late samples
+ head.setPosition(onTimeSamplePosition);
+
+ // yield the available samples
+ timedYieldSamples(buffer);
+
+ LOGV("*** 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 the buffer which was just released is part of the buffer at the head
+ // of the queue, be sure to update the amt of the buffer which has been
+ // consumed. If the buffer being returned is not part of the head of the
+ // queue, its either because the buffer is part of the silence buffer, or
+ // because the head of the timed queue was trimmed after the mixer called
+ // getNextBuffer but before the mixer called releaseBuffer.
+ if ((buffer->raw != mTimedSilenceBuffer) && mTimedBufferQueue.size()) {
+ TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
+
+ void* start = head.buffer()->pointer();
+ void* end = head.buffer()->pointer() + head.buffer()->size();
+
+ if ((buffer->raw >= start) && (buffer->raw <= end)) {
+ 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
@@ -3702,7 +4236,7 @@
}
}
-status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
{
audio_track_cblk_t* cblk = this->cblk();
uint32_t framesAvail;
@@ -4024,7 +4558,8 @@
: RefBase(),
mAudioFlinger(audioFlinger),
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
}
@@ -4040,6 +4575,31 @@
return mMemoryDealer;
}
+// Reserve one of the limited slots for a timed audio track associated
+// with this client
+bool AudioFlinger::Client::reserveTimedTrack()
+{
+ const int kMaxTimedTracksPerClient = 4;
+
+ Mutex::Autolock _l(mTimedTrackLock);
+
+ if (mTimedTrackCount >= kMaxTimedTracksPerClient) {
+ LOGW("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,
@@ -4111,6 +4671,38 @@
return mTrack->attachAuxEffect(EffectId);
}
+status_t AudioFlinger::TrackHandle::allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer) {
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->allocateTimedBuffer(size, buffer);
+}
+
+status_t AudioFlinger::TrackHandle::queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) {
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->queueTimedBuffer(buffer, pts);
+}
+
+status_t AudioFlinger::TrackHandle::setMediaTimeTransform(
+ const LinearTransform& xform, int target) {
+
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->setMediaTimeTransform(
+ xform, static_cast<TimedAudioTrack::TargetTimeline>(target));
+}
+
status_t AudioFlinger::TrackHandle::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
@@ -4348,7 +4940,8 @@
}
buffer.frameCount = mFrameCount;
- if (LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) {
+ if (LIKELY(mActiveTrack->getNextBuffer(
+ &buffer, AudioBufferProvider::kInvalidPTS) == NO_ERROR)) {
size_t framesOut = buffer.frameCount;
if (mResampler == 0) {
// no resampling
@@ -4627,7 +5220,7 @@
return NO_ERROR;
}
-status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
{
size_t framesReq = buffer->frameCount;
size_t framesReady = mFrameCount - mRsmpInIndex;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 9bd2c7f..71870a2 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -22,6 +22,8 @@
#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>
@@ -61,7 +63,7 @@
// ----------------------------------------------------------------------------
-static const nsecs_t kStandbyTimeInNsecs = seconds(3);
+static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
class AudioFlinger :
public BinderService<AudioFlinger>,
@@ -84,6 +86,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status);
@@ -97,6 +100,7 @@
virtual status_t setMasterMute(bool muted);
virtual float masterVolume() const;
+ virtual float masterVolumeSW() const;
virtual bool masterMute() const;
virtual status_t setStreamVolume(int stream, float value, int output);
@@ -188,6 +192,7 @@
AUDIO_HW_SET_MIC_MUTE,
AUDIO_SET_VOICE_VOLUME,
AUDIO_SET_PARAMETER,
+ AUDIO_HW_GET_MASTER_VOLUME,
};
// record interface
@@ -221,6 +226,11 @@
audio_hw_device_t* findSuitableHwDev_l(uint32_t devices);
void purgeStaleEffects_l();
+ static Mutex mStaticInitLock;
+ static bool mStaticInitDone;
+ static bool doStaticInit();
+ 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);
@@ -235,12 +245,18 @@
pid_t pid() const { return mPid; }
sp<AudioFlinger> audioFlinger() { return mAudioFlinger; }
+ bool reserveTimedTrack();
+ void releaseTimedTrack();
+
private:
Client(const Client&);
Client& operator = (const Client&);
sp<AudioFlinger> mAudioFlinger;
sp<MemoryDealer> mMemoryDealer;
pid_t mPid;
+
+ Mutex mTimedTrackLock;
+ int mTimedTrackCount;
};
// --- Notification Client ---
@@ -346,7 +362,9 @@
TrackBase(const TrackBase&);
TrackBase& operator = (const TrackBase&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0;
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts) = 0;
virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
uint32_t format() const {
@@ -611,7 +629,6 @@
int16_t *mainBuffer() { return mMainBuffer; }
int auxEffectId() { return mAuxEffectId; }
-
protected:
friend class ThreadBase;
friend class TrackHandle;
@@ -622,7 +639,11 @@
Track(const Track&);
Track& operator = (const Track&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
+ virtual uint32_t framesReady() const;
+
bool isMuted() { return mMute; }
bool isPausing() const {
return mState == PAUSING;
@@ -638,6 +659,8 @@
return (mStreamType == AUDIO_STREAM_CNT);
}
+ virtual bool isTimedTrack() const { return false; }
+
// we don't really need a lock for these
float mVolume[2];
volatile bool mMute;
@@ -655,6 +678,79 @@
bool mHasVolumeController;
}; // end of Track
+ class TimedTrack : public Track {
+ public:
+ static sp<TimedTrack> create(const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_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,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_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 {
@@ -728,6 +824,7 @@
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status);
AudioStreamOut* getOutput();
@@ -917,6 +1014,12 @@
virtual void setVolume(float left, float right);
virtual sp<IMemory> getCblk() const;
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:
@@ -964,7 +1067,9 @@
RecordTrack(const RecordTrack&);
RecordTrack& operator = (const RecordTrack&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
bool mOverflow;
};
@@ -1000,7 +1105,8 @@
AudioStreamIn* clearInput();
virtual audio_stream_t* stream();
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
virtual bool checkForNewParameters_l();
virtual String8 getParameters(const String8& keys);
@@ -1384,6 +1490,12 @@
friend class RecordThread;
friend class PlaybackThread;
+ enum master_volume_support {
+ MVS_NONE,
+ MVS_SETONLY,
+ MVS_FULL,
+ };
+
mutable Mutex mLock;
DefaultKeyedVector< pid_t, wp<Client> > mClients;
@@ -1397,6 +1509,8 @@
DefaultKeyedVector< int, sp<PlaybackThread> > mPlaybackThreads;
PlaybackThread::stream_type_t mStreamTypes[AUDIO_STREAM_CNT];
float mMasterVolume;
+ float mMasterVolumeSW;
+ master_volume_support mMasterVolumeSupportLvl;
bool mMasterMute;
DefaultKeyedVector< int, sp<RecordThread> > mRecordThreads;
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index 1200f75..98415b1 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "AudioMixer"
//#define LOG_NDEBUG 0
+#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
@@ -30,6 +31,9 @@
#include <system/audio.h>
+#include <common_time/local_clock.h>
+#include <common_time/cc_helper.h>
+
#include "AudioMixer.h"
namespace android {
@@ -47,6 +51,9 @@
AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate)
: mActiveTrack(0), mTrackNames(0), mSampleRate(sampleRate)
{
+ LocalClock lc;
+ mLocalTimeFreq = lc.getLocalFreq();
+
mState.enabledTracks= 0;
mState.needsChanged = 0;
mState.frameCount = frameCount;
@@ -74,6 +81,7 @@
t->in = 0;
t->mainBuffer = NULL;
t->auxBuffer = NULL;
+ t->localTimeFreq = mLocalTimeFreq;
t++;
}
}
@@ -293,6 +301,7 @@
if (resampler == 0) {
resampler = AudioResampler::create(
format, channelCount, devSampleRate);
+ resampler->setLocalTimeFreq(localTimeFreq);
}
return true;
}
@@ -357,13 +366,13 @@
-void AudioMixer::process()
+void AudioMixer::process(int64_t pts)
{
- mState.hook(&mState);
+ mState.hook(&mState, pts);
}
-void AudioMixer::process__validate(state_t* state)
+void AudioMixer::process__validate(state_t* state, int64_t pts)
{
LOGW_IF(!state->needsChanged,
"in process__validate() but nothing's invalid");
@@ -467,7 +476,7 @@
countActiveTracks, state->enabledTracks,
all16BitsStereoNoResample, resampling, volumeRamp);
- state->hook(state);
+ state->hook(state, pts);
// Now that the volume ramp has been done, set optimal state and
// track hooks for subsequent mixer process
@@ -876,7 +885,7 @@
}
// no-op case
-void AudioMixer::process__nop(state_t* state)
+void AudioMixer::process__nop(state_t* state, int64_t pts)
{
uint32_t e0 = state->enabledTracks;
size_t bufSize = state->frameCount * sizeof(int16_t) * MAX_NUM_CHANNELS;
@@ -906,7 +915,9 @@
size_t outFrames = state->frameCount;
while (outFrames) {
t1.buffer.frameCount = outFrames;
- t1.bufferProvider->getNextBuffer(&t1.buffer);
+ int64_t outputPTS = calculateOutputPTS(
+ t1, pts, state->frameCount - outFrames);
+ t1.bufferProvider->getNextBuffer(&t1.buffer, outputPTS);
if (!t1.buffer.raw) break;
outFrames -= t1.buffer.frameCount;
t1.bufferProvider->releaseBuffer(&t1.buffer);
@@ -916,7 +927,7 @@
}
// generic code without resampling
-void AudioMixer::process__genericNoResampling(state_t* state)
+void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
{
int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
@@ -928,7 +939,7 @@
e0 &= ~(1<<i);
track_t& t = state->tracks[i];
t.buffer.frameCount = state->frameCount;
- t.bufferProvider->getNextBuffer(&t.buffer);
+ t.bufferProvider->getNextBuffer(&t.buffer, pts);
t.frameCount = t.buffer.frameCount;
t.in = t.buffer.raw;
// t.in == NULL can happen if the track was flushed just after having
@@ -982,7 +993,9 @@
if (t.frameCount == 0 && outFrames) {
t.bufferProvider->releaseBuffer(&t.buffer);
t.buffer.frameCount = (state->frameCount - numFrames) - (BLOCKSIZE - outFrames);
- t.bufferProvider->getNextBuffer(&t.buffer);
+ int64_t outputPTS = calculateOutputPTS(
+ t, pts, numFrames + (BLOCKSIZE - outFrames));
+ t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
t.in = t.buffer.raw;
if (t.in == NULL) {
enabledTracks &= ~(1<<i);
@@ -1011,7 +1024,7 @@
// generic code with resampling
-void AudioMixer::process__genericResampling(state_t* state)
+void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
{
int32_t* const outTemp = state->outputTemp;
const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount;
@@ -1050,6 +1063,7 @@
// acquire/release the buffers because it's done by
// the resampler.
if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) {
+ t.resampler->setPTS(pts);
(t.hook)(&t, outTemp, numFrames, state->resampleTemp, aux);
} else {
@@ -1057,7 +1071,8 @@
while (outFrames < numFrames) {
t.buffer.frameCount = numFrames - outFrames;
- t.bufferProvider->getNextBuffer(&t.buffer);
+ int64_t outputPTS = calculateOutputPTS(t, pts, outFrames);
+ t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
t.in = t.buffer.raw;
// t.in == NULL can happen if the track was flushed just after having
// been enabled for mixing.
@@ -1077,7 +1092,8 @@
}
// one track, 16 bits stereo without resampling is the most common case
-void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
+ int64_t pts)
{
const int i = 31 - __builtin_clz(state->enabledTracks);
const track_t& t = state->tracks[i];
@@ -1092,7 +1108,8 @@
const uint32_t vrl = t.volumeRL;
while (numFrames) {
b.frameCount = numFrames;
- t.bufferProvider->getNextBuffer(&b);
+ int64_t outputPTS = calculateOutputPTS(t, pts, out - t.mainBuffer);
+ t.bufferProvider->getNextBuffer(&b, outputPTS);
int16_t const *in = b.i16;
// in == NULL can happen if the track was flushed just after having
@@ -1135,7 +1152,8 @@
// 2 tracks is also a common case
// NEVER used in current implementation of process__validate()
// only use if the 2 tracks have the same output buffer
-void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state,
+ int64_t pts)
{
int i;
uint32_t en = state->enabledTracks;
@@ -1169,7 +1187,9 @@
if (frameCount0 == 0) {
b0.frameCount = numFrames;
- t0.bufferProvider->getNextBuffer(&b0);
+ int64_t outputPTS = calculateOutputPTS(t0, pts,
+ out - t0.mainBuffer);
+ t0.bufferProvider->getNextBuffer(&b0, outputPTS);
if (b0.i16 == NULL) {
if (buff == NULL) {
buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1183,7 +1203,9 @@
}
if (frameCount1 == 0) {
b1.frameCount = numFrames;
- t1.bufferProvider->getNextBuffer(&b1);
+ int64_t outputPTS = calculateOutputPTS(t1, pts,
+ out - t0.mainBuffer);
+ t1.bufferProvider->getNextBuffer(&b1, outputPTS);
if (b1.i16 == NULL) {
if (buff == NULL) {
buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1230,6 +1252,11 @@
}
}
+int64_t AudioMixer::calculateOutputPTS(const track_t& t, int64_t basePTS,
+ int outputFrameIndex)
+{
+ return basePTS + ((outputFrameIndex * t.localTimeFreq) / t.sampleRate);
+}
+
// ----------------------------------------------------------------------------
}; // namespace android
-
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index 0137185..200ad03 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -85,7 +85,7 @@
status_t setParameter(int target, int name, void *value);
status_t setBufferProvider(AudioBufferProvider* bufferProvider);
- void process();
+ void process(int64_t pts);
uint32_t trackNames() const { return mTrackNames; }
@@ -127,7 +127,7 @@
struct state_t;
struct track_t;
- typedef void (*mix_t)(state_t* state);
+ typedef void (*mix_t)(state_t* state, int64_t pts);
typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux);
static const int BLOCKSIZE = 16; // 4 cache lines
@@ -165,6 +165,8 @@
int32_t* mainBuffer;
int32_t* auxBuffer;
+ uint64_t localTimeFreq;
+
bool setResampler(uint32_t sampleRate, uint32_t devSampleRate);
bool doesResample() const;
void resetResampler();
@@ -188,6 +190,8 @@
uint32_t mTrackNames;
const uint32_t mSampleRate;
+ int64_t mLocalTimeFreq;
+
state_t mState __attribute__((aligned(32)));
void invalidateState(uint32_t mask);
@@ -199,12 +203,17 @@
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__TwoTracks16BitsStereoNoResampling(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);
+ static void process__TwoTracks16BitsStereoNoResampling(state_t* state,
+ int64_t pts);
+
+ 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 9ee5a30..cd2857f 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -59,10 +59,12 @@
#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1
void AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
- uint32_t &phaseFraction, uint32_t phaseIncrement);
+ uint32_t &phaseFraction, uint32_t phaseIncrement)
+ __attribute__((noinline));
void AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
- uint32_t &phaseFraction, uint32_t phaseIncrement);
+ uint32_t &phaseFraction, uint32_t phaseIncrement)
+ __attribute__((noinline));
#endif // ASM_ARM_RESAMP1
static inline int32_t Interp(int32_t x0, int32_t x1, uint32_t f) {
@@ -118,7 +120,8 @@
int32_t sampleRate) :
mBitDepth(bitDepth), mChannelCount(inChannelCount),
mSampleRate(sampleRate), mInSampleRate(sampleRate), mInputIndex(0),
- mPhaseFraction(0) {
+ mPhaseFraction(0), mLocalTimeFreq(0),
+ mPTS(AudioBufferProvider::kInvalidPTS) {
// sanity check on format
if ((bitDepth != 16) ||(inChannelCount < 1) || (inChannelCount > 2)) {
LOGE("Unsupported sample format, %d bits, %d channels", bitDepth,
@@ -152,6 +155,23 @@
mVolume[1] = right;
}
+void AudioResampler::setLocalTimeFreq(uint64_t freq) {
+ mLocalTimeFreq = freq;
+}
+
+void AudioResampler::setPTS(int64_t pts) {
+ mPTS = pts;
+}
+
+int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) {
+
+ if (mPTS == AudioBufferProvider::kInvalidPTS) {
+ return AudioBufferProvider::kInvalidPTS;
+ } else {
+ return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate);
+ }
+}
+
void AudioResampler::reset() {
mInputIndex = 0;
mPhaseFraction = 0;
@@ -198,7 +218,8 @@
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto resampleStereo16_exit;
}
@@ -292,7 +313,8 @@
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
@@ -602,4 +624,3 @@
// ----------------------------------------------------------------------------
}
; // namespace android
-
diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h
index ffa690a..2b1488b 100644
--- a/services/audioflinger/AudioResampler.h
+++ b/services/audioflinger/AudioResampler.h
@@ -49,6 +49,10 @@
virtual void init() = 0;
virtual void setSampleRate(int32_t inSampleRate);
virtual void setVolume(int16_t left, int16_t right);
+ virtual void setLocalTimeFreq(uint64_t freq);
+
+ // set the PTS of the next buffer output by the resampler
+ virtual void setPTS(int64_t pts);
virtual void resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) = 0;
@@ -73,6 +77,8 @@
AudioResampler(const AudioResampler&);
AudioResampler& operator=(const AudioResampler&);
+ int64_t calculateOutputPTS(int outputFrameIndex);
+
int32_t mBitDepth;
int32_t mChannelCount;
int32_t mSampleRate;
@@ -87,6 +93,8 @@
size_t mInputIndex;
int32_t mPhaseIncrement;
uint32_t mPhaseFraction;
+ uint64_t mLocalTimeFreq;
+ int64_t mPTS;
};
// ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp
index 4d721f6..e864b30 100644
--- a/services/audioflinger/AudioResamplerCubic.cpp
+++ b/services/audioflinger/AudioResamplerCubic.cpp
@@ -65,7 +65,7 @@
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL)
return;
// LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -95,7 +95,8 @@
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL)
goto save_state; // ugly, but efficient
in = mBuffer.i16;
@@ -130,7 +131,7 @@
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL)
return;
// LOGW("New buffer: offset=%p, frames=%d\n", mBuffer.raw, mBuffer.frameCount);
@@ -160,7 +161,8 @@
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL)
goto save_state; // ugly, but efficient
// LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -181,4 +183,3 @@
// ----------------------------------------------------------------------------
}
; // namespace android
-
diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp
index 9e5e254..6184795 100644
--- a/services/audioflinger/AudioResamplerSinc.cpp
+++ b/services/audioflinger/AudioResamplerSinc.cpp
@@ -204,7 +204,8 @@
// buffer is empty, fetch a new one
while (buffer.frameCount == 0) {
buffer.frameCount = inFrameCount;
- provider->getNextBuffer(&buffer);
+ provider->getNextBuffer(&buffer,
+ calculateOutputPTS(outputIndex / 2));
if (buffer.raw == NULL) {
goto resample_exit;
}
@@ -355,4 +356,3 @@
// ----------------------------------------------------------------------------
}; // namespace android
-
diff --git a/services/common_time/Android.mk b/services/common_time/Android.mk
new file mode 100644
index 0000000..e534d49
--- /dev/null
+++ b/services/common_time/Android.mk
@@ -0,0 +1,34 @@
+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
+
+# Uncomment to enable vesbose logging and debug service.
+#TIME_SERVICE_DEBUG=true
+ifeq ($(TIME_SERVICE_DEBUG), true)
+LOCAL_SRC_FILES += diag_thread.cpp
+LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG
+endif
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcommon_time_client \
+ libutils
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := common_time
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/common_time/clock_recovery.cpp b/services/common_time/clock_recovery.cpp
new file mode 100644
index 0000000..4a1b57e
--- /dev/null
+++ b/services/common_time/clock_recovery.cpp
@@ -0,0 +1,321 @@
+/*
+ * 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
+
+// Define log macro so we can make LOGV into LOGE when we are exclusively
+// debugging this code.
+#ifdef TIME_SERVICE_DEBUG
+#define LOG_TS LOGE
+#else
+#define LOG_TS LOGV
+#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);
+
+ 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)
+ LOGW("Failed to start A@H clock recovery diagnostic thread.");
+ } else
+ LOGW("Failed to allocate diagnostic thread.");
+#endif
+}
+
+ClockRecoveryLoop::~ClockRecoveryLoop() {
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_->stopWorkThread();
+#endif
+}
+
+// Constants.
+const float ClockRecoveryLoop::dT = 1.0;
+const float ClockRecoveryLoop::Kc = 1.0f;
+const float ClockRecoveryLoop::Ti = 15.0f;
+const float ClockRecoveryLoop::Tf = 0.05;
+const float ClockRecoveryLoop::bias_Fc = 0.01;
+const float ClockRecoveryLoop::bias_RC = (dT / (2 * 3.14159f * bias_Fc));
+const float ClockRecoveryLoop::bias_Alpha = (dT / (bias_RC + dT));
+const int64_t ClockRecoveryLoop::panic_thresh_ = 50000;
+const int64_t ClockRecoveryLoop::control_thresh_ = 10000;
+const float ClockRecoveryLoop::COmin = -100.0f;
+const float ClockRecoveryLoop::COmax = 100.0f;
+
+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_);
+
+ int64_t local_common_time = 0;
+ common_clock_->localToCommon(local_time, &local_common_time);
+ int64_t raw_delta = nominal_common_time - local_common_time;
+
+#ifdef TIME_SERVICE_DEBUG
+ LOGE("local=%lld, common=%lld, delta=%lld, rtt=%lld\n",
+ local_common_time, nominal_common_time,
+ raw_delta, rtt);
+#endif
+
+ // 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;
+ float delta_f, dCO;
+ int32_t correction_cur;
+
+ 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.
+ LOGE("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;
+ uint32_t current_point = filter_wr_;
+ filter_wr_ = (filter_wr_ + 1) % kFilterSize;
+ if (!filter_wr_)
+ filter_full_ = true;
+
+ uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_;
+ uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end);
+ // We only use packets with low RTTs for control. If the packet RTT
+ // is less than the panic threshold, we can probably eat the jitter with the
+ // control loop. Otherwise, take the packet only if it better than all
+ // of the packets we have in the history. That way we try to track
+ // something, even if it is noisy.
+ if (current_point == min_rtt || rtt < control_thresh_) {
+ delta_f = delta = nominal_common_time - observed_common;
+
+ // 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_ + rtt;
+ if ((delta > effective_panic_thresh) ||
+ (delta < -effective_panic_thresh)) {
+ // PANIC!!!
+ reset_l(false, true);
+ return false;
+ }
+
+ } else {
+ // We do not have a good packet to look at, but we also do not want to
+ // free-run the clock at some crazy slew rate. So we guess the
+ // trajectory of the clock based on the last controller output and the
+ // estimated bias of our clock against the master.
+ // The net effect of this is that CO == CObias after some extended
+ // period of no feedback.
+ delta_f = last_delta_f_ - dT*(CO - CObias);
+ delta = delta_f;
+ }
+
+ // Velocity form PI control equation.
+ dCO = Kc * (1.0f + dT/Ti) * delta_f - Kc * last_delta_f_;
+ CO += dCO * Tf; // Filter CO by applying gain <1 here.
+
+ // Save error terms for later.
+ last_delta_f_ = delta_f;
+ last_delta_ = delta;
+
+ // Clamp CO to +/- 100ppm.
+ if (CO < COmin)
+ CO = COmin;
+ else if (CO > COmax)
+ CO = COmax;
+
+ // Update the controller bias.
+ CObias = bias_Alpha * CO + (1.0f - bias_Alpha) * lastCObias;
+ lastCObias = CObias;
+
+ // Convert PPM to 16-bit int range. Add some guard band (-0.01) so we
+ // don't get fp weirdness.
+ correction_cur = CO * 327.66;
+
+ // 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();
+ }
+
+ LOG_TS("clock_loop %lld %f %f %f %d\n", raw_delta, delta_f, CO, CObias, correction_cur);
+
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_->pushDisciplineEvent(
+ local_time,
+ observed_common,
+ nominal_common_time,
+ correction_cur,
+ rtt);
+#endif
+
+ return true;
+}
+
+int32_t ClockRecoveryLoop::getLastErrorEstimate() {
+ Mutex::Autolock lock(&lock_);
+
+ if (last_delta_valid_)
+ return last_delta_;
+ else
+ return ICommonClock::kErrorEstimateUnknown;
+}
+
+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;
+ last_delta_f_ = 0.0;
+ correction_cur_ = 0x0;
+ CO = 0.0f;
+ lastCObias = CObias = 0.0f;
+ applySlew();
+ }
+
+ filter_wr_ = 0;
+ filter_full_ = false;
+}
+
+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. CO is in ppm.
+ common_clock_->setSlew(local_clock_->getLocalTime(), CO);
+ }
+}
+
+} // namespace android
diff --git a/services/common_time/clock_recovery.h b/services/common_time/clock_recovery.h
new file mode 100644
index 0000000..b7362be
--- /dev/null
+++ b/services/common_time/clock_recovery.h
@@ -0,0 +1,138 @@
+/*
+ * 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:
+
+ // Tuned using the "Good Gain" method.
+ // See:
+ // http://techteach.no/publications/books/dynamics_and_control/tuning_pid_controller.pdf
+
+ // Controller period (1Hz for now).
+ static const float dT;
+
+ // Controller gain, positive and unitless. Larger values converge faster,
+ // but can cause instability.
+ static const float Kc;
+
+ // Integral reset time. Smaller values cause loop to track faster, but can
+ // also cause instability.
+ static const float Ti;
+
+ // Controller output filter time constant. Range (0-1). Smaller values make
+ // output smoother, but slow convergence.
+ static const float Tf;
+
+ // Low-pass filter for bias tracker.
+ static const float bias_Fc; // HZ
+ static const float bias_RC; // Computed in constructor.
+ static const float bias_Alpha; // Computed inconstructor.
+
+ // The maximum allowed error (as indicated by a pushDisciplineEvent) before
+ // we panic.
+ static const int64_t panic_thresh_;
+
+ // The maximum allowed error rtt time for packets to be used for control
+ // feedback, unless the packet is the best in recent memory.
+ static const int64_t control_thresh_;
+
+ 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 reset_l(bool position, bool frequency);
+ 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_;
+
+ // parameters maintained while running and reset during a reset
+ // of the frequency correction.
+ bool last_delta_valid_;
+ int32_t last_delta_;
+ float last_delta_f_;
+ int32_t integrated_error_;
+ int32_t correction_cur_;
+
+ // Contoller Output.
+ float CO;
+
+ // Bias tracking for trajectory estimation.
+ float CObias;
+ float lastCObias;
+
+ // Controller output bounds. The controller will not try to
+ // slew faster that +/-100ppm offset from center per interation.
+ static const float COmin;
+ static const float COmax;
+
+ // State kept for filtering the discipline data.
+ static const uint32_t kFilterSize = 16;
+ DisciplineDataPoint filter_data_[kFilterSize];
+ uint32_t filter_wr_;
+ bool filter_full_;
+
+ static const uint32_t kStartupFilterSize = 4;
+ DisciplineDataPoint startup_filter_data_[kStartupFilterSize];
+ uint32_t startup_filter_wr_;
+
+#ifdef TIME_SERVICE_DEBUG
+ sp<DiagThread> diag_thread_;
+#endif
+};
+
+} // namespace android
+
+#endif // __CLOCK_RECOVERY_H__
diff --git a/services/common_time/common_clock.cpp b/services/common_time/common_clock.cpp
new file mode 100644
index 0000000..c8edf35
--- /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)) {
+ LOGE("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)) {
+ LOGE("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 alredy been reduced, no need to do so here.
+ LinearTransform::reduce(&n1, &d2);
+ LinearTransform::reduce(&n2, &d1);
+ LinearTransform::reduce(&n2, &d2);
+
+ cur_trans_.a_zero = new_local_basis;
+ cur_trans_.b_zero = new_common_basis;
+ cur_trans_.a_to_b_numer = n1 * n2;
+ cur_trans_.a_to_b_denom = d1 * d2;
+
+ return OK;
+}
+
+} // namespace android
diff --git a/services/common_time/common_clock.h b/services/common_time/common_clock.h
new file mode 100644
index 0000000..b786fdc
--- /dev/null
+++ b/services/common_time/common_clock.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __COMMON_CLOCK_H__
+#define __COMMON_CLOCK_H__
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock {
+ public:
+ CommonClock();
+
+ bool init(uint64_t local_freq);
+
+ status_t localToCommon(int64_t local, int64_t *common_out) const;
+ status_t commonToLocal(int64_t common, int64_t *local_out) const;
+ int64_t localDurationToCommonDuration(int64_t localDur) const;
+ uint64_t getCommonFreq() const { return kCommonFreq; }
+ bool isValid() const { return cur_trans_valid_; }
+ status_t setSlew(int64_t change_time, int32_t ppm);
+ void setBasis(int64_t local, int64_t common);
+ void resetBasis();
+ private:
+ mutable Mutex lock_;
+
+ int32_t cur_slew_;
+ uint32_t local_to_common_freq_numer_;
+ uint32_t local_to_common_freq_denom_;
+
+ LinearTransform duration_trans_;
+ LinearTransform cur_trans_;
+ bool cur_trans_valid_;
+
+ static const uint64_t kCommonFreq = 1000000ull;
+};
+
+} // namespace android
+#endif // __COMMON_CLOCK_H__
diff --git a/services/common_time/common_clock_service.cpp b/services/common_time/common_clock_service.cpp
new file mode 100644
index 0000000..9ca6f35
--- /dev/null
+++ b/services/common_time/common_clock_service.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <common_time/local_clock.h>
+#include <utils/String8.h>
+
+#include "common_clock_service.h"
+#include "common_clock.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonClockService> CommonClockService::instantiate(
+ CommonTimeServer& timeServer) {
+ sp<CommonClockService> tcc = new CommonClockService(timeServer);
+ if (tcc == NULL)
+ return NULL;
+
+ defaultServiceManager()->addService(ICommonClock::kServiceName, tcc);
+ return tcc;
+}
+
+status_t CommonClockService::dump(int fd, const Vector<String16>& args) {
+ Mutex::Autolock lock(mRegistrationLock);
+ return mTimeServer.dumpClockInterface(fd, args, mListeners.size());
+}
+
+status_t CommonClockService::isCommonTimeValid(bool* valid,
+ uint32_t* timelineID) {
+ return mTimeServer.isCommonTimeValid(valid, timelineID);
+}
+
+status_t CommonClockService::commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) {
+ return mTimeServer.getCommonClock().commonToLocal(commonTime, localTime);
+}
+
+status_t CommonClockService::localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) {
+ return mTimeServer.getCommonClock().localToCommon(localTime, commonTime);
+}
+
+status_t CommonClockService::getCommonTime(int64_t* commonTime) {
+ return localTimeToCommonTime(mTimeServer.getLocalClock().getLocalTime(), commonTime);
+}
+
+status_t CommonClockService::getCommonFreq(uint64_t* freq) {
+ *freq = mTimeServer.getCommonClock().getCommonFreq();
+ return OK;
+}
+
+status_t CommonClockService::getLocalTime(int64_t* localTime) {
+ *localTime = mTimeServer.getLocalClock().getLocalTime();
+ return OK;
+}
+
+status_t CommonClockService::getLocalFreq(uint64_t* freq) {
+ *freq = mTimeServer.getLocalClock().getLocalFreq();
+ return OK;
+}
+
+status_t CommonClockService::getEstimatedError(int32_t* estimate) {
+ *estimate = mTimeServer.getEstimatedError();
+ return OK;
+}
+
+status_t CommonClockService::getTimelineID(uint64_t* id) {
+ *id = mTimeServer.getTimelineID();
+ return OK;
+}
+
+status_t CommonClockService::getState(State* state) {
+ *state = mTimeServer.getState();
+ return OK;
+}
+
+status_t CommonClockService::getMasterAddr(struct sockaddr_storage* addr) {
+ return mTimeServer.getMasterAddr(addr);
+}
+
+status_t CommonClockService::registerListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mRegistrationLock);
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ // check whether this is a duplicate
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder())
+ return ALREADY_EXISTS;
+ }
+ }
+
+ mListeners.add(listener);
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+ return listener->asBinder()->linkToDeath(this);
+}
+
+status_t CommonClockService::unregisterListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mRegistrationLock);
+ status_t ret_val = NAME_NOT_FOUND;
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder()) {
+ mListeners[i]->asBinder()->unlinkToDeath(this);
+ mListeners.removeAt(i);
+ ret_val = OK;
+ break;
+ }
+ }
+ }
+
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+ return ret_val;
+}
+
+void CommonClockService::binderDied(const wp<IBinder>& who) {
+ Mutex::Autolock lock(mRegistrationLock);
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == who) {
+ mListeners.removeAt(i);
+ break;
+ }
+ }
+ }
+
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+}
+
+void CommonClockService::notifyOnTimelineChanged(uint64_t timelineID) {
+ Mutex::Autolock lock(mCallbackLock);
+
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ mListeners[i]->onTimelineChanged(timelineID);
+ }
+}
+
+}; // namespace android
diff --git a/services/common_time/common_clock_service.h b/services/common_time/common_clock_service.h
new file mode 100644
index 0000000..a65e398
--- /dev/null
+++ b/services/common_time/common_clock_service.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <common_time/ICommonClock.h>
+
+#ifndef ANDROID_COMMON_CLOCK_SERVICE_H
+#define ANDROID_COMMON_CLOCK_SERVICE_H
+
+namespace android {
+
+class CommonTimeServer;
+
+class CommonClockService : public BnCommonClock,
+ public android::IBinder::DeathRecipient {
+ public:
+ static sp<CommonClockService> instantiate(CommonTimeServer& timeServer);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t *timelineID);
+ virtual status_t commonTimeToLocalTime(int64_t common_time,
+ int64_t* local_time);
+ virtual status_t localTimeToCommonTime(int64_t local_time,
+ int64_t* common_time);
+ virtual status_t getCommonTime(int64_t* common_time);
+ virtual status_t getCommonFreq(uint64_t* freq);
+ virtual status_t getLocalTime(int64_t* local_time);
+ virtual status_t getLocalFreq(uint64_t* freq);
+ virtual status_t getEstimatedError(int32_t* estimate);
+ virtual status_t getTimelineID(uint64_t* id);
+ virtual status_t getState(ICommonClock::State* state);
+ virtual status_t getMasterAddr(struct sockaddr_storage* addr);
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener);
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener);
+
+ void notifyOnTimelineChanged(uint64_t timelineID);
+
+ private:
+ CommonClockService(CommonTimeServer& timeServer)
+ : mTimeServer(timeServer) { };
+
+ virtual void binderDied(const wp<IBinder>& who);
+
+ CommonTimeServer& mTimeServer;
+
+ // locks used to synchronize access to the list of registered listeners.
+ // The callback lock is held whenever the list is used to perform callbacks
+ // or while the list is being modified. The registration lock used to
+ // serialize access across registerListener, unregisterListener, and
+ // binderDied.
+ //
+ // The reason for two locks is that registerListener, unregisterListener,
+ // and binderDied each call into the core service and obtain the core
+ // service thread lock when they call reevaluateAutoDisableState. The core
+ // service thread obtains the main thread lock whenever its thread is
+ // running, and sometimes needs to call notifyOnTimelineChanged which then
+ // obtains the callback lock. If callers of registration functions were
+ // holding the callback lock when they called into the core service, we
+ // would have a classic A/B, B/A ordering deadlock. To avoid this, the
+ // registration functions hold the registration lock for the duration of
+ // their call, but hold the callback lock only while they mutate the list.
+ // This way, the list's size cannot change (because of the registration
+ // lock) during the call into reevaluateAutoDisableState, but the core work
+ // thread can still safely call notifyOnTimelineChanged while holding the
+ // main thread lock.
+ Mutex mCallbackLock;
+ Mutex mRegistrationLock;
+
+ Vector<sp<ICommonClockListener> > mListeners;
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_CLOCK_SERVICE_H
diff --git a/services/common_time/common_time_config_service.cpp b/services/common_time/common_time_config_service.cpp
new file mode 100644
index 0000000..9585618
--- /dev/null
+++ b/services/common_time/common_time_config_service.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <utils/String8.h>
+
+#include "common_time_config_service.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonTimeConfigService> CommonTimeConfigService::instantiate(
+ CommonTimeServer& timeServer) {
+ sp<CommonTimeConfigService> ctcs = new CommonTimeConfigService(timeServer);
+ if (ctcs == NULL)
+ return NULL;
+
+ defaultServiceManager()->addService(ICommonTimeConfig::kServiceName, ctcs);
+ return ctcs;
+}
+
+status_t CommonTimeConfigService::dump(int fd, const Vector<String16>& args) {
+ return mTimeServer.dumpConfigInterface(fd, args);
+}
+
+status_t CommonTimeConfigService::getMasterElectionPriority(uint8_t *priority) {
+ return mTimeServer.getMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::setMasterElectionPriority(uint8_t priority) {
+ return mTimeServer.setMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::getMasterElectionEndpoint(
+ struct sockaddr_storage *addr) {
+ return mTimeServer.getMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::setMasterElectionEndpoint(
+ const struct sockaddr_storage *addr) {
+ return mTimeServer.setMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::getMasterElectionGroupId(uint64_t *id) {
+ return mTimeServer.getMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::setMasterElectionGroupId(uint64_t id) {
+ return mTimeServer.setMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::getInterfaceBinding(String16& ifaceName) {
+ String8 tmp;
+ status_t ret = mTimeServer.getInterfaceBinding(tmp);
+ ifaceName = String16(tmp);
+ return ret;
+}
+
+status_t CommonTimeConfigService::setInterfaceBinding(const String16& ifaceName) {
+ String8 tmp(ifaceName);
+ return mTimeServer.setInterfaceBinding(tmp);
+}
+
+status_t CommonTimeConfigService::getMasterAnnounceInterval(int *interval) {
+ return mTimeServer.getMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::setMasterAnnounceInterval(int interval) {
+ return mTimeServer.setMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::getClientSyncInterval(int *interval) {
+ return mTimeServer.getClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::setClientSyncInterval(int interval) {
+ return mTimeServer.setClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::getPanicThreshold(int *threshold) {
+ return mTimeServer.getPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::setPanicThreshold(int threshold) {
+ return mTimeServer.setPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::getAutoDisable(bool *autoDisable) {
+ return mTimeServer.getAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::setAutoDisable(bool autoDisable) {
+ return mTimeServer.setAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::forceNetworklessMasterMode() {
+ return mTimeServer.forceNetworklessMasterMode();
+}
+
+}; // namespace android
diff --git a/services/common_time/common_time_config_service.h b/services/common_time/common_time_config_service.h
new file mode 100644
index 0000000..8283c24
--- /dev/null
+++ b/services/common_time/common_time_config_service.h
@@ -0,0 +1,59 @@
+/* * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <common_time/ICommonTimeConfig.h>
+
+#ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+#define ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+
+namespace android {
+
+class String16;
+class CommonTimeServer;
+
+class CommonTimeConfigService : public BnCommonTimeConfig {
+ public:
+ static sp<CommonTimeConfigService> instantiate(CommonTimeServer& timeServer);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t getMasterElectionPriority(uint8_t *priority);
+ virtual status_t setMasterElectionPriority(uint8_t priority);
+ virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr);
+ virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr);
+ virtual status_t getMasterElectionGroupId(uint64_t *id);
+ virtual status_t setMasterElectionGroupId(uint64_t id);
+ virtual status_t getInterfaceBinding(String16& ifaceName);
+ virtual status_t setInterfaceBinding(const String16& ifaceName);
+ virtual status_t getMasterAnnounceInterval(int *interval);
+ virtual status_t setMasterAnnounceInterval(int interval);
+ virtual status_t getClientSyncInterval(int *interval);
+ virtual status_t setClientSyncInterval(int interval);
+ virtual status_t getPanicThreshold(int *threshold);
+ virtual status_t setPanicThreshold(int threshold);
+ virtual status_t getAutoDisable(bool *autoDisable);
+ virtual status_t setAutoDisable(bool autoDisable);
+ virtual status_t forceNetworklessMasterMode();
+
+ private:
+ CommonTimeConfigService(CommonTimeServer& timeServer)
+ : mTimeServer(timeServer) { }
+ CommonTimeServer& mTimeServer;
+
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_TIME_CONFIG_SERVICE_H
diff --git a/services/common_time/common_time_server.cpp b/services/common_time/common_time_server.cpp
new file mode 100644
index 0000000..48fea66
--- /dev/null
+++ b/services/common_time/common_time_server.cpp
@@ -0,0 +1,1379 @@
+/*
+ * 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 <limits>
+#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"
+
+using std::numeric_limits;
+
+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::kSetupRetryTimeout = 30000;
+const int64_t CommonTimeServer::kNoGoodDataPanicThreshold = 600000000ll;
+
+// 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) {
+ LOGE("%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(kSetupRetryTimeout);
+ }
+
+ // 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.
+ LOGI("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())
+ LOGE("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())
+ LOGE("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));
+ LOGI("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) {
+ LOGW("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) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGE("%s:%d ioctl failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) {
+ LOGE("%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) {
+ LOGE("%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: {
+ LOGD("%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) {
+ LOGE("%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) {
+ LOGE("%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));
+ LOGI("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) * 5)) {
+ LOGV("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, rttCommon);
+ mClient_LastGoodSyncRX = clientRxLocalTime;
+
+ if (result) {
+ // indicate to listeners that we've synced to the common timeline
+ notifyClockSync();
+ } else {
+ LOGE("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)
+ LOGE("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)
+ LOGE("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)
+ LOGE("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));
+
+ LOGI("%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();
+ }
+
+ LOGI("%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()) {
+ LOGI("%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 {
+ LOGI("%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) {
+ LOGI("%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) {
+ LOGI("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>(std::numeric_limits<int>::max()))
+ return std::numeric_limits<int>::max();
+
+ 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 >= kNoGoodDataPanicThreshold)
+ return true;
+ }
+
+ return false;
+}
+
+void CommonTimeServer::PacketRTTLog::logTX(int64_t txTime) {
+ txTimes[wrPtr] = txTime;
+ rxTimes[wrPtr] = 0;
+ wrPtr = (wrPtr + 1) % RTT_LOG_SIZE;
+ if (!wrPtr)
+ logFull = true;
+}
+
+void CommonTimeServer::PacketRTTLog::logRX(int64_t txTime, int64_t rxTime) {
+ if (!logFull && !wrPtr)
+ return;
+
+ uint32_t i = logFull ? wrPtr : 0;
+ do {
+ if (txTimes[i] == txTime) {
+ rxTimes[i] = rxTime;
+ break;
+ }
+ i = (i + 1) % RTT_LOG_SIZE;
+ } while (i != wrPtr);
+}
+
+} // namespace android
diff --git a/services/common_time/common_time_server.h b/services/common_time/common_time_server.h
new file mode 100644
index 0000000..2aa2b4c
--- /dev/null
+++ b/services/common_time/common_time_server.h
@@ -0,0 +1,329 @@
+/*
+ * 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 kSetupRetryTimeout;
+ static const int64_t kNoGoodDataPanicThreshold;
+
+ /*** status while in the Initial state ***/
+ int mInitial_WhoIsMasterRequestTimeouts;
+ static const int kInitial_NumWhoIsMasterRetries;
+ static const int kInitial_WhoIsMasterTimeoutMs;
+
+ /*** status while in the Client state ***/
+ uint64_t mClient_MasterDeviceID;
+ uint8_t mClient_MasterDevicePriority;
+ bool mClient_SyncRequestPending;
+ int mClient_SyncRequestTimeouts;
+ uint32_t mClient_SyncsSentToCurMaster;
+ uint32_t mClient_SyncRespsRXedFromCurMaster;
+ uint32_t mClient_ExpiredSyncRespsRXedFromCurMaster;
+ int64_t mClient_FirstSyncTX;
+ int64_t mClient_LastGoodSyncRX;
+ PacketRTTLog mClient_PacketRTTLog;
+ static const int kClient_NumSyncRequestRetries;
+
+
+ /*** status while in the Master state ***/
+ static const uint32_t kDefaultMaster_AnnouncementIntervalMs;
+
+ /*** status while in the Ronin state ***/
+ int mRonin_WhoIsMasterRequestTimeouts;
+ static const int kRonin_NumWhoIsMasterRetries;
+ static const int kRonin_WhoIsMasterTimeoutMs;
+
+ /*** status while in the WaitForElection state ***/
+ static const int kWaitForElection_TimeoutMs;
+
+ static const int kInfiniteTimeout;
+
+ static const char* stateToString(ICommonClock::State s);
+ static void sockaddrToString(const sockaddr_storage& addr, bool addrValid,
+ char* buf, size_t bufLen);
+ static bool sockaddrMatch(const sockaddr_storage& a1,
+ const sockaddr_storage& a2,
+ bool matchAddressOnly);
+};
+
+} // namespace android
+
+#endif // ANDROID_COMMON_TIME_SERVER_H
diff --git a/services/common_time/common_time_server_api.cpp b/services/common_time/common_time_server_api.cpp
new file mode 100644
index 0000000..657a382
--- /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) {
+ LOGI("Waking up service, auto-disable is engaged and service now has%s"
+ " clients", mCommonClockHasClients ? "" : " no");
+ wakeupThread_l();
+ }
+}
+
+#define dump_printf(a, b...) do { \
+ int res; \
+ res = snprintf(buffer, sizeof(buffer), a, b); \
+ buffer[sizeof(buffer) - 1] = 0; \
+ if (res > 0) \
+ write(fd, buffer, res); \
+} while (0)
+#define checked_percentage(a, b) ((0 == b) ? 0.0f : ((100.0f * a) / b))
+
+status_t CommonTimeServer::dumpClockInterface(int fd,
+ const Vector<String16>& args,
+ size_t activeClients) {
+ AutoMutex _lock(&mLock);
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump CommonClockService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, buffer, strlen(buffer));
+ } else {
+ int64_t commonTime;
+ int64_t localTime;
+ bool synced;
+ char maStr[64];
+
+ localTime = mLocalClock.getLocalTime();
+ synced = (OK == mCommonClock.localToCommon(localTime, &commonTime));
+ sockaddrToString(mMasterEP, mMasterEPValid, maStr, sizeof(maStr));
+
+ dump_printf("Common Clock Service Status\nLocal time : %lld\n",
+ localTime);
+
+ if (synced)
+ dump_printf("Common time : %lld\n", commonTime);
+ else
+ dump_printf("Common time : %s\n", "not synced");
+
+ dump_printf("Timeline ID : %016llx\n", mTimelineID);
+ dump_printf("State : %s\n", stateToString(mState));
+ dump_printf("Master Addr : %s\n", maStr);
+
+
+ if (synced) {
+ int32_t est = (ICommonClock::STATE_MASTER != mState)
+ ? mClockRecovery.getLastErrorEstimate()
+ : 0;
+ dump_printf("Error Est. : %.3f msec\n",
+ static_cast<float>(est) / 1000.0);
+ } else {
+ dump_printf("Error Est. : %s\n", "unknown");
+ }
+
+ dump_printf("Syncs TXes : %u\n", mClient_SyncsSentToCurMaster);
+ dump_printf("Syncs RXes : %u (%.2f%%)\n",
+ mClient_SyncRespsRXedFromCurMaster,
+ checked_percentage(
+ mClient_SyncRespsRXedFromCurMaster,
+ mClient_SyncsSentToCurMaster));
+ dump_printf("RXs Expired : %u (%.2f%%)\n",
+ mClient_ExpiredSyncRespsRXedFromCurMaster,
+ checked_percentage(
+ mClient_ExpiredSyncRespsRXedFromCurMaster,
+ mClient_SyncsSentToCurMaster));
+
+ if (!mClient_LastGoodSyncRX) {
+ dump_printf("Last Good RX : %s\n", "unknown");
+ } else {
+ int64_t localDelta, usecDelta;
+ localDelta = localTime - mClient_LastGoodSyncRX;
+ usecDelta = mCommonClock.localDurationToCommonDuration(localDelta);
+ dump_printf("Last Good RX : %lld uSec ago\n", usecDelta);
+ }
+
+ dump_printf("Active Clients : %u\n", activeClients);
+ mClient_PacketRTTLog.dumpLog(fd, mCommonClock);
+ }
+
+ return NO_ERROR;
+}
+
+status_t CommonTimeServer::dumpConfigInterface(int fd,
+ const Vector<String16>& args) {
+ AutoMutex _lock(&mLock);
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump CommonTimeConfigService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, buffer, strlen(buffer));
+ } else {
+ char meStr[64];
+
+ sockaddrToString(mMasterElectionEP, true, meStr, sizeof(meStr));
+
+ dump_printf("Common Time Config Service Status\n"
+ "Bound Interface : %s\n",
+ mBindIfaceValid ? mBindIface.string() : "<unbound>");
+ dump_printf("Master Election Endpoint : %s\n", meStr);
+ dump_printf("Master Election Group ID : %016llx\n", mSyncGroupID);
+ dump_printf("Master Announce Interval : %d mSec\n",
+ mMasterAnnounceIntervalMs);
+ dump_printf("Client Sync Interval : %d mSec\n",
+ mSyncRequestIntervalMs);
+ dump_printf("Panic Threshold : %d uSec\n",
+ mPanicThresholdUsec);
+ dump_printf("Base ME Prio : 0x%02x\n",
+ static_cast<uint32_t>(mMasterPriority));
+ dump_printf("Effective ME Prio : 0x%02x\n",
+ static_cast<uint32_t>(effectivePriority()));
+ dump_printf("Auto Disable Allowed : %s\n",
+ mAutoDisable ? "yes" : "no");
+ dump_printf("Auto Disable Engaged : %s\n",
+ shouldAutoDisable() ? "yes" : "no");
+ }
+
+ return NO_ERROR;
+}
+
+void CommonTimeServer::PacketRTTLog::dumpLog(int fd, const CommonClock& cclk) {
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ uint32_t avail = !logFull ? wrPtr : RTT_LOG_SIZE;
+
+ if (!avail)
+ return;
+
+ dump_printf("\nPacket Log (%d entries)\n", avail);
+
+ uint32_t ndx = 0;
+ uint32_t i = logFull ? wrPtr : 0;
+ do {
+ if (rxTimes[i]) {
+ int64_t delta = rxTimes[i] - txTimes[i];
+ int64_t deltaUsec = cclk.localDurationToCommonDuration(delta);
+ dump_printf("pkt[%2d] : localTX %12lld localRX %12lld "
+ "(%.3f msec RTT)\n",
+ ndx, txTimes[i], rxTimes[i],
+ static_cast<float>(deltaUsec) / 1000.0);
+ } else {
+ dump_printf("pkt[%2d] : localTX %12lld localRX never\n",
+ ndx, txTimes[i]);
+ }
+ i = (i + 1) % RTT_LOG_SIZE;
+ ndx++;
+ } while (i != wrPtr);
+}
+
+#undef dump_printf
+#undef checked_percentage
+
+} // namespace android
diff --git a/services/common_time/common_time_server_packets.cpp b/services/common_time/common_time_server_packets.cpp
new file mode 100644
index 0000000..9833c37
--- /dev/null
+++ b/services/common_time/common_time_server_packets.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include "common_time_server_packets.h"
+
+namespace android {
+
+const uint32_t TimeServicePacketHeader::kMagic =
+ (static_cast<uint32_t>('c') << 24) |
+ (static_cast<uint32_t>('c') << 16) |
+ (static_cast<uint32_t>('l') << 8) |
+ static_cast<uint32_t>('k');
+
+const uint16_t TimeServicePacketHeader::kCurVersion = 1;
+
+#define SERIALIZE_FIELD(field_name, type, converter) \
+ do { \
+ if ((offset + sizeof(field_name)) > length) \
+ return -1; \
+ *((type*)(data + offset)) = converter(field_name); \
+ offset += sizeof(field_name); \
+ } while (0)
+#define SERIALIZE_INT16(field_name) SERIALIZE_FIELD(field_name, int16_t, htons)
+#define SERIALIZE_INT32(field_name) SERIALIZE_FIELD(field_name, int32_t, htonl)
+#define SERIALIZE_INT64(field_name) SERIALIZE_FIELD(field_name, int64_t, htonq)
+
+#define DESERIALIZE_FIELD(field_name, type, converter) \
+ do { \
+ if ((offset + sizeof(field_name)) > length) \
+ return -1; \
+ field_name = converter(*((type*)(data + offset))); \
+ offset += sizeof(field_name); \
+ } while (0)
+#define DESERIALIZE_INT16(field_name) DESERIALIZE_FIELD(field_name, int16_t, ntohs)
+#define DESERIALIZE_INT32(field_name) DESERIALIZE_FIELD(field_name, int32_t, ntohl)
+#define DESERIALIZE_INT64(field_name) DESERIALIZE_FIELD(field_name, int64_t, ntohq)
+
+#define kDevicePriorityShift 56
+#define kDeviceIDMask ((static_cast<uint64_t>(1) << kDevicePriorityShift) - 1)
+
+inline uint64_t packDeviceID(uint64_t devID, uint8_t prio) {
+ return (devID & kDeviceIDMask) |
+ (static_cast<uint64_t>(prio) << kDevicePriorityShift);
+}
+
+inline uint64_t unpackDeviceID(uint64_t packed) {
+ return (packed & kDeviceIDMask);
+}
+
+inline uint8_t unpackDevicePriority(uint64_t packed) {
+ return static_cast<uint8_t>(packed >> kDevicePriorityShift);
+}
+
+ssize_t TimeServicePacketHeader::serializeHeader(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = 0;
+ int16_t pktType = static_cast<int16_t>(packetType);
+ SERIALIZE_INT32(magic);
+ SERIALIZE_INT16(version);
+ SERIALIZE_INT16(pktType);
+ SERIALIZE_INT64(timelineID);
+ SERIALIZE_INT64(syncGroupID);
+ return offset;
+}
+
+ssize_t TimeServicePacketHeader::deserializeHeader(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = 0;
+ int16_t tmp;
+ DESERIALIZE_INT32(magic);
+ DESERIALIZE_INT16(version);
+ DESERIALIZE_INT16(tmp);
+ DESERIALIZE_INT64(timelineID);
+ DESERIALIZE_INT64(syncGroupID);
+ packetType = static_cast<TimeServicePacketType>(tmp);
+ return offset;
+}
+
+ssize_t TimeServicePacketHeader::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t ret, tmp;
+
+ ret = serializeHeader(data, length);
+ if (ret < 0)
+ return ret;
+
+ data += ret;
+ length -= ret;
+
+ switch (packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ tmp =((WhoIsMasterRequestPacket*)(this))->serializePacket(data,
+ length);
+ break;
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ tmp =((WhoIsMasterResponsePacket*)(this))->serializePacket(data,
+ length);
+ break;
+ case TIME_PACKET_SYNC_REQUEST:
+ tmp =((SyncRequestPacket*)(this))->serializePacket(data, length);
+ break;
+ case TIME_PACKET_SYNC_RESPONSE:
+ tmp =((SyncResponsePacket*)(this))->serializePacket(data, length);
+ break;
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ tmp =((MasterAnnouncementPacket*)(this))->serializePacket(data,
+ length);
+ break;
+ default:
+ return -1;
+ }
+
+ if (tmp < 0)
+ return tmp;
+
+ return ret + tmp;
+}
+
+ssize_t UniversalTimeServicePacket::deserializePacket(
+ const uint8_t* data,
+ uint32_t length,
+ uint64_t expectedSyncGroupID) {
+ ssize_t ret;
+ TimeServicePacketHeader* header;
+ if (length < 8)
+ return -1;
+
+ packetType = ntohs(*((uint16_t*)(data + 6)));
+ switch (packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ ret = p.who_is_master_request.deserializePacket(data, length);
+ header = &p.who_is_master_request;
+ break;
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ ret = p.who_is_master_response.deserializePacket(data, length);
+ header = &p.who_is_master_response;
+ break;
+ case TIME_PACKET_SYNC_REQUEST:
+ ret = p.sync_request.deserializePacket(data, length);
+ header = &p.sync_request;
+ break;
+ case TIME_PACKET_SYNC_RESPONSE:
+ ret = p.sync_response.deserializePacket(data, length);
+ header = &p.sync_response;
+ break;
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ ret = p.master_announcement.deserializePacket(data, length);
+ header = &p.master_announcement;
+ break;
+ default:
+ return -1;
+ }
+
+ if ((ret >= 0) && !header->checkPacket(expectedSyncGroupID))
+ ret = -1;
+
+ return ret;
+}
+
+ssize_t WhoIsMasterRequestPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(senderDeviceID, senderDevicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterRequestPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ senderDeviceID = unpackDeviceID(packed);
+ senderDevicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(deviceID, devicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ deviceID = unpackDeviceID(packed);
+ devicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+ssize_t SyncRequestPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ SERIALIZE_INT64(clientTxLocalTime);
+ }
+ return offset;
+}
+
+ssize_t SyncRequestPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ DESERIALIZE_INT64(clientTxLocalTime);
+ }
+ return offset;
+}
+
+ssize_t SyncResponsePacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ SERIALIZE_INT64(clientTxLocalTime);
+ SERIALIZE_INT64(masterRxCommonTime);
+ SERIALIZE_INT64(masterTxCommonTime);
+ SERIALIZE_INT32(nak);
+ }
+ return offset;
+}
+
+ssize_t SyncResponsePacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ DESERIALIZE_INT64(clientTxLocalTime);
+ DESERIALIZE_INT64(masterRxCommonTime);
+ DESERIALIZE_INT64(masterTxCommonTime);
+ DESERIALIZE_INT32(nak);
+ }
+ return offset;
+}
+
+ssize_t MasterAnnouncementPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(deviceID, devicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t MasterAnnouncementPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ deviceID = unpackDeviceID(packed);
+ devicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+} // namespace android
+
diff --git a/services/common_time/common_time_server_packets.h b/services/common_time/common_time_server_packets.h
new file mode 100644
index 0000000..57ba8a2
--- /dev/null
+++ b/services/common_time/common_time_server_packets.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_COMMON_TIME_SERVER_PACKETS_H
+#define ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+
+namespace android {
+
+/***** time sync protocol packets *****/
+
+enum TimeServicePacketType {
+ TIME_PACKET_WHO_IS_MASTER_REQUEST = 1,
+ TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+ TIME_PACKET_SYNC_REQUEST,
+ TIME_PACKET_SYNC_RESPONSE,
+ TIME_PACKET_MASTER_ANNOUNCEMENT,
+};
+
+class TimeServicePacketHeader {
+ public:
+ friend class UniversalTimeServicePacket;
+ // magic number identifying the protocol
+ uint32_t magic;
+
+ // protocol version of the packet
+ uint16_t version;
+
+ // type of the packet
+ TimeServicePacketType packetType;
+
+ // the timeline ID
+ uint64_t timelineID;
+
+ // synchronization group this packet belongs to (used to operate multiple
+ // synchronization domains which all use the same master election endpoint)
+ uint64_t syncGroupID;
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+
+ protected:
+ void initHeader(TimeServicePacketType type,
+ const uint64_t tlID,
+ const uint64_t groupID) {
+ magic = kMagic;
+ version = kCurVersion;
+ packetType = type;
+ timelineID = tlID;
+ syncGroupID = groupID;
+ }
+
+ bool checkPacket(uint64_t expectedSyncGroupID) const {
+ return ((magic == kMagic) &&
+ (version == kCurVersion) &&
+ (!expectedSyncGroupID || (syncGroupID == expectedSyncGroupID)));
+ }
+
+ ssize_t serializeHeader(uint8_t* data, uint32_t length);
+ ssize_t deserializeHeader(const uint8_t* data, uint32_t length);
+
+ private:
+ static const uint32_t kMagic;
+ static const uint16_t kCurVersion;
+};
+
+// packet querying for a suitable master
+class WhoIsMasterRequestPacket : public TimeServicePacketHeader {
+ public:
+ uint64_t senderDeviceID;
+ uint8_t senderDevicePriority;
+
+ void initHeader(const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_REQUEST,
+ ICommonClock::kInvalidTimelineID,
+ groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a WhoIsMaster request
+class WhoIsMasterResponsePacket : public TimeServicePacketHeader {
+ public:
+ uint64_t deviceID;
+ uint8_t devicePriority;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// packet sent by a client requesting correspondence between local
+// and common time
+class SyncRequestPacket : public TimeServicePacketHeader {
+ public:
+ // local time when this request was transmitted
+ int64_t clientTxLocalTime;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_REQUEST,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a sync request sent by the master
+class SyncResponsePacket : public TimeServicePacketHeader {
+ public:
+ // local time when this request was transmitted by the client
+ int64_t clientTxLocalTime;
+
+ // common time when the master received the request
+ int64_t masterRxCommonTime;
+
+ // common time when the master transmitted the response
+ int64_t masterTxCommonTime;
+
+ // flag that is set if the recipient of the sync request is not acting
+ // as a master for the requested timeline
+ uint32_t nak;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_RESPONSE,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// announcement of the master's presence
+class MasterAnnouncementPacket : public TimeServicePacketHeader {
+ public:
+ // the master's device ID
+ uint64_t deviceID;
+ uint8_t devicePriority;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_MASTER_ANNOUNCEMENT,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+class UniversalTimeServicePacket {
+ public:
+ uint16_t packetType;
+ union {
+ WhoIsMasterRequestPacket who_is_master_request;
+ WhoIsMasterResponsePacket who_is_master_response;
+ SyncRequestPacket sync_request;
+ SyncResponsePacket sync_response;
+ MasterAnnouncementPacket master_announcement;
+ } p;
+
+ ssize_t deserializePacket(const uint8_t* data,
+ uint32_t length,
+ uint64_t expectedSyncGroupID);
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+
diff --git a/services/common_time/diag_thread.cpp b/services/common_time/diag_thread.cpp
new file mode 100644
index 0000000..eebe983
--- /dev/null
+++ b/services/common_time/diag_thread.cpp
@@ -0,0 +1,323 @@
+/*
+ * 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) {
+ LOGE("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) {
+ LOGE("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)
+ LOGE("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)
+ LOGE("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) {
+ LOGE("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) {
+ LOGE("Bind failed.");
+ goto bailout;
+ }
+
+ if (listen(listen_fd_, 1) < 0) {
+ LOGE("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 rtt) {
+ 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.rtt = rtt;
+
+ 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()) {
+ LOGE("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) {
+ LOGE("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) {
+ LOGW("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\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.rtt);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (data_fd_ >= 0)
+ write(data_fd_, buf, strlen(buf));
+
+ discipline_log_.erase(discipline_log_.begin());
+ }
+ }
+ }
+
+bailout:
+ cleanupDataSocket();
+ cleanupListenSocket();
+ return false;
+}
+
+} // namespace android
diff --git a/services/common_time/diag_thread.h b/services/common_time/diag_thread.h
new file mode 100644
index 0000000..c630e0d
--- /dev/null
+++ b/services/common_time/diag_thread.h
@@ -0,0 +1,76 @@
+/*
+ * 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 rtt);
+
+ 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 rtt;
+ } DisciplineEventRecord;
+
+ bool openListenSocket();
+ void cleanupListenSocket();
+ void cleanupDataSocket();
+ void resetLogIDs();
+
+ CommonClock* common_clock_;
+ LocalClock* local_clock_;
+ int listen_fd_;
+ int data_fd_;
+
+ int64_t kernel_logID_basis_;
+ bool kernel_logID_basis_known_;
+
+ static const size_t kMaxDisciplineLogSize = 16;
+ Mutex discipline_log_lock_;
+ List<DisciplineEventRecord> discipline_log_;
+ int64_t discipline_log_ID_;
+};
+
+} // namespace android
+
+#endif //__ DIAG_THREAD_H__
diff --git a/services/common_time/main.cpp b/services/common_time/main.cpp
new file mode 100644
index 0000000..49eb30a
--- /dev/null
+++ b/services/common_time/main.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include "common_time_server.h"
+
+int main(int argc, char *argv[]) {
+ using namespace android;
+
+ sp<CommonTimeServer> service = new CommonTimeServer();
+ if (service == NULL)
+ return 1;
+
+ ProcessState::self()->startThreadPool();
+ service->run("CommonTimeServer", ANDROID_PRIORITY_NORMAL);
+
+ IPCThreadState::self()->joinThreadPool();
+ return 0;
+}
+
diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java
new file mode 100644
index 0000000..9a25d2e
--- /dev/null
+++ b/services/java/com/android/server/CommonTimeManagementService.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
+import android.net.NetworkInfo;
+import android.os.Binder;
+import android.os.CommonTimeConfig;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+/**
+ * @hide
+ * <p>CommonTimeManagementService manages the configuration of the native Common Time service,
+ * reconfiguring the native service as appropriate in response to changes in network configuration.
+ */
+class CommonTimeManagementService extends Binder {
+ /*
+ * Constants and globals.
+ */
+ private static final String TAG = CommonTimeManagementService.class.getSimpleName();
+ private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000;
+ private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable";
+ private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi";
+ private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio";
+ private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout";
+ private static final boolean AUTO_DISABLE;
+ private static final boolean ALLOW_WIFI;
+ private static final byte BASE_SERVER_PRIO;
+ private static final int NO_INTERFACE_TIMEOUT;
+ private static final InterfaceScoreRule[] IFACE_SCORE_RULES;
+
+ static {
+ int tmp;
+ AUTO_DISABLE = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1));
+ ALLOW_WIFI = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0));
+ tmp = SystemProperties.getInt(SERVER_PRIO_PROP, 1);
+ NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000);
+
+ if (tmp < 1)
+ BASE_SERVER_PRIO = 1;
+ else
+ if (tmp > 30)
+ BASE_SERVER_PRIO = 30;
+ else
+ BASE_SERVER_PRIO = (byte)tmp;
+
+ if (ALLOW_WIFI) {
+ IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+ new InterfaceScoreRule("wlan", (byte)1),
+ new InterfaceScoreRule("eth", (byte)2),
+ };
+ } else {
+ IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+ new InterfaceScoreRule("eth", (byte)2),
+ };
+ }
+ };
+
+ /*
+ * Internal state
+ */
+ private final Context mContext;
+ private INetworkManagementService mNetMgr;
+ private CommonTimeConfig mCTConfig;
+ private String mCurIface;
+ private Handler mReconnectHandler = new Handler();
+ private Handler mNoInterfaceHandler = new Handler();
+ private Object mLock = new Object();
+ private boolean mDetectedAtStartup = false;
+ private byte mEffectivePrio = BASE_SERVER_PRIO;
+
+ /*
+ * Callback handler implementations.
+ */
+ private INetworkManagementEventObserver mIfaceObserver =
+ new INetworkManagementEventObserver.Stub() {
+
+ public void interfaceStatusChanged(String iface, boolean up) {
+ reevaluateServiceState();
+ }
+ public void interfaceLinkStateChanged(String iface, boolean up) {
+ reevaluateServiceState();
+ }
+ public void interfaceAdded(String iface) {
+ reevaluateServiceState();
+ }
+ public void interfaceRemoved(String iface) {
+ reevaluateServiceState();
+ }
+ public void limitReached(String limitName, String iface) { }
+ };
+
+ private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reevaluateServiceState();
+ }
+ };
+
+ private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener =
+ new CommonTimeConfig.OnServerDiedListener() {
+ public void onServerDied() {
+ scheduleTimeConfigReconnect();
+ }
+ };
+
+ private Runnable mReconnectRunnable = new Runnable() {
+ public void run() { connectToTimeConfig(); }
+ };
+
+ private Runnable mNoInterfaceRunnable = new Runnable() {
+ public void run() { handleNoInterfaceTimeout(); }
+ };
+
+ /*
+ * Public interface (constructor, systemReady and dump)
+ */
+ public CommonTimeManagementService(Context context) {
+ mContext = context;
+ }
+
+ void systemReady() {
+ if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) {
+ Log.i(TAG, "No common time service detected on this platform. " +
+ "Common time services will be unavailable.");
+ return;
+ }
+
+ mDetectedAtStartup = true;
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNetMgr = INetworkManagementService.Stub.asInterface(b);
+
+ // Network manager is running along-side us, so we should never receiver a remote exception
+ // while trying to register this observer.
+ try {
+ mNetMgr.registerObserver(mIfaceObserver);
+ }
+ catch (RemoteException e) { }
+
+ // Register with the connectivity manager for connectivity changed intents.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiver(mConnectivityMangerObserver, filter);
+
+ // Connect to the common time config service and apply the initial configuration.
+ connectToTimeConfig();
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println(String.format(
+ "Permission Denial: can't dump CommonTimeManagement service from from " +
+ "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid()));
+ return;
+ }
+
+ if (!mDetectedAtStartup) {
+ pw.println("Native Common Time service was not detected at startup. " +
+ "Service is unavailable");
+ return;
+ }
+
+ synchronized (mLock) {
+ pw.println("Current Common Time Management Service Config:");
+ pw.println(String.format(" Native service : %s",
+ (null == mCTConfig) ? "reconnecting"
+ : "alive"));
+ pw.println(String.format(" Bound interface : %s",
+ (null == mCurIface ? "unbound" : mCurIface)));
+ pw.println(String.format(" Allow WiFi : %s", ALLOW_WIFI ? "yes" : "no"));
+ pw.println(String.format(" Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no"));
+ pw.println(String.format(" Server Priority : %d", mEffectivePrio));
+ pw.println(String.format(" No iface timeout : %d", NO_INTERFACE_TIMEOUT));
+ }
+ }
+
+ /*
+ * Inner helper classes
+ */
+ private static class InterfaceScoreRule {
+ public final String mPrefix;
+ public final byte mScore;
+ public InterfaceScoreRule(String prefix, byte score) {
+ mPrefix = prefix;
+ mScore = score;
+ }
+ };
+
+ /*
+ * Internal implementation
+ */
+ private void cleanupTimeConfig() {
+ mReconnectHandler.removeCallbacks(mReconnectRunnable);
+ mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+ if (null != mCTConfig) {
+ mCTConfig.release();
+ mCTConfig = null;
+ }
+ }
+
+ private void connectToTimeConfig() {
+ // Get access to the common time service configuration interface. If we catch a remote
+ // exception in the process (service crashed or no running for w/e reason), schedule an
+ // attempt to reconnect in the future.
+ cleanupTimeConfig();
+ try {
+ synchronized (mLock) {
+ mCTConfig = new CommonTimeConfig();
+ mCTConfig.setServerDiedListener(mCTServerDiedListener);
+ mCurIface = mCTConfig.getInterfaceBinding();
+ mCTConfig.setAutoDisable(AUTO_DISABLE);
+ mCTConfig.setMasterElectionPriority(mEffectivePrio);
+ }
+
+ if (NO_INTERFACE_TIMEOUT >= 0)
+ mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+
+ reevaluateServiceState();
+ }
+ catch (RemoteException e) {
+ scheduleTimeConfigReconnect();
+ }
+ }
+
+ private void scheduleTimeConfigReconnect() {
+ cleanupTimeConfig();
+ Log.w(TAG, String.format("Native service died, will reconnect in %d mSec",
+ NATIVE_SERVICE_RECONNECT_TIMEOUT));
+ mReconnectHandler.postDelayed(mReconnectRunnable,
+ NATIVE_SERVICE_RECONNECT_TIMEOUT);
+ }
+
+ private void handleNoInterfaceTimeout() {
+ if (null != mCTConfig) {
+ Log.i(TAG, "Timeout waiting for interface to come up. " +
+ "Forcing networkless master mode.");
+ if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode())
+ scheduleTimeConfigReconnect();
+ }
+ }
+
+ private void reevaluateServiceState() {
+ String bindIface = null;
+ byte bestScore = -1;
+ try {
+ // Check to see if this interface is suitable to use for time synchronization.
+ //
+ // TODO : This selection algorithm needs to be enhanced for use with mobile devices. In
+ // particular, the choice of whether to a wireless interface or not should not be an all
+ // or nothing thing controlled by properties. It would probably be better if the
+ // platform had some concept of public wireless networks vs. home or friendly wireless
+ // networks (something a user would configure in settings or when a new interface is
+ // added). Then this algorithm could pick only wireless interfaces which were flagged
+ // as friendly, and be dormant when on public wireless networks.
+ //
+ // Another issue which needs to be dealt with is the use of driver supplied interface
+ // name to determine the network type. The fact that the wireless interface on a device
+ // is named "wlan0" is just a matter of convention; its not a 100% rule. For example,
+ // there are devices out there where the wireless is name "tiwlan0", not "wlan0". The
+ // internal network management interfaces in Android have all of the information needed
+ // to make a proper classification, there is just no way (currently) to fetch an
+ // interface's type (available from the ConnectionManager) as well as its address
+ // (available from either the java.net interfaces or from the NetworkManagment service).
+ // Both can enumerate interfaces, but that is no way to correlate their results (no
+ // common shared key; although using the interface name in the connection manager would
+ // be a good start). Until this gets resolved, we resort to substring searching for
+ // tags like wlan and eth.
+ //
+ String ifaceList[] = mNetMgr.listInterfaces();
+ if (null != ifaceList) {
+ for (String iface : ifaceList) {
+
+ byte thisScore = -1;
+ for (InterfaceScoreRule r : IFACE_SCORE_RULES) {
+ if (iface.contains(r.mPrefix)) {
+ thisScore = r.mScore;
+ break;
+ }
+ }
+
+ if (thisScore <= bestScore)
+ continue;
+
+ InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface);
+ if (null == config)
+ continue;
+
+ if (config.isActive()) {
+ bindIface = iface;
+ bestScore = thisScore;
+ }
+ }
+ }
+ }
+ catch (RemoteException e) {
+ // Bad news; we should not be getting remote exceptions from the connectivity manager
+ // since it is running in SystemServer along side of us. It probably does not matter
+ // what we do here, but go ahead and unbind the common time service in this case, just
+ // so we have some defined behavior.
+ bindIface = null;
+ }
+
+ boolean doRebind = true;
+ synchronized (mLock) {
+ if ((null != bindIface) && (null == mCurIface)) {
+ Log.e(TAG, String.format("Binding common time service to %s.", bindIface));
+ mCurIface = bindIface;
+ } else
+ if ((null == bindIface) && (null != mCurIface)) {
+ Log.e(TAG, "Unbinding common time service.");
+ mCurIface = null;
+ } else
+ if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) {
+ Log.e(TAG, String.format("Switching common time service binding from %s to %s.",
+ mCurIface, bindIface));
+ mCurIface = bindIface;
+ } else {
+ doRebind = false;
+ }
+ }
+
+ if (doRebind && (null != mCTConfig)) {
+ byte newPrio = (bestScore > 0)
+ ? (byte)(bestScore * BASE_SERVER_PRIO)
+ : BASE_SERVER_PRIO;
+ if (newPrio != mEffectivePrio) {
+ mEffectivePrio = newPrio;
+ mCTConfig.setMasterElectionPriority(mEffectivePrio);
+ }
+
+ int res = mCTConfig.setNetworkBinding(mCurIface);
+ if (res != CommonTimeConfig.SUCCESS)
+ scheduleTimeConfigReconnect();
+
+ else if (NO_INTERFACE_TIMEOUT >= 0) {
+ mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+ if (null == mCurIface)
+ mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java
index f7fe39e..02a0872 100644
--- a/services/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/java/com/android/server/NetworkTimeUpdateService.java
@@ -55,7 +55,7 @@
private static final int EVENT_AUTO_TIME_CHANGED = 1;
private static final int EVENT_POLL_NETWORK_TIME = 2;
- private static final int EVENT_WIFI_CONNECTED = 3;
+ private static final int EVENT_NETWORK_CONNECTED = 3;
/** Normal polling frequency */
private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs
@@ -239,8 +239,9 @@
if (netInfo != null) {
// Verify that it's a WIFI connection
if (netInfo.getState() == NetworkInfo.State.CONNECTED &&
- netInfo.getType() == ConnectivityManager.TYPE_WIFI ) {
- mHandler.obtainMessage(EVENT_WIFI_CONNECTED).sendToTarget();
+ (netInfo.getType() == ConnectivityManager.TYPE_WIFI ||
+ netInfo.getType() == ConnectivityManager.TYPE_ETHERNET) ) {
+ mHandler.obtainMessage(EVENT_NETWORK_CONNECTED).sendToTarget();
}
}
}
@@ -259,7 +260,7 @@
switch (msg.what) {
case EVENT_AUTO_TIME_CHANGED:
case EVENT_POLL_NETWORK_TIME:
- case EVENT_WIFI_CONNECTED:
+ case EVENT_NETWORK_CONNECTED:
onPollNetworkTime(msg.what);
break;
}
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 2a0d2a0..0538a88 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -50,6 +50,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
import android.provider.Settings;
@@ -163,6 +164,7 @@
private boolean mDoneBooting = false;
private boolean mBootCompleted = false;
+ private boolean mHeadless = false;
private int mStayOnConditions = 0;
private final int[] mBroadcastQueue = new int[] { -1, -1, -1 };
private final int[] mBroadcastWhy = new int[3];
@@ -512,6 +514,7 @@
mButtonLight = lights.getLight(LightsService.LIGHT_ID_BUTTONS);
mKeyboardLight = lights.getLight(LightsService.LIGHT_ID_KEYBOARD);
mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
+ mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
nativeInit();
synchronized (mLocks) {
@@ -1877,9 +1880,11 @@
}
private void updateNativePowerStateLocked() {
- nativeSetPowerState(
- (mPowerState & SCREEN_ON_BIT) != 0,
- (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
+ if (!mHeadless) {
+ nativeSetPowerState(
+ (mPowerState & SCREEN_ON_BIT) != 0,
+ (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
+ }
}
private int screenOffFinishedAnimatingLocked(int reason) {
@@ -2223,11 +2228,13 @@
mScreenOffHandler.postAtTime(this, now+(1000/60));
}
} else {
- // It's pretty scary to hold mLocks for this long, and we should
- // redesign this, but it works for now.
- nativeStartSurfaceFlingerAnimation(
- mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR
- ? 0 : mAnimationSetting);
+ if (!mHeadless) {
+ // It's pretty scary to hold mLocks for this long, and we should
+ // redesign this, but it works for now.
+ nativeStartSurfaceFlingerAnimation(
+ mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR
+ ? 0 : mAnimationSetting);
+ }
mScreenBrightness.jumpToTargetLocked();
}
}
diff --git a/services/java/com/android/server/SerialService.java b/services/java/com/android/server/SerialService.java
new file mode 100644
index 0000000..5d2b2a0
--- /dev/null
+++ b/services/java/com/android/server/SerialService.java
@@ -0,0 +1,58 @@
+/*
+ * 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 an
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.hardware.ISerialManager;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class SerialService extends ISerialManager.Stub {
+
+ private final Context mContext;
+ private final String[] mSerialPorts;
+
+ public SerialService(Context context) {
+ mContext = context;
+ mSerialPorts = context.getResources().getStringArray(
+ com.android.internal.R.array.config_serialPorts);
+ }
+
+ public String[] getSerialPorts() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+
+ ArrayList<String> ports = new ArrayList<String>();
+ for (int i = 0; i < mSerialPorts.length; i++) {
+ String path = mSerialPorts[i];
+ if (new File(path).exists()) {
+ ports.add(path);
+ }
+ }
+ String[] result = new String[ports.size()];
+ ports.toArray(result);
+ return result;
+ }
+
+ public ParcelFileDescriptor openSerialPort(String path) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+ return native_open(path);
+ }
+
+ private native ParcelFileDescriptor native_open(String path);
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3ae62ad..25bee92 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,6 +108,7 @@
String factoryTestStr = SystemProperties.get("ro.factorytest");
int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
: Integer.parseInt(factoryTestStr);
+ final boolean headless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
LightsService lights = null;
PowerManagerService power = null;
@@ -126,10 +127,12 @@
BluetoothA2dpService bluetoothA2dp = null;
DockObserver dock = null;
UsbService usb = null;
+ SerialService serial = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
NetworkTimeUpdateService networkTimeUpdater = null;
+ CommonTimeManagementService commonTimeMgmtService = null;
// Critical services...
try {
@@ -231,10 +234,13 @@
bluetooth = new BluetoothService(context);
ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth);
bluetooth.initAfterRegistration();
- bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
- ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
- bluetoothA2dp);
- bluetooth.initAfterA2dpRegistration();
+
+ if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
+ bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
+ ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
+ bluetoothA2dp);
+ bluetooth.initAfterA2dpRegistration();
+ }
int airplaneModeOn = Settings.System.getInt(mContentResolver,
Settings.System.AIRPLANE_MODE_ON, 0);
@@ -397,14 +403,24 @@
}
try {
- /*
- * NotificationManagerService is dependant on MountService,
- * (for media / usb notifications) so we must start MountService first.
- */
- Slog.i(TAG, "Mount Service");
- ServiceManager.addService("mount", new MountService(context));
+ Slog.i(TAG, "UpdateLock Service");
+ ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
+ new UpdateLockService(context));
} catch (Throwable e) {
- reportWtf("starting Mount Service", e);
+ reportWtf("starting UpdateLockService", e);
+ }
+
+ if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+ try {
+ /*
+ * NotificationManagerService is dependant on MountService,
+ * (for media / usb notifications) so we must start MountService first.
+ */
+ Slog.i(TAG, "Mount Service");
+ ServiceManager.addService("mount", new MountService(context));
+ } catch (Throwable e) {
+ reportWtf("starting Mount Service", e);
+ }
}
try {
@@ -456,19 +472,26 @@
reportWtf("starting DropBoxManagerService", e);
}
- try {
- Slog.i(TAG, "Wallpaper Service");
- wallpaper = new WallpaperManagerService(context);
- ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
- } catch (Throwable e) {
- reportWtf("starting Wallpaper Service", e);
+ if (context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableWallpaperService)) {
+ try {
+ Slog.i(TAG, "Wallpaper Service");
+ if (!headless) {
+ wallpaper = new WallpaperManagerService(context);
+ ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
+ }
+ } catch (Throwable e) {
+ reportWtf("starting Wallpaper Service", e);
+ }
}
- try {
- Slog.i(TAG, "Audio Service");
- ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
- } catch (Throwable e) {
- reportWtf("starting Audio Service", e);
+ if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
+ try {
+ Slog.i(TAG, "Audio Service");
+ ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
+ } catch (Throwable e) {
+ reportWtf("starting Audio Service", e);
+ }
}
try {
@@ -497,6 +520,15 @@
}
try {
+ Slog.i(TAG, "Serial Service");
+ // Serial port support
+ serial = new SerialService(context);
+ ServiceManager.addService(Context.SERIAL_SERVICE, serial);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting SerialService", e);
+ }
+
+ try {
Slog.i(TAG, "UI Mode Manager Service");
// Listen for UI mode changes
uiMode = new UiModeManagerService(context);
@@ -552,6 +584,14 @@
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
+
+ try {
+ Slog.i(TAG, "CommonTimeManagementService");
+ commonTimeMgmtService = new CommonTimeManagementService(context);
+ ServiceManager.addService("commontime_management", commonTimeMgmtService);
+ } catch (Throwable e) {
+ reportWtf("starting CommonTimeManagementService service", e);
+ }
}
// Before things start rolling, be sure we have decided whether
@@ -630,6 +670,7 @@
final LocationManagerService locationF = location;
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
+ final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
final TextServicesManagerService textServiceManagerServiceF = tsms;
final StatusBarManagerService statusBarF = statusBar;
@@ -642,7 +683,7 @@
public void run() {
Slog.i(TAG, "Making services ready");
- startSystemUi(contextF);
+ if (!headless) startSystemUi(contextF);
try {
if (batteryF != null) batteryF.systemReady();
} catch (Throwable e) {
@@ -729,6 +770,11 @@
reportWtf("making Network Time Service ready", e);
}
try {
+ if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making Common time management service ready", e);
+ }
+ try {
if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady();
} catch (Throwable e) {
reportWtf("making Text Services Manager Service ready", e);
diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index c7fbc00..1709001 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -90,6 +90,7 @@
private int mNightMode = UiModeManager.MODE_NIGHT_NO;
private boolean mCarModeEnabled = false;
private boolean mCharging = false;
+ private final int mDefaultUiModeType;
private final boolean mCarModeKeepsScreenOn;
private final boolean mDeskModeKeepsScreenOn;
@@ -347,6 +348,8 @@
mConfiguration.setToDefaults();
+ mDefaultUiModeType = context.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultUiModeType);
mCarModeKeepsScreenOn = (context.getResources().getInteger(
com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
mDeskModeKeepsScreenOn = (context.getResources().getInteger(
@@ -452,7 +455,7 @@
}
final void updateConfigurationLocked(boolean sendIt) {
- int uiMode = Configuration.UI_MODE_TYPE_NORMAL;
+ int uiMode = mDefaultUiModeType;
if (mCarModeEnabled) {
uiMode = Configuration.UI_MODE_TYPE_CAR;
} else if (isDeskDockState(mDockState)) {
diff --git a/services/java/com/android/server/UpdateLockService.java b/services/java/com/android/server/UpdateLockService.java
new file mode 100644
index 0000000..1ffd196
--- /dev/null
+++ b/services/java/com/android/server/UpdateLockService.java
@@ -0,0 +1,125 @@
+/*
+ * 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 android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IUpdateLock;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.TokenWatcher;
+import android.os.UpdateLock;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class UpdateLockService extends IUpdateLock.Stub {
+ static final boolean DEBUG = false;
+ static final String TAG = "UpdateLockService";
+
+ // signatureOrSystem required to use update locks
+ static final String PERMISSION = "android.permission.UPDATE_LOCK";
+
+ Context mContext;
+ LockWatcher mLocks;
+
+ class LockWatcher extends TokenWatcher {
+ LockWatcher(Handler h, String tag) {
+ super(h, tag);
+ }
+
+ public void acquired() {
+ if (DEBUG) {
+ Slog.d(TAG, "first acquire; broadcasting convenient=false");
+ }
+ sendLockChangedBroadcast(false);
+ }
+ public void released() {
+ if (DEBUG) {
+ Slog.d(TAG, "last release; broadcasting convenient=true");
+ }
+ sendLockChangedBroadcast(true);
+ }
+ }
+
+ UpdateLockService(Context context) {
+ mContext = context;
+ mLocks = new LockWatcher(new Handler(), "UpdateLocks");
+
+ // Consider just-booting to be a reasonable time to allow
+ // interruptions for update installation etc.
+ sendLockChangedBroadcast(true);
+ }
+
+ void sendLockChangedBroadcast(boolean state) {
+ // Safe early during boot because this broadcast only goes to registered receivers.
+ long oldIdent = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(UpdateLock.UPDATE_LOCK_CHANGED)
+ .putExtra(UpdateLock.NOW_IS_CONVENIENT, state)
+ .putExtra(UpdateLock.TIMESTAMP, System.currentTimeMillis())
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendStickyBroadcast(intent);
+ } finally {
+ Binder.restoreCallingIdentity(oldIdent);
+ }
+ }
+
+ @Override
+ public void acquireUpdateLock(IBinder token, String tag) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "acquire(" + token + ") by " + makeTag(tag));
+ }
+
+ mContext.enforceCallingOrSelfPermission(PERMISSION, "acquireUpdateLock");
+ mLocks.acquire(token, makeTag(tag));
+ }
+
+ @Override
+ public void releaseUpdateLock(IBinder token) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "release(" + token + ')');
+ }
+
+ mContext.enforceCallingOrSelfPermission(PERMISSION, "releaseUpdateLock");
+ mLocks.release(token);
+ };
+
+ private String makeTag(String tag) {
+ return "{tag=" + tag
+ + " uid=" + Binder.getCallingUid()
+ + " pid=" + Binder.getCallingPid() + '}';
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump update lock service from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ mLocks.dump(pw);
+ }
+}
diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java
index 6a63eac..a7a46dd 100644
--- a/services/java/com/android/server/WiredAccessoryObserver.java
+++ b/services/java/com/android/server/WiredAccessoryObserver.java
@@ -30,8 +30,11 @@
import android.media.AudioManager;
import android.util.Log;
+import java.io.File;
import java.io.FileReader;
import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
/**
* <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock.
@@ -39,17 +42,6 @@
class WiredAccessoryObserver extends UEventObserver {
private static final String TAG = WiredAccessoryObserver.class.getSimpleName();
private static final boolean LOG = true;
- private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */
- private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w",
- "/sys/class/switch/h2w/state",
- "/sys/class/switch/h2w/name"},
- {"DEVPATH=/devices/virtual/switch/usb_audio",
- "/sys/class/switch/usb_audio/state",
- "/sys/class/switch/usb_audio/name"},
- {"DEVPATH=/devices/virtual/switch/hdmi",
- "/sys/class/switch/hdmi/state",
- "/sys/class/switch/hdmi/name"} };
-
private static final int BIT_HEADSET = (1 << 0);
private static final int BIT_HEADSET_NO_MIC = (1 << 1);
private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
@@ -60,10 +52,89 @@
BIT_HDMI_AUDIO);
private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
+ private static class UEventInfo {
+ private final String mDevName;
+ private final int mState1Bits;
+ private final int mState2Bits;
+
+ public UEventInfo(String devName, int state1Bits, int state2Bits) {
+ mDevName = devName;
+ mState1Bits = state1Bits;
+ mState2Bits = state2Bits;
+ }
+
+ public String getDevName() { return mDevName; }
+
+ public String getDevPath() {
+ return String.format("DEVPATH=/devices/virtual/switch/%s", mDevName);
+ }
+
+ public String getSwitchStatePath() {
+ return String.format("/sys/class/switch/%s/state", mDevName);
+ }
+
+ public boolean checkSwitchExists() {
+ File f = new File(getSwitchStatePath());
+ return ((null != f) && f.exists());
+ }
+
+ public int computeNewHeadsetState(int headsetState, int switchState) {
+ int preserveMask = ~(mState1Bits | mState2Bits);
+ int setBits = ((switchState == 1) ? mState1Bits :
+ ((switchState == 2) ? mState2Bits : 0));
+
+ return ((headsetState & preserveMask) | setBits);
+ }
+ }
+
+ private static List<UEventInfo> makeObservedUEventList() {
+ List<UEventInfo> retVal = new ArrayList<UEventInfo>();
+ UEventInfo uei;
+
+ // Monitor h2w
+ uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC);
+ if (uei.checkSwitchExists()) {
+ retVal.add(uei);
+ } else {
+ Slog.w(TAG, "This kernel does not have wired headset support");
+ }
+
+ // Monitor USB
+ uei = new UEventInfo("usb_audio", BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL);
+ if (uei.checkSwitchExists()) {
+ retVal.add(uei);
+ } else {
+ Slog.w(TAG, "This kernel does not have usb audio support");
+ }
+
+ // Monitor HDMI
+ //
+ // If the kernel has support for the "hdmi_audio" switch, use that. It will be signalled
+ // only when the HDMI driver has a video mode configured, and the downstream sink indicates
+ // support for audio in its EDID.
+ //
+ // If the kernel does not have an "hdmi_audio" switch, just fall back on the older "hdmi"
+ // switch instead.
+ uei = new UEventInfo("hdmi_audio", BIT_HDMI_AUDIO, 0);
+ if (uei.checkSwitchExists()) {
+ retVal.add(uei);
+ } else {
+ uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0);
+ if (uei.checkSwitchExists()) {
+ retVal.add(uei);
+ } else {
+ Slog.w(TAG, "This kernel does not have HDMI audio support");
+ }
+ }
+
+ return retVal;
+ }
+
+ private static List<UEventInfo> uEventInfo = makeObservedUEventList();
+
private int mHeadsetState;
private int mPrevHeadsetState;
private String mHeadsetName;
- private int switchState;
private final Context mContext;
private final WakeLock mWakeLock; // held while there is a pending route change
@@ -85,11 +156,12 @@
// one on the board, one on the dock and one on HDMI:
// observe three UEVENTs
init(); // set initial status
- for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
- startObserving(uEventInfo[i][0]);
+ for (int i = 0; i < uEventInfo.size(); ++i) {
+ UEventInfo uei = uEventInfo.get(i);
+ startObserving(uei.getDevPath());
}
}
- }
+ }
@Override
public void onUEvent(UEventObserver.UEvent event) {
@@ -106,50 +178,47 @@
private synchronized final void updateState(String name, int state)
{
- if (name.equals("usb_audio")) {
- switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)) |
- ((state == 1) ? BIT_USB_HEADSET_ANLG :
- ((state == 2) ? BIT_USB_HEADSET_DGTL : 0)));
- } else if (name.equals("hdmi")) {
- switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
- BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) |
- ((state == 1) ? BIT_HDMI_AUDIO : 0));
- } else {
- switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG|
- BIT_USB_HEADSET_DGTL)) |
- ((state == 1) ? BIT_HEADSET :
- ((state == 2) ? BIT_HEADSET_NO_MIC : 0)));
+ // FIXME: When ueventd informs of a change in state for a switch, it does not have to be
+ // the case that the name reported by /sys/class/switch/<device>/name is the same as
+ // <device>. For normal users of the linux switch class driver, it will be. But it is
+ // technically possible to hook the print_name method in the class driver and return a
+ // different name each and every time the name sysfs entry is queried.
+ //
+ // Right now this is not the case for any of the switch implementations used here. I'm not
+ // certain anyone would ever choose to implement such a dynamic name, or what it would mean
+ // for the implementation at this level, but if it ever happens, we will need to revisit
+ // this code.
+ for (int i = 0; i < uEventInfo.size(); ++i) {
+ UEventInfo uei = uEventInfo.get(i);
+ if (name.equals(uei.getDevName())) {
+ update(name, uei.computeNewHeadsetState(mHeadsetState, state));
+ return;
+ }
}
- update(name, switchState);
}
private synchronized final void init() {
char[] buffer = new char[1024];
-
- String newName = mHeadsetName;
- int newState = mHeadsetState;
mPrevHeadsetState = mHeadsetState;
if (LOG) Slog.v(TAG, "init()");
- for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
+ for (int i = 0; i < uEventInfo.size(); ++i) {
+ UEventInfo uei = uEventInfo.get(i);
try {
- FileReader file = new FileReader(uEventInfo[i][1]);
+ int curState;
+ FileReader file = new FileReader(uei.getSwitchStatePath());
int len = file.read(buffer, 0, 1024);
file.close();
- newState = Integer.valueOf((new String(buffer, 0, len)).trim());
+ curState = Integer.valueOf((new String(buffer, 0, len)).trim());
- file = new FileReader(uEventInfo[i][2]);
- len = file.read(buffer, 0, 1024);
- file.close();
- newName = new String(buffer, 0, len).trim();
-
- if (newState > 0) {
- updateState(newName, newState);
+ if (curState > 0) {
+ updateState(uei.getDevName(), curState);
}
} catch (FileNotFoundException e) {
- Slog.w(TAG, "This kernel does not have wired headset support");
+ Slog.w(TAG, uei.getSwitchStatePath() +
+ " not found while attempting to determine initial switch state");
} catch (Exception e) {
Slog.e(TAG, "" , e);
}
@@ -191,8 +260,12 @@
mHeadsetState = headsetState;
if (headsetState == 0) {
- Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
- mContext.sendBroadcast(intent);
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_sendAudioBecomingNoisy)) {
+ Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+ mContext.sendBroadcast(intent);
+ }
+
// It can take hundreds of ms flush the audio pipeline after
// apps pause audio playback, but audio route changes are
// immediate, so delay the route change by 1000ms.
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index cffb391..cd40cf1 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -262,7 +262,14 @@
static final String[] EMPTY_STRING_ARRAY = new String[0];
public ActivityStack mMainStack;
-
+
+ private final boolean mHeadless;
+
+ // Whether we should show our dialogs (ANR, crash, etc) or just perform their
+ // default actuion automatically. Important for devices without direct input
+ // devices.
+ private boolean mShowDialogs = true;
+
/**
* Description of a request to start a new activity, which has been held
* due to app switches being disabled.
@@ -889,7 +896,7 @@
return;
}
AppErrorResult res = (AppErrorResult) data.get("result");
- if (!mSleeping && !mShuttingDown) {
+ if (mShowDialogs && !mSleeping && !mShuttingDown) {
Dialog d = new AppErrorDialog(mContext, res, proc);
d.show();
proc.crashDialog = d;
@@ -940,7 +947,7 @@
return;
}
AppErrorResult res = (AppErrorResult) data.get("result");
- if (!mSleeping && !mShuttingDown) {
+ if (mShowDialogs && !mSleeping && !mShuttingDown) {
Dialog d = new StrictModeViolationDialog(mContext, res, proc);
d.show();
proc.crashDialog = d;
@@ -1060,16 +1067,22 @@
}
} break;
case SHOW_UID_ERROR_MSG: {
- // XXX This is a temporary dialog, no need to localize.
- AlertDialog d = new BaseErrorDialog(mContext);
- d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
- d.setCancelable(false);
- d.setTitle("System UIDs Inconsistent");
- d.setMessage("UIDs on the system are inconsistent, you need to wipe your data partition or your device will be unstable.");
- d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky",
- mHandler.obtainMessage(IM_FEELING_LUCKY_MSG));
- mUidAlert = d;
- d.show();
+ String title = "System UIDs Inconsistent";
+ String text = "UIDs on the system are inconsistent, you need to wipe your"
+ + " data partition or your device will be unstable.";
+ Log.e(TAG, title + ": " + text);
+ if (mShowDialogs) {
+ // XXX This is a temporary dialog, no need to localize.
+ AlertDialog d = new BaseErrorDialog(mContext);
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+ d.setCancelable(false);
+ d.setTitle(title);
+ d.setMessage(text);
+ d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky",
+ mHandler.obtainMessage(IM_FEELING_LUCKY_MSG));
+ mUidAlert = d;
+ d.show();
+ }
} break;
case IM_FEELING_LUCKY_MSG: {
if (mUidAlert != null) {
@@ -1496,6 +1509,7 @@
mUsageStatsService = new UsageStatsService(new File(
systemDir, "usagestats").toString());
+ mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -2034,6 +2048,13 @@
}
boolean startHomeActivityLocked() {
+ if (mHeadless) {
+ // Added because none of the other calls to ensureBootCompleted seem to fire
+ // when running headless.
+ ensureBootCompleted();
+ return false;
+ }
+
if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
// We are running in factory test mode, but unable to find
@@ -3929,7 +3950,9 @@
if (hr.app == null && app.info.uid == hr.info.applicationInfo.uid
&& processName.equals(hr.processName)) {
try {
- if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
+ if (mHeadless) {
+ Slog.e(TAG, "Starting activities not supported on headless device: " + hr);
+ } else if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
didSomething = true;
}
} catch (Exception e) {
@@ -7208,7 +7231,7 @@
mMainStack.resumeTopActivityLocked(null);
} else {
ActivityRecord r = mMainStack.topRunningActivityLocked(null);
- if (r.app == app) {
+ if (r != null && r.app == app) {
// If the top running activity is from this crashing
// process, then terminate it to avoid getting in a loop.
Slog.w(TAG, " Force finishing activity "
@@ -13442,6 +13465,9 @@
*/
public boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean persistent, boolean initLocale) {
+ // do nothing if we are headless
+ if (mHeadless) return true;
+
int changes = 0;
boolean kept = true;
@@ -13470,6 +13496,10 @@
mConfiguration = newConfig;
Slog.i(TAG, "Config changed: " + newConfig);
+ // TODO: If our config changes, should we auto dismiss any currently
+ // showing dialogs?
+ mShowDialogs = shouldShowDialogs(newConfig);
+
final Configuration configCopy = new Configuration(mConfiguration);
AttributeCache ac = AttributeCache.instance();
@@ -13537,6 +13567,19 @@
return kept;
}
+
+ /**
+ * Decide based on the configuration whether we should shouw the ANR,
+ * crash, etc dialogs. The idea is that if there is no affordnace to
+ * press the on-screen buttons, we shouldn't show the dialog.
+ *
+ * A thought: SystemUI might also want to get told about this, the Power
+ * dialog / global actions also might want different behaviors.
+ */
+ private static final boolean shouldShowDialogs(Configuration config) {
+ return !(config.keyboard == Configuration.KEYBOARD_NOKEYS
+ && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH);
+ }
/**
* Save the locale. You must be inside a synchronized (this) block.
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 36442a0..b54c519 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -2020,6 +2020,39 @@
return false;
}
+ static final void printFlags(PrintWriter pw, int val, Object[] spec) {
+ pw.print("[ ");
+ for (int i=0; i<spec.length; i+=2) {
+ int mask = (Integer)spec[i];
+ if ((val & mask) != 0) {
+ pw.print(spec[i+1]);
+ pw.print(" ");
+ }
+ }
+ pw.print("]");
+ }
+
+ static final Object[] FLAG_DUMP_SPEC = new Object[] {
+ ApplicationInfo.FLAG_SYSTEM, "SYSTEM",
+ ApplicationInfo.FLAG_DEBUGGABLE, "DEBUGGABLE",
+ ApplicationInfo.FLAG_HAS_CODE, "HAS_CODE",
+ ApplicationInfo.FLAG_PERSISTENT, "PERSISTENT",
+ ApplicationInfo.FLAG_FACTORY_TEST, "FACTORY_TEST",
+ ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING, "ALLOW_TASK_REPARENTING",
+ ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA, "ALLOW_CLEAR_USER_DATA",
+ ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, "UPDATED_SYSTEM_APP",
+ ApplicationInfo.FLAG_TEST_ONLY, "TEST_ONLY",
+ ApplicationInfo.FLAG_VM_SAFE_MODE, "VM_SAFE_MODE",
+ ApplicationInfo.FLAG_ALLOW_BACKUP, "ALLOW_BACKUP",
+ ApplicationInfo.FLAG_KILL_AFTER_RESTORE, "KILL_AFTER_RESTORE",
+ ApplicationInfo.FLAG_RESTORE_ANY_VERSION, "RESTORE_ANY_VERSION",
+ ApplicationInfo.FLAG_EXTERNAL_STORAGE, "EXTERNAL_STORAGE",
+ ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP",
+ ApplicationInfo.FLAG_STOPPED, "STOPPED",
+ ApplicationInfo.FLAG_FORWARD_LOCK, "FORWARD_LOCK",
+ ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
+ };
+
void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Date date = new Date();
@@ -2060,6 +2093,7 @@
pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
pw.print(" versionCode="); pw.println(ps.versionCode);
if (ps.pkg != null) {
+ pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println();
pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
@@ -2261,4 +2295,4 @@
pw.println("Settings parse messages:");
pw.print(mReadMessages.toString());
}
-}
\ No newline at end of file
+}
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index ed83fbe..c2ded8a 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -43,6 +43,7 @@
import android.os.Process;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UEventObserver;
import android.provider.Settings;
@@ -206,6 +207,9 @@
}
private static String addFunction(String functions, String function) {
+ if ("none".equals(functions)) {
+ return function;
+ }
if (!containsFunction(functions, function)) {
if (functions.length() > 0) {
functions += ",";
@@ -222,6 +226,9 @@
split[i] = null;
}
}
+ if (split.length == 1 && split[0] == null) {
+ return "none";
+ }
StringBuilder builder = new StringBuilder();
for (int i = 0; i < split.length; i++) {
String s = split[i];
@@ -365,11 +372,7 @@
for (int i = 0; i < 20; i++) {
// State transition is done when sys.usb.state is set to the new configuration
if (state.equals(SystemProperties.get("sys.usb.state"))) return true;
- try {
- // try again in 50ms
- Thread.sleep(50);
- } catch (InterruptedException e) {
- }
+ SystemClock.sleep(50);
}
Slog.e(TAG, "waitForState(" + state + ") FAILED");
return false;
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 3f72dec..1c1e88f 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -250,6 +250,7 @@
private static final String SYSTEM_SECURE = "ro.secure";
private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+ private static final String SYSTEM_HEADLESS = "ro.config.headless";
/**
* Condition waited on by {@link #reenableKeyguard} to know the call to
@@ -259,6 +260,8 @@
*/
private boolean mKeyguardDisabled = false;
+ private final boolean mHeadless;
+
private static final int ALLOW_DISABLE_YES = 1;
private static final int ALLOW_DISABLE_NO = 0;
private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager
@@ -753,6 +756,7 @@
mAllowBootMessages = showBootMsgs;
mLimitedAlphaCompositing = context.getResources().getBoolean(
com.android.internal.R.bool.config_sf_limitedAlpha);
+ mHeadless = "1".equals(SystemProperties.get(SYSTEM_HEADLESS, "0"));
mPowerManager = pm;
mPowerManager.setPolicy(mPolicy);
@@ -4839,7 +4843,7 @@
public void performBootTimeout() {
synchronized(mWindowMap) {
- if (mDisplayEnabled) {
+ if (mDisplayEnabled || mHeadless) {
return;
}
Slog.w(TAG, "***** BOOT TIMEOUT: forcing display enabled");
@@ -5007,6 +5011,8 @@
// TODO: more accounting of which pid(s) turned it on, keep count,
// only allow disables from pids which have count on, etc.
public void showStrictModeViolation(boolean on) {
+ if (mHeadless) return;
+
int pid = Binder.getCallingPid();
synchronized(mWindowMap) {
// Ignoring requests to enable the red border from clients
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index 6fa5dfa..c63b84d 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -9,6 +9,7 @@
com_android_server_InputWindowHandle.cpp \
com_android_server_LightsService.cpp \
com_android_server_PowerManagerService.cpp \
+ com_android_server_SerialService.cpp \
com_android_server_SystemServer.cpp \
com_android_server_UsbDeviceManager.cpp \
com_android_server_UsbHostManager.cpp \
diff --git a/services/jni/com_android_server_BatteryService.cpp b/services/jni/com_android_server_BatteryService.cpp
index 2ceb535..33027a4 100644
--- a/services/jni/com_android_server_BatteryService.cpp
+++ b/services/jni/com_android_server_BatteryService.cpp
@@ -233,75 +233,75 @@
DIR* dir = opendir(POWER_SUPPLY_PATH);
if (dir == NULL) {
LOGE("Could not open %s\n", POWER_SUPPLY_PATH);
- return -1;
- }
- while ((entry = readdir(dir))) {
- const char* name = entry->d_name;
+ } else {
+ while ((entry = readdir(dir))) {
+ const char* name = entry->d_name;
- // ignore "." and ".."
- if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
- continue;
- }
-
- char buf[20];
- // Look for "type" file in each subdirectory
- snprintf(path, sizeof(path), "%s/%s/type", POWER_SUPPLY_PATH, name);
- int length = readFromFile(path, buf, sizeof(buf));
- if (length > 0) {
- if (buf[length - 1] == '\n')
- buf[length - 1] = 0;
-
- if (strcmp(buf, "Mains") == 0) {
- snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.acOnlinePath = strdup(path);
+ // ignore "." and ".."
+ if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
+ continue;
}
- else if (strcmp(buf, "USB") == 0) {
- snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.usbOnlinePath = strdup(path);
- }
- else if (strcmp(buf, "Battery") == 0) {
- snprintf(path, sizeof(path), "%s/%s/status", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryStatusPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/health", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryHealthPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/present", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryPresentPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/capacity", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryCapacityPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0) {
- gPaths.batteryVoltagePath = strdup(path);
- // voltage_now is in microvolts, not millivolts
- gVoltageDivisor = 1000;
- } else {
- snprintf(path, sizeof(path), "%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
+ char buf[20];
+ // Look for "type" file in each subdirectory
+ snprintf(path, sizeof(path), "%s/%s/type", POWER_SUPPLY_PATH, name);
+ int length = readFromFile(path, buf, sizeof(buf));
+ if (length > 0) {
+ if (buf[length - 1] == '\n')
+ buf[length - 1] = 0;
+
+ if (strcmp(buf, "Mains") == 0) {
+ snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
if (access(path, R_OK) == 0)
+ gPaths.acOnlinePath = strdup(path);
+ }
+ else if (strcmp(buf, "USB") == 0) {
+ snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.usbOnlinePath = strdup(path);
+ }
+ else if (strcmp(buf, "Battery") == 0) {
+ snprintf(path, sizeof(path), "%s/%s/status", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryStatusPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/health", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryHealthPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/present", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryPresentPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/capacity", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryCapacityPath = strdup(path);
+
+ snprintf(path, sizeof(path), "%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0) {
gPaths.batteryVoltagePath = strdup(path);
- }
+ // voltage_now is in microvolts, not millivolts
+ gVoltageDivisor = 1000;
+ } else {
+ snprintf(path, sizeof(path), "%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryVoltagePath = strdup(path);
+ }
- snprintf(path, sizeof(path), "%s/%s/temp", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0) {
- gPaths.batteryTemperaturePath = strdup(path);
- } else {
- snprintf(path, sizeof(path), "%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
+ snprintf(path, sizeof(path), "%s/%s/temp", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0) {
gPaths.batteryTemperaturePath = strdup(path);
- }
+ } else {
+ snprintf(path, sizeof(path), "%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryTemperaturePath = strdup(path);
+ }
- snprintf(path, sizeof(path), "%s/%s/technology", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryTechnologyPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/technology", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryTechnologyPath = strdup(path);
+ }
}
}
+ closedir(dir);
}
- closedir(dir);
if (!gPaths.acOnlinePath)
LOGE("acOnlinePath not found");
diff --git a/services/jni/com_android_server_SerialService.cpp b/services/jni/com_android_server_SerialService.cpp
new file mode 100644
index 0000000..4bb7e36
--- /dev/null
+++ b/services/jni/com_android_server_SerialService.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "SerialServiceJNI"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+namespace android
+{
+
+static struct parcel_file_descriptor_offsets_t
+{
+ jclass mClass;
+ jmethodID mConstructor;
+} gParcelFileDescriptorOffsets;
+
+static jobject android_server_SerialService_open(JNIEnv *env, jobject thiz, jstring path)
+{
+ const char *pathStr = env->GetStringUTFChars(path, NULL);
+
+ int fd = open(pathStr, O_RDWR | O_NOCTTY);
+ if (fd < 0) {
+ LOGE("could not open %s", pathStr);
+ env->ReleaseStringUTFChars(path, pathStr);
+ return NULL;
+ }
+ env->ReleaseStringUTFChars(path, pathStr);
+
+ jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+ if (fileDescriptor == NULL) {
+ return NULL;
+ }
+ return env->NewObject(gParcelFileDescriptorOffsets.mClass,
+ gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
+}
+
+
+static JNINativeMethod method_table[] = {
+ { "native_open", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
+ (void*)android_server_SerialService_open },
+};
+
+int register_android_server_SerialService(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("com/android/server/SerialService");
+ if (clazz == NULL) {
+ LOGE("Can't find com/android/server/SerialService");
+ return -1;
+ }
+
+ clazz = env->FindClass("android/os/ParcelFileDescriptor");
+ LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
+ gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+ gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
+ LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
+ "Unable to find constructor for android.os.ParcelFileDescriptor");
+
+ return jniRegisterNativeMethods(env, "com/android/server/SerialService",
+ method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index 4178039..0a93525 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -27,6 +27,7 @@
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
+int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
@@ -49,6 +50,7 @@
LOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_PowerManagerService(env);
+ register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
diff --git a/tests/SerialChat/Android.mk b/tests/SerialChat/Android.mk
new file mode 100644
index 0000000..a534e1a
--- /dev/null
+++ b/tests/SerialChat/Android.mk
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SerialChat
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SerialChat/AndroidManifest.xml b/tests/SerialChat/AndroidManifest.xml
new file mode 100644
index 0000000..0efdb58
--- /dev/null
+++ b/tests/SerialChat/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.serialchat">
+
+ <uses-permission android:name="android.permission.SERIAL_PORT"/>
+
+ <application android:label="Serial Chat">
+ <activity android:name="SerialChat" android:label="Serial Chat">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SerialChat/res/layout/serial_chat.xml b/tests/SerialChat/res/layout/serial_chat.xml
new file mode 100644
index 0000000..596ecbf
--- /dev/null
+++ b/tests/SerialChat/res/layout/serial_chat.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <ScrollView android:id="@+id/scroll"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ >
+ <TextView android:id="@+id/log"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:textSize="12sp"
+ android:textColor="#ffffffff"
+ />
+ </ScrollView>
+
+ <EditText android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:capitalize="sentences"
+ android:autoText="true"
+ android:singleLine="true"
+ />
+
+</LinearLayout>
+
+
diff --git a/tests/SerialChat/src/com/android/serialchat/SerialChat.java b/tests/SerialChat/src/com/android/serialchat/SerialChat.java
new file mode 100644
index 0000000..faec312
--- /dev/null
+++ b/tests/SerialChat/src/com/android/serialchat/SerialChat.java
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+package com.android.serialchat;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.SerialManager;
+import android.hardware.SerialPort;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.nio.ByteBuffer;
+import java.io.IOException;
+
+public class SerialChat extends Activity implements Runnable, TextView.OnEditorActionListener {
+
+ private static final String TAG = "SerialChat";
+
+ private TextView mLog;
+ private EditText mEditText;
+ private ByteBuffer mInputBuffer;
+ private ByteBuffer mOutputBuffer;
+ private SerialManager mSerialManager;
+ private SerialPort mSerialPort;
+ private boolean mPermissionRequestPending;
+
+ private static final int MESSAGE_LOG = 1;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);
+ setContentView(R.layout.serial_chat);
+ mLog = (TextView)findViewById(R.id.log);
+ mEditText = (EditText)findViewById(R.id.message);
+ mEditText.setOnEditorActionListener(this);
+
+ if (false) {
+ mInputBuffer = ByteBuffer.allocateDirect(1024);
+ mOutputBuffer = ByteBuffer.allocateDirect(1024);
+ } else {
+ mInputBuffer = ByteBuffer.allocate(1024);
+ mOutputBuffer = ByteBuffer.allocate(1024);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ String[] ports = mSerialManager.getSerialPorts();
+ if (ports != null && ports.length > 0) {
+ try {
+ mSerialPort = mSerialManager.openSerialPort(ports[0], 115200);
+ if (mSerialPort != null) {
+ new Thread(this).start();
+ }
+ } catch (IOException e) {
+ }
+ }
+
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mSerialPort != null) {
+ try {
+ mSerialPort.close();
+ } catch (IOException e) {
+ }
+ mSerialPort = null;
+ }
+ super.onDestroy();
+ }
+
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (/* actionId == EditorInfo.IME_ACTION_DONE && */ mSerialPort != null) {
+ try {
+ String text = v.getText().toString();
+ Log.d(TAG, "write: " + text);
+ byte[] bytes = text.getBytes();
+ mOutputBuffer.clear();
+ mOutputBuffer.put(bytes);
+ mSerialPort.write(mOutputBuffer, bytes.length);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed", e);
+ }
+ v.setText("");
+ return true;
+ }
+ Log.d(TAG, "onEditorAction " + actionId + " event: " + event);
+ return false;
+ }
+
+ public void run() {
+ Log.d(TAG, "run");
+ int ret = 0;
+ byte[] buffer = new byte[1024];
+ while (ret >= 0) {
+ try {
+ Log.d(TAG, "calling read");
+ mInputBuffer.clear();
+ ret = mSerialPort.read(mInputBuffer);
+ Log.d(TAG, "read returned " + ret);
+ mInputBuffer.get(buffer, 0, ret);
+ } catch (IOException e) {
+ Log.e(TAG, "read failed", e);
+ break;
+ }
+
+ if (ret > 0) {
+ Message m = Message.obtain(mHandler, MESSAGE_LOG);
+ String text = new String(buffer, 0, ret);
+ Log.d(TAG, "chat: " + text);
+ m.obj = text;
+ mHandler.sendMessage(m);
+ }
+ }
+ Log.d(TAG, "thread out");
+ }
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_LOG:
+ mLog.setText(mLog.getText() + (String)msg.obj);
+ break;
+ }
+ }
+ };
+}
+
+
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 3d6537a..f0c215e 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1019,6 +1019,11 @@
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
| ResTable_config::UI_MODE_TYPE_TELEVISION;
return true;
+ } else if (strcmp(name, "appliance") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+ | ResTable_config::UI_MODE_TYPE_APPLIANCE;
+ return true;
}
return false;
diff --git a/tools/aidl/AST.cpp b/tools/aidl/AST.cpp
index 752ef7c..bfa6765 100755
--- a/tools/aidl/AST.cpp
+++ b/tools/aidl/AST.cpp
@@ -111,6 +111,21 @@
fprintf(to, "%s", this->value.c_str());
}
+StringLiteralExpression::StringLiteralExpression(const string& v)
+ :value(v)
+{
+}
+
+StringLiteralExpression::~StringLiteralExpression()
+{
+}
+
+void
+StringLiteralExpression::Write(FILE* to)
+{
+ fprintf(to, "\"%s\"", this->value.c_str());
+}
+
Variable::Variable()
:type(NULL),
name(),
@@ -277,6 +292,17 @@
{
}
+MethodCall::MethodCall(const string& n, int argc = 0, ...)
+ :obj(NULL),
+ clazz(NULL),
+ name(n)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
MethodCall::MethodCall(Expression* o, const string& n)
:obj(o),
clazz(NULL),
@@ -367,11 +393,29 @@
{
}
+NewExpression::NewExpression(Type* t, int argc = 0, ...)
+ :type(t)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
NewExpression::~NewExpression()
{
}
void
+NewExpression::init(int n, va_list args)
+{
+ for (int i=0; i<n; i++) {
+ Expression* expression = (Expression*)va_arg(args, void*);
+ this->arguments.push_back(expression);
+ }
+}
+
+void
NewExpression::Write(FILE* to)
{
fprintf(to, "new %s(", this->type->InstantiableName().c_str());
@@ -636,6 +680,20 @@
fprintf(to, "}\n");
}
+Break::Break()
+{
+}
+
+Break::~Break()
+{
+}
+
+void
+Break::Write(FILE* to)
+{
+ fprintf(to, "break;\n");
+}
+
Method::Method()
:ClassElement(),
modifiers(0),
@@ -678,7 +736,7 @@
fprintf(to, "%s\n", this->comment.c_str());
}
- WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | FINAL | OVERRIDE);
+ WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | ABSTRACT | FINAL | OVERRIDE);
if (this->returnType != NULL) {
string dim;
diff --git a/tools/aidl/AST.h b/tools/aidl/AST.h
index 3156356..ead5e7a 100755
--- a/tools/aidl/AST.h
+++ b/tools/aidl/AST.h
@@ -54,6 +54,16 @@
virtual void Write(FILE* to);
};
+// TODO: also escape the contents. not needed for now
+struct StringLiteralExpression : public Expression
+{
+ string value;
+
+ StringLiteralExpression(const string& value);
+ virtual ~StringLiteralExpression();
+ virtual void Write(FILE* to);
+};
+
struct Variable : public Expression
{
Type* type;
@@ -104,7 +114,7 @@
virtual void Write(FILE* to) = 0;
};
-struct StatementBlock
+struct StatementBlock : public Statement
{
vector<Statement*> statements;
@@ -146,6 +156,7 @@
vector<string> exceptions;
MethodCall(const string& name);
+ MethodCall(const string& name, int argc, ...);
MethodCall(Expression* obj, const string& name);
MethodCall(Type* clazz, const string& name);
MethodCall(Expression* obj, const string& name, int argc, ...);
@@ -174,8 +185,12 @@
vector<Expression*> arguments;
NewExpression(Type* type);
+ NewExpression(Type* type, int argc, ...);
virtual ~NewExpression();
virtual void Write(FILE* to);
+
+private:
+ void init(int n, va_list args);
};
struct NewArrayExpression : public Expression
@@ -292,6 +307,13 @@
virtual void Write(FILE* to);
};
+struct Break : public Statement
+{
+ Break();
+ virtual ~Break();
+ virtual void Write(FILE* to);
+};
+
struct Method : public ClassElement
{
string comment;
diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk
index 2ad0728..77d46ab 100644
--- a/tools/aidl/Android.mk
+++ b/tools/aidl/Android.mk
@@ -17,7 +17,9 @@
search_path.cpp \
AST.cpp \
Type.cpp \
- generate_java.cpp
+ generate_java.cpp \
+ generate_java_binder.cpp \
+ generate_java_rpc.cpp
LOCAL_CFLAGS := -g
LOCAL_MODULE := aidl
diff --git a/tools/aidl/Type.cpp b/tools/aidl/Type.cpp
index 6b69864..700d6ef 100755
--- a/tools/aidl/Type.cpp
+++ b/tools/aidl/Type.cpp
@@ -11,6 +11,7 @@
Type* FLOAT_TYPE;
Type* DOUBLE_TYPE;
Type* STRING_TYPE;
+Type* OBJECT_TYPE;
Type* CHAR_SEQUENCE_TYPE;
Type* TEXT_UTILS_TYPE;
Type* REMOTE_EXCEPTION_TYPE;
@@ -21,9 +22,13 @@
Type* BINDER_PROXY_TYPE;
Type* PARCEL_TYPE;
Type* PARCELABLE_INTERFACE_TYPE;
+Type* CONTEXT_TYPE;
Type* MAP_TYPE;
Type* LIST_TYPE;
Type* CLASSLOADER_TYPE;
+Type* RPC_DATA_TYPE;
+Type* RPC_ERROR_TYPE;
+Type* EVENT_FAKE_TYPE;
Expression* NULL_VALUE;
Expression* THIS_VALUE;
@@ -34,38 +39,48 @@
void
register_base_types()
{
- VOID_TYPE = new BasicType("void", "XXX", "XXX", "XXX", "XXX", "XXX");
+ VOID_TYPE = new BasicType("void",
+ "XXX", "XXX", "XXX", "XXX", "XXX",
+ "XXX", "XXX", "XXX", "XXX", "XXX");
NAMES.Add(VOID_TYPE);
BOOLEAN_TYPE = new BooleanType();
NAMES.Add(BOOLEAN_TYPE);
- BYTE_TYPE = new BasicType("byte", "writeByte", "readByte",
- "writeByteArray", "createByteArray", "readByteArray");
+ BYTE_TYPE = new BasicType("byte",
+ "writeByte", "readByte", "writeByteArray", "createByteArray", "readByteArray",
+ "putByte", "getByte", "putByteArray", "createByteArray", "getByteArray");
NAMES.Add(BYTE_TYPE);
CHAR_TYPE = new CharType();
NAMES.Add(CHAR_TYPE);
- INT_TYPE = new BasicType("int", "writeInt", "readInt",
- "writeIntArray", "createIntArray", "readIntArray");
+ INT_TYPE = new BasicType("int",
+ "writeInt", "readInt", "writeIntArray", "createIntArray", "readIntArray",
+ "putInteger", "getInteger", "putIntegerArray", "createIntegerArray", "getIntegerArray");
NAMES.Add(INT_TYPE);
- LONG_TYPE = new BasicType("long", "writeLong", "readLong",
- "writeLongArray", "createLongArray", "readLongArray");
+ LONG_TYPE = new BasicType("long",
+ "writeLong", "readLong", "writeLongArray", "createLongArray", "readLongArray",
+ "putLong", "getLong", "putLongArray", "createLongArray", "getLongArray");
NAMES.Add(LONG_TYPE);
- FLOAT_TYPE = new BasicType("float", "writeFloat", "readFloat",
- "writeFloatArray", "createFloatArray", "readFloatArray");
+ FLOAT_TYPE = new BasicType("float",
+ "writeFloat", "readFloat", "writeFloatArray", "createFloatArray", "readFloatArray",
+ "putFloat", "getFloat", "putFloatArray", "createFloatArray", "getFloatArray");
NAMES.Add(FLOAT_TYPE);
- DOUBLE_TYPE = new BasicType("double", "writeDouble", "readDouble",
- "writeDoubleArray", "createDoubleArray", "readDoubleArray");
+ DOUBLE_TYPE = new BasicType("double",
+ "writeDouble", "readDouble", "writeDoubleArray", "createDoubleArray", "readDoubleArray",
+ "putDouble", "getDouble", "putDoubleArray", "createDoubleArray", "getDoubleArray");
NAMES.Add(DOUBLE_TYPE);
STRING_TYPE = new StringType();
NAMES.Add(STRING_TYPE);
+ OBJECT_TYPE = new Type("java.lang", "Object", Type::BUILT_IN, false, false, false);
+ NAMES.Add(OBJECT_TYPE);
+
CHAR_SEQUENCE_TYPE = new CharSequenceType();
NAMES.Add(CHAR_SEQUENCE_TYPE);
@@ -75,8 +90,7 @@
LIST_TYPE = new ListType();
NAMES.Add(LIST_TYPE);
- TEXT_UTILS_TYPE = new Type("android.text", "TextUtils",
- Type::BUILT_IN, false, false);
+ TEXT_UTILS_TYPE = new Type("android.text", "TextUtils", Type::BUILT_IN, false, false, false);
NAMES.Add(TEXT_UTILS_TYPE);
REMOTE_EXCEPTION_TYPE = new RemoteExceptionType();
@@ -103,6 +117,19 @@
PARCELABLE_INTERFACE_TYPE = new ParcelableInterfaceType();
NAMES.Add(PARCELABLE_INTERFACE_TYPE);
+ CONTEXT_TYPE = new Type("android.content", "Context", Type::BUILT_IN, false, false, false);
+ NAMES.Add(CONTEXT_TYPE);
+
+ RPC_DATA_TYPE = new RpcDataType();
+ NAMES.Add(RPC_DATA_TYPE);
+
+ RPC_ERROR_TYPE = new UserDataType("android.support.place.rpc", "RpcError",
+ true, __FILE__, __LINE__);
+ NAMES.Add(RPC_ERROR_TYPE);
+
+ EVENT_FAKE_TYPE = new Type("event", Type::BUILT_IN, false, false, false);
+ NAMES.Add(EVENT_FAKE_TYPE);
+
CLASSLOADER_TYPE = new ClassLoaderType();
NAMES.Add(CLASSLOADER_TYPE);
@@ -129,27 +156,30 @@
// ================================================================
-Type::Type(const string& name, int kind, bool canWriteToParcel, bool canBeOut)
+Type::Type(const string& name, int kind, bool canWriteToParcel, bool canWriteToRpcData,
+ bool canBeOut)
:m_package(),
m_name(name),
m_declFile(""),
m_declLine(-1),
m_kind(kind),
m_canWriteToParcel(canWriteToParcel),
+ m_canWriteToRpcData(canWriteToRpcData),
m_canBeOut(canBeOut)
{
m_qualifiedName = name;
}
Type::Type(const string& package, const string& name,
- int kind, bool canWriteToParcel, bool canBeOut,
- const string& declFile, int declLine)
+ int kind, bool canWriteToParcel, bool canWriteToRpcData,
+ bool canBeOut, const string& declFile, int declLine)
:m_package(package),
m_name(name),
m_declFile(declFile),
m_declLine(declLine),
m_kind(kind),
m_canWriteToParcel(canWriteToParcel),
+ m_canWriteToRpcData(canWriteToRpcData),
m_canBeOut(canBeOut)
{
if (package.length() > 0) {
@@ -182,6 +212,12 @@
}
string
+Type::RpcCreatorName() const
+{
+ return "";
+}
+
+string
Type::InstantiableName() const
{
return QualifiedName();
@@ -244,6 +280,26 @@
}
void
+Type::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* WriteToRpcData error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* ReadFromRpcData error "
+ + m_qualifiedName + " */"));
+}
+
+void
Type::SetQualifiedName(const string& qualified)
{
m_qualifiedName = qualified;
@@ -264,29 +320,35 @@
// ================================================================
-BasicType::BasicType(const string& name, const string& marshallMethod,
- const string& unmarshallMethod,
- const string& writeArray, const string& createArray,
- const string& readArray)
- :Type(name, BUILT_IN, true, false),
- m_marshallMethod(marshallMethod),
- m_unmarshallMethod(unmarshallMethod),
- m_writeArrayMethod(writeArray),
- m_createArrayMethod(createArray),
- m_readArrayMethod(readArray)
+BasicType::BasicType(const string& name, const string& marshallParcel,
+ const string& unmarshallParcel, const string& writeArrayParcel,
+ const string& createArrayParcel, const string& readArrayParcel,
+ const string& marshallRpc, const string& unmarshallRpc,
+ const string& writeArrayRpc, const string& createArrayRpc, const string& readArrayRpc)
+ :Type(name, BUILT_IN, true, true, false),
+ m_marshallParcel(marshallParcel),
+ m_unmarshallParcel(unmarshallParcel),
+ m_writeArrayParcel(writeArrayParcel),
+ m_createArrayParcel(createArrayParcel),
+ m_readArrayParcel(readArrayParcel),
+ m_marshallRpc(marshallRpc),
+ m_unmarshallRpc(unmarshallRpc),
+ m_writeArrayRpc(writeArrayRpc),
+ m_createArrayRpc(createArrayRpc),
+ m_readArrayRpc(readArrayRpc)
{
}
void
BasicType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
{
- addTo->Add(new MethodCall(parcel, m_marshallMethod, 1, v));
+ addTo->Add(new MethodCall(parcel, m_marshallParcel, 1, v));
}
void
BasicType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
{
- addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallMethod)));
+ addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallParcel)));
}
bool
@@ -298,27 +360,40 @@
void
BasicType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
{
- addTo->Add(new MethodCall(parcel, m_writeArrayMethod, 1, v));
+ addTo->Add(new MethodCall(parcel, m_writeArrayParcel, 1, v));
}
void
BasicType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable**)
{
- addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayMethod)));
+ addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayParcel)));
}
void
BasicType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
{
- addTo->Add(new MethodCall(parcel, m_readArrayMethod, 1, v));
+ addTo->Add(new MethodCall(parcel, m_readArrayParcel, 1, v));
}
+void
+BasicType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, m_marshallRpc, 2, k, v));
+}
+
+void
+BasicType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, m_unmarshallRpc, 1, k)));
+}
// ================================================================
BooleanType::BooleanType()
- :Type("boolean", BUILT_IN, true, false)
+ :Type("boolean", BUILT_IN, true, true, false)
{
}
@@ -362,11 +437,24 @@
addTo->Add(new MethodCall(parcel, "readBooleanArray", 1, v));
}
+void
+BooleanType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putBoolean", 2, k, v));
+}
+
+void
+BooleanType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getBoolean", 1, k)));
+}
// ================================================================
CharType::CharType()
- :Type("char", BUILT_IN, true, false)
+ :Type("char", BUILT_IN, true, true, false)
{
}
@@ -408,10 +496,24 @@
addTo->Add(new MethodCall(parcel, "readCharArray", 1, v));
}
+void
+CharType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putChar", 2, k, v));
+}
+
+void
+CharType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getChar", 1, k)));
+}
+
// ================================================================
StringType::StringType()
- :Type("java.lang", "String", BUILT_IN, true, false)
+ :Type("java.lang", "String", BUILT_IN, true, true, false)
{
}
@@ -458,10 +560,24 @@
addTo->Add(new MethodCall(parcel, "readStringArray", 1, v));
}
+void
+StringType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putString", 2, k, v));
+}
+
+void
+StringType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getString", 1, k)));
+}
+
// ================================================================
CharSequenceType::CharSequenceType()
- :Type("java.lang", "CharSequence", BUILT_IN, true, false)
+ :Type("java.lang", "CharSequence", BUILT_IN, true, true, false)
{
}
@@ -521,7 +637,7 @@
// ================================================================
RemoteExceptionType::RemoteExceptionType()
- :Type("android.os", "RemoteException", BUILT_IN, false, false)
+ :Type("android.os", "RemoteException", BUILT_IN, false, false, false)
{
}
@@ -540,7 +656,7 @@
// ================================================================
RuntimeExceptionType::RuntimeExceptionType()
- :Type("java.lang", "RuntimeException", BUILT_IN, false, false)
+ :Type("java.lang", "RuntimeException", BUILT_IN, false, false, false)
{
}
@@ -560,7 +676,7 @@
// ================================================================
IBinderType::IBinderType()
- :Type("android.os", "IBinder", BUILT_IN, true, false)
+ :Type("android.os", "IBinder", BUILT_IN, true, false, false)
{
}
@@ -599,7 +715,7 @@
// ================================================================
IInterfaceType::IInterfaceType()
- :Type("android.os", "IInterface", BUILT_IN, false, false)
+ :Type("android.os", "IInterface", BUILT_IN, false, false, false)
{
}
@@ -619,7 +735,7 @@
// ================================================================
BinderType::BinderType()
- :Type("android.os", "Binder", BUILT_IN, false, false)
+ :Type("android.os", "Binder", BUILT_IN, false, false, false)
{
}
@@ -640,7 +756,7 @@
// ================================================================
BinderProxyType::BinderProxyType()
- :Type("android.os", "BinderProxy", BUILT_IN, false, false)
+ :Type("android.os", "BinderProxy", BUILT_IN, false, false, false)
{
}
@@ -661,7 +777,7 @@
// ================================================================
ParcelType::ParcelType()
- :Type("android.os", "Parcel", BUILT_IN, false, false)
+ :Type("android.os", "Parcel", BUILT_IN, false, false, false)
{
}
@@ -680,7 +796,7 @@
// ================================================================
ParcelableInterfaceType::ParcelableInterfaceType()
- :Type("android.os", "Parcelable", BUILT_IN, false, false)
+ :Type("android.os", "Parcelable", BUILT_IN, false, false, false)
{
}
@@ -699,7 +815,7 @@
// ================================================================
MapType::MapType()
- :Type("java.util", "Map", BUILT_IN, true, true)
+ :Type("java.util", "Map", BUILT_IN, true, false, true)
{
}
@@ -729,8 +845,7 @@
}
void
-MapType::ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
+MapType::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
{
EnsureClassLoader(addTo, cl);
addTo->Add(new MethodCall(parcel, "readMap", 2, v, *cl));
@@ -740,7 +855,7 @@
// ================================================================
ListType::ListType()
- :Type("java.util", "List", BUILT_IN, true, true)
+ :Type("java.util", "List", BUILT_IN, true, true, true)
{
}
@@ -771,24 +886,45 @@
addTo->Add(new MethodCall(parcel, "readList", 2, v, *cl));
}
+void
+ListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putList", 2, k, v));
+}
+
+void
+ListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getList", 1, k)));
+}
// ================================================================
-ParcelableType::ParcelableType(const string& package, const string& name,
- bool builtIn, const string& declFile, int declLine)
- :Type(package, name, builtIn ? BUILT_IN : PARCELABLE, true, true,
- declFile, declLine)
+UserDataType::UserDataType(const string& package, const string& name,
+ bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
+ const string& declFile, int declLine)
+ //:Type(package, name, builtIn ? BUILT_IN : USERDATA, canWriteToParcel, canWriteToRpcData,
+ :Type(package, name, builtIn ? BUILT_IN : USERDATA, true, true,
+ true, declFile, declLine)
{
}
string
-ParcelableType::CreatorName() const
+UserDataType::CreatorName() const
{
return QualifiedName() + ".CREATOR";
}
+string
+UserDataType::RpcCreatorName() const
+{
+ return QualifiedName() + ".RPC_CREATOR";
+}
+
void
-ParcelableType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+UserDataType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
{
// if (v != null) {
// parcel.writeInt(1);
@@ -811,7 +947,7 @@
}
void
-ParcelableType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+UserDataType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
{
// if (0 != parcel.readInt()) {
// v = CLASS.CREATOR.createFromParcel(parcel)
@@ -832,7 +968,7 @@
}
void
-ParcelableType::ReadFromParcel(StatementBlock* addTo, Variable* v,
+UserDataType::ReadFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable**)
{
// TODO: really, we don't need to have this extra check, but we
@@ -848,20 +984,20 @@
}
bool
-ParcelableType::CanBeArray() const
+UserDataType::CanBeArray() const
{
return true;
}
void
-ParcelableType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+UserDataType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
{
addTo->Add(new MethodCall(parcel, "writeTypedArray", 2, v,
BuildWriteToParcelFlags(flags)));
}
void
-ParcelableType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+UserDataType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable**)
{
string creator = v->type->QualifiedName() + ".CREATOR";
@@ -870,20 +1006,36 @@
}
void
-ParcelableType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+UserDataType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
{
string creator = v->type->QualifiedName() + ".CREATOR";
addTo->Add(new MethodCall(parcel, "readTypedArray", 2,
v, new LiteralExpression(creator)));
}
+void
+UserDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ // data.putFlattenable(k, v);
+ addTo->Add(new MethodCall(data, "putFlattenable", 2, k, v));
+}
+
+void
+UserDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl)
+{
+ // data.getFlattenable(k, CLASS.RPC_CREATOR);
+ addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenable", 2, k,
+ new FieldVariable(v->type, "RPC_CREATOR"))));
+}
// ================================================================
InterfaceType::InterfaceType(const string& package, const string& name,
bool builtIn, bool oneway,
const string& declFile, int declLine)
- :Type(package, name, builtIn ? BUILT_IN : INTERFACE, true, false,
+ :Type(package, name, builtIn ? BUILT_IN : INTERFACE, true, false, false,
declFile, declLine)
,m_oneway(oneway)
{
@@ -922,7 +1074,7 @@
GenericType::GenericType(const string& package, const string& name,
const vector<Type*>& args)
- :Type(package, name, BUILT_IN, true, true)
+ :Type(package, name, BUILT_IN, true, true, true)
{
m_args = args;
@@ -942,6 +1094,12 @@
SetQualifiedName(m_importName + gen);
}
+const vector<Type*>&
+GenericType::GenericArgumentTypes() const
+{
+ return m_args;
+}
+
string
GenericType::GenericArguments() const
{
@@ -1041,10 +1199,65 @@
}
}
+void
+GenericListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ Type* generic = GenericArgumentTypes()[0];
+ if (generic == RPC_DATA_TYPE) {
+ addTo->Add(new MethodCall(data, "putRpcDataList", 2, k, v));
+ } else if (generic->RpcCreatorName() != "") {
+ addTo->Add(new MethodCall(data, "putFlattenableList", 2, k, v));
+ } else {
+ addTo->Add(new MethodCall(data, "putList", 2, k, v));
+ }
+}
+
+void
+GenericListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl)
+{
+ Type* generic = GenericArgumentTypes()[0];
+ if (generic == RPC_DATA_TYPE) {
+ addTo->Add(new Assignment(v, new MethodCall(data, "getRpcDataList", 2, k)));
+ } else if (generic->RpcCreatorName() != "") {
+ addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenableList", 2, k,
+ new LiteralExpression(generic->RpcCreatorName()))));
+ } else {
+ string classArg = GenericArgumentTypes()[0]->QualifiedName();
+ classArg += ".class";
+ addTo->Add(new Assignment(v, new MethodCall(data, "getList", 2, k,
+ new LiteralExpression(classArg))));
+ }
+}
+
+
+// ================================================================
+
+RpcDataType::RpcDataType()
+ :UserDataType("android.support.place.rpc", "RpcData", true, true, true)
+{
+}
+
+void
+RpcDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putRpcData", 2, k, v));
+}
+
+void
+RpcDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getRpcData", 1, k)));
+}
+
+
// ================================================================
ClassLoaderType::ClassLoaderType()
- :Type("java.lang", "ClassLoader", BUILT_IN, false, false)
+ :Type("java.lang", "ClassLoader", BUILT_IN, false, false, false)
{
}
diff --git a/tools/aidl/Type.h b/tools/aidl/Type.h
index 662e3a2..ae12720 100755
--- a/tools/aidl/Type.h
+++ b/tools/aidl/Type.h
@@ -13,7 +13,7 @@
// kinds
enum {
BUILT_IN,
- PARCELABLE,
+ USERDATA,
INTERFACE,
GENERATED
};
@@ -24,9 +24,9 @@
};
Type(const string& name, int kind, bool canWriteToParcel,
- bool canBeOut);
+ bool canWriteToRpcData, bool canBeOut);
Type(const string& package, const string& name,
- int kind, bool canWriteToParcel, bool canBeOut,
+ int kind, bool canWriteToParcel, bool canWriteToRpcData, bool canBeOut,
const string& declFile = "", int declLine = -1);
virtual ~Type();
@@ -36,11 +36,13 @@
inline int Kind() const { return m_kind; }
inline string DeclFile() const { return m_declFile; }
inline int DeclLine() const { return m_declLine; }
- inline bool CanBeMarshalled() const { return m_canWriteToParcel; }
+ inline bool CanWriteToParcel() const { return m_canWriteToParcel; }
+ inline bool CanWriteToRpcData() const { return m_canWriteToRpcData; }
inline bool CanBeOutParameter() const { return m_canBeOut; }
virtual string ImportType() const;
virtual string CreatorName() const;
+ virtual string RpcCreatorName() const;
virtual string InstantiableName() const;
virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
@@ -59,6 +61,11 @@
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
protected:
void SetQualifiedName(const string& qualified);
Expression* BuildWriteToParcelFlags(int flags);
@@ -74,17 +81,24 @@
int m_declLine;
int m_kind;
bool m_canWriteToParcel;
+ bool m_canWriteToRpcData;
bool m_canBeOut;
};
class BasicType : public Type
{
public:
- BasicType(const string& name, const string& marshallMethod,
- const string& unmarshallMethod,
- const string& writeArray,
- const string& createArray,
- const string& readArray);
+ BasicType(const string& name,
+ const string& marshallParcel,
+ const string& unmarshallParcel,
+ const string& writeArrayParcel,
+ const string& createArrayParcel,
+ const string& readArrayParcel,
+ const string& marshallRpc,
+ const string& unmarshallRpc,
+ const string& writeArrayRpc,
+ const string& createArrayRpc,
+ const string& readArrayRpc);
virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, int flags);
@@ -100,12 +114,22 @@
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
private:
- string m_marshallMethod;
- string m_unmarshallMethod;
- string m_writeArrayMethod;
- string m_createArrayMethod;
- string m_readArrayMethod;
+ string m_marshallParcel;
+ string m_unmarshallParcel;
+ string m_writeArrayParcel;
+ string m_createArrayParcel;
+ string m_readArrayParcel;
+ string m_marshallRpc;
+ string m_unmarshallRpc;
+ string m_writeArrayRpc;
+ string m_createArrayRpc;
+ string m_readArrayRpc;
};
class BooleanType : public Type
@@ -126,6 +150,11 @@
Variable* parcel, Variable** cl);
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
class CharType : public Type
@@ -146,6 +175,11 @@
Variable* parcel, Variable** cl);
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
@@ -169,6 +203,11 @@
Variable* parcel, Variable** cl);
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
class CharSequenceType : public Type
@@ -305,15 +344,22 @@
Variable* parcel, Variable** cl);
virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
-class ParcelableType : public Type
+class UserDataType : public Type
{
public:
- ParcelableType(const string& package, const string& name,
- bool builtIn, const string& declFile, int declLine);
+ UserDataType(const string& package, const string& name,
+ bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
+ const string& declFile = "", int declLine = -1);
virtual string CreatorName() const;
+ virtual string RpcCreatorName() const;
virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, int flags);
@@ -330,6 +376,11 @@
Variable* parcel, Variable** cl);
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
class InterfaceType : public Type
@@ -357,6 +408,7 @@
GenericType(const string& package, const string& name,
const vector<Type*>& args);
+ const vector<Type*>& GenericArgumentTypes() const;
string GenericArguments() const;
virtual string ImportType() const;
@@ -374,6 +426,22 @@
vector<Type*> m_args;
};
+class RpcDataType : public UserDataType
+{
+public:
+ RpcDataType();
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
+class ClassLoaderType : public Type
+{
+public:
+ ClassLoaderType();
+};
class GenericListType : public GenericType
{
@@ -391,16 +459,15 @@
virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
private:
string m_creator;
};
-class ClassLoaderType : public Type
-{
-public:
- ClassLoaderType();
-};
-
class Namespace
{
public:
@@ -438,11 +505,13 @@
extern Type* VOID_TYPE;
extern Type* BOOLEAN_TYPE;
+extern Type* BYTE_TYPE;
extern Type* CHAR_TYPE;
extern Type* INT_TYPE;
extern Type* LONG_TYPE;
extern Type* FLOAT_TYPE;
extern Type* DOUBLE_TYPE;
+extern Type* OBJECT_TYPE;
extern Type* STRING_TYPE;
extern Type* CHAR_SEQUENCE_TYPE;
extern Type* TEXT_UTILS_TYPE;
@@ -455,6 +524,13 @@
extern Type* PARCEL_TYPE;
extern Type* PARCELABLE_INTERFACE_TYPE;
+extern Type* CONTEXT_TYPE;
+
+extern Type* RPC_DATA_TYPE;
+extern Type* RPC_ERROR_TYPE;
+extern Type* RPC_CONTEXT_TYPE;
+extern Type* EVENT_FAKE_TYPE;
+
extern Expression* NULL_VALUE;
extern Expression* THIS_VALUE;
extern Expression* SUPER_VALUE;
diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp
index fb4067a..3d314db 100644
--- a/tools/aidl/aidl.cpp
+++ b/tools/aidl/aidl.cpp
@@ -29,7 +29,7 @@
test_document(document_item_type* d)
{
while (d) {
- if (d->item_type == INTERFACE_TYPE) {
+ if (d->item_type == INTERFACE_TYPE_BINDER) {
interface_type* c = (interface_type*)d;
printf("interface %s %s {\n", c->package, c->name.data);
interface_item_type *q = (interface_item_type*)c->interface_items;
@@ -50,9 +50,14 @@
}
printf("}\n");
}
- else if (d->item_type == PARCELABLE_TYPE) {
- parcelable_type* b = (parcelable_type*)d;
- printf("parcelable %s %s;\n", b->package, b->name.data);
+ else if (d->item_type == USER_DATA_TYPE) {
+ user_data_type* b = (user_data_type*)d;
+ if ((b->flattening_methods & PARCELABLE_DATA) != 0) {
+ printf("parcelable %s %s;\n", b->package, b->name.data);
+ }
+ if ((b->flattening_methods & RPC_DATA) != 0) {
+ printf("flattenable %s %s;\n", b->package, b->name.data);
+ }
}
else {
printf("UNKNOWN d=0x%08lx d->item_type=%d\n", (long)d, d->item_type);
@@ -238,11 +243,12 @@
{
int err = 0;
while (items) {
- if (items->item_type == PARCELABLE_TYPE) {
- parcelable_type* p = (parcelable_type*)items;
+ if (items->item_type == USER_DATA_TYPE) {
+ user_data_type* p = (user_data_type*)items;
err |= check_filename(filename, p->package, &p->name);
}
- else if (items->item_type == INTERFACE_TYPE) {
+ else if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
interface_type* c = (interface_type*)items;
err |= check_filename(filename, c->package, &c->name);
}
@@ -264,8 +270,8 @@
{
case Type::INTERFACE:
return "an interface";
- case Type::PARCELABLE:
- return "a parcelable";
+ case Type::USERDATA:
+ return "a user data";
default:
return "ERROR";
}
@@ -290,12 +296,14 @@
int err = 0;
while (items) {
Type* type;
- if (items->item_type == PARCELABLE_TYPE) {
- parcelable_type* p = (parcelable_type*)items;
- type = new ParcelableType(p->package ? p->package : "",
- p->name.data, false, filename, p->name.lineno);
+ if (items->item_type == USER_DATA_TYPE) {
+ user_data_type* p = (user_data_type*)items;
+ type = new UserDataType(p->package ? p->package : "", p->name.data,
+ false, ((p->flattening_methods & PARCELABLE_DATA) != 0),
+ ((p->flattening_methods & RPC_DATA) != 0), filename, p->name.lineno);
}
- else if (items->item_type == INTERFACE_TYPE) {
+ else if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
interface_type* c = (interface_type*)items;
type = new InterfaceType(c->package ? c->package : "",
c->name.data, false, c->oneway,
@@ -310,7 +318,7 @@
if (old == NULL) {
NAMES.Add(type);
- if (items->item_type == INTERFACE_TYPE) {
+ if (items->item_type == INTERFACE_TYPE_BINDER) {
// for interfaces, also add the stub and proxy types, we don't
// bother checking these for duplicates, because the parser
// won't let us do it.
@@ -319,17 +327,30 @@
string name = c->name.data;
name += ".Stub";
Type* stub = new Type(c->package ? c->package : "",
- name, Type::GENERATED, false, false,
+ name, Type::GENERATED, false, false, false,
filename, c->name.lineno);
NAMES.Add(stub);
name = c->name.data;
name += ".Stub.Proxy";
Type* proxy = new Type(c->package ? c->package : "",
- name, Type::GENERATED, false, false,
+ name, Type::GENERATED, false, false, false,
filename, c->name.lineno);
NAMES.Add(proxy);
}
+ else if (items->item_type == INTERFACE_TYPE_RPC) {
+ // for interfaces, also add the service base type, we don't
+ // bother checking these for duplicates, because the parser
+ // won't let us do it.
+ interface_type* c = (interface_type*)items;
+
+ string name = c->name.data;
+ name += ".ServiceBase";
+ Type* base = new Type(c->package ? c->package : "",
+ name, Type::GENERATED, false, false, false,
+ filename, c->name.lineno);
+ NAMES.Add(base);
+ }
} else {
if (old->Kind() == Type::BUILT_IN) {
fprintf(stderr, "%s:%d attempt to redefine built in class %s\n",
@@ -381,7 +402,7 @@
}
static int
-check_method(const char* filename, method_type* m)
+check_method(const char* filename, int kind, method_type* m)
{
int err = 0;
@@ -394,10 +415,20 @@
return err;
}
- if (!returnType->CanBeMarshalled()) {
- fprintf(stderr, "%s:%d return type %s can't be marshalled.\n", filename,
- m->type.type.lineno, m->type.type.data);
- err = 1;
+ if (returnType == EVENT_FAKE_TYPE) {
+ if (kind != INTERFACE_TYPE_RPC) {
+ fprintf(stderr, "%s:%d event methods only supported for rpc interfaces\n",
+ filename, m->type.type.lineno);
+ err = 1;
+ }
+ } else {
+ if (!(kind == INTERFACE_TYPE_BINDER ? returnType->CanWriteToParcel()
+ : returnType->CanWriteToRpcData())) {
+ fprintf(stderr, "%s:%d return type %s can't be marshalled. kind=%d p=%d m=%d\n", filename,
+ m->type.type.lineno, m->type.type.data, kind,
+ returnType->CanWriteToParcel(), returnType->CanWriteToRpcData());
+ err = 1;
+ }
}
if (m->type.dimension > 0 && !returnType->CanBeArray()) {
@@ -429,14 +460,31 @@
err = 1;
goto next;
}
+
+ if (t == EVENT_FAKE_TYPE) {
+ fprintf(stderr, "%s:%d parameter %s (%d) event can not be used as a parameter %s\n",
+ filename, m->type.type.lineno, arg->name.data, index,
+ arg->type.type.data);
+ err = 1;
+ goto next;
+ }
- if (!t->CanBeMarshalled()) {
+ if (!(kind == INTERFACE_TYPE_BINDER ? t->CanWriteToParcel() : t->CanWriteToRpcData())) {
fprintf(stderr, "%s:%d parameter %d: '%s %s' can't be marshalled.\n",
filename, m->type.type.lineno, index,
arg->type.type.data, arg->name.data);
err = 1;
}
+ if (returnType == EVENT_FAKE_TYPE
+ && convert_direction(arg->direction.data) != IN_PARAMETER) {
+ fprintf(stderr, "%s:%d parameter %d: '%s %s' All paremeters on events must be 'in'.\n",
+ filename, m->type.type.lineno, index,
+ arg->type.type.data, arg->name.data);
+ err = 1;
+ goto next;
+ }
+
if (arg->direction.data == NULL
&& (arg->type.dimension != 0 || t->CanBeOutParameter())) {
fprintf(stderr, "%s:%d parameter %d: '%s %s' can be an out"
@@ -479,7 +527,7 @@
// check that the name doesn't match a keyword
if (matches_keyword(arg->name.data)) {
fprintf(stderr, "%s:%d parameter %d %s is named the same as a"
- " Java keyword\n",
+ " Java or aidl keyword\n",
filename, m->name.lineno, index, arg->name.data);
err = 1;
}
@@ -497,8 +545,9 @@
{
int err = 0;
while (items) {
- // (nothing to check for PARCELABLE_TYPE)
- if (items->item_type == INTERFACE_TYPE) {
+ // (nothing to check for USER_DATA_TYPE)
+ if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
map<string,method_type*> methodNames;
interface_type* c = (interface_type*)items;
@@ -507,7 +556,7 @@
if (member->item_type == METHOD_TYPE) {
method_type* m = (method_type*)member;
- err |= check_method(filename, m);
+ err |= check_method(filename, items->item_type, m);
// prevent duplicate methods
if (methodNames.find(m->name.data) == methodNames.end()) {
@@ -544,26 +593,29 @@
const document_item_type* next = items->next;
if (items->next != NULL) {
int lineno = -1;
- if (next->item_type == INTERFACE_TYPE) {
+ if (next->item_type == INTERFACE_TYPE_BINDER) {
lineno = ((interface_type*)next)->interface_token.lineno;
}
- else if (next->item_type == PARCELABLE_TYPE) {
- lineno = ((parcelable_type*)next)->parcelable_token.lineno;
+ else if (next->item_type == INTERFACE_TYPE_RPC) {
+ lineno = ((interface_type*)next)->interface_token.lineno;
+ }
+ else if (next->item_type == USER_DATA_TYPE) {
+ lineno = ((user_data_type*)next)->keyword_token.lineno;
}
fprintf(stderr, "%s:%d aidl can only handle one interface per file\n",
filename, lineno);
return 1;
}
- if (items->item_type == PARCELABLE_TYPE) {
+ if (items->item_type == USER_DATA_TYPE) {
*onlyParcelable = true;
if (options.failOnParcelable) {
fprintf(stderr, "%s:%d aidl can only generate code for interfaces, not"
- " parcelables,\n", filename,
- ((parcelable_type*)items)->parcelable_token.lineno);
- fprintf(stderr, "%s:%d .aidl files that only declare parcelables "
- "don't need to go in the Makefile.\n", filename,
- ((parcelable_type*)items)->parcelable_token.lineno);
+ " parcelables or flattenables,\n", filename,
+ ((user_data_type*)items)->keyword_token.lineno);
+ fprintf(stderr, "%s:%d .aidl files that only declare parcelables or flattenables"
+ "may not go in the Makefile.\n", filename,
+ ((user_data_type*)items)->keyword_token.lineno);
return 1;
}
} else {
@@ -598,7 +650,7 @@
slash = "";
}
- if (items->item_type == INTERFACE_TYPE) {
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
fprintf(to, "%s: \\\n", options.outputFileName.c_str());
} else {
// parcelable: there's no output file.
@@ -658,12 +710,12 @@
generate_outputFileName(const Options& options, const document_item_type* items)
{
// items has already been checked to have only one interface.
- if (items->item_type == INTERFACE_TYPE) {
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
interface_type* type = (interface_type*)items;
return generate_outputFileName2(options, type->name, type->package);
- } else if (items->item_type == PARCELABLE_TYPE) {
- parcelable_type* type = (parcelable_type*)items;
+ } else if (items->item_type == USER_DATA_TYPE) {
+ user_data_type* type = (user_data_type*)items;
return generate_outputFileName2(options, type->name, type->package);
}
@@ -734,24 +786,40 @@
document_item_type* doc;
if (0 == strcmp("parcelable", type)) {
- parcelable_type* parcl = (parcelable_type*)malloc(
- sizeof(parcelable_type));
- memset(parcl, 0, sizeof(parcelable_type));
- parcl->document_item.item_type = PARCELABLE_TYPE;
- parcl->parcelable_token.lineno = lineno;
- parcl->parcelable_token.data = strdup(type);
+ user_data_type* parcl = (user_data_type*)malloc(
+ sizeof(user_data_type));
+ memset(parcl, 0, sizeof(user_data_type));
+ parcl->document_item.item_type = USER_DATA_TYPE;
+ parcl->keyword_token.lineno = lineno;
+ parcl->keyword_token.data = strdup(type);
parcl->package = packagename ? strdup(packagename) : NULL;
parcl->name.lineno = lineno;
parcl->name.data = strdup(classname);
parcl->semicolon_token.lineno = lineno;
parcl->semicolon_token.data = strdup(";");
+ parcl->flattening_methods = PARCELABLE_DATA;
+ doc = (document_item_type*)parcl;
+ }
+ else if (0 == strcmp("flattenable", type)) {
+ user_data_type* parcl = (user_data_type*)malloc(
+ sizeof(user_data_type));
+ memset(parcl, 0, sizeof(user_data_type));
+ parcl->document_item.item_type = USER_DATA_TYPE;
+ parcl->keyword_token.lineno = lineno;
+ parcl->keyword_token.data = strdup(type);
+ parcl->package = packagename ? strdup(packagename) : NULL;
+ parcl->name.lineno = lineno;
+ parcl->name.data = strdup(classname);
+ parcl->semicolon_token.lineno = lineno;
+ parcl->semicolon_token.data = strdup(";");
+ parcl->flattening_methods = RPC_DATA;
doc = (document_item_type*)parcl;
}
else if (0 == strcmp("interface", type)) {
interface_type* iface = (interface_type*)malloc(
sizeof(interface_type));
memset(iface, 0, sizeof(interface_type));
- iface->document_item.item_type = INTERFACE_TYPE;
+ iface->document_item.item_type = INTERFACE_TYPE_BINDER;
iface->interface_token.lineno = lineno;
iface->interface_token.data = strdup(type);
iface->package = packagename ? strdup(packagename) : NULL;
@@ -923,9 +991,14 @@
}
document_item_type* doc = g_document;
string line;
- if (doc->item_type == PARCELABLE_TYPE) {
- line = "parcelable ";
- parcelable_type* parcelable = (parcelable_type*)doc;
+ if (doc->item_type == USER_DATA_TYPE) {
+ user_data_type* parcelable = (user_data_type*)doc;
+ if ((parcelable->flattening_methods & PARCELABLE_DATA) != 0) {
+ line = "parcelable ";
+ }
+ if ((parcelable->flattening_methods & RPC_DATA) != 0) {
+ line = "flattenable ";
+ }
if (parcelable->package) {
line += parcelable->package;
line += '.';
@@ -995,5 +1068,3 @@
fprintf(stderr, "aidl: internal error\n");
return 1;
}
-
-
diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h
index 9ca5deb..f203dbb0 100644
--- a/tools/aidl/aidl_language.h
+++ b/tools/aidl/aidl_language.h
@@ -63,8 +63,9 @@
} method_type;
enum {
- PARCELABLE_TYPE = 12,
- INTERFACE_TYPE
+ USER_DATA_TYPE = 12,
+ INTERFACE_TYPE_BINDER,
+ INTERFACE_TYPE_RPC
};
typedef struct document_item_type {
@@ -72,13 +73,21 @@
struct document_item_type* next;
} document_item_type;
-typedef struct parcelable_type {
+
+// for user_data_type.flattening_methods
+enum {
+ PARCELABLE_DATA = 0x1,
+ RPC_DATA = 0x2
+};
+
+typedef struct user_data_type {
document_item_type document_item;
- buffer_type parcelable_token;
+ buffer_type keyword_token; // only the first one
char* package;
buffer_type name;
buffer_type semicolon_token;
-} parcelable_type;
+ int flattening_methods;
+} user_data_type;
typedef struct interface_type {
document_item_type document_item;
@@ -100,7 +109,7 @@
method_type* method;
interface_item_type* interface_item;
interface_type* interface_obj;
- parcelable_type* parcelable;
+ user_data_type* user_data;
document_item_type* document_item;
} lexer_type;
diff --git a/tools/aidl/aidl_language_l.l b/tools/aidl/aidl_language_l.l
index 567b1cf..7c5290c 100644
--- a/tools/aidl/aidl_language_l.l
+++ b/tools/aidl/aidl_language_l.l
@@ -81,6 +81,8 @@
/* keywords */
parcelable { SET_BUFFER(PARCELABLE); return PARCELABLE; }
interface { SET_BUFFER(INTERFACE); return INTERFACE; }
+flattenable { SET_BUFFER(FLATTENABLE); return FLATTENABLE; }
+rpc { SET_BUFFER(INTERFACE); return RPC; }
in { SET_BUFFER(IN); return IN; }
out { SET_BUFFER(OUT); return OUT; }
inout { SET_BUFFER(INOUT); return INOUT; }
diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y
index 3d65f17..3c16e15 100644
--- a/tools/aidl/aidl_language_y.y
+++ b/tools/aidl/aidl_language_y.y
@@ -19,6 +19,8 @@
%token ARRAY
%token PARCELABLE
%token INTERFACE
+%token FLATTENABLE
+%token RPC
%token IN
%token OUT
%token INOUT
@@ -72,36 +74,61 @@
;
declaration:
- parcelable_decl { $$.document_item = (document_item_type*)$1.parcelable; }
+ parcelable_decl { $$.document_item = (document_item_type*)$1.user_data; }
| interface_decl { $$.document_item = (document_item_type*)$1.interface_item; }
;
parcelable_decl:
- PARCELABLE IDENTIFIER ';' {
- parcelable_type* b = (parcelable_type*)malloc(sizeof(parcelable_type));
- b->document_item.item_type = PARCELABLE_TYPE;
+ PARCELABLE IDENTIFIER ';' {
+ user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
+ b->document_item.item_type = USER_DATA_TYPE;
b->document_item.next = NULL;
- b->parcelable_token = $1.buffer;
+ b->keyword_token = $1.buffer;
b->name = $2.buffer;
b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
b->semicolon_token = $3.buffer;
- $$.parcelable = b;
+ b->flattening_methods = PARCELABLE_DATA | RPC_DATA;
+ $$.user_data = b;
}
| PARCELABLE ';' {
fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name.\n",
g_currentFilename, $1.buffer.lineno);
- $$.parcelable = NULL;
+ $$.user_data = NULL;
}
| PARCELABLE error ';' {
fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name, saw \"%s\".\n",
g_currentFilename, $2.buffer.lineno, $2.buffer.data);
- $$.parcelable = NULL;
+ $$.user_data = NULL;
}
+ | FLATTENABLE IDENTIFIER ';' {
+ user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
+ b->document_item.item_type = USER_DATA_TYPE;
+ b->document_item.next = NULL;
+ b->keyword_token = $1.buffer;
+ b->name = $2.buffer;
+ b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
+ b->semicolon_token = $3.buffer;
+ b->flattening_methods = PARCELABLE_DATA | RPC_DATA;
+ $$.user_data = b;
+ }
+ | FLATTENABLE ';' {
+ fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name.\n",
+ g_currentFilename, $1.buffer.lineno);
+ $$.user_data = NULL;
+ }
+ | FLATTENABLE error ';' {
+ fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name, saw \"%s\".\n",
+ g_currentFilename, $2.buffer.lineno, $2.buffer.data);
+ $$.user_data = NULL;
+ }
+
;
interface_header:
INTERFACE {
interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_BINDER;
+ c->document_item.next = NULL;
c->interface_token = $1.buffer;
c->oneway = false;
memset(&c->oneway_token, 0, sizeof(buffer_type));
@@ -110,19 +137,34 @@
}
| ONEWAY INTERFACE {
interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_BINDER;
+ c->document_item.next = NULL;
c->interface_token = $2.buffer;
c->oneway = true;
c->oneway_token = $1.buffer;
c->comments_token = &c->oneway_token;
$$.interface_obj = c;
}
+ | RPC {
+ interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_RPC;
+ c->document_item.next = NULL;
+ c->interface_token = $1.buffer;
+ c->oneway = false;
+ memset(&c->oneway_token, 0, sizeof(buffer_type));
+ c->comments_token = &c->interface_token;
+ $$.interface_obj = c;
+ }
+ ;
+
+interface_keywords:
+ INTERFACE
+ | RPC
;
interface_decl:
interface_header IDENTIFIER '{' interface_items '}' {
interface_type* c = $1.interface_obj;
- c->document_item.item_type = INTERFACE_TYPE;
- c->document_item.next = NULL;
c->name = $2.buffer;
c->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
c->open_brace_token = $3.buffer;
@@ -130,12 +172,12 @@
c->close_brace_token = $5.buffer;
$$.interface_obj = c;
}
- | INTERFACE error '{' interface_items '}' {
+ | interface_keywords error '{' interface_items '}' {
fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
g_currentFilename, $2.buffer.lineno, $2.buffer.data);
$$.document_item = NULL;
}
- | INTERFACE error '}' {
+ | interface_keywords error '}' {
fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
g_currentFilename, $2.buffer.lineno, $2.buffer.data);
$$.document_item = NULL;
diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp
index 83e3bbc..9e57407 100644
--- a/tools/aidl/generate_java.cpp
+++ b/tools/aidl/generate_java.cpp
@@ -1,5 +1,4 @@
#include "generate_java.h"
-#include "AST.h"
#include "Type.h"
#include <string.h>
#include <stdio.h>
@@ -7,18 +6,6 @@
#include <string.h>
// =================================================
-class VariableFactory
-{
-public:
- VariableFactory(const string& base); // base must be short
- Variable* Get(Type* type);
- Variable* Get(int index);
-private:
- vector<Variable*> m_vars;
- string m_base;
- int m_index;
-};
-
VariableFactory::VariableFactory(const string& base)
:m_base(base),
m_index(0)
@@ -43,195 +30,7 @@
}
// =================================================
-class StubClass : public Class
-{
-public:
- StubClass(Type* type, Type* interfaceType);
- virtual ~StubClass();
-
- Variable* transact_code;
- Variable* transact_data;
- Variable* transact_reply;
- Variable* transact_flags;
- SwitchStatement* transact_switch;
-private:
- void make_as_interface(Type* interfaceType);
-};
-
-StubClass::StubClass(Type* type, Type* interfaceType)
- :Class()
-{
- this->comment = "/** Local-side IPC implementation stub class. */";
- this->modifiers = PUBLIC | ABSTRACT | STATIC;
- this->what = Class::CLASS;
- this->type = type;
- this->extends = BINDER_NATIVE_TYPE;
- this->interfaces.push_back(interfaceType);
-
- // descriptor
- Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
- new Variable(STRING_TYPE, "DESCRIPTOR"));
- descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
- this->elements.push_back(descriptor);
-
- // ctor
- Method* ctor = new Method;
- ctor->modifiers = PUBLIC;
- ctor->comment = "/** Construct the stub at attach it to the "
- "interface. */";
- ctor->name = "Stub";
- ctor->statements = new StatementBlock;
- MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
- 2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
- ctor->statements->Add(attach);
- this->elements.push_back(ctor);
-
- // asInterface
- make_as_interface(interfaceType);
-
- // asBinder
- Method* asBinder = new Method;
- asBinder->modifiers = PUBLIC;
- asBinder->returnType = IBINDER_TYPE;
- asBinder->name = "asBinder";
- asBinder->statements = new StatementBlock;
- asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
- this->elements.push_back(asBinder);
-
- // onTransact
- this->transact_code = new Variable(INT_TYPE, "code");
- this->transact_data = new Variable(PARCEL_TYPE, "data");
- this->transact_reply = new Variable(PARCEL_TYPE, "reply");
- this->transact_flags = new Variable(INT_TYPE, "flags");
- Method* onTransact = new Method;
- onTransact->modifiers = PUBLIC | OVERRIDE;
- onTransact->returnType = BOOLEAN_TYPE;
- onTransact->name = "onTransact";
- onTransact->parameters.push_back(this->transact_code);
- onTransact->parameters.push_back(this->transact_data);
- onTransact->parameters.push_back(this->transact_reply);
- onTransact->parameters.push_back(this->transact_flags);
- onTransact->statements = new StatementBlock;
- onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
- this->elements.push_back(onTransact);
- this->transact_switch = new SwitchStatement(this->transact_code);
-
- onTransact->statements->Add(this->transact_switch);
- MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
- this->transact_code, this->transact_data,
- this->transact_reply, this->transact_flags);
- onTransact->statements->Add(new ReturnStatement(superCall));
-}
-
-StubClass::~StubClass()
-{
-}
-
-void
-StubClass::make_as_interface(Type *interfaceType)
-{
- Variable* obj = new Variable(IBINDER_TYPE, "obj");
-
- Method* m = new Method;
- m->comment = "/**\n * Cast an IBinder object into an ";
- m->comment += interfaceType->QualifiedName();
- m->comment += " interface,\n";
- m->comment += " * generating a proxy if needed.\n */";
- m->modifiers = PUBLIC | STATIC;
- m->returnType = interfaceType;
- m->name = "asInterface";
- m->parameters.push_back(obj);
- m->statements = new StatementBlock;
-
- IfStatement* ifstatement = new IfStatement();
- ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
- ifstatement->statements = new StatementBlock;
- ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
- m->statements->Add(ifstatement);
-
- // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
- MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
- queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
- IInterfaceType* iinType = new IInterfaceType();
- Variable *iin = new Variable(iinType, "iin");
- VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, iinType);
- m->statements->Add(iinVd);
-
- // Ensure the instance type of the local object is as expected.
- // One scenario where this is needed is if another package (with a
- // different class loader) runs in the same process as the service.
-
- // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
- Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
- Comparison* instOfCheck = new Comparison(iin, " instanceof ",
- new LiteralExpression(interfaceType->QualifiedName()));
- IfStatement* instOfStatement = new IfStatement();
- instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
- instOfStatement->statements = new StatementBlock;
- instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
- m->statements->Add(instOfStatement);
-
- string proxyType = interfaceType->QualifiedName();
- proxyType += ".Stub.Proxy";
- NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
- ne->arguments.push_back(obj);
- m->statements->Add(new ReturnStatement(ne));
-
- this->elements.push_back(m);
-}
-
-
-
-// =================================================
-class ProxyClass : public Class
-{
-public:
- ProxyClass(Type* type, InterfaceType* interfaceType);
- virtual ~ProxyClass();
-
- Variable* mRemote;
- bool mOneWay;
-};
-
-ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
- :Class()
-{
- this->modifiers = PRIVATE | STATIC;
- this->what = Class::CLASS;
- this->type = type;
- this->interfaces.push_back(interfaceType);
-
- mOneWay = interfaceType->OneWay();
-
- // IBinder mRemote
- mRemote = new Variable(IBINDER_TYPE, "mRemote");
- this->elements.push_back(new Field(PRIVATE, mRemote));
-
- // Proxy()
- Variable* remote = new Variable(IBINDER_TYPE, "remote");
- Method* ctor = new Method;
- ctor->name = "Proxy";
- ctor->statements = new StatementBlock;
- ctor->parameters.push_back(remote);
- ctor->statements->Add(new Assignment(mRemote, remote));
- this->elements.push_back(ctor);
-
- // IBinder asBinder()
- Method* asBinder = new Method;
- asBinder->modifiers = PUBLIC;
- asBinder->returnType = IBINDER_TYPE;
- asBinder->name = "asBinder";
- asBinder->statements = new StatementBlock;
- asBinder->statements->Add(new ReturnStatement(mRemote));
- this->elements.push_back(asBinder);
-}
-
-ProxyClass::~ProxyClass()
-{
-}
-
-// =================================================
-static string
+string
gather_comments(extra_text_type* extra)
{
string s;
@@ -249,7 +48,7 @@
return s;
}
-static string
+string
append(const char* a, const char* b)
{
string s = a;
@@ -257,379 +56,25 @@
return s;
}
-static void
-generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel)
-{
- Variable* len = new Variable(INT_TYPE, v->name + "_length");
- addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
- IfStatement* lencheck = new IfStatement();
- lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
- lencheck->statements->Add(new Assignment(v, NULL_VALUE));
- lencheck->elseif = new IfStatement();
- lencheck->elseif->statements->Add(new Assignment(v,
- new NewArrayExpression(t, len)));
- addTo->Add(lencheck);
-}
-
-static void
-generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags)
-{
- if (v->dimension == 0) {
- t->WriteToParcel(addTo, v, parcel, flags);
- }
- if (v->dimension == 1) {
- t->WriteArrayToParcel(addTo, v, parcel, flags);
- }
-}
-
-static void
-generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
-{
- if (v->dimension == 0) {
- t->CreateFromParcel(addTo, v, parcel, cl);
- }
- if (v->dimension == 1) {
- t->CreateArrayFromParcel(addTo, v, parcel, cl);
- }
-}
-
-static void
-generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
-{
- if (v->dimension == 0) {
- t->ReadFromParcel(addTo, v, parcel, cl);
- }
- if (v->dimension == 1) {
- t->ReadArrayFromParcel(addTo, v, parcel, cl);
- }
-}
-
-
-static void
-generate_method(const method_type* method, Class* interface,
- StubClass* stubClass, ProxyClass* proxyClass, int index)
-{
- arg_type* arg;
- int i;
- bool hasOutParams = false;
-
- const bool oneway = proxyClass->mOneWay || method->oneway;
-
- // == the TRANSACT_ constant =============================================
- string transactCodeName = "TRANSACTION_";
- transactCodeName += method->name.data;
-
- char transactCodeValue[50];
- sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
-
- Field* transactCode = new Field(STATIC | FINAL,
- new Variable(INT_TYPE, transactCodeName));
- transactCode->value = transactCodeValue;
- stubClass->elements.push_back(transactCode);
-
- // == the declaration in the interface ===================================
- Method* decl = new Method;
- decl->comment = gather_comments(method->comments_token->extra);
- decl->modifiers = PUBLIC;
- decl->returnType = NAMES.Search(method->type.type.data);
- decl->returnTypeDimension = method->type.dimension;
- decl->name = method->name.data;
-
- arg = method->args;
- while (arg != NULL) {
- decl->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
-
- decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
-
- interface->elements.push_back(decl);
-
- // == the stub method ====================================================
-
- Case* c = new Case(transactCodeName);
-
- MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
-
- // interface token validation is the very first thing we do
- c->statements->Add(new MethodCall(stubClass->transact_data,
- "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
-
- // args
- Variable* cl = NULL;
- VariableFactory stubArgs("_arg");
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(t);
- v->dimension = arg->type.dimension;
-
- c->statements->Add(new VariableDeclaration(v));
-
- if (convert_direction(arg->direction.data) & IN_PARAMETER) {
- generate_create_from_parcel(t, c->statements, v,
- stubClass->transact_data, &cl);
- } else {
- if (arg->type.dimension == 0) {
- c->statements->Add(new Assignment(
- v, new NewExpression(v->type)));
- }
- else if (arg->type.dimension == 1) {
- generate_new_array(v->type, c->statements, v,
- stubClass->transact_data);
- }
- else {
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
- __LINE__);
- }
- }
-
- realCall->arguments.push_back(v);
-
- arg = arg->next;
- }
-
- // the real call
- Variable* _result = NULL;
- if (0 == strcmp(method->type.type.data, "void")) {
- c->statements->Add(realCall);
-
- if (!oneway) {
- // report that there were no exceptions
- MethodCall* ex = new MethodCall(stubClass->transact_reply,
- "writeNoException", 0);
- c->statements->Add(ex);
- }
- } else {
- _result = new Variable(decl->returnType, "_result",
- decl->returnTypeDimension);
- c->statements->Add(new VariableDeclaration(_result, realCall));
-
- if (!oneway) {
- // report that there were no exceptions
- MethodCall* ex = new MethodCall(stubClass->transact_reply,
- "writeNoException", 0);
- c->statements->Add(ex);
- }
-
- // marshall the return value
- generate_write_to_parcel(decl->returnType, c->statements, _result,
- stubClass->transact_reply,
- Type::PARCELABLE_WRITE_RETURN_VALUE);
- }
-
- // out parameters
- i = 0;
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(i++);
-
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- generate_write_to_parcel(t, c->statements, v,
- stubClass->transact_reply,
- Type::PARCELABLE_WRITE_RETURN_VALUE);
- hasOutParams = true;
- }
-
- arg = arg->next;
- }
-
- // return true
- c->statements->Add(new ReturnStatement(TRUE_VALUE));
- stubClass->transact_switch->cases.push_back(c);
-
- // == the proxy method ===================================================
- Method* proxy = new Method;
- proxy->comment = gather_comments(method->comments_token->extra);
- proxy->modifiers = PUBLIC;
- proxy->returnType = NAMES.Search(method->type.type.data);
- proxy->returnTypeDimension = method->type.dimension;
- proxy->name = method->name.data;
- proxy->statements = new StatementBlock;
- arg = method->args;
- while (arg != NULL) {
- proxy->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
- proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
- proxyClass->elements.push_back(proxy);
-
- // the parcels
- Variable* _data = new Variable(PARCEL_TYPE, "_data");
- proxy->statements->Add(new VariableDeclaration(_data,
- new MethodCall(PARCEL_TYPE, "obtain")));
- Variable* _reply = NULL;
- if (!oneway) {
- _reply = new Variable(PARCEL_TYPE, "_reply");
- proxy->statements->Add(new VariableDeclaration(_reply,
- new MethodCall(PARCEL_TYPE, "obtain")));
- }
-
- // the return value
- _result = NULL;
- if (0 != strcmp(method->type.type.data, "void")) {
- _result = new Variable(proxy->returnType, "_result",
- method->type.dimension);
- proxy->statements->Add(new VariableDeclaration(_result));
- }
-
- // try and finally
- TryStatement* tryStatement = new TryStatement();
- proxy->statements->Add(tryStatement);
- FinallyStatement* finallyStatement = new FinallyStatement();
- proxy->statements->Add(finallyStatement);
-
- // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
- tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
- 1, new LiteralExpression("DESCRIPTOR")));
-
- // the parameters
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- int dir = convert_direction(arg->direction.data);
- if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
- IfStatement* checklen = new IfStatement();
- checklen->expression = new Comparison(v, "==", NULL_VALUE);
- checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
- new LiteralExpression("-1")));
- checklen->elseif = new IfStatement();
- checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
- 1, new FieldVariable(v, "length")));
- tryStatement->statements->Add(checklen);
- }
- else if (dir & IN_PARAMETER) {
- generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
- }
- arg = arg->next;
- }
-
- // the transact call
- MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
- new LiteralExpression("Stub." + transactCodeName),
- _data, _reply ? _reply : NULL_VALUE,
- new LiteralExpression(
- oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
- tryStatement->statements->Add(call);
-
- // throw back exceptions.
- if (_reply) {
- MethodCall* ex = new MethodCall(_reply, "readException", 0);
- tryStatement->statements->Add(ex);
- }
-
- // returning and cleanup
- if (_reply != NULL) {
- if (_result != NULL) {
- generate_create_from_parcel(proxy->returnType,
- tryStatement->statements, _result, _reply, &cl);
- }
-
- // the out/inout parameters
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- generate_read_from_parcel(t, tryStatement->statements,
- v, _reply, &cl);
- }
- arg = arg->next;
- }
-
- finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
- }
- finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
-
- if (_result != NULL) {
- proxy->statements->Add(new ReturnStatement(_result));
- }
-}
-
-static void
-generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
-{
- // the interface descriptor transaction handler
- Case* c = new Case("INTERFACE_TRANSACTION");
- c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
- 1, new LiteralExpression("DESCRIPTOR")));
- c->statements->Add(new ReturnStatement(TRUE_VALUE));
- stub->transact_switch->cases.push_back(c);
-
- // and the proxy-side method returning the descriptor directly
- Method* getDesc = new Method;
- getDesc->modifiers = PUBLIC;
- getDesc->returnType = STRING_TYPE;
- getDesc->returnTypeDimension = 0;
- getDesc->name = "getInterfaceDescriptor";
- getDesc->statements = new StatementBlock;
- getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
- proxy->elements.push_back(getDesc);
-}
-
-static Class*
-generate_interface_class(const interface_type* iface)
-{
- InterfaceType* interfaceType = static_cast<InterfaceType*>(
- NAMES.Find(iface->package, iface->name.data));
-
- // the interface class
- Class* interface = new Class;
- interface->comment = gather_comments(iface->comments_token->extra);
- interface->modifiers = PUBLIC;
- interface->what = Class::INTERFACE;
- interface->type = interfaceType;
- interface->interfaces.push_back(IINTERFACE_TYPE);
-
- // the stub inner class
- StubClass* stub = new StubClass(
- NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
- interfaceType);
- interface->elements.push_back(stub);
-
- // the proxy inner class
- ProxyClass* proxy = new ProxyClass(
- NAMES.Find(iface->package,
- append(iface->name.data, ".Stub.Proxy").c_str()),
- interfaceType);
- stub->elements.push_back(proxy);
-
- // stub and proxy support for getInterfaceDescriptor()
- generate_interface_descriptors(stub, proxy);
-
- // all the declared methods of the interface
- int index = 0;
- interface_item_type* item = iface->interface_items;
- while (item != NULL) {
- if (item->item_type == METHOD_TYPE) {
- generate_method((method_type*)item, interface, stub, proxy, index);
- }
- item = item->next;
- index++;
- }
-
- return interface;
-}
-
+// =================================================
int
generate_java(const string& filename, const string& originalSrc,
interface_type* iface)
{
+ Class* cl;
+
+ if (iface->document_item.item_type == INTERFACE_TYPE_BINDER) {
+ cl = generate_binder_interface_class(iface);
+ }
+ else if (iface->document_item.item_type == INTERFACE_TYPE_RPC) {
+ cl = generate_rpc_interface_class(iface);
+ }
+
Document* document = new Document;
document->comment = "";
if (iface->package) document->package = iface->package;
document->originalSrc = originalSrc;
- document->classes.push_back(generate_interface_class(iface));
+ document->classes.push_back(cl);
// printf("outputting... filename=%s\n", filename.c_str());
FILE* to;
diff --git a/tools/aidl/generate_java.h b/tools/aidl/generate_java.h
index 203fe23..4bfcfeb 100644
--- a/tools/aidl/generate_java.h
+++ b/tools/aidl/generate_java.h
@@ -2,6 +2,7 @@
#define GENERATE_JAVA_H
#include "aidl_language.h"
+#include "AST.h"
#include <string>
@@ -10,5 +11,23 @@
int generate_java(const string& filename, const string& originalSrc,
interface_type* iface);
+Class* generate_binder_interface_class(const interface_type* iface);
+Class* generate_rpc_interface_class(const interface_type* iface);
+
+string gather_comments(extra_text_type* extra);
+string append(const char* a, const char* b);
+
+class VariableFactory
+{
+public:
+ VariableFactory(const string& base); // base must be short
+ Variable* Get(Type* type);
+ Variable* Get(int index);
+private:
+ vector<Variable*> m_vars;
+ string m_base;
+ int m_index;
+};
+
#endif // GENERATE_JAVA_H
diff --git a/tools/aidl/generate_java_binder.cpp b/tools/aidl/generate_java_binder.cpp
new file mode 100644
index 0000000..2e459a8
--- /dev/null
+++ b/tools/aidl/generate_java_binder.cpp
@@ -0,0 +1,559 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// =================================================
+class StubClass : public Class
+{
+public:
+ StubClass(Type* type, Type* interfaceType);
+ virtual ~StubClass();
+
+ Variable* transact_code;
+ Variable* transact_data;
+ Variable* transact_reply;
+ Variable* transact_flags;
+ SwitchStatement* transact_switch;
+private:
+ void make_as_interface(Type* interfaceType);
+};
+
+StubClass::StubClass(Type* type, Type* interfaceType)
+ :Class()
+{
+ this->comment = "/** Local-side IPC implementation stub class. */";
+ this->modifiers = PUBLIC | ABSTRACT | STATIC;
+ this->what = Class::CLASS;
+ this->type = type;
+ this->extends = BINDER_NATIVE_TYPE;
+ this->interfaces.push_back(interfaceType);
+
+ // descriptor
+ Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
+ new Variable(STRING_TYPE, "DESCRIPTOR"));
+ descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
+ this->elements.push_back(descriptor);
+
+ // ctor
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->comment = "/** Construct the stub at attach it to the "
+ "interface. */";
+ ctor->name = "Stub";
+ ctor->statements = new StatementBlock;
+ MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
+ 2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
+ ctor->statements->Add(attach);
+ this->elements.push_back(ctor);
+
+ // asInterface
+ make_as_interface(interfaceType);
+
+ // asBinder
+ Method* asBinder = new Method;
+ asBinder->modifiers = PUBLIC;
+ asBinder->returnType = IBINDER_TYPE;
+ asBinder->name = "asBinder";
+ asBinder->statements = new StatementBlock;
+ asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
+ this->elements.push_back(asBinder);
+
+ // onTransact
+ this->transact_code = new Variable(INT_TYPE, "code");
+ this->transact_data = new Variable(PARCEL_TYPE, "data");
+ this->transact_reply = new Variable(PARCEL_TYPE, "reply");
+ this->transact_flags = new Variable(INT_TYPE, "flags");
+ Method* onTransact = new Method;
+ onTransact->modifiers = PUBLIC | OVERRIDE;
+ onTransact->returnType = BOOLEAN_TYPE;
+ onTransact->name = "onTransact";
+ onTransact->parameters.push_back(this->transact_code);
+ onTransact->parameters.push_back(this->transact_data);
+ onTransact->parameters.push_back(this->transact_reply);
+ onTransact->parameters.push_back(this->transact_flags);
+ onTransact->statements = new StatementBlock;
+ onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+ this->elements.push_back(onTransact);
+ this->transact_switch = new SwitchStatement(this->transact_code);
+
+ onTransact->statements->Add(this->transact_switch);
+ MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
+ this->transact_code, this->transact_data,
+ this->transact_reply, this->transact_flags);
+ onTransact->statements->Add(new ReturnStatement(superCall));
+}
+
+StubClass::~StubClass()
+{
+}
+
+void
+StubClass::make_as_interface(Type *interfaceType)
+{
+ Variable* obj = new Variable(IBINDER_TYPE, "obj");
+
+ Method* m = new Method;
+ m->comment = "/**\n * Cast an IBinder object into an ";
+ m->comment += interfaceType->QualifiedName();
+ m->comment += " interface,\n";
+ m->comment += " * generating a proxy if needed.\n */";
+ m->modifiers = PUBLIC | STATIC;
+ m->returnType = interfaceType;
+ m->name = "asInterface";
+ m->parameters.push_back(obj);
+ m->statements = new StatementBlock;
+
+ IfStatement* ifstatement = new IfStatement();
+ ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
+ ifstatement->statements = new StatementBlock;
+ ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
+ m->statements->Add(ifstatement);
+
+ // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
+ MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
+ queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
+ IInterfaceType* iinType = new IInterfaceType();
+ Variable *iin = new Variable(iinType, "iin");
+ VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, iinType);
+ m->statements->Add(iinVd);
+
+ // Ensure the instance type of the local object is as expected.
+ // One scenario where this is needed is if another package (with a
+ // different class loader) runs in the same process as the service.
+
+ // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
+ Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
+ Comparison* instOfCheck = new Comparison(iin, " instanceof ",
+ new LiteralExpression(interfaceType->QualifiedName()));
+ IfStatement* instOfStatement = new IfStatement();
+ instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
+ instOfStatement->statements = new StatementBlock;
+ instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
+ m->statements->Add(instOfStatement);
+
+ string proxyType = interfaceType->QualifiedName();
+ proxyType += ".Stub.Proxy";
+ NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
+ ne->arguments.push_back(obj);
+ m->statements->Add(new ReturnStatement(ne));
+
+ this->elements.push_back(m);
+}
+
+
+
+// =================================================
+class ProxyClass : public Class
+{
+public:
+ ProxyClass(Type* type, InterfaceType* interfaceType);
+ virtual ~ProxyClass();
+
+ Variable* mRemote;
+ bool mOneWay;
+};
+
+ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
+ :Class()
+{
+ this->modifiers = PRIVATE | STATIC;
+ this->what = Class::CLASS;
+ this->type = type;
+ this->interfaces.push_back(interfaceType);
+
+ mOneWay = interfaceType->OneWay();
+
+ // IBinder mRemote
+ mRemote = new Variable(IBINDER_TYPE, "mRemote");
+ this->elements.push_back(new Field(PRIVATE, mRemote));
+
+ // Proxy()
+ Variable* remote = new Variable(IBINDER_TYPE, "remote");
+ Method* ctor = new Method;
+ ctor->name = "Proxy";
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(remote);
+ ctor->statements->Add(new Assignment(mRemote, remote));
+ this->elements.push_back(ctor);
+
+ // IBinder asBinder()
+ Method* asBinder = new Method;
+ asBinder->modifiers = PUBLIC;
+ asBinder->returnType = IBINDER_TYPE;
+ asBinder->name = "asBinder";
+ asBinder->statements = new StatementBlock;
+ asBinder->statements->Add(new ReturnStatement(mRemote));
+ this->elements.push_back(asBinder);
+}
+
+ProxyClass::~ProxyClass()
+{
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel)
+{
+ Variable* len = new Variable(INT_TYPE, v->name + "_length");
+ addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
+ IfStatement* lencheck = new IfStatement();
+ lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
+ lencheck->statements->Add(new Assignment(v, NULL_VALUE));
+ lencheck->elseif = new IfStatement();
+ lencheck->elseif->statements->Add(new Assignment(v,
+ new NewArrayExpression(t, len)));
+ addTo->Add(lencheck);
+}
+
+static void
+generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags)
+{
+ if (v->dimension == 0) {
+ t->WriteToParcel(addTo, v, parcel, flags);
+ }
+ if (v->dimension == 1) {
+ t->WriteArrayToParcel(addTo, v, parcel, flags);
+ }
+}
+
+static void
+generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl)
+{
+ if (v->dimension == 0) {
+ t->CreateFromParcel(addTo, v, parcel, cl);
+ }
+ if (v->dimension == 1) {
+ t->CreateArrayFromParcel(addTo, v, parcel, cl);
+ }
+}
+
+static void
+generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl)
+{
+ if (v->dimension == 0) {
+ t->ReadFromParcel(addTo, v, parcel, cl);
+ }
+ if (v->dimension == 1) {
+ t->ReadArrayFromParcel(addTo, v, parcel, cl);
+ }
+}
+
+
+static void
+generate_method(const method_type* method, Class* interface,
+ StubClass* stubClass, ProxyClass* proxyClass, int index)
+{
+ arg_type* arg;
+ int i;
+ bool hasOutParams = false;
+
+ const bool oneway = proxyClass->mOneWay || method->oneway;
+
+ // == the TRANSACT_ constant =============================================
+ string transactCodeName = "TRANSACTION_";
+ transactCodeName += method->name.data;
+
+ char transactCodeValue[50];
+ sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
+
+ Field* transactCode = new Field(STATIC | FINAL,
+ new Variable(INT_TYPE, transactCodeName));
+ transactCode->value = transactCodeValue;
+ stubClass->elements.push_back(transactCode);
+
+ // == the declaration in the interface ===================================
+ Method* decl = new Method;
+ decl->comment = gather_comments(method->comments_token->extra);
+ decl->modifiers = PUBLIC;
+ decl->returnType = NAMES.Search(method->type.type.data);
+ decl->returnTypeDimension = method->type.dimension;
+ decl->name = method->name.data;
+
+ arg = method->args;
+ while (arg != NULL) {
+ decl->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+
+ interface->elements.push_back(decl);
+
+ // == the stub method ====================================================
+
+ Case* c = new Case(transactCodeName);
+
+ MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
+
+ // interface token validation is the very first thing we do
+ c->statements->Add(new MethodCall(stubClass->transact_data,
+ "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
+
+ // args
+ Variable* cl = NULL;
+ VariableFactory stubArgs("_arg");
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ v->dimension = arg->type.dimension;
+
+ c->statements->Add(new VariableDeclaration(v));
+
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ generate_create_from_parcel(t, c->statements, v,
+ stubClass->transact_data, &cl);
+ } else {
+ if (arg->type.dimension == 0) {
+ c->statements->Add(new Assignment(v, new NewExpression(v->type)));
+ }
+ else if (arg->type.dimension == 1) {
+ generate_new_array(v->type, c->statements, v,
+ stubClass->transact_data);
+ }
+ else {
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+ __LINE__);
+ }
+ }
+
+ realCall->arguments.push_back(v);
+
+ arg = arg->next;
+ }
+
+ // the real call
+ Variable* _result = NULL;
+ if (0 == strcmp(method->type.type.data, "void")) {
+ c->statements->Add(realCall);
+
+ if (!oneway) {
+ // report that there were no exceptions
+ MethodCall* ex = new MethodCall(stubClass->transact_reply,
+ "writeNoException", 0);
+ c->statements->Add(ex);
+ }
+ } else {
+ _result = new Variable(decl->returnType, "_result",
+ decl->returnTypeDimension);
+ c->statements->Add(new VariableDeclaration(_result, realCall));
+
+ if (!oneway) {
+ // report that there were no exceptions
+ MethodCall* ex = new MethodCall(stubClass->transact_reply,
+ "writeNoException", 0);
+ c->statements->Add(ex);
+ }
+
+ // marshall the return value
+ generate_write_to_parcel(decl->returnType, c->statements, _result,
+ stubClass->transact_reply,
+ Type::PARCELABLE_WRITE_RETURN_VALUE);
+ }
+
+ // out parameters
+ i = 0;
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(i++);
+
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ generate_write_to_parcel(t, c->statements, v,
+ stubClass->transact_reply,
+ Type::PARCELABLE_WRITE_RETURN_VALUE);
+ hasOutParams = true;
+ }
+
+ arg = arg->next;
+ }
+
+ // return true
+ c->statements->Add(new ReturnStatement(TRUE_VALUE));
+ stubClass->transact_switch->cases.push_back(c);
+
+ // == the proxy method ===================================================
+ Method* proxy = new Method;
+ proxy->comment = gather_comments(method->comments_token->extra);
+ proxy->modifiers = PUBLIC;
+ proxy->returnType = NAMES.Search(method->type.type.data);
+ proxy->returnTypeDimension = method->type.dimension;
+ proxy->name = method->name.data;
+ proxy->statements = new StatementBlock;
+ arg = method->args;
+ while (arg != NULL) {
+ proxy->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+ proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+ proxyClass->elements.push_back(proxy);
+
+ // the parcels
+ Variable* _data = new Variable(PARCEL_TYPE, "_data");
+ proxy->statements->Add(new VariableDeclaration(_data,
+ new MethodCall(PARCEL_TYPE, "obtain")));
+ Variable* _reply = NULL;
+ if (!oneway) {
+ _reply = new Variable(PARCEL_TYPE, "_reply");
+ proxy->statements->Add(new VariableDeclaration(_reply,
+ new MethodCall(PARCEL_TYPE, "obtain")));
+ }
+
+ // the return value
+ _result = NULL;
+ if (0 != strcmp(method->type.type.data, "void")) {
+ _result = new Variable(proxy->returnType, "_result",
+ method->type.dimension);
+ proxy->statements->Add(new VariableDeclaration(_result));
+ }
+
+ // try and finally
+ TryStatement* tryStatement = new TryStatement();
+ proxy->statements->Add(tryStatement);
+ FinallyStatement* finallyStatement = new FinallyStatement();
+ proxy->statements->Add(finallyStatement);
+
+ // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
+ tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
+ 1, new LiteralExpression("DESCRIPTOR")));
+
+ // the parameters
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ int dir = convert_direction(arg->direction.data);
+ if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
+ IfStatement* checklen = new IfStatement();
+ checklen->expression = new Comparison(v, "==", NULL_VALUE);
+ checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
+ new LiteralExpression("-1")));
+ checklen->elseif = new IfStatement();
+ checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
+ 1, new FieldVariable(v, "length")));
+ tryStatement->statements->Add(checklen);
+ }
+ else if (dir & IN_PARAMETER) {
+ generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
+ }
+ arg = arg->next;
+ }
+
+ // the transact call
+ MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
+ new LiteralExpression("Stub." + transactCodeName),
+ _data, _reply ? _reply : NULL_VALUE,
+ new LiteralExpression(
+ oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
+ tryStatement->statements->Add(call);
+
+ // throw back exceptions.
+ if (_reply) {
+ MethodCall* ex = new MethodCall(_reply, "readException", 0);
+ tryStatement->statements->Add(ex);
+ }
+
+ // returning and cleanup
+ if (_reply != NULL) {
+ if (_result != NULL) {
+ generate_create_from_parcel(proxy->returnType,
+ tryStatement->statements, _result, _reply, &cl);
+ }
+
+ // the out/inout parameters
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ generate_read_from_parcel(t, tryStatement->statements,
+ v, _reply, &cl);
+ }
+ arg = arg->next;
+ }
+
+ finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
+ }
+ finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
+
+ if (_result != NULL) {
+ proxy->statements->Add(new ReturnStatement(_result));
+ }
+}
+
+static void
+generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
+{
+ // the interface descriptor transaction handler
+ Case* c = new Case("INTERFACE_TRANSACTION");
+ c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
+ 1, new LiteralExpression("DESCRIPTOR")));
+ c->statements->Add(new ReturnStatement(TRUE_VALUE));
+ stub->transact_switch->cases.push_back(c);
+
+ // and the proxy-side method returning the descriptor directly
+ Method* getDesc = new Method;
+ getDesc->modifiers = PUBLIC;
+ getDesc->returnType = STRING_TYPE;
+ getDesc->returnTypeDimension = 0;
+ getDesc->name = "getInterfaceDescriptor";
+ getDesc->statements = new StatementBlock;
+ getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
+ proxy->elements.push_back(getDesc);
+}
+
+Class*
+generate_binder_interface_class(const interface_type* iface)
+{
+ InterfaceType* interfaceType = static_cast<InterfaceType*>(
+ NAMES.Find(iface->package, iface->name.data));
+
+ // the interface class
+ Class* interface = new Class;
+ interface->comment = gather_comments(iface->comments_token->extra);
+ interface->modifiers = PUBLIC;
+ interface->what = Class::INTERFACE;
+ interface->type = interfaceType;
+ interface->interfaces.push_back(IINTERFACE_TYPE);
+
+ // the stub inner class
+ StubClass* stub = new StubClass(
+ NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
+ interfaceType);
+ interface->elements.push_back(stub);
+
+ // the proxy inner class
+ ProxyClass* proxy = new ProxyClass(
+ NAMES.Find(iface->package,
+ append(iface->name.data, ".Stub.Proxy").c_str()),
+ interfaceType);
+ stub->elements.push_back(proxy);
+
+ // stub and proxy support for getInterfaceDescriptor()
+ generate_interface_descriptors(stub, proxy);
+
+ // all the declared methods of the interface
+ int index = 0;
+ interface_item_type* item = iface->interface_items;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ generate_method((method_type*)item, interface, stub, proxy, index);
+ }
+ item = item->next;
+ index++;
+ }
+
+ return interface;
+}
+
diff --git a/tools/aidl/generate_java_rpc.cpp b/tools/aidl/generate_java_rpc.cpp
new file mode 100644
index 0000000..2b50b76
--- /dev/null
+++ b/tools/aidl/generate_java_rpc.cpp
@@ -0,0 +1,996 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+Type* ANDROID_CONTEXT_TYPE = new Type("android.content",
+ "Context", Type::BUILT_IN, false, false, false);
+Type* PRESENTER_BASE_TYPE = new Type("android.support.place.connector",
+ "EventListener", Type::BUILT_IN, false, false, false);
+Type* PRESENTER_LISTENER_BASE_TYPE = new Type("android.support.place.connector",
+ "EventListener.Listener", Type::BUILT_IN, false, false, false);
+Type* RPC_BROKER_TYPE = new Type("android.support.place.connector", "Broker",
+ Type::BUILT_IN, false, false, false);
+Type* PLACE_INFO_TYPE = new Type("android.support.place.connector", "PlaceInfo",
+ Type::BUILT_IN, false, false, false);
+// TODO: Just use Endpoint, so this works for all endpoints.
+Type* RPC_CONNECTOR_TYPE = new Type("android.support.place.connector", "Connector",
+ Type::BUILT_IN, false, false, false);
+Type* RPC_ENDPOINT_INFO_TYPE = new UserDataType("android.support.place.rpc",
+ "EndpointInfo", true, __FILE__, __LINE__);
+Type* RPC_RESULT_HANDLER_TYPE = new UserDataType("android.support.place.rpc", "RpcResultHandler",
+ true, __FILE__, __LINE__);
+Type* RPC_ERROR_LISTENER_TYPE = new Type("android.support.place.rpc", "RpcErrorHandler",
+ Type::BUILT_IN, false, false, false);
+Type* RPC_CONTEXT_TYPE = new UserDataType("android.support.place.rpc", "RpcContext", true,
+ __FILE__, __LINE__);
+
+static void generate_create_from_data(Type* t, StatementBlock* addTo, const string& key,
+ Variable* v, Variable* data, Variable** cl);
+static void generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from);
+static void generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data);
+
+static string
+format_int(int n)
+{
+ char str[20];
+ sprintf(str, "%d", n);
+ return string(str);
+}
+
+static string
+class_name_leaf(const string& str)
+{
+ string::size_type pos = str.rfind('.');
+ if (pos == string::npos) {
+ return str;
+ } else {
+ return string(str, pos+1);
+ }
+}
+
+static string
+results_class_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "On");
+ return str;
+}
+
+static string
+results_method_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "on");
+ return str;
+}
+
+static string
+push_method_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "push");
+ return str;
+}
+
+// =================================================
+class DispatcherClass : public Class
+{
+public:
+ DispatcherClass(const interface_type* iface, Expression* target);
+ virtual ~DispatcherClass();
+
+ void AddMethod(const method_type* method);
+ void DoneWithMethods();
+
+ Method* processMethod;
+ Variable* actionParam;
+ Variable* requestParam;
+ Variable* rpcContextParam;
+ Variable* errorParam;
+ Variable* requestData;
+ Variable* resultData;
+ IfStatement* dispatchIfStatement;
+ Expression* targetExpression;
+
+private:
+ void generate_process();
+};
+
+DispatcherClass::DispatcherClass(const interface_type* iface, Expression* target)
+ :Class(),
+ dispatchIfStatement(NULL),
+ targetExpression(target)
+{
+ generate_process();
+}
+
+DispatcherClass::~DispatcherClass()
+{
+}
+
+void
+DispatcherClass::generate_process()
+{
+ // byte[] process(String action, byte[] params, RpcContext context, RpcError status)
+ this->processMethod = new Method;
+ this->processMethod->modifiers = PUBLIC;
+ this->processMethod->returnType = BYTE_TYPE;
+ this->processMethod->returnTypeDimension = 1;
+ this->processMethod->name = "process";
+ this->processMethod->statements = new StatementBlock;
+
+ this->actionParam = new Variable(STRING_TYPE, "action");
+ this->processMethod->parameters.push_back(this->actionParam);
+
+ this->requestParam = new Variable(BYTE_TYPE, "requestParam", 1);
+ this->processMethod->parameters.push_back(this->requestParam);
+
+ this->rpcContextParam = new Variable(RPC_CONTEXT_TYPE, "context", 0);
+ this->processMethod->parameters.push_back(this->rpcContextParam);
+
+ this->errorParam = new Variable(RPC_ERROR_TYPE, "errorParam", 0);
+ this->processMethod->parameters.push_back(this->errorParam);
+
+ this->requestData = new Variable(RPC_DATA_TYPE, "request");
+ this->processMethod->statements->Add(new VariableDeclaration(requestData,
+ new NewExpression(RPC_DATA_TYPE, 1, this->requestParam)));
+
+ this->resultData = new Variable(RPC_DATA_TYPE, "resultData");
+ this->processMethod->statements->Add(new VariableDeclaration(this->resultData,
+ NULL_VALUE));
+}
+
+void
+DispatcherClass::AddMethod(const method_type* method)
+{
+ arg_type* arg;
+
+ // The if/switch statement
+ IfStatement* ifs = new IfStatement();
+ ifs->expression = new MethodCall(new StringLiteralExpression(method->name.data), "equals",
+ 1, this->actionParam);
+ StatementBlock* block = ifs->statements = new StatementBlock;
+ if (this->dispatchIfStatement == NULL) {
+ this->dispatchIfStatement = ifs;
+ this->processMethod->statements->Add(dispatchIfStatement);
+ } else {
+ this->dispatchIfStatement->elseif = ifs;
+ this->dispatchIfStatement = ifs;
+ }
+
+ // The call to decl (from above)
+ MethodCall* realCall = new MethodCall(this->targetExpression, method->name.data);
+
+ // args
+ Variable* classLoader = NULL;
+ VariableFactory stubArgs("_arg");
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ v->dimension = arg->type.dimension;
+
+ // Unmarshall the parameter
+ block->Add(new VariableDeclaration(v));
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ generate_create_from_data(t, block, arg->name.data, v,
+ this->requestData, &classLoader);
+ } else {
+ if (arg->type.dimension == 0) {
+ block->Add(new Assignment(v, new NewExpression(v->type)));
+ }
+ else if (arg->type.dimension == 1) {
+ generate_new_array(v->type, block, v, this->requestData);
+ }
+ else {
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+ __LINE__);
+ }
+ }
+
+ // Add that parameter to the method call
+ realCall->arguments.push_back(v);
+
+ arg = arg->next;
+ }
+
+ // Add a final parameter: RpcContext. Contains data about
+ // incoming request (e.g., certificate)
+ realCall->arguments.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+
+ Type* returnType = NAMES.Search(method->type.type.data);
+ if (returnType == EVENT_FAKE_TYPE) {
+ returnType = VOID_TYPE;
+ }
+
+ // the real call
+ bool first = true;
+ Variable* _result = NULL;
+ if (returnType == VOID_TYPE) {
+ block->Add(realCall);
+ } else {
+ _result = new Variable(returnType, "_result",
+ method->type.dimension);
+ block->Add(new VariableDeclaration(_result, realCall));
+
+ // need the result RpcData
+ if (first) {
+ block->Add(new Assignment(this->resultData,
+ new NewExpression(RPC_DATA_TYPE)));
+ first = false;
+ }
+
+ // marshall the return value
+ generate_write_to_data(returnType, block,
+ new StringLiteralExpression("_result"), _result, this->resultData);
+ }
+
+ // out parameters
+ int i = 0;
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(i++);
+
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ // need the result RpcData
+ if (first) {
+ block->Add(new Assignment(this->resultData, new NewExpression(RPC_DATA_TYPE)));
+ first = false;
+ }
+
+ generate_write_to_data(t, block, new StringLiteralExpression(arg->name.data),
+ v, this->resultData);
+ }
+
+ arg = arg->next;
+ }
+}
+
+void
+DispatcherClass::DoneWithMethods()
+{
+ if (this->dispatchIfStatement == NULL) {
+ return;
+ }
+
+ this->elements.push_back(this->processMethod);
+
+ IfStatement* fallthrough = new IfStatement();
+ fallthrough->statements = new StatementBlock;
+ fallthrough->statements->Add(new ReturnStatement(
+ new MethodCall(SUPER_VALUE, "process", 4,
+ this->actionParam, this->requestParam,
+ this->rpcContextParam,
+ this->errorParam)));
+ this->dispatchIfStatement->elseif = fallthrough;
+ IfStatement* s = new IfStatement;
+ s->statements = new StatementBlock;
+ this->processMethod->statements->Add(s);
+ s->expression = new Comparison(this->resultData, "!=", NULL_VALUE);
+ s->statements->Add(new ReturnStatement(new MethodCall(this->resultData, "serialize")));
+ s->elseif = new IfStatement;
+ s = s->elseif;
+ s->statements->Add(new ReturnStatement(NULL_VALUE));
+}
+
+// =================================================
+class RpcProxyClass : public Class
+{
+public:
+ RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType);
+ virtual ~RpcProxyClass();
+
+ Variable* endpoint;
+ Variable* broker;
+
+private:
+ void generate_ctor();
+ void generate_get_endpoint_info();
+};
+
+RpcProxyClass::RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType)
+ :Class()
+{
+ this->comment = gather_comments(iface->comments_token->extra);
+ this->modifiers = PUBLIC;
+ this->what = Class::CLASS;
+ this->type = interfaceType;
+
+ // broker
+ this->broker = new Variable(RPC_BROKER_TYPE, "_broker");
+ this->elements.push_back(new Field(PRIVATE, this->broker));
+ // endpoint
+ this->endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "_endpoint");
+ this->elements.push_back(new Field(PRIVATE, this->endpoint));
+
+ // methods
+ generate_ctor();
+ generate_get_endpoint_info();
+}
+
+RpcProxyClass::~RpcProxyClass()
+{
+}
+
+void
+RpcProxyClass::generate_ctor()
+{
+ Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+ Variable* endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "endpoint");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(broker);
+ ctor->parameters.push_back(endpoint);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new Assignment(this->broker, broker));
+ ctor->statements->Add(new Assignment(this->endpoint, endpoint));
+}
+
+void
+RpcProxyClass::generate_get_endpoint_info()
+{
+ Method* get = new Method;
+ get->modifiers = PUBLIC;
+ get->returnType = RPC_ENDPOINT_INFO_TYPE;
+ get->name = "getEndpointInfo";
+ get->statements = new StatementBlock;
+ this->elements.push_back(get);
+
+ get->statements->Add(new ReturnStatement(this->endpoint));
+}
+
+// =================================================
+class EventListenerClass : public DispatcherClass
+{
+public:
+ EventListenerClass(const interface_type* iface, Type* listenerType);
+ virtual ~EventListenerClass();
+
+ Variable* _listener;
+
+private:
+ void generate_ctor();
+};
+
+Expression*
+generate_get_listener_expression(Type* cast)
+{
+ return new Cast(cast, new MethodCall(THIS_VALUE, "getView"));
+}
+
+EventListenerClass::EventListenerClass(const interface_type* iface, Type* listenerType)
+ :DispatcherClass(iface, new FieldVariable(THIS_VALUE, "_listener"))
+{
+ this->modifiers = PRIVATE;
+ this->what = Class::CLASS;
+ this->type = new Type(iface->package ? iface->package : "",
+ append(iface->name.data, ".Presenter"),
+ Type::GENERATED, false, false, false);
+ this->extends = PRESENTER_BASE_TYPE;
+
+ this->_listener = new Variable(listenerType, "_listener");
+ this->elements.push_back(new Field(PRIVATE, this->_listener));
+
+ // methods
+ generate_ctor();
+}
+
+EventListenerClass::~EventListenerClass()
+{
+}
+
+void
+EventListenerClass::generate_ctor()
+{
+ Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+ Variable* listener = new Variable(this->_listener->type, "listener");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(broker);
+ ctor->parameters.push_back(listener);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new MethodCall("super", 2, broker, listener));
+ ctor->statements->Add(new Assignment(this->_listener, listener));
+}
+
+// =================================================
+class ListenerClass : public Class
+{
+public:
+ ListenerClass(const interface_type* iface);
+ virtual ~ListenerClass();
+
+ bool needed;
+
+private:
+ void generate_ctor();
+};
+
+ListenerClass::ListenerClass(const interface_type* iface)
+ :Class(),
+ needed(false)
+{
+ this->comment = "/** Extend this to listen to the events from this class. */";
+ this->modifiers = STATIC | PUBLIC ;
+ this->what = Class::CLASS;
+ this->type = new Type(iface->package ? iface->package : "",
+ append(iface->name.data, ".Listener"),
+ Type::GENERATED, false, false, false);
+ this->extends = PRESENTER_LISTENER_BASE_TYPE;
+}
+
+ListenerClass::~ListenerClass()
+{
+}
+
+// =================================================
+class EndpointBaseClass : public DispatcherClass
+{
+public:
+ EndpointBaseClass(const interface_type* iface);
+ virtual ~EndpointBaseClass();
+
+ bool needed;
+
+private:
+ void generate_ctor();
+};
+
+EndpointBaseClass::EndpointBaseClass(const interface_type* iface)
+ :DispatcherClass(iface, THIS_VALUE),
+ needed(false)
+{
+ this->comment = "/** Extend this to implement a link service. */";
+ this->modifiers = STATIC | PUBLIC | ABSTRACT;
+ this->what = Class::CLASS;
+ this->type = new Type(iface->package ? iface->package : "",
+ append(iface->name.data, ".EndpointBase"),
+ Type::GENERATED, false, false, false);
+ this->extends = RPC_CONNECTOR_TYPE;
+
+ // methods
+ generate_ctor();
+}
+
+EndpointBaseClass::~EndpointBaseClass()
+{
+}
+
+void
+EndpointBaseClass::generate_ctor()
+{
+ Variable* container = new Variable(ANDROID_CONTEXT_TYPE, "context");
+ Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+ Variable* place = new Variable(PLACE_INFO_TYPE, "placeInfo");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(container);
+ ctor->parameters.push_back(broker);
+ ctor->parameters.push_back(place);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new MethodCall("super", 3, container, broker, place));
+}
+
+// =================================================
+class ResultDispatcherClass : public Class
+{
+public:
+ ResultDispatcherClass();
+ virtual ~ResultDispatcherClass();
+
+ void AddMethod(int index, const string& name, Method** method, Variable** param);
+
+ bool needed;
+ Variable* methodId;
+ Variable* callback;
+ Method* onResultMethod;
+ Variable* resultParam;
+ SwitchStatement* methodSwitch;
+
+private:
+ void generate_ctor();
+ void generate_onResult();
+};
+
+ResultDispatcherClass::ResultDispatcherClass()
+ :Class(),
+ needed(false)
+{
+ this->modifiers = PRIVATE | FINAL;
+ this->what = Class::CLASS;
+ this->type = new Type("_ResultDispatcher", Type::GENERATED, false, false, false);
+ this->interfaces.push_back(RPC_RESULT_HANDLER_TYPE);
+
+ // methodId
+ this->methodId = new Variable(INT_TYPE, "methodId");
+ this->elements.push_back(new Field(PRIVATE, this->methodId));
+ this->callback = new Variable(OBJECT_TYPE, "callback");
+ this->elements.push_back(new Field(PRIVATE, this->callback));
+
+ // methods
+ generate_ctor();
+ generate_onResult();
+}
+
+ResultDispatcherClass::~ResultDispatcherClass()
+{
+}
+
+void
+ResultDispatcherClass::generate_ctor()
+{
+ Variable* methodIdParam = new Variable(INT_TYPE, "methId");
+ Variable* callbackParam = new Variable(OBJECT_TYPE, "cbObj");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(methodIdParam);
+ ctor->parameters.push_back(callbackParam);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new Assignment(this->methodId, methodIdParam));
+ ctor->statements->Add(new Assignment(this->callback, callbackParam));
+}
+
+void
+ResultDispatcherClass::generate_onResult()
+{
+ this->onResultMethod = new Method;
+ this->onResultMethod->modifiers = PUBLIC;
+ this->onResultMethod->returnType = VOID_TYPE;
+ this->onResultMethod->returnTypeDimension = 0;
+ this->onResultMethod->name = "onResult";
+ this->onResultMethod->statements = new StatementBlock;
+ this->elements.push_back(this->onResultMethod);
+
+ this->resultParam = new Variable(BYTE_TYPE, "result", 1);
+ this->onResultMethod->parameters.push_back(this->resultParam);
+
+ this->methodSwitch = new SwitchStatement(this->methodId);
+ this->onResultMethod->statements->Add(this->methodSwitch);
+}
+
+void
+ResultDispatcherClass::AddMethod(int index, const string& name, Method** method, Variable** param)
+{
+ Method* m = new Method;
+ m->modifiers = PUBLIC;
+ m->returnType = VOID_TYPE;
+ m->returnTypeDimension = 0;
+ m->name = name;
+ m->statements = new StatementBlock;
+ *param = new Variable(BYTE_TYPE, "result", 1);
+ m->parameters.push_back(*param);
+ this->elements.push_back(m);
+ *method = m;
+
+ Case* c = new Case(format_int(index));
+ c->statements->Add(new MethodCall(new LiteralExpression("this"), name, 1, this->resultParam));
+ c->statements->Add(new Break());
+
+ this->methodSwitch->cases.push_back(c);
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from)
+{
+ fprintf(stderr, "aidl: implement generate_new_array %s:%d\n", __FILE__, __LINE__);
+ exit(1);
+}
+
+static void
+generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, Variable* v,
+ Variable* data, Variable** cl)
+{
+ Expression* k = new StringLiteralExpression(key);
+ if (v->dimension == 0) {
+ t->CreateFromRpcData(addTo, k, v, data, cl);
+ }
+ if (v->dimension == 1) {
+ //t->ReadArrayFromRpcData(addTo, v, data, cl);
+ fprintf(stderr, "aidl: implement generate_create_from_data for arrays%s:%d\n",
+ __FILE__, __LINE__);
+ }
+}
+
+static void
+generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, Variable* data)
+{
+ if (v->dimension == 0) {
+ t->WriteToRpcData(addTo, k, v, data, 0);
+ }
+ if (v->dimension == 1) {
+ //t->WriteArrayToParcel(addTo, v, data);
+ fprintf(stderr, "aidl: implement generate_write_to_data for arrays%s:%d\n",
+ __FILE__, __LINE__);
+ }
+}
+
+// =================================================
+static Type*
+generate_results_method(const method_type* method, RpcProxyClass* proxyClass)
+{
+ arg_type* arg;
+
+ string resultsMethodName = results_method_name(method->name.data);
+ Type* resultsInterfaceType = new Type(results_class_name(method->name.data),
+ Type::GENERATED, false, false, false);
+
+ if (!method->oneway) {
+ Class* resultsClass = new Class;
+ resultsClass->modifiers = STATIC | PUBLIC;
+ resultsClass->what = Class::INTERFACE;
+ resultsClass->type = resultsInterfaceType;
+
+ Method* resultMethod = new Method;
+ resultMethod->comment = gather_comments(method->comments_token->extra);
+ resultMethod->modifiers = PUBLIC;
+ resultMethod->returnType = VOID_TYPE;
+ resultMethod->returnTypeDimension = 0;
+ resultMethod->name = resultsMethodName;
+ if (0 != strcmp("void", method->type.type.data)) {
+ resultMethod->parameters.push_back(new Variable(NAMES.Search(method->type.type.data),
+ "_result", method->type.dimension));
+ }
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ resultMethod->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ }
+ arg = arg->next;
+ }
+ resultsClass->elements.push_back(resultMethod);
+
+ if (resultMethod->parameters.size() > 0) {
+ proxyClass->elements.push_back(resultsClass);
+ return resultsInterfaceType;
+ }
+ }
+ //delete resultsInterfaceType;
+ return NULL;
+}
+
+static void
+generate_proxy_method(const method_type* method, RpcProxyClass* proxyClass,
+ ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+ arg_type* arg;
+ Method* proxyMethod = new Method;
+ proxyMethod->comment = gather_comments(method->comments_token->extra);
+ proxyMethod->modifiers = PUBLIC;
+ proxyMethod->returnType = VOID_TYPE;
+ proxyMethod->returnTypeDimension = 0;
+ proxyMethod->name = method->name.data;
+ proxyMethod->statements = new StatementBlock;
+ proxyClass->elements.push_back(proxyMethod);
+
+ // The local variables
+ Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
+ proxyMethod->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
+
+ // Add the arguments
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ // Function signature
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ proxyMethod->parameters.push_back(v);
+
+ // Input parameter marshalling
+ generate_write_to_data(t, proxyMethod->statements,
+ new StringLiteralExpression(arg->name.data), v, _data);
+ }
+ arg = arg->next;
+ }
+
+ // If there is a results interface for this class
+ Expression* resultParameter;
+ if (resultsInterfaceType != NULL) {
+ // Result interface parameter
+ Variable* resultListener = new Variable(resultsInterfaceType, "_result");
+ proxyMethod->parameters.push_back(resultListener);
+
+ // Add the results dispatcher callback
+ resultsDispatcherClass->needed = true;
+ resultParameter = new NewExpression(resultsDispatcherClass->type, 2,
+ new LiteralExpression(format_int(index)), resultListener);
+ } else {
+ resultParameter = NULL_VALUE;
+ }
+
+ // All proxy methods take an error parameter
+ Variable* errorListener = new Variable(RPC_ERROR_LISTENER_TYPE, "_errors");
+ proxyMethod->parameters.push_back(errorListener);
+
+ // Call the broker
+ proxyMethod->statements->Add(new MethodCall(new FieldVariable(THIS_VALUE, "_broker"),
+ "sendRpc", 5,
+ proxyClass->endpoint,
+ new StringLiteralExpression(method->name.data),
+ new MethodCall(_data, "serialize"),
+ resultParameter,
+ errorListener));
+}
+
+static void
+generate_result_dispatcher_method(const method_type* method,
+ ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+ arg_type* arg;
+ Method* dispatchMethod;
+ Variable* dispatchParam;
+ resultsDispatcherClass->AddMethod(index, method->name.data, &dispatchMethod, &dispatchParam);
+
+ Variable* classLoader = NULL;
+ Variable* resultData = new Variable(RPC_DATA_TYPE, "resultData");
+ dispatchMethod->statements->Add(new VariableDeclaration(resultData,
+ new NewExpression(RPC_DATA_TYPE, 1, dispatchParam)));
+
+ // The callback method itself
+ MethodCall* realCall = new MethodCall(
+ new Cast(resultsInterfaceType, new FieldVariable(THIS_VALUE, "callback")),
+ results_method_name(method->name.data));
+
+ // The return value
+ {
+ Type* t = NAMES.Search(method->type.type.data);
+ if (t != VOID_TYPE) {
+ Variable* rv = new Variable(t, "rv");
+ dispatchMethod->statements->Add(new VariableDeclaration(rv));
+ generate_create_from_data(t, dispatchMethod->statements, "_result", rv,
+ resultData, &classLoader);
+ realCall->arguments.push_back(rv);
+ }
+ }
+
+ VariableFactory stubArgs("arg");
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ // Unmarshall the results
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ dispatchMethod->statements->Add(new VariableDeclaration(v));
+
+ generate_create_from_data(t, dispatchMethod->statements, arg->name.data, v,
+ resultData, &classLoader);
+
+ // Add the argument to the callback
+ realCall->arguments.push_back(v);
+ }
+ arg = arg->next;
+ }
+
+ // Call the callback method
+ dispatchMethod->statements->Add(realCall);
+}
+
+static void
+generate_regular_method(const method_type* method, RpcProxyClass* proxyClass,
+ EndpointBaseClass* serviceBaseClass, ResultDispatcherClass* resultsDispatcherClass,
+ int index)
+{
+ arg_type* arg;
+
+ // == the callback interface for results ================================
+ // the service base class
+ Type* resultsInterfaceType = generate_results_method(method, proxyClass);
+
+ // == the method in the proxy class =====================================
+ generate_proxy_method(method, proxyClass, resultsDispatcherClass, resultsInterfaceType, index);
+
+ // == the method in the result dispatcher class =========================
+ if (resultsInterfaceType != NULL) {
+ generate_result_dispatcher_method(method, resultsDispatcherClass, resultsInterfaceType,
+ index);
+ }
+
+ // == The abstract method that the service developers implement ==========
+ Method* decl = new Method;
+ decl->comment = gather_comments(method->comments_token->extra);
+ decl->modifiers = PUBLIC | ABSTRACT;
+ decl->returnType = NAMES.Search(method->type.type.data);
+ decl->returnTypeDimension = method->type.dimension;
+ decl->name = method->name.data;
+ arg = method->args;
+ while (arg != NULL) {
+ decl->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ // Add the default RpcContext param to all methods
+ decl->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+
+ serviceBaseClass->elements.push_back(decl);
+
+
+ // == the dispatch method in the service base class ======================
+ serviceBaseClass->AddMethod(method);
+}
+
+static void
+generate_event_method(const method_type* method, RpcProxyClass* proxyClass,
+ EndpointBaseClass* serviceBaseClass, ListenerClass* listenerClass,
+ EventListenerClass* presenterClass, int index)
+{
+ arg_type* arg;
+ listenerClass->needed = true;
+
+ // == the push method in the service base class =========================
+ Method* push = new Method;
+ push->modifiers = PUBLIC;
+ push->name = push_method_name(method->name.data);
+ push->statements = new StatementBlock;
+ push->returnType = VOID_TYPE;
+ serviceBaseClass->elements.push_back(push);
+
+ // The local variables
+ Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
+ push->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
+
+ // Add the arguments
+ arg = method->args;
+ while (arg != NULL) {
+ // Function signature
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ push->parameters.push_back(v);
+
+ // Input parameter marshalling
+ generate_write_to_data(t, push->statements,
+ new StringLiteralExpression(arg->name.data), v, _data);
+
+ arg = arg->next;
+ }
+
+ // Send the notifications
+ push->statements->Add(new MethodCall("pushEvent", 2,
+ new StringLiteralExpression(method->name.data),
+ new MethodCall(_data, "serialize")));
+
+ // == the event callback dispatcher method ====================================
+ presenterClass->AddMethod(method);
+
+ // == the event method in the listener base class =====================
+ Method* event = new Method;
+ event->modifiers = PUBLIC;
+ event->name = method->name.data;
+ event->statements = new StatementBlock;
+ event->returnType = VOID_TYPE;
+ listenerClass->elements.push_back(event);
+ arg = method->args;
+ while (arg != NULL) {
+ event->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ // Add a final parameter: RpcContext. Contains data about
+ // incoming request (e.g., certificate)
+ event->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+}
+
+static void
+generate_listener_methods(RpcProxyClass* proxyClass, Type* presenterType, Type* listenerType)
+{
+ // AndroidAtHomePresenter _presenter;
+ // void startListening(Listener listener) {
+ // stopListening();
+ // _presenter = new Presenter(_broker, listener);
+ // _presenter.startListening(_endpoint);
+ // }
+ // void stopListening() {
+ // if (_presenter != null) {
+ // _presenter.stopListening();
+ // }
+ // }
+
+ Variable* _presenter = new Variable(presenterType, "_presenter");
+ proxyClass->elements.push_back(new Field(PRIVATE, _presenter));
+
+ Variable* listener = new Variable(listenerType, "listener");
+
+ Method* startListeningMethod = new Method;
+ startListeningMethod->modifiers = PUBLIC;
+ startListeningMethod->returnType = VOID_TYPE;
+ startListeningMethod->name = "startListening";
+ startListeningMethod->statements = new StatementBlock;
+ startListeningMethod->parameters.push_back(listener);
+ proxyClass->elements.push_back(startListeningMethod);
+
+ startListeningMethod->statements->Add(new MethodCall(THIS_VALUE, "stopListening"));
+ startListeningMethod->statements->Add(new Assignment(_presenter,
+ new NewExpression(presenterType, 2, proxyClass->broker, listener)));
+ startListeningMethod->statements->Add(new MethodCall(_presenter,
+ "startListening", 1, proxyClass->endpoint));
+
+ Method* stopListeningMethod = new Method;
+ stopListeningMethod->modifiers = PUBLIC;
+ stopListeningMethod->returnType = VOID_TYPE;
+ stopListeningMethod->name = "stopListening";
+ stopListeningMethod->statements = new StatementBlock;
+ proxyClass->elements.push_back(stopListeningMethod);
+
+ IfStatement* ifst = new IfStatement;
+ ifst->expression = new Comparison(_presenter, "!=", NULL_VALUE);
+ stopListeningMethod->statements->Add(ifst);
+
+ ifst->statements->Add(new MethodCall(_presenter, "stopListening"));
+ ifst->statements->Add(new Assignment(_presenter, NULL_VALUE));
+}
+
+Class*
+generate_rpc_interface_class(const interface_type* iface)
+{
+ // the proxy class
+ InterfaceType* interfaceType = static_cast<InterfaceType*>(
+ NAMES.Find(iface->package, iface->name.data));
+ RpcProxyClass* proxy = new RpcProxyClass(iface, interfaceType);
+
+ // the listener class
+ ListenerClass* listener = new ListenerClass(iface);
+
+ // the presenter class
+ EventListenerClass* presenter = new EventListenerClass(iface, listener->type);
+
+ // the service base class
+ EndpointBaseClass* base = new EndpointBaseClass(iface);
+ proxy->elements.push_back(base);
+
+ // the result dispatcher
+ ResultDispatcherClass* results = new ResultDispatcherClass();
+
+ // all the declared methods of the proxy
+ int index = 0;
+ interface_item_type* item = iface->interface_items;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ if (NAMES.Search(((method_type*)item)->type.type.data) == EVENT_FAKE_TYPE) {
+ generate_event_method((method_type*)item, proxy, base, listener, presenter, index);
+ } else {
+ generate_regular_method((method_type*)item, proxy, base, results, index);
+ }
+ }
+ item = item->next;
+ index++;
+ }
+ presenter->DoneWithMethods();
+ base->DoneWithMethods();
+
+ // only add this if there are methods with results / out parameters
+ if (results->needed) {
+ proxy->elements.push_back(results);
+ }
+ if (listener->needed) {
+ proxy->elements.push_back(listener);
+ proxy->elements.push_back(presenter);
+ generate_listener_methods(proxy, presenter->type, listener->type);
+ }
+
+ return proxy;
+}
diff --git a/wifi/java/android/net/wifi/NetworkUpdateResult.java b/wifi/java/android/net/wifi/NetworkUpdateResult.java
index 6b7b68b..234bbe1 100644
--- a/wifi/java/android/net/wifi/NetworkUpdateResult.java
+++ b/wifi/java/android/net/wifi/NetworkUpdateResult.java
@@ -22,6 +22,7 @@
int netId;
boolean ipChanged;
boolean proxyChanged;
+ boolean isNewNetwork = false;
public NetworkUpdateResult(int id) {
netId = id;
@@ -58,4 +59,12 @@
public boolean hasProxyChanged() {
return proxyChanged;
}
+
+ public boolean isNewNetwork() {
+ return isNewNetwork;
+ }
+
+ public void setIsNewNetwork(boolean isNew) {
+ isNewNetwork = isNew;
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 568a485..26f7199 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -269,7 +269,8 @@
}
}
WifiNative.saveConfigCommand();
- sendConfiguredNetworksChangedBroadcast();
+ sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ?
+ WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
return result;
}
@@ -281,15 +282,18 @@
static void forgetNetwork(int netId) {
if (WifiNative.removeNetworkCommand(netId)) {
WifiNative.saveConfigCommand();
+ WifiConfiguration target = null;
synchronized (sConfiguredNetworks) {
WifiConfiguration config = sConfiguredNetworks.get(netId);
if (config != null) {
- sConfiguredNetworks.remove(netId);
+ target = sConfiguredNetworks.remove(netId);
sNetworkIds.remove(configKey(config));
}
}
- writeIpAndProxyConfigurations();
- sendConfiguredNetworksChangedBroadcast();
+ if (target != null) {
+ writeIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast(target, WifiManager.CHANGE_REASON_REMOVED);
+ }
} else {
loge("Failed to remove network " + netId);
}
@@ -305,7 +309,11 @@
*/
static int addOrUpdateNetwork(WifiConfiguration config) {
NetworkUpdateResult result = addOrUpdateNetworkNative(config);
- sendConfiguredNetworksChangedBroadcast();
+ if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+ sendConfiguredNetworksChangedBroadcast(sConfiguredNetworks.get(result.getNetworkId()),
+ result.isNewNetwork ? WifiManager.CHANGE_REASON_ADDED :
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
return result.getNetworkId();
}
@@ -319,16 +327,19 @@
*/
static boolean removeNetwork(int netId) {
boolean ret = WifiNative.removeNetworkCommand(netId);
+ WifiConfiguration config = null;
synchronized (sConfiguredNetworks) {
if (ret) {
- WifiConfiguration config = sConfiguredNetworks.get(netId);
+ config = sConfiguredNetworks.get(netId);
if (config != null) {
- sConfiguredNetworks.remove(netId);
+ config = sConfiguredNetworks.remove(netId);
sNetworkIds.remove(configKey(config));
}
}
}
- sendConfiguredNetworksChangedBroadcast();
+ if (config != null) {
+ sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+ }
return ret;
}
@@ -338,11 +349,23 @@
* API. The more powerful selectNetwork()/saveNetwork() is used by the
* state machine for connecting to a network
*
- * @param netId network to be removed
+ * @param netId network to be enabled
*/
static boolean enableNetwork(int netId, boolean disableOthers) {
boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
- sendConfiguredNetworksChangedBroadcast();
+ if (disableOthers) {
+ sendConfiguredNetworksChangedBroadcast();
+ } else {
+ WifiConfiguration enabledNetwork = null;
+ synchronized(sConfiguredNetworks) {
+ enabledNetwork = sConfiguredNetworks.get(netId);
+ }
+ // check just in case the network was removed by someone else.
+ if (enabledNetwork != null) {
+ sendConfiguredNetworksChangedBroadcast(enabledNetwork,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ }
return ret;
}
@@ -375,15 +398,20 @@
*/
static boolean disableNetwork(int netId, int reason) {
boolean ret = WifiNative.disableNetworkCommand(netId);
+ WifiConfiguration network = null;
synchronized (sConfiguredNetworks) {
WifiConfiguration config = sConfiguredNetworks.get(netId);
/* Only change the reason if the network was not previously disabled */
if (config != null && config.status != Status.DISABLED) {
config.status = Status.DISABLED;
config.disableReason = reason;
+ network = config;
}
}
- sendConfiguredNetworksChangedBroadcast();
+ if (network != null) {
+ sendConfiguredNetworksChangedBroadcast(network,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
return ret;
}
@@ -545,9 +573,29 @@
return false;
}
+ /**
+ * Should be called when a single network configuration is made.
+ * @param network The network configuration that changed.
+ * @param reason The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED,
+ * WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE.
+ */
+ private static void sendConfiguredNetworksChangedBroadcast(WifiConfiguration network,
+ int reason) {
+ Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
+ intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network);
+ intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
+ sContext.sendBroadcast(intent);
+ }
+
+ /**
+ * Should be called when multiple network configuration changes are made.
+ */
private static void sendConfiguredNetworksChangedBroadcast() {
Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
sContext.sendBroadcast(intent);
}
@@ -1078,6 +1126,7 @@
}
NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(sConfig, config);
+ result.setIsNewNetwork(newNetwork);
result.setNetworkId(netId);
return result;
}
@@ -1177,7 +1226,8 @@
if (ipChanged || proxyChanged) {
currentConfig.linkProperties = linkProperties;
writeIpAndProxyConfigurations();
- sendConfiguredNetworksChangedBroadcast();
+ sendConfiguredNetworksChangedBroadcast(currentConfig,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
}
return new NetworkUpdateResult(ipChanged, proxyChanged);
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 40ac2a0..0e19c4c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -308,12 +308,53 @@
/**
* Broadcast intent action indicating that the configured networks changed.
- * This can be as a result of adding/updating/deleting a network
+ * This can be as a result of adding/updating/deleting a network. If
+ * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set to true the new configuration
+ * can be retreived with the {@link #EXTRA_WIFI_CONFIGURATION} extra. If multiple
+ * Wi-Fi configurations changed, {@link #EXTRA_WIFI_CONFIGURATION} will not be present.
* @hide
*/
public static final String CONFIGURED_NETWORKS_CHANGED_ACTION =
"android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
/**
+ * The lookup key for a (@link android.net.wifi.WifiConfiguration} object representing
+ * the changed Wi-Fi configuration when the {@link #CONFIGURED_NETWORKS_CHANGED_ACTION}
+ * broadcast is sent.
+ * @hide
+ */
+ public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
+ /**
+ * Multiple network configurations have changed.
+ * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
+ *
+ * @hide
+ */
+ public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
+ /**
+ * The lookup key for an integer indicating the reason a Wi-Fi network configuration
+ * has changed. Only present if {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is {@code false}
+ * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
+ * @hide
+ */
+ public static final String EXTRA_CHANGE_REASON = "changeReason";
+ /**
+ * The configuration is new and was added.
+ * @hide
+ */
+ public static final int CHANGE_REASON_ADDED = 0;
+ /**
+ * The configuration was removed and is no longer present in the system's list of
+ * configured networks.
+ * @hide
+ */
+ public static final int CHANGE_REASON_REMOVED = 1;
+ /**
+ * The configuration has changed as a result of explicit action or because the system
+ * took an automated action such as disabling a malfunctioning configuration.
+ * @hide
+ */
+ public static final int CHANGE_REASON_CONFIG_CHANGE = 2;
+ /**
* An access point scan has completed, and results are available from the supplicant.
* Call {@link #getScanResults()} to obtain the results.
*/
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 82abe3a..df622d6 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -415,7 +415,7 @@
* Starting and shutting down driver too quick causes problems leading to driver
* being in a bad state. Delay driver stop.
*/
- private static final int DELAYED_DRIVER_STOP_MS = 2 * 60 * 1000; /* 2 minutes */
+ private final int mDriverStopDelayMs;
private int mDelayedStopCounter;
private boolean mInDelayedStop = false;
@@ -583,6 +583,9 @@
mDefaultSupplicantScanIntervalMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_wifi_supplicant_scan_interval);
+ mDriverStopDelayMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_wifi_driver_stop_delay);
+
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
@@ -1742,7 +1745,9 @@
* 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);
WifiConfigStore.disableNetwork(mLastNetworkId,
@@ -2597,7 +2602,7 @@
} else {
/* send regular delayed shut down */
sendMessageDelayed(obtainMessage(CMD_DELAYED_STOP_DRIVER,
- mDelayedStopCounter, 0), DELAYED_DRIVER_STOP_MS);
+ mDelayedStopCounter, 0), mDriverStopDelayMs);
}
break;
case CMD_START_DRIVER: